User:Irukaza/tools/wikihighlight.js:修订间差异

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
(创建页面,内容为“//<pre> Disable signature replacing CodeMirror.defineMode('mediawiki', function() { function arrayRemove(array, object) { var index = array.indexOf(object); if…”)
 
imported>=海豚=
无编辑摘要
第1行: 第1行:
//<pre> Disable signature replacing
//<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() {
CodeMirror.defineMode('mediawiki', function() {
function arrayRemove(array, object) {
   function arrayRemove(array, object) {
var index = array.indexOf(object);
     var index = array.indexOf(object);
if (index !== -1) array.splice(index, 1);
     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 module = {};
   var EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name
var config = {
   var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
protocols: [
   var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://',
   var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;
'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 ALLOWED_TAGS = {
var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
     bdi: true,
var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
     ins: true,
var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;
     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
   };


var ALLOWED_TAGS = {
   function generateStyleMixinTagHandler(style) {
bdi: true,
     return {
ins: true,
       open: function(stream, state) {
u: true,
         state.mixinStyle.push(style);
font: true,
       },
big: true,
       close: function(stream, state) {
small: true,
         var index = state.mixinStyle.indexOf(style);
sub: true,
         if (index !== -1) state.mixinStyle.splice(index, 1);
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) {
   ALLOWED_TAGS.s = ALLOWED_TAGS.strike = ALLOWED_TAGS.del = generateStyleMixinTagHandler('strikethrough'); // Alias
return {
   ALLOWED_TAGS.b = ALLOWED_TAGS.strong = generateStyleMixinTagHandler('strong'); // Alias
open: function(stream, state) {
   ALLOWED_TAGS.i = ALLOWED_TAGS.em = generateStyleMixinTagHandler('em'); // Alias
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.nowiki = {
ALLOWED_TAGS['b'] = ALLOWED_TAGS['strong'] = generateStyleMixinTagHandler('strong'); // Alias
     open: function(stream, state) {
ALLOWED_TAGS['i'] = ALLOWED_TAGS['em'] = generateStyleMixinTagHandler('em'); // Alias
       state.unclosedTags.pop();
       state.handler = parseNowikiTag;
     },
     canSelfClose: true
       // close never reached
   };


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


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


// Extension:Cite
   // Other extensions
ALLOWED_TAGS['ref'] = true;
   ALLOWED_TAGS.categorytree = true;
ALLOWED_TAGS['references'] = false;
   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;


// Other extensions
   // 先按 nowiki 处理
ALLOWED_TAGS['categorytree'] = true;
   ALLOWED_TAGS.math = ALLOWED_TAGS.nowiki;       // 应按照LaTeX处理
ALLOWED_TAGS['charinsert'] = true;
   ALLOWED_TAGS.quiz = ALLOWED_TAGS.score = ALLOWED_TAGS.source = ALLOWED_TAGS.syntaxhighlight = ALLOWED_TAGS.nowiki;
ALLOWED_TAGS['choose'] = true;
   ALLOWED_TAGS.templatedata = ALLOWED_TAGS.nowiki;   // 应按照JSON处理
ALLOWED_TAGS['dynamicpagelist'] = true;
   ALLOWED_TAGS.timeline = ALLOWED_TAGS.nowiki;     // 中文维基不常用
ALLOWED_TAGS['flashmp3'] = true;
ALLOWED_TAGS['gallery'] = true;
ALLOWED_TAGS['imagemap'] = true;
ALLOWED_TAGS['indicator'] = true;
ALLOWED_TAGS['inputbox'] = true;
ALLOWED_TAGS['poem'] = true;
ALLOWED_TAGS['poll'] = true;
ALLOWED_TAGS['sm2'] = true;


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


/* Internal link parsing */
   /* Internal link parsing */


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


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


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


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


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


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


// {{, ~~~, '' will start their effect, so detect and correct
     // {{, <img src="https://www.hmoegirl.com/images/avatars/574894/128.png?ver=1598093246" style="width: 2.5em;border-radius: 50%;"/> [[User:=海豚=|=海豚=]]([[用户讨论:=海豚=/杂谈|留言]] · [[特殊:用户贡献/=海豚=|贡献]] · [[用户讨论:=海豚=|讨论]]), '' will start their effect, so detect and correct
var match = new RegExp("\\{\\{|~~~|''").exec(text);
     var match = new RegExp("\\{\\{|<img src="https://www.hmoegirl.com/images/avatars/574894/128.png?ver=1598093246" style="width: 2.5em;border-radius: 50%;"/> [[User:=海豚=|=海豚=]]([[用户讨论:=海豚=/杂谈|留言]] · [[特殊:用户贡献/=海豚=|贡献]] · [[用户讨论:=海豚=|讨论]])|''").exec(text);
if (match) {
     if (match) {
// Pushback the wrongly included part
       // Pushback the wrongly included part
stream.backUp(text.length - match.index);
       stream.backUp(text.length - match.index);
text = text.substring(0, match.index);
       text = text.substring(0, match.index);
}
     }


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


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


// Template
   // Template


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


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


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


// Tag handlers
   // Tag handlers


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


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


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


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


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


return module;
   return module;
});
});
};
var done = function () {


$(function() {
$(function() {
var target = $('#wpTextbox1');
   var target = $('#wpTextbox1');
if(target.length) {
   if(target.length) {
var cm = CodeMirror.fromTextArea(target[0], {
     // 与文本框同步样式
lineNumbers: true,
     document.styleSheets[0].insertRule('.CodeMirror {' +
lineWrapping: true,
       'font-family:' + target.css('font-family') + ' !important;' +
mode: 'mediawiki'
       'font-size:' + target.css('font-size') + ' !important;' +
});
       'height:' + target.css('height') + ' !important;' +
cm.on('change', function () {
     '}', 0);
target.trigger('input');
 
});
     var cm = CodeMirror.fromTextArea(target[0], {
$.valHooks['textarea'] = {
       lineNumbers: true,
get: function(elem){ if(elem === target[0]) return cm.getValue(); else return elem.value; },
       lineWrapping: true,
set: function(elem, value){ if(elem === target[0]) cm.setValue(value); else elem.value = value; }
       mode: 'mediawiki'
};
     });
var origTextSelection = $.fn.textSelection;
 
$.fn.textSelection = function(command, options) {
     cm.on('change', function () {
if (cm.getTextArea() !== this[0]) {
       target.trigger('input');
return origTextSelection.call(this, command, options);
     });
}
     $.valHooks.textarea = {
var fn, retval;
       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; }
fn = {
     };
/**
     var origTextSelection = $.fn.textSelection;
* Get the contents of the textarea
     $.fn.textSelection = function(command, options) {
*/
       if (cm.getTextArea() !== this[0]) {
getContents: function() {
         return origTextSelection.call(this, command, options);
return cm.doc.getValue();
       }
},
       var fn, retval;
 
setContents: function(newContents) {
       fn = {
cm.doc.setValue(newContents);
         /**
},
         * Get the contents of the textarea
         */
/**
         getContents: function() {
* Get the currently selected text in this textarea. Will focus the textarea
           return cm.doc.getValue();
* in some browsers (IE/Opera)
         },
*/
 
getSelection: function() {
         setContents: function(newContents) {
return cm.doc.getSelection();
           cm.doc.setValue(newContents);
},
         },
 
/**
         /**
* Inserts text at the beginning and end of a text selection, optionally
         * Get the currently selected text in this textarea. Will focus the textarea
* inserting text at the caret when selection is empty.
         * in some browsers (IE/Opera)
*/
         */
encapsulateSelection: function(options) {
         getSelection: function() {
return this.each(function() {
           return cm.doc.getSelection();
var insertText,
         },
selText,
 
selectPeri = options.selectPeri,
         /**
pre = options.pre,
         * Inserts text at the beginning and end of a text selection, optionally
post = options.post,
         * inserting text at the caret when selection is empty.
startCursor = cm.doc.getCursor(true),
         */
endCursor = cm.doc.getCursor(false);
         encapsulateSelection: function(options) {
           return this.each(function() {
if (options.selectionStart !== undefined) {
             var insertText,
// fn[command].call( this, options );
               selText,
fn.setSelection({
               selectPeri = options.selectPeri,
start: options.selectionStart,
               pre = options.pre,
end: options.selectionEnd
               post = options.post,
}); // not tested
               startCursor = cm.doc.getCursor(true),
}
               endCursor = cm.doc.getCursor(false);
 
selText = cm.doc.getSelection();
             if (options.selectionStart !== undefined) {
if (!selText) {
               // fn[command].call( this, options );
selText = options.peri;
               fn.setSelection({
} else if (options.replace) {
                 start: options.selectionStart,
selectPeri = false;
                 end: options.selectionEnd
selText = options.peri;
               }); // not tested
} else {
             }
selectPeri = false;
 
while (selText.charAt(selText.length - 1) === ' ') {
             selText = cm.doc.getSelection();
// Exclude ending space char
             if (!selText) {
selText = selText.substring(0, selText.length - 1);
               selText = options.peri;
post += ' ';
             } else if (options.replace) {
}
               selectPeri = false;
while (selText.charAt(0) === ' ') {
               selText = options.peri;
// Exclude prepending space char
             } else {
selText = selText.substring(1, selText.length);
               selectPeri = false;
pre = ' ' + pre;
               while (selText.charAt(selText.length - 1) === ' ') {
}
                 // Exclude ending space char
}
                 selText = selText.substring(0, selText.length - 1);
                 post += ' ';
/**
               }
* Do the splitlines stuff.
               while (selText.charAt(0) === ' ') {
*
                 // Exclude prepending space char
* Wrap each line of the selected text with pre and post
                 selText = selText.substring(1, selText.length);
*/
                 pre = ' ' + pre;
function doSplitLines(selText, pre, post) {
               }
var i,
             }
insertText = '',
 
selTextArr = selText.split('\n');
             /**
             * Do the splitlines stuff.
for (i = 0; i < selTextArr.length; i++) {
             *
insertText += pre + selTextArr[i] + post;
             * Wrap each line of the selected text with pre and post
if (i !== selTextArr.length - 1) {
             */
insertText += '\n';
             function doSplitLines(selText, pre, post) {
}
               var i,
}
                 insertText = '',
return insertText;
                 selTextArr = selText.split('\n');
}
 
               for (i = 0; i < selTextArr.length; i++) {
if (options.splitlines) {
                 insertText += pre + selTextArr[i] + post;
selectPeri = false;
                 if (i !== selTextArr.length - 1) {
insertText = doSplitLines(selText, pre, post);
                   insertText += '\n';
} else {
                 }
insertText = pre + selText + post;
               }
}
               return insertText;
             }
if (options.ownline) {
 
if (startCursor.ch !== 0) {
             if (options.splitlines) {
insertText = '\n' + insertText;
               selectPeri = false;
pre += '\n';
               insertText = doSplitLines(selText, pre, post);
}
             } else {
               insertText = pre + selText + post;
if (cm.doc.getLine(endCursor.line).length !== endCursor.ch) {
             }
insertText += '\n';
 
post += '\n';
             if (options.ownline) {
}
               if (startCursor.ch !== 0) {
}
                 insertText = '\n' + insertText;
                 pre += '\n';
cm.doc.replaceSelection(insertText);
               }
 
if (selectPeri) {
               if (cm.doc.getLine(endCursor.line).length !== endCursor.ch) {
cm.doc.setSelection(
                 insertText += '\n';
cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length),
                 post += '\n';
cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length + selText.length)
               }
);
             }
}
 
});
             cm.doc.replaceSelection(insertText);
},
 
             if (selectPeri) {
/**
               cm.doc.setSelection(
* Get the position (in resolution of bytes not necessarily characters)
                 cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length),
* in a textarea
                 cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length + selText.length)
*/
               );
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];
         /**
}
         * Get the position (in resolution of bytes not necessarily characters)
return caretPos;
         * in a textarea
},
         */
         getCaretPosition: function(options) {
setSelection: function(options) {
           var caretPos = cm.doc.indexFromPos(cm.doc.getCursor(true)),
return this.each(function() {
             endPos = cm.doc.indexFromPos(cm.doc.getCursor(false));
cm.doc.setSelection(cm.doc.posFromIndex(options.start), cm.doc.posFromIndex(options.end));
           if (options.startAndEnd) {
});
             return [caretPos, endPos];
},
           }
           return caretPos;
/**
         },
* Scroll a textarea to the current cursor position. You can set the cursor
 
* position with setSelection()
         setSelection: function(options) {
*/
           return this.each(function() {
scrollToCaretPosition: function() {
             cm.doc.setSelection(cm.doc.posFromIndex(options.start), cm.doc.posFromIndex(options.end));
return this.each(function() {
           });
cm.scrollIntoView(null);
         },
});
 
}
         /**
};
         * Scroll a textarea to the current cursor position. You can set the cursor
         * position with setSelection()
switch (command) {
         */
// case 'getContents': // no params
         scrollToCaretPosition: function() {
// case 'setContents': // no params with defaults
           return this.each(function() {
// case 'getSelection': // no params
             cm.scrollIntoView(null);
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
       switch (command) {
ownline: false, // Put the inserted text on a line of its own
         // case 'getContents': // no params
replace: false, // If there is a selection, replace it with peri instead of leaving it alone
         // case 'setContents': // no params with defaults
selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
         // case 'getSelection': // no params
splitlines: false, // If multiple lines are selected, encapsulate each line individually
         case 'encapsulateSelection':
selectionStart: undefined, // Position to start selection at
           options = $.extend({
selectionEnd: undefined // Position to end selection at. Defaults to start
             pre: '', // Text to insert before the cursor/selection
}, options);
             peri: '', // Text to insert between pre and post and select afterwards
break;
             post: '', // Text to insert after the cursor/selection
case 'getCaretPosition':
             ownline: false, // Put the inserted text on a line of its own
options = $.extend({
             replace: false, // If there is a selection, replace it with peri instead of leaving it alone
// Return [start, end] instead of just start
             selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
startAndEnd: false
             splitlines: false, // If multiple lines are selected, encapsulate each line individually
}, options);
             selectionStart: undefined, // Position to start selection at
// FIXME: We may not need character position-based functions if we insert markers in the right places
             selectionEnd: undefined // Position to end selection at. Defaults to start
break;
           }, options);
case 'setSelection':
           break;
options = $.extend({
         case 'getCaretPosition':
// Position to start selection at
           options = $.extend({
start: undefined,
             // Return [start, end] instead of just start
// Position to end selection at. Defaults to start
             startAndEnd: false
end: undefined,
           }, options);
// Element to start selection in (iframe only)
           // FIXME: We may not need character position-based functions if we insert markers in the right places
startContainer: undefined,
           break;
// Element to end selection in (iframe only). Defaults to startContainer
         case 'setSelection':
endContainer: undefined
           options = $.extend({
}, options);
             // Position to start selection at
             start: undefined,
if (options.end === undefined) {
             // Position to end selection at. Defaults to start
options.end = options.start;
             end: undefined,
}
             // Element to start selection in (iframe only)
if (options.endContainer === undefined) {
             startContainer: undefined,
options.endContainer = options.startContainer;
             // Element to end selection in (iframe only). Defaults to startContainer
}
             endContainer: undefined
// FIXME: We may not need character position-based functions if we insert markers in the right places
           }, options);
break;
 
case 'scrollToCaretPosition':
           if (options.end === undefined) {
options = $.extend({
             options.end = options.start;
force: false // Force a scroll even if the caret position is already visible
           }
}, options);
           if (options.endContainer === undefined) {
break;
             options.endContainer = options.startContainer;
}
           }
           // FIXME: We may not need character position-based functions if we insert markers in the right places
retval = fn[command].call(this, options);
           break;
         case 'scrollToCaretPosition':
return retval;
           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;
     };
   };
});
});
//</pre>
 
};

2021年3月28日 (日) 18:57的版本

//<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];

        // {{, <img src="https://www.hmoegirl.com/images/avatars/574894/128.png?ver=1598093246" style="width: 2.5em;border-radius: 50%;"/> [[User:=海豚=|=海豚=]]([[用户讨论:=海豚=/杂谈|留言]] · [[特殊:用户贡献/=海豚=|贡献]] · [[用户讨论:=海豚=|讨论]]), '' will start their effect, so detect and correct
        var match = new RegExp("\\{\\{|<img src="https://www.hmoegirl.com/images/avatars/574894/128.png?ver=1598093246" style="width: 2.5em;border-radius: 50%;"/> [[User:=海豚=|=海豚=]]([[用户讨论:=海豚=/杂谈|留言]] · [[特殊:用户贡献/=海豚=|贡献]] · [[用户讨论:=海豚=|讨论]])|''").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;
        };
    };
});

};