- 欢迎加入官方Discord和Telegram群组!
- 我们正在删除政治内容页面,还您一个干净的H萌娘,净化社区的同时也可以让您认真学习和讨论性知识(?
- 不知道接下来该看什么?可以去查阅本站的优质条目
- 如果您在H萌娘上发现某些内容错误/空缺,请勇于修正/添加!编辑H萌娘其实很容易!(由于遭到破坏,自动确认用户外的编辑现在会先审核 )
- 有任何意见、建议、求助都可以在 讨论版 提出!
User:Irukaza/tools/wikihighlight.js
跳到导航
跳到搜索
注意:这类代码页面在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- 按住
CTRL+SHIFT+DEL 或 ⌘-Shift-R
来清除缓存! - 或尝试在地址栏的地址最后添加代码
?_=1
来访问最新页面。 - 你还可以在设置中勾选小工具在页面右上角添加清除缓存按钮!
//<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>