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

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
无编辑摘要
imported>Irukaza
(Irukaza移动页面用户:=海豚=/tools/wikihighlight.js用户:Irukaza/tools/wikihighlight.js,不留重定向:文本替换 - 替换“=海豚=”为“Irukaza”)
 
(未显示1个用户的6个中间版本)
第1行: 第1行:
//<pre>
//<pre> Disable signature replacing
 
/**
* 原始出处:[[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 EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name
var module = {};
   var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
var config = {
   var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
protocols: [
   var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;
'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 ALLOWED_TAGS = {
var EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name
     bdi: true,
var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
     ins: true,
var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
     u: true,
var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;
     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) {
var ALLOWED_TAGS = {
     return {
bdi: true,
       open: function(stream, state) {
ins: true,
         state.mixinStyle.push(style);
u: true,
       },
font: true,
       close: function(stream, state) {
big: true,
         var index = state.mixinStyle.indexOf(style);
small: true,
         if (index !== -1) state.mixinStyle.splice(index, 1);
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,
style: true,
script: true
};


   ALLOWED_TAGS.s = ALLOWED_TAGS.strike = ALLOWED_TAGS.del = generateStyleMixinTagHandler('strikethrough'); // Alias
function generateStyleMixinTagHandler(style) {
   ALLOWED_TAGS.b = ALLOWED_TAGS.strong = generateStyleMixinTagHandler('strong'); // Alias
return {
   ALLOWED_TAGS.i = ALLOWED_TAGS.em = generateStyleMixinTagHandler('em'); // Alias
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.nowiki = {
ALLOWED_TAGS['s'] = ALLOWED_TAGS['strike'] = ALLOWED_TAGS['del'] = generateStyleMixinTagHandler('strikethrough'); // Alias
     open: function(stream, state) {
ALLOWED_TAGS['b'] = ALLOWED_TAGS['strong'] = generateStyleMixinTagHandler('strong'); // Alias
       state.unclosedTags.pop();
ALLOWED_TAGS['i'] = ALLOWED_TAGS['em'] = generateStyleMixinTagHandler('em'); // Alias
       state.handler = parseNowikiTag;
     },
     canSelfClose: true
       // close never reached
   };


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


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


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

2022年1月22日 (六) 18:15的最新版本

//<pre> Disable signature replacing
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,
		style: true,
		script: 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['charinsert'] = true;
	ALLOWED_TAGS['choose'] = true;
	ALLOWED_TAGS['dynamicpagelist'] = true;
	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
	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;
});

$(function() {
	var target = $('#wpTextbox1');
	if(target.length) {
		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);
		
			return retval;
		};
	};
});
//</pre>