User:ArthurLau1997/工具/WikitextHighlight.js

维基百科,自由的百科全书

注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。

/**
 * 此JavaScript的原始版本由User:Nbdd0121贡献,这位用户是维基百科和萌娘百科的用户且用户名相同。改进版本由User:維基搗蛋鬼(萌娘百科)User:逆襲的天邪鬼(维基百科)贡献。在此向两位用户致谢。
 * 本页面创建时,由于User:ArthurLau1997(我)使用的中文维基百科测试功能“Wiki文本语法高亮”长时间无法正常运行,经由其他用户介绍找到了这个脚本。为了避免跨站拉取脚本所带来的包括但不限于加载速度、稳定性等问题,我将此脚本复制了一份到中文维基百科,即本页面。
 * 原脚本页面建于萌娘百科,URL: https://zh.moegirl.org/index.php?title=User:維基搗蛋鬼/editor.js
 * 许可证【重要】:根据User:Nbdd0121在萌娘百科的用户页,“这位用户的所有贡献都以 CC BY 4.0 授权”。其用户页的存档如下:https://web.archive.org/web/20170610101506/https://zh.moegirl.org/User:Nbdd0121#。
 * 许可证【重要】:这个脚本由User:維基搗蛋鬼(萌娘百科)User:逆襲的天邪鬼(维基百科)改进。按照萌娘百科的条款“文本内容除另有声明外,均在CC BY-NC-SA 3.0 协议下提供”,并根据原始作者的授权方式,此脚本可以转载至此。
 * 许可证【重要】:如果本页的代码中存在本人做出的贡献,本人同意以维基百科规定的协议授权我的贡献。
 * 在您复制使用本页的代码时您应保留许可证信息。
 * User:ArthurLau1997不是本脚本的作者,技术问题我可能无法自己解决,但是您可以将发现的问题告知我,我会尽力处理。
 * 此脚本注释中所述的用户名除非特别说明,均指本页面创建时(2017-10-19)的用户名。
*/

//<pre>

/**
 * 原始出处:[[User:Nbdd0121/tools/wikihighlight.js]]
 *
 * 发现好东西,当然要感谢[[User:Nbdd0121]]了。而且,我要准备拿给维基娘用一用了……
 *
 * 待处理问题:
 * 1. 三个花括号例如{{{1|}}}
 * 2. (胡改一下结果意外修好了orz)在萌娘百科就能填编辑摘要,而到了维基百科点击“摘要”时会自动跳到编辑器上面
 * 3. 收拾<math>等特殊标签
 * 4. 很明显,link-ts和fixlinkstyle跪了……
 *
 * 其他:
 * 要不要给Wikiplus也加个特技呢?
 */

