User:Irukaza/tools/wikihighlight.js:修订间差异
跳到导航
跳到搜索
imported>=海豚= 无编辑摘要 |
imported>=海豚= 无编辑摘要 |
||
| 第1行: | 第1行: | ||
//<pre> | //<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) { | |||
var index = array.indexOf(object); | |||
if (index !== -1) array.splice(index, 1); | |||
} | |||
var module = {}; | |||
var config = { | |||
protocols: [ | |||
'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://', | |||
'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:', | |||
'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://', | |||
'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:', | |||
// Note '//'' should not be included here | |||
], | |||
linktrail: false | |||
}; | |||
var EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name | |||
var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|')); | |||
var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//'); | |||
var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/; | |||
var ALLOWED_TAGS = { | |||
bdi: true, | |||
ins: true, | |||
u: true, | |||
font: true, | |||
big: true, | |||
small: true, | |||
sub: true, | |||
sup: true, | |||
h1: true, | |||
h2: true, | |||
h3: true, | |||
h4: true, | |||
h5: true, | |||
h6: true, | |||
cite: true, | |||
code: true, | |||
strike: true, | |||
tt: true, | |||
var: true, | |||
div: true, | |||
center: true, | |||
blockquote: true, | |||
ol: true, | |||
ul: true, | |||
dl: true, | |||
table: true, | |||
caption: true, | |||
pre: true, | |||
ruby: true, | |||
rb: true, | |||
rp: true, | |||
rt: true, | |||
rtc: true, | |||
p: true, | |||
span: true, | |||
abbr: true, | |||
dfn: true, | |||
kbd: true, | |||
samp: true, | |||
data: true, | |||
time: true, | |||
mark: true, | |||
br: false, | |||
wbr: false, | |||
hr: false, | |||
li: true, | |||
dt: true, | |||
dd: true, | |||
td: true, | |||
th: true, | |||
tr: true, | |||
// These tags are added here but they are not html | |||
noinclude: true, | |||
includeonly: true, | |||
onlyinclude: true | |||
}; | |||
function generateStyleMixinTagHandler(style) { | |||
return { | |||
open: function(stream, state) { | |||
state.mixinStyle.push(style); | |||
}, | |||
close: function(stream, state) { | |||
var index = state.mixinStyle.indexOf(style); | |||
if (index !== -1) state.mixinStyle.splice(index, 1); | |||
} | |||
}; | |||
} | |||
ALLOWED_TAGS.s = ALLOWED_TAGS.strike = ALLOWED_TAGS.del = generateStyleMixinTagHandler('strikethrough'); // Alias | |||
ALLOWED_TAGS.b = ALLOWED_TAGS.strong = generateStyleMixinTagHandler('strong'); // Alias | |||
ALLOWED_TAGS.i = ALLOWED_TAGS.em = generateStyleMixinTagHandler('em'); // Alias | |||
ALLOWED_TAGS.nowiki = { | |||
open: function(stream, state) { | |||
state.unclosedTags.pop(); | |||
state.handler = parseNowikiTag; | |||
}, | |||
canSelfClose: true | |||
// close never reached | |||
}; | |||
ALLOWED_TAGS.pre = { | |||
open: function(stream, state) { | |||
state.mixinStyle.push('mw-pre'); | |||
state.unclosedTags.pop(); | |||
state.handler = parsePreTag; | |||
}, | |||
canSelfClose: true | |||
// close never reached | |||
}; | |||
// Extension:Cite | |||
ALLOWED_TAGS.ref = true; | |||
ALLOWED_TAGS.references = false; | |||
// Other extensions | |||
ALLOWED_TAGS.categorytree = true; | |||
ALLOWED_TAGS.ce = true; | |||
ALLOWED_TAGS.charinsert = true; | |||
ALLOWED_TAGS.gallery = true; | |||
ALLOWED_TAGS.graph = true; | |||
ALLOWED_TAGS.hiero = true; | |||
ALLOWED_TAGS.imagemap = true; | |||
ALLOWED_TAGS.indicator = true; | |||
ALLOWED_TAGS.inputbox = true; | |||
ALLOWED_TAGS.maplink = true; | |||
ALLOWED_TAGS.poem = true; | |||
// 先按 nowiki 处理 | |||
ALLOWED_TAGS.math = ALLOWED_TAGS.nowiki; // 应按照LaTeX处理 | |||
ALLOWED_TAGS.quiz = ALLOWED_TAGS.score = ALLOWED_TAGS.source = ALLOWED_TAGS.syntaxhighlight = ALLOWED_TAGS.nowiki; | |||
ALLOWED_TAGS.templatedata = ALLOWED_TAGS.nowiki; // 应按照JSON处理 | |||
ALLOWED_TAGS.timeline = ALLOWED_TAGS.nowiki; // 中文维基不常用 | |||
// Utility | |||
function tagCanSelfClose(tagname) { | |||
var tag = ALLOWED_TAGS[tagname]; | |||
if (tag === false) { | |||
return true; | |||
} | |||
if (typeof tag !== 'object') { | |||
return false; | |||
} | |||
if ('canSelfClose' in tag) { | |||
return tag.canSelfClose; | |||
} | |||
return false; | |||
} | |||
function makeStyle(style, state) { | |||
if (state.bold) { | |||
style += ' strong'; | |||
} | |||
if (state.italic) { | |||
style += ' em'; | |||
} | |||
style += ' ' + state.mixinStyle.join(' '); | |||
return style; | |||
} | |||
function parseWikitext(stream, state) { | |||
var sol = stream.sol(); | |||
var match = stream.match(EXT_LINK_PROTOCOL_NOREL); | |||
if (match) { | |||
if (stream.match(EXT_LINK_ADDR, false)) { | |||
// The URL must looks like a URL | |||
state.stack.push(state.handler); | |||
state.handler = parseFreeExternalLink; | |||
return 'mw-extlink'; | |||
} else { | |||
// Does not look like URL, backUp | |||
stream.backUp(match[0].length); | |||
} | |||
} | |||
stream.backUp(1); | |||
var sow = !/\w/.exec(stream.next()); | |||
if (sow) { | |||
match = stream.match(/(?:ISBN|RFC|PMID)\s+/); | |||
if (match) { | |||
if (match[0].startsWith('ISBN')) { | |||
var match2 = stream.match(/(?:97[89][- ]?)?(?:[0-9][- ]?){9}[0-9Xx]\b/); | |||
if (match2) { | |||
return 'mw-isbn'; | |||
} | |||
} else { | |||
var match2 = stream.match(/[0-9]+\b/); | |||
if (match2) { | |||
if (match[0].startsWith('RFC')) { | |||
return 'mw-rfc'; | |||
} else { | |||
return 'mw-pmid'; | |||
} | |||
} | |||
} | |||
stream.backUp(match[0].length); | |||
} | |||
} | |||
if (sol) { | |||
// Table | |||
if (stream.match(/\s*(:*)\s*(?=\{\|)/)) { | |||
state.stack.push(state.handler); | |||
state.handler = parseTableStart; | |||
return 'mw-ident'; | |||
} | |||
switch (stream.peek()) { | |||
case '-': | |||
if (stream.match(/-{4,}/)) { | |||
return 'mw-hr'; | |||
} | |||
break; | |||
case '#': // TODO #REDIRECT | |||
case '*': | |||
case ';': | |||
case ':': | |||
stream.match(/[*#;:]*/); | |||
return 'mw-ident'; | |||
case ' ': | |||
stream.next(); | |||
return 'line-cm-mw-pre'; | |||
case '=': | |||
match = stream.match(/(={1,6})(?=.+?\1\s*$)/); | |||
if (match) { | |||
state.handler = makeParseSectionHeader(match[1].length); | |||
return 'mw-section line-cm-mw-section-' + match[1].length; | |||
} | |||
break; | |||
} | |||
} | |||
switch (stream.peek()) { | |||
case '\'': | |||
if (stream.match(/'+(?=''''')/)) { // more than 5 apostrophes, only last five are considered | |||
return makeStyle('', state); | |||
} | |||
if (stream.match(/'(?='''(?!'))/)) { // 4 apostrophes, only last three are considered | |||
return makeStyle('', state); | |||
} | |||
if (stream.match("'''")) { | |||
if (!state.bold) { | |||
state.bold = true; | |||
return 'mw-bold-start'; | |||
} else { | |||
state.bold = false; | |||
return 'mw-bold-end'; | |||
} | |||
} else if (stream.match("''")) { | |||
if (!state.italic) { | |||
state.italic = true; | |||
return 'mw-italic-start'; | |||
} else { | |||
state.italic = false; | |||
return 'mw-italic-end'; | |||
} | |||
} | |||
// TODO Mismatch Recovery | |||
break; | |||
case '~': | |||
var match = stream.match(/~{3,5}/); | |||
if (match) { | |||
return 'mw-signature'; | |||
} | |||
break; | |||
case '_': | |||
if (sow) { | |||
var match = stream.match(/\b__[A-Z_]+?__/); | |||
if (match) { | |||
return 'mw-magic-word'; | |||
} | |||
} | |||
break; | |||
case '{': | |||
if (stream.match('{{')) { | |||
state.stack.push(state.handler); | |||
state.handler = parseTemplateName; | |||
return 'mw-template-start'; | |||
} | |||
break; | |||
case '[': | |||
if (stream.match('[[')) { | |||
if (!stream.match(/[^\|\[\]]+(?:\|.*?)?\]\]/, false)) { // Not a link | |||
return makeStyle('', state); | |||
} | |||
state.stack.push(state.handler); | |||
state.handler = parseLinkTarget; | |||
return 'mw-link-start'; | |||
} else { | |||
stream.next(); | |||
var match = stream.match(EXT_LINK_PROTOCOL); | |||
if (match) { | |||
if (stream.match(EXT_LINK_ADDR, false) && stream.match(/.+?]/, false)) { | |||
// The URL must looks like a URL | |||
state.stack.push(state.handler); | |||
state.handler = parseExternalLink; | |||
// Still have to back up the URL, rendered differently | |||
stream.backUp(match[0].length); | |||
return 'mw-extlink-start'; | |||
} else { | |||
// Does not look like URL, backUp | |||
stream.backUp(match[0].length); | |||
} | |||
} | |||
// Bug reported by AnnAngela | |||
// [{{}} does not render correctly | |||
return makeStyle('', state); | |||
} | |||
break; | |||
case '&': | |||
return parseEntityOnly(stream, state); | |||
case '<': | |||
if (stream.match('<!--')) { | |||
state.stack.push(state.handler); | |||
state.handler = parseComment; | |||
return 'mw-comment'; | |||
} | |||
stream.next(); // eat < | |||
var closing = !!stream.eat('/'); | |||
var tagname = stream.match(/\w+/); | |||
if (!tagname || !(tagname[0] in ALLOWED_TAGS)) { | |||
// The eaten ones are treated as plain text if this is not a tag or not allowed | |||
return makeStyle('', state); | |||
} | |||
tagname = tagname[0]; | |||
var match = stream.match(/[^<]*?(\/)?>/, false); | |||
if (!match) { | |||
// No closing >, treat as text | |||
return makeStyle('', state); | |||
} | |||
var selfClose = false; | |||
if (match[1]) { | |||
// Self-closing tag processing | |||
if (!closing && !tagCanSelfClose(tagname)) { | |||
// Not self-closing tag, treat as text | |||
return makeStyle('', state); | |||
} | |||
selfClose = true; | |||
} | |||
if (closing) { | |||
var uc = state.unclosedTags.slice(); | |||
while (uc.length) { | |||
if (uc.pop() === tagname) { | |||
break; | |||
} | |||
} | |||
// If closing tag | |||
if (state.unclosedTags[uc.length] === tagname) { | |||
state.unclosedTags = uc; | |||
if (stream.match(/[^<]*?>/)) { | |||
if (typeof(ALLOWED_TAGS[tagname]) === 'object') | |||
ALLOWED_TAGS[tagname].close(stream, state); | |||
state.handler = state.stack.pop(); | |||
return 'mw-tag-close'; | |||
} | |||
} | |||
// Otherwise, treat as text | |||
return makeStyle('', state); | |||
} else { | |||
if (ALLOWED_TAGS[tagname] && !selfClose) { // If not self-closing | |||
state.unclosedTags.push(tagname); | |||
} | |||
state.stack.push(state.handler); | |||
state.handler = makeParseOpenTag(tagname, selfClose); | |||
return 'mw-tag-open'; | |||
} | |||
break; | |||
} | |||
stream.next(); | |||
return makeStyle('', state); | |||
} | |||
function parseFreeExternalLink(stream, state) { | |||
var match = stream.match(EXT_LINK_URL); | |||
var text = match[0]; | |||
// {{, ~~~, '' will start their effect, so detect and correct | |||
var match = /\{\{|~~~|''/.exec(text); | |||
if (match) { | |||
// Pushback the wrongly included part | |||
stream.backUp(text.length - match.index); | |||
text = text.substring(0, match.index); | |||
} | |||
// There are some symbols common in English, they are | |||
// not treated as part of URL if they are trailing. | |||
// If there is no left parenthesis, | |||
// we assume that right parenthese will then not be part of URL | |||
var regex = text.indexOf('(') !== -1 ? /[,;\\.:!?]+$/ : /[,;\\.:!?)]+$/; | |||
var match = regex.exec(text); | |||
var detLength = match ? match[0].length : 0; | |||
if (detLength !== 0) { | |||
stream.backUp(detLength); | |||
} | |||
state.handler = state.stack.pop(); | |||
return 'mw-extlink'; | |||
} | |||
function makeParseSectionHeader(count) { | |||
var regExp = new RegExp('={' + count + '}\\s*$'); | |||
return function(stream, state) { | |||
if (stream.match(regExp)) { | |||
return 'mw-section'; | |||
} | |||
return parseWikitext(stream, state); | |||
} | |||
} | |||
function parseComment(stream, state) { | |||
if (stream.match('-->')) { | |||
state.handler = state.stack.pop(); | |||
} else { | |||
stream.next(); | |||
} | |||
return 'mw-comment'; | |||
} | |||
function parseTableStart(stream, state) { | |||
stream.match('{|'); | |||
state.handler = state.stack.pop(); | |||
return 'mw-table-start'; | |||
} | |||
function makeParseOpenTag(tagname, selfClose) { | |||
return function(stream, state) { | |||
if (stream.match(/\/?>/)) { | |||
if (!selfClose) { | |||
state.handler = parseWikitext; | |||
if (typeof(ALLOWED_TAGS[tagname]) === 'object') { | |||
ALLOWED_TAGS[tagname].open(stream, state); | |||
} | |||
} else { | |||
state.handler = state.stack.pop(); | |||
} | |||
return 'mw-tag-open'; | |||
} else { | |||
stream.next(); | |||
return 'mw-tag-attr'; | |||
} | |||
}; | |||
} | |||
function parseEntityOnly(stream, state) { | |||
if (stream.next() === '&') { | |||
var success; | |||
if (stream.eat('#')) { | |||
if (stream.eat('x')) { | |||
success = stream.eatWhile(/[a-fA-F\d]/); | |||
} else { | |||
success = stream.eatWhile(/[\d]/); | |||
} | |||
} else { | |||
success = stream.eatWhile(/[\w\.\-:]/); | |||
} | |||
if (success) { | |||
success = stream.eat(';'); | |||
} | |||
if (success) { | |||
return makeStyle('mw-entity', state); | |||
} | |||
} | |||
return makeStyle('', state); | |||
} | |||
/* Internal link parsing */ | |||
function parseLinkTarget(stream, state) { | |||
stream.match(/.+?(?=\||\]\])/); | |||
if (stream.peek() === '|') { | |||
state.handler = parseLinkPipe; | |||
} else { | |||
state.handler = parseLinkEnd; | |||
} | |||
return 'mw-link-target'; | |||
} | |||
function parseLinkEnd(stream, state) { | |||
stream.match(']]'); | |||
if (config.linktrail) { | |||
state.handler = parseLinkTrail; | |||
} else { | |||
state.handler = state.stack.pop(); | |||
} | |||
return 'mw-link-end'; | |||
} | |||
function parseLinkTrail(stream, state) { | |||
stream.match(/\w*/); | |||
state.handler = state.stack.pop(); | |||
return 'mw-link-trail'; | |||
} | |||
function parseLinkPipe(stream, state) { | |||
stream.match('|'); | |||
state.handler = parseLinkText; | |||
return 'mw-link-pipe'; | |||
} | |||
function parseLinkText(stream, state) { | |||
if (stream.match(']]', false)) { | |||
// Maybe just return directly? | |||
state.handler = parseLinkEnd; | |||
return ''; | |||
} | |||
var ret = parseWikitext(stream, state); | |||
return ret + ' mw-link-text'; | |||
} | |||
// External link parsing | |||
function parseExternalLink(stream, state) { | |||
var match = stream.match(EXT_LINK_URL); | |||
var text = match[0]; | |||
// {{, ~~~, '' will start their effect, so detect and correct | |||
var match = new RegExp("\\{\\{|~~~|''").exec(text); | |||
if (match) { | |||
// Pushback the wrongly included part | |||
stream.backUp(text.length - match.index); | |||
text = text.substring(0, match.index); | |||
} | |||
state.handler = parseExternalLinkText; | |||
return 'mw-extlink-target'; | |||
} | |||
function parseExternalLinkText(stream, state) { | |||
if (stream.eat(']')) { | |||
state.handler = state.stack.pop(); | |||
return 'mw-extlink-end'; | |||
} | |||
var ret = parseWikitext(stream, state); | |||
return ret + ' mw-link-text'; | |||
} | |||
// Template | |||
function parseTemplateName(stream, state) { | |||
if (stream.eat('|')) { | |||
if (stream.match(/[^\|\{\}]*=/, false)) { | |||
state.handler = parseTemplateArgName; | |||
} else { | |||
state.handler = parseTemplateArg; | |||
} | |||
return 'mw-template-pipe'; | |||
} | |||
if (stream.match('}}')) { | |||
state.handler = state.stack.pop(); | |||
return 'mw-template-end'; | |||
} | |||
stream.next(); | |||
return 'mw-template-name'; | |||
} | |||
function parseTemplateArg(stream, state) { | |||
if (stream.eat('|')) { | |||
if (stream.match(/[^\|\{\}]*=/, false)) { | |||
state.handler = parseTemplateArgName; | |||
} | |||
return 'mw-template-pipe'; | |||
} | |||
if (stream.match('}}')) { | |||
state.handler = state.stack.pop(); | |||
return 'mw-template-end'; | |||
} | |||
var ret = parseWikitext(stream, state); | |||
return ret + ' mw-template-arg'; | |||
} | |||
function parseTemplateArgName(stream, state) { | |||
if (stream.eat('=')) { | |||
state.handler = parseTemplateArg; | |||
return 'mw-template-assign'; | |||
} | |||
// The below two cases are rare cases, where simple regex for detecting = fails | |||
if (stream.eat('|')) { | |||
if (!stream.match(/[^\|\{\}]*=/, false)) { | |||
state.handler = parseTemplateArg; | |||
} | |||
return 'mw-template-pipe'; | |||
} | |||
if (stream.match('}}')) { | |||
state.handler = state.stack.pop(); | |||
return 'mw-template-end'; | |||
} | |||
var ret = parseWikitext(stream, state); | |||
return ret + ' mw-template-argname'; | |||
} | |||
// Tag handlers | |||
function parseNowikiTag(stream, state) { | |||
if (stream.match(/<\/nowiki\s*>/)) { | |||
state.handler = state.stack.pop(); | |||
return 'mw-tag-close'; | |||
} | |||
return parseEntityOnly(stream, state); | |||
} | |||
function parsePreTag(stream, state) { | |||
if (stream.match(/<\/pre\s*>/)) { | |||
state.handler = state.stack.pop(); | |||
arrayRemove(state.mixinStyle, 'mw-pre'); | |||
return 'mw-tag-close'; | |||
} | |||
return parseEntityOnly(stream, state); | |||
} | |||
module.startState = function() { | |||
return { | |||
handler: parseWikitext, | |||
bold: false, | |||
italic: false, | |||
mixinStyle: [], | |||
unclosedTags: [], | |||
stack: [] | |||
}; | |||
}; | |||
module.copyState = function(state) { | |||
return { | |||
handler: state.handler, | |||
bold: state.bold, | |||
italic: state.italic, | |||
mixinStyle: state.mixinStyle.slice(), | |||
unclosedTags: state.unclosedTags.slice(), | |||
stack: state.stack.slice() | |||
} | |||
}; | |||
module.token = function(stream, state) { | |||
if (stream.sol()) { | |||
state.bold = false; | |||
state.italic = false; | |||
} | |||
try { | |||
return state.handler(stream, state); | |||
} catch (e) { | |||
stream.next(); | |||
state.handler = parseWikitext; | |||
console.error('Error in WikiHighlight', e.stack || e); | |||
return null; | |||
} | |||
} | |||
return module; | |||
}); | }); | ||
}; | |||
var done = function () { | |||
$(function() { | $(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; | |||
}; | |||
}; | |||
}); | }); | ||
//</pre> | //</pre> | ||
2021年3月28日 (日) 20:37的版本
//<pre>
/**
* 原始出处:[[User:Nbdd0121/tools/wikihighlight.js]]
*
* 发现好东西,当然要感谢[[User:Nbdd0121]]了。而且,我要准备拿给维基娘用一用了……
*
* 待处理问题:
* 1. 三个花括号例如{{{1|}}}
* 2. (胡改一下结果意外修好了orz)在萌娘百科就能填编辑摘要,而到了维基百科点击“摘要”时会自动跳到编辑器上面
* 3. 收拾<math>等特殊标签
* 4. 很明显,link-ts和fixlinkstyle跪了……
*
* 其他:
* 要不要给Wikiplus也加个特技呢?
*/
(function() {
var next = function () {
CodeMirror.defineMode('mediawiki', function() {
function arrayRemove(array, object) {
var index = array.indexOf(object);
if (index !== -1) array.splice(index, 1);
}
var module = {};
var config = {
protocols: [
'bitcoin:', 'ftp://', 'ftps://', 'geo:', 'git://', 'gopher://', 'http://',
'https://', 'irc://', 'ircs://', 'magnet:', 'mailto:', 'mms://', 'news:',
'nntp://', 'redis://', 'sftp://', 'sip:', 'sips:', 'sms:', 'ssh://',
'svn://', 'tel:', 'telnet://', 'urn:', 'worldwind://', 'xmpp:',
// Note '//'' should not be included here
],
linktrail: false
};
var EXT_LINK_ADDR = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])/; // Match host name, include IPv4, IPv6 and Domain name
var EXT_LINK_PROTOCOL_NOREL = new RegExp(config.protocols.join('|'));
var EXT_LINK_PROTOCOL = new RegExp(config.protocols.join('|') + '|//');
var EXT_LINK_URL = /(?:[0-9.]+|\[[0-9a-fA-F:.]+\]|[^\]\[<>"\s])[^\]\[<>"\s]*/;
var ALLOWED_TAGS = {
bdi: true,
ins: true,
u: true,
font: true,
big: true,
small: true,
sub: true,
sup: true,
h1: true,
h2: true,
h3: true,
h4: true,
h5: true,
h6: true,
cite: true,
code: true,
strike: true,
tt: true,
var: true,
div: true,
center: true,
blockquote: true,
ol: true,
ul: true,
dl: true,
table: true,
caption: true,
pre: true,
ruby: true,
rb: true,
rp: true,
rt: true,
rtc: true,
p: true,
span: true,
abbr: true,
dfn: true,
kbd: true,
samp: true,
data: true,
time: true,
mark: true,
br: false,
wbr: false,
hr: false,
li: true,
dt: true,
dd: true,
td: true,
th: true,
tr: true,
// These tags are added here but they are not html
noinclude: true,
includeonly: true,
onlyinclude: true
};
function generateStyleMixinTagHandler(style) {
return {
open: function(stream, state) {
state.mixinStyle.push(style);
},
close: function(stream, state) {
var index = state.mixinStyle.indexOf(style);
if (index !== -1) state.mixinStyle.splice(index, 1);
}
};
}
ALLOWED_TAGS.s = ALLOWED_TAGS.strike = ALLOWED_TAGS.del = generateStyleMixinTagHandler('strikethrough'); // Alias
ALLOWED_TAGS.b = ALLOWED_TAGS.strong = generateStyleMixinTagHandler('strong'); // Alias
ALLOWED_TAGS.i = ALLOWED_TAGS.em = generateStyleMixinTagHandler('em'); // Alias
ALLOWED_TAGS.nowiki = {
open: function(stream, state) {
state.unclosedTags.pop();
state.handler = parseNowikiTag;
},
canSelfClose: true
// close never reached
};
ALLOWED_TAGS.pre = {
open: function(stream, state) {
state.mixinStyle.push('mw-pre');
state.unclosedTags.pop();
state.handler = parsePreTag;
},
canSelfClose: true
// close never reached
};
// Extension:Cite
ALLOWED_TAGS.ref = true;
ALLOWED_TAGS.references = false;
// Other extensions
ALLOWED_TAGS.categorytree = true;
ALLOWED_TAGS.ce = true;
ALLOWED_TAGS.charinsert = true;
ALLOWED_TAGS.gallery = true;
ALLOWED_TAGS.graph = true;
ALLOWED_TAGS.hiero = true;
ALLOWED_TAGS.imagemap = true;
ALLOWED_TAGS.indicator = true;
ALLOWED_TAGS.inputbox = true;
ALLOWED_TAGS.maplink = true;
ALLOWED_TAGS.poem = true;
// 先按 nowiki 处理
ALLOWED_TAGS.math = ALLOWED_TAGS.nowiki; // 应按照LaTeX处理
ALLOWED_TAGS.quiz = ALLOWED_TAGS.score = ALLOWED_TAGS.source = ALLOWED_TAGS.syntaxhighlight = ALLOWED_TAGS.nowiki;
ALLOWED_TAGS.templatedata = ALLOWED_TAGS.nowiki; // 应按照JSON处理
ALLOWED_TAGS.timeline = ALLOWED_TAGS.nowiki; // 中文维基不常用
// Utility
function tagCanSelfClose(tagname) {
var tag = ALLOWED_TAGS[tagname];
if (tag === false) {
return true;
}
if (typeof tag !== 'object') {
return false;
}
if ('canSelfClose' in tag) {
return tag.canSelfClose;
}
return false;
}
function makeStyle(style, state) {
if (state.bold) {
style += ' strong';
}
if (state.italic) {
style += ' em';
}
style += ' ' + state.mixinStyle.join(' ');
return style;
}
function parseWikitext(stream, state) {
var sol = stream.sol();
var match = stream.match(EXT_LINK_PROTOCOL_NOREL);
if (match) {
if (stream.match(EXT_LINK_ADDR, false)) {
// The URL must looks like a URL
state.stack.push(state.handler);
state.handler = parseFreeExternalLink;
return 'mw-extlink';
} else {
// Does not look like URL, backUp
stream.backUp(match[0].length);
}
}
stream.backUp(1);
var sow = !/\w/.exec(stream.next());
if (sow) {
match = stream.match(/(?:ISBN|RFC|PMID)\s+/);
if (match) {
if (match[0].startsWith('ISBN')) {
var match2 = stream.match(/(?:97[89][- ]?)?(?:[0-9][- ]?){9}[0-9Xx]\b/);
if (match2) {
return 'mw-isbn';
}
} else {
var match2 = stream.match(/[0-9]+\b/);
if (match2) {
if (match[0].startsWith('RFC')) {
return 'mw-rfc';
} else {
return 'mw-pmid';
}
}
}
stream.backUp(match[0].length);
}
}
if (sol) {
// Table
if (stream.match(/\s*(:*)\s*(?=\{\|)/)) {
state.stack.push(state.handler);
state.handler = parseTableStart;
return 'mw-ident';
}
switch (stream.peek()) {
case '-':
if (stream.match(/-{4,}/)) {
return 'mw-hr';
}
break;
case '#': // TODO #REDIRECT
case '*':
case ';':
case ':':
stream.match(/[*#;:]*/);
return 'mw-ident';
case ' ':
stream.next();
return 'line-cm-mw-pre';
case '=':
match = stream.match(/(={1,6})(?=.+?\1\s*$)/);
if (match) {
state.handler = makeParseSectionHeader(match[1].length);
return 'mw-section line-cm-mw-section-' + match[1].length;
}
break;
}
}
switch (stream.peek()) {
case '\'':
if (stream.match(/'+(?=''''')/)) { // more than 5 apostrophes, only last five are considered
return makeStyle('', state);
}
if (stream.match(/'(?='''(?!'))/)) { // 4 apostrophes, only last three are considered
return makeStyle('', state);
}
if (stream.match("'''")) {
if (!state.bold) {
state.bold = true;
return 'mw-bold-start';
} else {
state.bold = false;
return 'mw-bold-end';
}
} else if (stream.match("''")) {
if (!state.italic) {
state.italic = true;
return 'mw-italic-start';
} else {
state.italic = false;
return 'mw-italic-end';
}
}
// TODO Mismatch Recovery
break;
case '~':
var match = stream.match(/~{3,5}/);
if (match) {
return 'mw-signature';
}
break;
case '_':
if (sow) {
var match = stream.match(/\b__[A-Z_]+?__/);
if (match) {
return 'mw-magic-word';
}
}
break;
case '{':
if (stream.match('{{')) {
state.stack.push(state.handler);
state.handler = parseTemplateName;
return 'mw-template-start';
}
break;
case '[':
if (stream.match('[[')) {
if (!stream.match(/[^\|\[\]]+(?:\|.*?)?\]\]/, false)) { // Not a link
return makeStyle('', state);
}
state.stack.push(state.handler);
state.handler = parseLinkTarget;
return 'mw-link-start';
} else {
stream.next();
var match = stream.match(EXT_LINK_PROTOCOL);
if (match) {
if (stream.match(EXT_LINK_ADDR, false) && stream.match(/.+?]/, false)) {
// The URL must looks like a URL
state.stack.push(state.handler);
state.handler = parseExternalLink;
// Still have to back up the URL, rendered differently
stream.backUp(match[0].length);
return 'mw-extlink-start';
} else {
// Does not look like URL, backUp
stream.backUp(match[0].length);
}
}
// Bug reported by AnnAngela
// [{{}} does not render correctly
return makeStyle('', state);
}
break;
case '&':
return parseEntityOnly(stream, state);
case '<':
if (stream.match('<!--')) {
state.stack.push(state.handler);
state.handler = parseComment;
return 'mw-comment';
}
stream.next(); // eat <
var closing = !!stream.eat('/');
var tagname = stream.match(/\w+/);
if (!tagname || !(tagname[0] in ALLOWED_TAGS)) {
// The eaten ones are treated as plain text if this is not a tag or not allowed
return makeStyle('', state);
}
tagname = tagname[0];
var match = stream.match(/[^<]*?(\/)?>/, false);
if (!match) {
// No closing >, treat as text
return makeStyle('', state);
}
var selfClose = false;
if (match[1]) {
// Self-closing tag processing
if (!closing && !tagCanSelfClose(tagname)) {
// Not self-closing tag, treat as text
return makeStyle('', state);
}
selfClose = true;
}
if (closing) {
var uc = state.unclosedTags.slice();
while (uc.length) {
if (uc.pop() === tagname) {
break;
}
}
// If closing tag
if (state.unclosedTags[uc.length] === tagname) {
state.unclosedTags = uc;
if (stream.match(/[^<]*?>/)) {
if (typeof(ALLOWED_TAGS[tagname]) === 'object')
ALLOWED_TAGS[tagname].close(stream, state);
state.handler = state.stack.pop();
return 'mw-tag-close';
}
}
// Otherwise, treat as text
return makeStyle('', state);
} else {
if (ALLOWED_TAGS[tagname] && !selfClose) { // If not self-closing
state.unclosedTags.push(tagname);
}
state.stack.push(state.handler);
state.handler = makeParseOpenTag(tagname, selfClose);
return 'mw-tag-open';
}
break;
}
stream.next();
return makeStyle('', state);
}
function parseFreeExternalLink(stream, state) {
var match = stream.match(EXT_LINK_URL);
var text = match[0];
// {{, ~~~, '' will start their effect, so detect and correct
var match = /\{\{|~~~|''/.exec(text);
if (match) {
// Pushback the wrongly included part
stream.backUp(text.length - match.index);
text = text.substring(0, match.index);
}
// There are some symbols common in English, they are
// not treated as part of URL if they are trailing.
// If there is no left parenthesis,
// we assume that right parenthese will then not be part of URL
var regex = text.indexOf('(') !== -1 ? /[,;\\.:!?]+$/ : /[,;\\.:!?)]+$/;
var match = regex.exec(text);
var detLength = match ? match[0].length : 0;
if (detLength !== 0) {
stream.backUp(detLength);
}
state.handler = state.stack.pop();
return 'mw-extlink';
}
function makeParseSectionHeader(count) {
var regExp = new RegExp('={' + count + '}\\s*$');
return function(stream, state) {
if (stream.match(regExp)) {
return 'mw-section';
}
return parseWikitext(stream, state);
}
}
function parseComment(stream, state) {
if (stream.match('-->')) {
state.handler = state.stack.pop();
} else {
stream.next();
}
return 'mw-comment';
}
function parseTableStart(stream, state) {
stream.match('{|');
state.handler = state.stack.pop();
return 'mw-table-start';
}
function makeParseOpenTag(tagname, selfClose) {
return function(stream, state) {
if (stream.match(/\/?>/)) {
if (!selfClose) {
state.handler = parseWikitext;
if (typeof(ALLOWED_TAGS[tagname]) === 'object') {
ALLOWED_TAGS[tagname].open(stream, state);
}
} else {
state.handler = state.stack.pop();
}
return 'mw-tag-open';
} else {
stream.next();
return 'mw-tag-attr';
}
};
}
function parseEntityOnly(stream, state) {
if (stream.next() === '&') {
var success;
if (stream.eat('#')) {
if (stream.eat('x')) {
success = stream.eatWhile(/[a-fA-F\d]/);
} else {
success = stream.eatWhile(/[\d]/);
}
} else {
success = stream.eatWhile(/[\w\.\-:]/);
}
if (success) {
success = stream.eat(';');
}
if (success) {
return makeStyle('mw-entity', state);
}
}
return makeStyle('', state);
}
/* Internal link parsing */
function parseLinkTarget(stream, state) {
stream.match(/.+?(?=\||\]\])/);
if (stream.peek() === '|') {
state.handler = parseLinkPipe;
} else {
state.handler = parseLinkEnd;
}
return 'mw-link-target';
}
function parseLinkEnd(stream, state) {
stream.match(']]');
if (config.linktrail) {
state.handler = parseLinkTrail;
} else {
state.handler = state.stack.pop();
}
return 'mw-link-end';
}
function parseLinkTrail(stream, state) {
stream.match(/\w*/);
state.handler = state.stack.pop();
return 'mw-link-trail';
}
function parseLinkPipe(stream, state) {
stream.match('|');
state.handler = parseLinkText;
return 'mw-link-pipe';
}
function parseLinkText(stream, state) {
if (stream.match(']]', false)) {
// Maybe just return directly?
state.handler = parseLinkEnd;
return '';
}
var ret = parseWikitext(stream, state);
return ret + ' mw-link-text';
}
// External link parsing
function parseExternalLink(stream, state) {
var match = stream.match(EXT_LINK_URL);
var text = match[0];
// {{, ~~~, '' will start their effect, so detect and correct
var match = new RegExp("\\{\\{|~~~|''").exec(text);
if (match) {
// Pushback the wrongly included part
stream.backUp(text.length - match.index);
text = text.substring(0, match.index);
}
state.handler = parseExternalLinkText;
return 'mw-extlink-target';
}
function parseExternalLinkText(stream, state) {
if (stream.eat(']')) {
state.handler = state.stack.pop();
return 'mw-extlink-end';
}
var ret = parseWikitext(stream, state);
return ret + ' mw-link-text';
}
// Template
function parseTemplateName(stream, state) {
if (stream.eat('|')) {
if (stream.match(/[^\|\{\}]*=/, false)) {
state.handler = parseTemplateArgName;
} else {
state.handler = parseTemplateArg;
}
return 'mw-template-pipe';
}
if (stream.match('}}')) {
state.handler = state.stack.pop();
return 'mw-template-end';
}
stream.next();
return 'mw-template-name';
}
function parseTemplateArg(stream, state) {
if (stream.eat('|')) {
if (stream.match(/[^\|\{\}]*=/, false)) {
state.handler = parseTemplateArgName;
}
return 'mw-template-pipe';
}
if (stream.match('}}')) {
state.handler = state.stack.pop();
return 'mw-template-end';
}
var ret = parseWikitext(stream, state);
return ret + ' mw-template-arg';
}
function parseTemplateArgName(stream, state) {
if (stream.eat('=')) {
state.handler = parseTemplateArg;
return 'mw-template-assign';
}
// The below two cases are rare cases, where simple regex for detecting = fails
if (stream.eat('|')) {
if (!stream.match(/[^\|\{\}]*=/, false)) {
state.handler = parseTemplateArg;
}
return 'mw-template-pipe';
}
if (stream.match('}}')) {
state.handler = state.stack.pop();
return 'mw-template-end';
}
var ret = parseWikitext(stream, state);
return ret + ' mw-template-argname';
}
// Tag handlers
function parseNowikiTag(stream, state) {
if (stream.match(/<\/nowiki\s*>/)) {
state.handler = state.stack.pop();
return 'mw-tag-close';
}
return parseEntityOnly(stream, state);
}
function parsePreTag(stream, state) {
if (stream.match(/<\/pre\s*>/)) {
state.handler = state.stack.pop();
arrayRemove(state.mixinStyle, 'mw-pre');
return 'mw-tag-close';
}
return parseEntityOnly(stream, state);
}
module.startState = function() {
return {
handler: parseWikitext,
bold: false,
italic: false,
mixinStyle: [],
unclosedTags: [],
stack: []
};
};
module.copyState = function(state) {
return {
handler: state.handler,
bold: state.bold,
italic: state.italic,
mixinStyle: state.mixinStyle.slice(),
unclosedTags: state.unclosedTags.slice(),
stack: state.stack.slice()
}
};
module.token = function(stream, state) {
if (stream.sol()) {
state.bold = false;
state.italic = false;
}
try {
return state.handler(stream, state);
} catch (e) {
stream.next();
state.handler = parseWikitext;
console.error('Error in WikiHighlight', e.stack || e);
return null;
}
}
return module;
});
};
var done = function () {
$(function() {
var target = $('#wpTextbox1');
if(target.length) {
// 与文本框同步样式
document.styleSheets[0].insertRule('.CodeMirror {' +
'font-family:' + target.css('font-family') + ' !important;' +
'font-size:' + target.css('font-size') + ' !important;' +
'height:' + target.css('height') + ' !important;' +
'}', 0);
var cm = CodeMirror.fromTextArea(target[0], {
lineNumbers: true,
lineWrapping: true,
mode: 'mediawiki'
});
cm.on('change', function () {
target.trigger('input');
});
$.valHooks.textarea = {
get: function(elem){ if(elem === target[0]) return cm.getValue(); else return elem.value; },
set: function(elem, value){ if(elem === target[0]) cm.setValue(value); else elem.value = value; }
};
var origTextSelection = $.fn.textSelection;
$.fn.textSelection = function(command, options) {
if (cm.getTextArea() !== this[0]) {
return origTextSelection.call(this, command, options);
}
var fn, retval;
fn = {
/**
* Get the contents of the textarea
*/
getContents: function() {
return cm.doc.getValue();
},
setContents: function(newContents) {
cm.doc.setValue(newContents);
},
/**
* Get the currently selected text in this textarea. Will focus the textarea
* in some browsers (IE/Opera)
*/
getSelection: function() {
return cm.doc.getSelection();
},
/**
* Inserts text at the beginning and end of a text selection, optionally
* inserting text at the caret when selection is empty.
*/
encapsulateSelection: function(options) {
return this.each(function() {
var insertText,
selText,
selectPeri = options.selectPeri,
pre = options.pre,
post = options.post,
startCursor = cm.doc.getCursor(true),
endCursor = cm.doc.getCursor(false);
if (options.selectionStart !== undefined) {
// fn[command].call( this, options );
fn.setSelection({
start: options.selectionStart,
end: options.selectionEnd
}); // not tested
}
selText = cm.doc.getSelection();
if (!selText) {
selText = options.peri;
} else if (options.replace) {
selectPeri = false;
selText = options.peri;
} else {
selectPeri = false;
while (selText.charAt(selText.length - 1) === ' ') {
// Exclude ending space char
selText = selText.substring(0, selText.length - 1);
post += ' ';
}
while (selText.charAt(0) === ' ') {
// Exclude prepending space char
selText = selText.substring(1, selText.length);
pre = ' ' + pre;
}
}
/**
* Do the splitlines stuff.
*
* Wrap each line of the selected text with pre and post
*/
function doSplitLines(selText, pre, post) {
var i,
insertText = '',
selTextArr = selText.split('\n');
for (i = 0; i < selTextArr.length; i++) {
insertText += pre + selTextArr[i] + post;
if (i !== selTextArr.length - 1) {
insertText += '\n';
}
}
return insertText;
}
if (options.splitlines) {
selectPeri = false;
insertText = doSplitLines(selText, pre, post);
} else {
insertText = pre + selText + post;
}
if (options.ownline) {
if (startCursor.ch !== 0) {
insertText = '\n' + insertText;
pre += '\n';
}
if (cm.doc.getLine(endCursor.line).length !== endCursor.ch) {
insertText += '\n';
post += '\n';
}
}
cm.doc.replaceSelection(insertText);
if (selectPeri) {
cm.doc.setSelection(
cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length),
cm.doc.posFromIndex(cm.doc.indexFromPos(startCursor) + pre.length + selText.length)
);
}
});
},
/**
* Get the position (in resolution of bytes not necessarily characters)
* in a textarea
*/
getCaretPosition: function(options) {
var caretPos = cm.doc.indexFromPos(cm.doc.getCursor(true)),
endPos = cm.doc.indexFromPos(cm.doc.getCursor(false));
if (options.startAndEnd) {
return [caretPos, endPos];
}
return caretPos;
},
setSelection: function(options) {
return this.each(function() {
cm.doc.setSelection(cm.doc.posFromIndex(options.start), cm.doc.posFromIndex(options.end));
});
},
/**
* Scroll a textarea to the current cursor position. You can set the cursor
* position with setSelection()
*/
scrollToCaretPosition: function() {
return this.each(function() {
cm.scrollIntoView(null);
});
}
};
switch (command) {
// case 'getContents': // no params
// case 'setContents': // no params with defaults
// case 'getSelection': // no params
case 'encapsulateSelection':
options = $.extend({
pre: '', // Text to insert before the cursor/selection
peri: '', // Text to insert between pre and post and select afterwards
post: '', // Text to insert after the cursor/selection
ownline: false, // Put the inserted text on a line of its own
replace: false, // If there is a selection, replace it with peri instead of leaving it alone
selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true)
splitlines: false, // If multiple lines are selected, encapsulate each line individually
selectionStart: undefined, // Position to start selection at
selectionEnd: undefined // Position to end selection at. Defaults to start
}, options);
break;
case 'getCaretPosition':
options = $.extend({
// Return [start, end] instead of just start
startAndEnd: false
}, options);
// FIXME: We may not need character position-based functions if we insert markers in the right places
break;
case 'setSelection':
options = $.extend({
// Position to start selection at
start: undefined,
// Position to end selection at. Defaults to start
end: undefined,
// Element to start selection in (iframe only)
startContainer: undefined,
// Element to end selection in (iframe only). Defaults to startContainer
endContainer: undefined
}, options);
if (options.end === undefined) {
options.end = options.start;
}
if (options.endContainer === undefined) {
options.endContainer = options.startContainer;
}
// FIXME: We may not need character position-based functions if we insert markers in the right places
break;
case 'scrollToCaretPosition':
options = $.extend({
force: false // Force a scroll even if the caret position is already visible
}, options);
break;
}
retval = fn[command].call(this, options);
// cm.focus();
return retval;
};
};
});
//</pre>