(function() {
var next = function () {

CodeMirror.defineMode('mediawiki', function() {
    function arrayRemove(array, object) {
        var index = array.indexOf(object);
        if (index !== -1) array.splice(index, 1);
    }

    var module = {};
    var config = {
        protocols: [
            'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://',
            'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:',
            'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://',
            'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:',
            // Note '//'' should not be included here
        ],
        linktrail: false
    };

    var EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name
    var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
    var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
    var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;

    var ALLOWED_TAGS = {
        bdi: true,
        ins: true,
        u: true,
        font: true,
        big: true,
        small: true,
        sub: true,
        sup: true,
        h1: true,
        h2: true,
        h3: true,
        h4: true,
        h5: true,
        h6: true,
        cite: true,
        code: true,
        strike: true,
        tt: true,
        var: true,
        div: true,
        center: true,
        blockquote: true,
        ol: true,
        ul: true,
        dl: true,
        table: true,
        caption: true,
        pre: true,
        ruby: true,
        rb: true,
        rp: true,
        rt: true,
        rtc: true,
        p: true,
        span: true,
        abbr: true,
        dfn: true,
        kbd: true,
        samp: true,
        data: true,
        time: true,
        mark: true,
        br: false,
        wbr: false,
        hr: false,
        li: true,
        dt: true,
        dd: true,
        td: true,
        th: true,
        tr: true,
        // These tags are added here but they are not html
        noinclude: true,
        includeonly: true,
        onlyinclude: true
    };

    function generateStyleMixinTagHandler(style) {
        return {
            open: function(stream, state) {
                state.mixinStyle.push(style);
            },
            close: function(stream, state) {
                var index = state.mixinStyle.indexOf(style);
                if (index !== -1) state.mixinStyle.splice(index, 1);
            }
        };
    }

    ALLOWED_TAGS.s = ALLOWED_TAGS.strike = ALLOWED_TAGS.del = generateStyleMixinTagHandler('strikethrough'); // Alias
    ALLOWED_TAGS.b = ALLOWED_TAGS.strong = generateStyleMixinTagHandler('strong'); // Alias
    ALLOWED_TAGS.i = ALLOWED_TAGS.em = generateStyleMixinTagHandler('em'); // Alias

    ALLOWED_TAGS.nowiki = {
        open: function(stream, state) {
            state.unclosedTags.pop();
            state.handler = parseNowikiTag;
        },
        canSelfClose: true
            // close never reached
    };

    ALLOWED_TAGS.pre = {
        open: function(stream, state) {
            state.mixinStyle.push('mw-pre');
            state.unclosedTags.pop();
            state.handler = parsePreTag;
        },
        canSelfClose: true
            // close never reached
    };

    // Extension:Cite
    ALLOWED_TAGS.ref = true;
    ALLOWED_TAGS.references = false;

    // Other extensions
    ALLOWED_TAGS.categorytree = true;
    ALLOWED_TAGS.ce = true;
    ALLOWED_TAGS.charinsert = true;
    ALLOWED_TAGS.gallery = true;
    ALLOWED_TAGS.graph = true;
    ALLOWED_TAGS.hiero = true;
    ALLOWED_TAGS.imagemap = true;
    ALLOWED_TAGS.indicator = true;
    ALLOWED_TAGS.inputbox = true;
    ALLOWED_TAGS.maplink = true;
    ALLOWED_TAGS.poem = true;

    // 先按 nowiki 处理
    ALLOWED_TAGS.math = ALLOWED_TAGS.nowiki;            // 应按照LaTeX处理
    ALLOWED_TAGS.quiz = ALLOWED_TAGS.score = ALLOWED_TAGS.source = ALLOWED_TAGS.syntaxhighlight = ALLOWED_TAGS.nowiki;
    ALLOWED_TAGS.templatedata = ALLOWED_TAGS.nowiki;    // 应按照JSON处理
    ALLOWED_TAGS.timeline = ALLOWED_TAGS.nowiki;        // 中文维基不常用

    // Utility
    function tagCanSelfClose(tagname) {
        var tag = ALLOWED_TAGS[tagname];
        if (tag === false) {
            return true;
        }
        if (typeof tag !== 'object') {
            return false;
        }
        if ('canSelfClose' in tag) {
            return tag.canSelfClose;
        }
        return false;
    }

    function makeStyle(style, state) {
        if (state.bold) {
            style += ' strong';
        }
        if (state.italic) {
            style += ' em';
        }
        style += ' ' + state.mixinStyle.join(' ');
        return style;
    }

    function parseWikitext(stream, state) {
        var sol = stream.sol();

        var match = stream.match(EXT_LINK_PROTOCOL_NOREL);
        if (match) {
            if (stream.match(EXT_LINK_ADDR, false)) {
                // The URL must looks like a URL
                state.stack.push(state.handler);
                state.handler = parseFreeExternalLink;
                return 'mw-extlink';
            } else {
                // Does not look like URL, backUp
                stream.backUp(match[0].length);
            }
        }

        stream.backUp(1);
        var sow = !/\w/.exec(stream.next());

        if (sow) {
            match = stream.match(/(?:ISBN|RFC|PMID)\s+/);
            if (match) {
                if (match[0].startsWith('ISBN')) {
                    var match2 = stream.match(/(?:97[89][- ]?)?(?:[0-9][- ]?){9}[0-9Xx]\b/);
                    if (match2) {
                        return 'mw-isbn';
                    }
                } else {
                    var match2 = stream.match(/[0-9]+\b/);
                    if (match2) {
                        if (match[0].startsWith('RFC')) {
                            return 'mw-rfc';
                        } else {
                            return 'mw-pmid';
                        }
                    }
                }
                stream.backUp(match[0].length);
            }
        }

        if (sol) {
            // Table
            if (stream.match(/\s*(:*)\s*(?=\{\|)/)) {
                state.stack.push(state.handler);
                state.handler = parseTableStart;
                return 'mw-ident';
            }
            switch (stream.peek()) {
                case '-':
                    if (stream.match(/-{4,}/)) {
                        return 'mw-hr';
                    }
                    break;
                case '#': // TODO #REDIRECT
                case '*':
                case ';':
                case ':':
                    stream.match(/[*#;:]*/);
                    return 'mw-ident';
                case ' ':
                    stream.next();
                    return 'line-cm-mw-pre';
                case '=':
                    match = stream.match(/(={1,6})(?=.+?\1\s*$)/);
                    if (match) {
                        state.handler = makeParseSectionHeader(match[1].length);
                        return 'mw-section line-cm-mw-section-' + match[1].length;
                    }
                    break;
            }
        }

        switch (stream.peek()) {
            case '\'':
                if (stream.match(/'+(?=''''')/)) { // more than 5 apostrophes, only last five are considered
                    return makeStyle('', state);
                }
                if (stream.match(/'(?='''(?!'))/)) { // 4 apostrophes, only last three are considered
                    return makeStyle('', state);
                }
                if (stream.match("'''")) {
                    if (!state.bold) {
                        state.bold = true;
                        return 'mw-bold-start';
                    } else {
                        state.bold = false;
                        return 'mw-bold-end';
                    }
                } else if (stream.match("''")) {
                    if (!state.italic) {
                        state.italic = true;
                        return 'mw-italic-start';
                    } else {
                        state.italic = false;
                        return 'mw-italic-end';
                    }
                }
                // TODO Mismatch Recovery
                break;
            case '~':
                var match = stream.match(/~{3,5}/);
                if (match) {
                    return 'mw-signature';
                }
                break;
            case '_':
                if (sow) {
                    var match = stream.match(/\b__[A-Z_]+?__/);
                    if (match) {
                        return 'mw-magic-word';
                    }
                }
                break;
            case '{':
                if (stream.match('{{')) {
                    state.stack.push(state.handler);
                    state.handler = parseTemplateName;
                    return 'mw-template-start';
                }
                break;
            case '[':
                if (stream.match('[[')) {
                    if (!stream.match(/[^\|\[\]]+(?:\|.*?)?\]\]/, false)) { // Not a link
                        return makeStyle('', state);
                    }
                    state.stack.push(state.handler);
                    state.handler = parseLinkTarget;
                    return 'mw-link-start';
                } else {
                    stream.next();
                    var match = stream.match(EXT_LINK_PROTOCOL);
                    if (match) {
                        if (stream.match(EXT_LINK_ADDR, false) && stream.match(/.+?]/, false)) {
                            // The URL must looks like a URL
                            state.stack.push(state.handler);
                            state.handler = parseExternalLink;
                            // Still have to back up the URL, rendered differently
                            stream.backUp(match[0].length);
                            return 'mw-extlink-start';
                        } else {
                            // Does not look like URL, backUp
                            stream.backUp(match[0].length);
                        }
                    }
                    // Bug reported by AnnAngela
                    // [{{}} does not render correctly
                    return makeStyle('', state);
                }
                break;
            case '&':
                return parseEntityOnly(stream, state);
            case '<':
                if (stream.match('<!--')) {
                    state.stack.push(state.handler);
                    state.handler = parseComment;
                    return 'mw-comment';
                }
                stream.next(); // eat <
                var closing = !!stream.eat('/');
                var tagname = stream.match(/\w+/);
                if (!tagname || !(tagname[0] in ALLOWED_TAGS)) {
                    // The eaten ones are treated as plain text if this is not a tag or not allowed
                    return makeStyle('', state);
                }
                tagname = tagname[0];
                var match = stream.match(/[^<]*?(\/)?>/, false);
                if (!match) {
                    // No closing >, treat as text
                    return makeStyle('', state);
                }
                var selfClose = false;
                if (match[1]) {
                    // Self-closing tag processing
                    if (!closing && !tagCanSelfClose(tagname)) {
                        // Not self-closing tag, treat as text
                        return makeStyle('', state);
                    }
                    selfClose = true;
                }

                if (closing) {
                    var uc = state.unclosedTags.slice();
                    while (uc.length) {
                        if (uc.pop() === tagname) {
                            break;
                        }
                    }
                    // If closing tag
                    if (state.unclosedTags[uc.length] === tagname) {
                        state.unclosedTags = uc;
                        if (stream.match(/[^<]*?>/)) {
                            if (typeof(ALLOWED_TAGS[tagname]) === 'object')
                                ALLOWED_TAGS[tagname].close(stream, state);

                            state.handler = state.stack.pop();
                            return 'mw-tag-close';
                        }
                    }
                    // Otherwise, treat as text
                    return makeStyle('', state);
                } else {
                    if (ALLOWED_TAGS[tagname] && !selfClose) { // If not self-closing
                        state.unclosedTags.push(tagname);
                    }
                    state.stack.push(state.handler);
                    state.handler = makeParseOpenTag(tagname, selfClose);
                    return 'mw-tag-open';
                }
                break;
        }

        stream.next();
        return makeStyle('', state);
    }

    function parseFreeExternalLink(stream, state) {
        var match = stream.match(EXT_LINK_URL);
        var text = match[0];

        // {{, ~~~, '' will start their effect, so detect and correct
        var match = /\{\{|~~~|''/.exec(text);
        if (match) {
            // Pushback the wrongly included part
            stream.backUp(text.length - match.index);
            text = text.substring(0, match.index);
        }

        // There are some symbols common in English, they are
        // not treated as part of URL if they are trailing.
        // If there is no left parenthesis,
        // we assume that right parenthese will then not be part of URL
        var regex = text.indexOf('(') !== -1 ? /[,;\\.:!?]+$/ : /[,;\\.:!?)]+$/;
        var match = regex.exec(text);
        var detLength = match ? match[0].length : 0;
        if (detLength !== 0) {
            stream.backUp(detLength);
        }

        state.handler = state.stack.pop();
        return 'mw-extlink';
    }

    function makeParseSectionHeader(count) {
        var regExp = new RegExp('={' + count + '}\\s*$');
        return function(stream, state) {
            if (stream.match(regExp)) {
                return 'mw-section';
            }
            return parseWikitext(stream, state);
        }
    }

    function parseComment(stream, state) {
        if (stream.match('-->')) {
            state.handler = state.stack.pop();
        } else {
            stream.next();
        }
        return 'mw-comment';
    }

    function parseTableStart(stream, state) {
        stream.match('{|');
        state.handler = state.stack.pop();
        return 'mw-table-start';
    }

    function makeParseOpenTag(tagname, selfClose) {
        return function(stream, state) {
            if (stream.match(/\/?>/)) {
                if (!selfClose) {
                    state.handler = parseWikitext;
                    if (typeof(ALLOWED_TAGS[tagname]) === 'object') {
                        ALLOWED_TAGS[tagname].open(stream, state);
                    }
                } else {
                    state.handler = state.stack.pop();
                }
                return 'mw-tag-open';
            } else {
                stream.next();
                return 'mw-tag-attr';
            }
        };
    }

    function parseEntityOnly(stream, state) {
        if (stream.next() === '&') {
            var success;
            if (stream.eat('#')) {
                if (stream.eat('x')) {
                    success = stream.eatWhile(/[a-fA-F\d]/);
                } else {
                    success = stream.eatWhile(/[\d]/);
                }
            } else {
                success = stream.eatWhile(/[\w\.\-:]/);
            }
            if (success) {
                success = stream.eat(';');
            }
            if (success) {
                return makeStyle('mw-entity', state);
            }
        }
        return makeStyle('', state);
    }

    /* Internal link parsing */

    function parseLinkTarget(stream, state) {
        stream.match(/.+?(?=\||\]\])/);
        if (stream.peek() === '|') {
            state.handler = parseLinkPipe;
        } else {
            state.handler = parseLinkEnd;
        }
        return 'mw-link-target';
    }

    function parseLinkEnd(stream, state) {
        stream.match(']]');
        if (config.linktrail) {
            state.handler = parseLinkTrail;
        } else {
            state.handler = state.stack.pop();
        }
        return 'mw-link-end';
    }

    function parseLinkTrail(stream, state) {
        stream.match(/\w*/);
        state.handler = state.stack.pop();
        return 'mw-link-trail';
    }

    function parseLinkPipe(stream, state) {
        stream.match('|');
        state.handler = parseLinkText;
        return 'mw-link-pipe';
    }

    function parseLinkText(stream, state) {
        if (stream.match(']]', false)) {
            // Maybe just return directly?
            state.handler = parseLinkEnd;
            return '';
        }
        var ret = parseWikitext(stream, state);
        return ret + ' mw-link-text';
    }

    // External link parsing
    function parseExternalLink(stream, state) {
        var match = stream.match(EXT_LINK_URL);
        var text = match[0];

        // {{, ~~~, '' will start their effect, so detect and correct
        var match = new RegExp("\\{\\{|~~~|''").exec(text);
        if (match) {
            // Pushback the wrongly included part
            stream.backUp(text.length - match.index);
            text = text.substring(0, match.index);
        }

        state.handler = parseExternalLinkText;
        return 'mw-extlink-target';
    }

    function parseExternalLinkText(stream, state) {
        if (stream.eat(']')) {
            state.handler = state.stack.pop();
            return 'mw-extlink-end';
        }
        var ret = parseWikitext(stream, state);
        return ret + ' mw-link-text';
    }

    // Template

    function parseTemplateName(stream, state) {
        if (stream.eat('|')) {
            if (stream.match(/[^\|\{\}]*=/, false)) {
                state.handler = parseTemplateArgName;
            } else {
                state.handler = parseTemplateArg;
            }
            return 'mw-template-pipe';
        }
        if (stream.match('}}')) {
            state.handler = state.stack.pop();
            return 'mw-template-end';
        }
        stream.next();
        return 'mw-template-name';
    }

    function parseTemplateArg(stream, state) {
        if (stream.eat('|')) {
            if (stream.match(/[^\|\{\}]*=/, false)) {
                state.handler = parseTemplateArgName;
            }
            return 'mw-template-pipe';
        }
        if (stream.match('}}')) {
            state.handler = state.stack.pop();
            return 'mw-template-end';
        }
        var ret = parseWikitext(stream, state);
        return ret + ' mw-template-arg';
    }

    function parseTemplateArgName(stream, state) {
        if (stream.eat('=')) {
            state.handler = parseTemplateArg;
            return 'mw-template-assign';
        }
        // The below two cases are rare cases, where simple regex for detecting = fails
        if (stream.eat('|')) {
            if (!stream.match(/[^\|\{\}]*=/, false)) {
                state.handler = parseTemplateArg;
            }
            return 'mw-template-pipe';
        }
        if (stream.match('}}')) {
            state.handler = state.stack.pop();
            return 'mw-template-end';
        }
        var ret = parseWikitext(stream, state);
        return ret + ' mw-template-argname';
    }

    // Tag handlers

    function parseNowikiTag(stream, state) {
        if (stream.match(/<\/nowiki\s*>/)) {
            state.handler = state.stack.pop();
            return 'mw-tag-close';
        }
        return parseEntityOnly(stream, state);
    }

    function parsePreTag(stream, state) {
        if (stream.match(/<\/pre\s*>/)) {
            state.handler = state.stack.pop();
            arrayRemove(state.mixinStyle, 'mw-pre');
            return 'mw-tag-close';
        }
        return parseEntityOnly(stream, state);
    }

    module.startState = function() {
        return {
            handler: parseWikitext,
            bold: false,
            italic: false,
            mixinStyle: [],
            unclosedTags: [],
            stack: []
        };
    };

    module.copyState = function(state) {
        return {
            handler: state.handler,
            bold: state.bold,
            italic: state.italic,
            mixinStyle: state.mixinStyle.slice(),
            unclosedTags: state.unclosedTags.slice(),
            stack: state.stack.slice()
        }
    };

    module.token = function(stream, state) {
        if (stream.sol()) {
            state.bold = false;
            state.italic = false;
        }
        try {
            return state.handler(stream, state);
        } catch (e) {
            stream.next();
            state.handler = parseWikitext;
            console.error('Error in WikiHighlight', e.stack || e);
            return null;
        }
    }

    return module;
});

};



var done = function () {

$(function() {
    var target = $('#wpTextbox1');
    if(target.length) {
        // 与文本框同步样式
        document.styleSheets[0].insertRule('.CodeMirror {' +
            'font-family:' + target.css('font-family') + ' !important;' +
            'font-size:' + target.css('font-size') + ' !important;' +
            'height:' + target.css('height') + ' !important;' +
        '}', 0);

        var cm = CodeMirror.fromTextArea(target[0], {
            lineNumbers: true,
            lineWrapping: true,
            mode: 'mediawiki'
        });

        cm.on('change', function () {
            target.trigger('input');
        });
        $.valHooks.textarea = {
            get: function(elem){ if(elem === target[0]) return cm.getValue(); else return elem.value; },
            set: function(elem, value){ if(elem === target[0]) cm.setValue(value); else elem.value = value; }
        };
        var origTextSelection = $.fn.textSelection;
        $.fn.textSelection = function(command, options) {
            if (cm.getTextArea() !== this[0]) {
                return origTextSelection.call(this, command, options);
            }
            var fn, retval;

            fn = {
                /**
                 * Get the contents of the textarea
                 */
                getContents: function() {
                    return cm.doc.getValue();
                },

                setContents: function(newContents) {
                    cm.doc.setValue(newContents);
                },

                /**
                 * Get the currently selected text in this textarea. Will focus the textarea
                 * in some browsers (IE/Opera)
                 */
                getSelection: function() {
                    return cm.doc.getSelection();
                },

                /**
                 * Inserts text at the beginning and end of a text selection, optionally
                 * inserting text at the caret when selection is empty.
                 */
                encapsulateSelection: function(options) {
                    return this.each(function() {
                        var insertText,
                            selText,
                            selectPeri = options.selectPeri,
                            pre = options.pre,
                            post = options.post,
                            startCursor = cm.doc.getCursor(true),
                            endCursor = cm.doc.getCursor(false);

                        if (options.selectionStart !== undefined) {
                            // fn[command].call( this, options );
                            fn.setSelection({
                                start: options.selectionStart,
                                end: options.selectionEnd
                            }); // not tested
                        }

                        selText = cm.doc.getSelection();
                        if (!selText) {
                            selText = options.peri;
                        } else if (options.replace) {
                            selectPeri = false;
                            selText = options.peri;
                        } else {
                            selectPeri = false;
                            while (selText.charAt(selText.length - 1) === ' ') {
                                // Exclude ending space char
                                selText = selText.substring(0, selText.length - 1);
                                post += ' ';
                            }
                            while (selText.charAt(0) === ' ') {
                                // Exclude prepending space char
                                selText = selText.substring(1, selText.length);
                                pre = ' ' + pre;
                            }
                        }

                        /**
                         * Do the splitlines stuff.
                         *
                         * Wrap each line of the selected text with pre and post
                         */
                        function doSplitLines(selText, pre, post) {
                            var i,
                                insertText = '',
                                selTextArr = selText.split('\n');

                            for (i = 0; i < selTextArr.length; i++) {
                                insertText += pre + selTextArr[i] + post;
                                if (i !== selTextArr.length - 1) {
                                    insertText += '\n';
                                }
                            }
                            return insertText;
                        }

                        if (options.splitlines) {
                            selectPeri = false;
                            insertText = doSplitLines(selText, pre, post);
                        } else {
                            insertText = pre + selText + post;
                        }

                        if (options.ownline) {
                            if (startCursor.ch !== 0) {
                                insertText = '\n' + insertText;
                                pre += '\n';
                            }

                            if (cm.doc.getLine(endCursor.line).length !== endCursor.ch) {
                                insertText += '\n';
                                post += '\n';
                            }
                        }

                        cm.doc.replaceSelection(insertText);

                        if (selectPeri) {
                            cm.doc.setSelection(
                                cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length),
                                cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length + selText.length)
                            );
                        }
                    });
                },

                /**
                 * Get the position (in resolution of bytes not necessarily characters)
                 * in a textarea
                 */
                getCaretPosition: function(options) {
                    var caretPos = cm.doc.indexFromPos(cm.doc.getCursor(true)),
                        endPos = cm.doc.indexFromPos(cm.doc.getCursor(false));
                    if (options.startAndEnd) {
                        return [caretPos, endPos];
                    }
                    return caretPos;
                },

                setSelection: function(options) {
                    return this.each(function() {
                        cm.doc.setSelection(cm.doc.posFromIndex(options.start), cm.doc.posFromIndex(options.end));
                    });
                },

                /**
                 * Scroll a textarea to the current cursor position. You can set the cursor
                 * position with setSelection()
                 */
                scrollToCaretPosition: function() {
                    return this.each(function() {
                        cm.scrollIntoView(null);
                    });
                }
            };

            switch (command) {
                // case 'getContents': // no params
                // case 'setContents': // no params with defaults
                // case 'getSelection': // no params
                case 'encapsulateSelection':
                    options = $.extend({
                        pre: '', // Text to insert before the cursor/selection
                        peri: '', // Text to insert between pre and post and select afterwards
                        post: '', // Text to insert after the cursor/selection
                        ownline: false, // Put the inserted text on a line of its own
                        replace: false, // If there is a selection, replace it with peri instead of leaving it alone
                        selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
                        splitlines: false, // If multiple lines are selected, encapsulate each line individually
                        selectionStart: undefined, // Position to start selection at
                        selectionEnd: undefined // Position to end selection at. Defaults to start
                    }, options);
                    break;
                case 'getCaretPosition':
                    options = $.extend({
                        // Return [start, end] instead of just start
                        startAndEnd: false
                    }, options);
                    // FIXME: We may not need character position-based functions if we insert markers in the right places
                    break;
                case 'setSelection':
                    options = $.extend({
                        // Position to start selection at
                        start: undefined,
                        // Position to end selection at. Defaults to start
                        end: undefined,
                        // Element to start selection in (iframe only)
                        startContainer: undefined,
                        // Element to end selection in (iframe only). Defaults to startContainer
                        endContainer: undefined
                    }, options);

                    if (options.end === undefined) {
                        options.end = options.start;
                    }
                    if (options.endContainer === undefined) {
                        options.endContainer = options.startContainer;
                    }
                    // FIXME: We may not need character position-based functions if we insert markers in the right places
                    break;
                case 'scrollToCaretPosition':
                    options = $.extend({
                        force: false // Force a scroll even if the caret position is already visible
                    }, options);
                    break;
            }

            retval = fn[command].call(this, options);
            // cm.focus();

            return retval;
        };
    };
});

};



if (typeof CodeMirror === 'undefined' && ['edit', 'submit'].indexOf(mw.config.get('wgAction')) !== -1 && mw.config.get("wgPageContentModel") === 'wikitext') {
    mw.loader.load('//zh.moegirl.org/index.php?title=User:Nbdd0121/tools/codemirror.js&action=raw&ctype=text/javascript');
    mw.loader.load('//zh.moegirl.org/index.php?title=User:Nbdd0121/tools/codemirror.css&action=raw&ctype=text/css', 'text/css');

    var check = function () {
        if(typeof CodeMirror !== 'undefined') {
            mw.loader.load('//zh.moegirl.org/index.php?title=User:維基搗蛋鬼/editor.css&action=raw&ctype=text/css', 'text/css');
            next();
            done();
        } else {
            setTimeout(check, 100);
        }
    }

    setTimeout(check, 100);
}

})();
//</pre>