Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
//based on [[mw:User:Remember the dot/Syntax highlighter.js]] with a completely different parser
//Dokumentation unter [[Benutzer:Schnark/js/syntaxhighlight]]
//<nowiki>
/*global mediaWiki*/
(function ($, mw, libs) {
"use strict";
var map, protocols,
wikiSyntax, cssSyntax, jsSyntax, jsonSyntax, luaSyntax,
defaultWikitextColors, defaultCssColors, defaultJsColors, strongColors,
hasOwn = Object.prototype.hasOwnProperty;
map = {
opera: [['>=', 15]],
msie: false
};
function clone (array) {
return [Array.prototype.slice.call(array[0]), Array.prototype.slice.call(array[1])];
}
function Parser (syntax) {
var re, res = [];
this.syntax = {};
this.syntax.noparse = syntax.noparse || {};
this.syntax.fn = [];
for (re in syntax.parse) {
if (hasOwn.call(syntax.parse, re)) {
res.push('(' + re + ')');
this.syntax.fn.push(syntax.parse[re]);
}
}
this.syntax.re = new RegExp(res.join('|'), 'g');
this.syntax.eol = syntax.eol || function (o) {
return o;
};
this.syntax.parens = syntax.parens || {
open: '([{',
close: ')]}'
};
this.syntax.parens.all = this.syntax.parens.open + this.syntax.parens.close;
this.cache = {};
}
Parser.prototype = {
open: function (type) {
this.openTags.push(type);
},
close: function (type) {
if (this.openTags.length === 0) {
return;
}
if (this.openTags[this.openTags.length - 1] === type) {
this.openTags.pop();
return;
}
var i = this.openTags.lastIndexOf(type);
if (i > -1) {
this.openTags.length = i;
}
},
isOpen: function (type) {
if (this.openTags.length === 0) {
return false;
}
if (this.openTags[this.openTags.length - 1] === type) {
return true;
}
return this.openTags.lastIndexOf(type) > -1;
},
current: function () {
return this.openTags[this.openTags.length - 1] || '';
},
noparse: function () {
return this.syntax.noparse[this.current()] || false;
},
updateNoparse: function (type, re) {
this.syntax.noparse[type] = re;
},
exec: function (re) {
re.lastIndex = this.pos;
return re.exec(this.text);
},
getText: function (l) {
return this.text.substring(this.pos, this.pos + l);
},
write: function (text) {
if (text === '') {
return;
}
this.pos += text.length;
this.output.push([text, this.current()]);
},
parse: function (text) {
if (text === this.oldText) {
return this.oldParse;
}
this.oldText = text;
this.oldParse = [];
var i, open = [], ret, par = text.split('\n');
for (i = 0; i < par.length; i++) {
ret = this.parseParagraph(par[i], open);
this.oldParse = this.oldParse.concat(ret[0]);
open = this.syntax.eol(ret[1]);
}
return this.oldParse;
},
parseParagraph: function (par, open) {
if (par === '') {
return [
[['\n', open[open.length - 1] || '']],
open
];
}
var key = open.join('|');
if (!this.cache[key]) {
this.cache[key] = {};
}
if (!this.cache[key][par + '\n']) {
this.cache[key][par + '\n'] = this.reallyParseParagraph(par, open);
}
return clone(this.cache[key][par + '\n']);
},
reallyParseParagraph: function (par, open) {
var noparse, result, i, word;
this.output = [];
this.openTags = open;
this.text = par;
this.pos = 0;
while (this.pos < this.text.length) {
noparse = this.noparse();
if (noparse) {
result = this.exec(noparse);
if (result) {
this.write(this.getText(result.index + result[0].length - this.pos));
this.close(this.current());
} else {
this.write(this.text.substring(this.pos));
}
} else {
result = this.exec(this.syntax.re);
if (result) {
this.write(this.getText(result.index - this.pos));
for (i = 0; i < this.syntax.fn.length; i++) {
if (result[i + 1]) {
word = result[i + 1];
this.syntax.fn[i].call(this, word, this.text.substring(this.pos + word.length), this.text.substring(0, this.pos));
break;
}
}
} else {
this.write(this.text.substring(this.pos));
}
}
}
this.write('\n');
return clone([this.output, this.openTags]);
},
findMatchingParen: function (text, pos) {
var me = text.charAt(pos - 1), other, myPos = pos - 1, dir, depth = 1;
if (me === '' || this.syntax.parens.all.indexOf(me) === -1) {
me = text.charAt(pos);
myPos = pos;
}
if (me === '' || this.syntax.parens.all.indexOf(me) === -1) {
return false;
}
dir = this.syntax.parens.open.indexOf(me) === -1 ? -1 : 1;
other = this.syntax.parens[dir === 1 ? 'close' : 'open'].charAt(this.syntax.parens[dir === 1 ? 'open' : 'close'].indexOf(me));
pos = myPos;
do {
pos += dir;
if (text.charAt(pos) === me) {
depth++;
} else if (text.charAt(pos) === other) {
depth--;
if (depth === 0) {
return [Math.min(pos, myPos), Math.max(pos, myPos)];
}
}
} while (text.charAt(pos) !== '');
return false;
},
parseWithParen: function (text, pos) {
var output = this.parse(text), parens = this.findMatchingParen(text, pos), newOutput = [], i, j = 0, oldLen = 0, newLen, t;
if (!parens) {
return output;
}
parens.push(Infinity);
for (i = 0; i < output.length; i++) {
t = output[i][0];
newLen = oldLen + t.length;
while (parens[j] < newLen) {
if (parens[j] > oldLen) {
newOutput.push([t.substring(0, parens[j] - oldLen), output[i][1]]);
t = t.substring(parens[j] - oldLen);
}
newOutput.push([t.charAt(0), 'matching-paren']);
t = t.substring(1);
oldLen = parens[j] + 1;
j++;
}
if (t) {
newOutput.push([t, output[i][1]]);
}
oldLen = newLen;
}
return newOutput;
}
};
function makeParserFunctionOpen (type) {
return function (text) {
this.open(type);
this.write(text);
};
}
function makeParserFunctionType (type) {
return function (text) {
this.open(type);
this.write(text);
this.close(type);
};
}
function makeParserFunctionClose (type) {
return function (text) {
this.write(text);
this.close(type);
};
}
protocols = new RegExp('^(?:' + mw.config.get('wgUrlProtocols') + ')');
function parsePlainLink (proto, text) {
/*jshint validthis: true*///Parser-Funktion mit explizitem Kontext
var link = /[^ <>|\[\]]*/.exec(text)[0], punc = ',;.:!?';
if (link.indexOf('(') === -1) {
punc += ')';
}
while (link && punc.indexOf(link.charAt(link.length - 1)) !== -1) {
link = link.substring(0, link.length - 1);
}
this.open('externalLink');
this.write(proto + link);
this.close('externalLink');
}
function keywords (words, type) {
var i, syntax = {};
for (i = 0; i < words.length; i++) {
syntax['\\b' + words[i] + '\\b'] = makeParserFunctionType(type || 'keyword');
}
return syntax;
}
wikiSyntax = {
noparse: {
'comment': /-->/g,
'<hiero>': /<\/hiero>/gi,
'<math>': /<\/math>/gi,
'<nowiki>': /<\/nowiki>/gi,
'<pre>': /<\/pre>/gi,
'<score>': /<\/score>/gi,
'<source>': /<\/source>/gi,
'<syntaxhighlight>': /<\/syntaxhighlight>/gi,
'<templatedata>': /<\/templatedata>/gi,
'<timeline>': /<\/timeline>/gi
},
parse: {
'^=': makeParserFunctionOpen('heading'),
'^ *:*\\{\\|': makeParserFunctionOpen('table'),
'^ ': function () {
if (!this.isOpen('template') && !this.isOpen('table')) {
this.open('pre');
}
this.write(' ');
},
'^[*#:;]+': makeParserFunctionType('listAndIndent'),
'^-{4,}': makeParserFunctionType('hr'),
'\\[\\[': makeParserFunctionOpen('wikilink'),
'\\[': function (x, text) {
if (protocols.test(text)) {
this.close('externalLink');
this.open('externalLink');
}
this.write('[');
},
'\\]': function (x, text) {
if (this.isOpen('externalLink')) {
this.write(']');
this.close('externalLink');
} else if (text.charAt(0) === ']') {
this.write(']]');
this.close('wikilink');
} else {
this.write(']');
}
},
'<!--': makeParserFunctionOpen('comment'),
'</?[a-zA-Z]+[^>]*>': function (tag) {
var tagParts = (/<(\/?)([a-z]+)([^>]*)>/i).exec(tag),
tagname = tagParts[2].toLowerCase(),
selfclosing = tagParts[3] && tagParts[3].charAt(tagParts[3].length - 1) === '/',
singleTag = ['br', 'hr', 'wbr', 'nowiki', 'ref', 'references', 'section'],
selfclosingTag = ['br', 'hr', 'wbr'];
if (tagParts[1]) {
this.write(tag);
this.close('<' + tagname + '>');
} else if ((selfclosing && $.inArray(tagname, singleTag) > -1) || $.inArray(tagname, selfclosingTag) > -1) {
this.open('<' + tagname + '>');
this.write(tag);
this.close('<' + tagname + '>');
} else {
this.open('<' + tagname + '>');
this.write(tag);
}
},
'\\{\\{+': function (braces, text) {
//Multiple braces are either parameters, or templates, or just braces. It is impossible to tell, unless you go ahead and check in which order they are closed. That's something we just wo'n't do, so this code might fail, but it should work for all common patterns.
//if the following text starts with # or !}}, the last pair of braces is start of a "template" (well, of a parser function)
var isTemplate = (text.charAt(0) === '#' || (text.charAt(0) === '!' && text.indexOf('!}}') === 0)), count = braces.length - (isTemplate ? 2 : 0), i;
if (count === 1) {
this.write('{'); //{{{!}} is { + {{!}}
} else {
switch (count % 3) {
case 1: //especially {{{{ gets a template with a name specified by "template" (parser function in most cases), count is at least 4
this.open('template');
this.write('{{');
count -= 2;
/*falls through*/
case 2: //especially {{{{{ gets a template with a name specified by parameter
this.open('template');
this.write('{{');
count -= 2;
/*falls through*/
case 0: //especially {{{{{{ gets a parameter with a name specified by parameter
for (i = 0; i < count / 3; i++) {
this.open('parameter');
this.write('{{{');
}
}
}
if (isTemplate) {
this.open('template');
this.write('{{');
}
},
'\\}': function (x, text, before) {
var closeTable = (/^ *\|$/.test(before)), count = 1, i;
if (text.charAt(0) === '}') {
count = 2;
if (text.charAt(1) === '}') {
count = 3;
}
}
if (count === 1) {
this.write('}');
if (closeTable) {
this.close('table');
}
return;
}
if (count === 3 && this.current() !== 'template' && this.isOpen('parameter')) {
this.write('}}}');
this.close('parameter');
return;
}
if (!closeTable || !this.isOpen('table')) {
this.write('}}');
this.close('template');
return;
}
if (!this.isOpen('template')) {
this.write('}');
this.close('table');
return;
}
//both an open table and a template, and a string "|}}"
for (i = this.openTags.length - 1; i >= 0; i--) {
if (this.openTags[i] === 'table') {
this.write('}');
this.close('table');
return;
} else if (this.openTags[i] === 'template') {
this.write('}}');
this.close('template');
return;
}
}
},
'^\\|-+|^\\|\\+|^[|!]|\\|\\||!!': function (s, next) {
if (this.current() === 'table' && !(s === '|' && next.charAt(0) === '}')) {
this.open('table-syntax');
this.write(s);
this.close('table-syntax');
} else {
this.write(s);
}
},
'~{3,5}': makeParserFunctionType('signature'),
'https?://': parsePlainLink, //TODO? Should we use mw.config.get('wgUrlProtocols') instead?
'\'\'+': function (apos, next) { //FIXME this is a bit too simple
var b = this.isOpen('bold'), i = this.isOpen('italic');
if (apos.length === 4) {
this.write('\'');
apos = '\'\'\'';
} else if (apos.length > 5) {
this.write(apos.substr(5));
apos = '\'\'\'\'\'';
} else if (apos.length === 3 && !b && next.indexOf('\'\'\'') === -1 && (i || next.indexOf('\'\'') !== -1)) { //just a guess
this.write('\'');
apos = '\'\'';
}
if (apos.length === 2) {
if (i) {
this.write('\'\'');
this.close('italic');
} else {
this.open('italic');
this.write('\'\'');
}
} else if (apos.length === 3) {
if (b) {
this.write('\'\'\'');
this.close('bold');
} else {
this.open('bold');
this.write('\'\'\'');
}
} else { //if (apos.length === 5)
if (b && i) { //both bold and italic, figure out which we have to close first
for (i = this.openTags.length - 1; i >= 0; i--) {
if (this.openTags[i] === 'bold') {
this.write('\'\'\'');
this.close('bold');
this.write('\'\'');
this.close('italic');
return;
} else if (this.openTags[i] === 'italic') {
this.write('\'\'');
this.close('italic');
this.write('\'\'\'');
this.close('bold');
return;
}
}
} else if (!b && !i) { //figure out which we should open first
if (next.indexOf('\'\'') === next.indexOf('\'\'\'')) {
this.open('italic');
this.write('\'\'');
this.open('bold');
this.write('\'\'\'');
} else {
this.open('bold');
this.write('\'\'\'');
this.open('italic');
this.write('\'\'');
}
} else if (b /* && !i */) {
this.write('\'\'\'');
this.close('bold');
this.open('italic');
this.write('\'\'');
} else /* if (!b && i) */ {
this.write('\'\'');
this.close('italic');
this.open('bold');
this.write('\'\'\'');
}
}
},
'&(?:#\\d+|#[xX][a-fA-F0-9]+|\\w+);': makeParserFunctionType('entity'),
'__[A-Z_]+__': makeParserFunctionType('magic'),
'\u2013': makeParserFunctionType('char-endash'),
'\u2014': makeParserFunctionType('char-emdash'),
'\u2212': makeParserFunctionType('char-minus')
},
eol: function (open) {
var i;
for (i = 0; i < open.length; i++) {
if ($.inArray(open[i], ['externalLink', 'bold', 'italic', 'heading', 'pre']) !== -1) {
open.length = i;
break;
}
}
return open;
}
};
cssSyntax = {
noparse: {
'comment': /\*\//g
},
parse: {
'/\\*': makeParserFunctionOpen('comment'),
'!important': function () {
if (!this.isOpen('decleration')) {
this.write('!important');
return;
}
this.open('important');
this.write('!important');
this.close('important');
},
'#[^ ,.*#:>{\\[~]*': function (id) {
if (this.isOpen('decleration')) {
this.write('#');
return;
}
this.open('id');
this.write(id);
this.close('id');
},
'\\.[^ ,.*#:>{\\[~]*': function (cls) {
if (this.isOpen('decleration')) {
this.write('.');
return;
}
this.open('class');
this.write(cls);
this.close('class');
},
':[^ ,.*#:>{\\[~]*': function (pseudo, text) {
var i = 0;
if (this.isOpen('decleration')) {
if (pseudo === ':') {
while (text.charAt(i++) === ' ') {
pseudo += ' ';
}
} else {
pseudo = ':';
}
this.write(pseudo);
this.open('value');
return;
}
this.open('pseudo');
this.write(pseudo);
this.close('pseudo');
},
'@\\S*': function (at) {
if (this.isOpen('decleration')) {
this.write('@');
return;
}
this.open('at');
this.write(at);
this.close('at');
},
'\\[': function () {
if (this.isOpen('decleration')) {
this.write('[');
return;
}
this.open('attr');
this.write('[');
},
'\\]': function () {
this.write(']');
this.close('attr');
},
'\\{': function () {
this.open('decleration');
this.write('{');
},
'\\}': function () {
this.close('value');
this.write('}');
this.close('decleration');
},
';': function () {
this.close('value');
this.write(';');
},
'url\\([^)]*\\)?': makeParserFunctionType('string'),
'"(?:[^\\\\"]+|\\\\.)*"?': makeParserFunctionType('string'), // /"(?:[^\\"]+|\\.)*"?/
'\'(?:[^\\\\\']+|\\\\.)*\'?': makeParserFunctionType('string') // /'(?:[^\\']+|\\.)*'?/
}
};
jsSyntax = {
noparse: {
'comment': /\*\//g
},
parse: $.extend({
'"(?:[^\\\\"]+|\\\\.)*"?': makeParserFunctionType('string'),
'\'(?:[^\\\\\']+|\\\\.)*\'?': makeParserFunctionType('string'),
'//': function (c, text) {
this.open('comment');
this.write(c + text);
this.close('comment');
},
'/\\*': makeParserFunctionOpen('comment'),
'/(?:[^\\\\/]+|\\\\.)*/?': function (re, after, before) {
if (/[)\]\w]\s*$/.test(before)) { //probably not a regexp
this.write('/');
} else {
this.open('regexp');
this.write(re);
this.close('regexp');
}
},
'\\[': makeParserFunctionOpen('array'),
'\\]': makeParserFunctionClose('array')
},
//reserved words
keywords(['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', /*'do',*/ 'else', 'finally', 'for', 'function',
'if', /*'in',*/ 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while'], 'reserved'),
//literals, globals
keywords(['Array', 'Boolean', 'Date', 'Error', 'eval', 'false', 'Function', 'Infinity', 'isFinite', 'isNaN', 'JSON', 'Math',
'NaN', 'null', 'Number', 'Object', 'parseInt', 'parseFloat', 'RegExp', 'String', 'true', 'undefined'], 'global'),
//common functions etc. (incomplete)
keywords(['abs', 'addEventListener', 'appendChild', 'apply', 'call', 'ceil', 'charAt', 'charCodeAt', 'clearInterval',
'clearTimeout', 'concat', 'console', 'createElement', 'decodeURIComponent', 'decodeURI', 'document',
'encodeURIComponent', 'encodeURI', 'exec', 'floor', 'fromCharCode', 'getElementById', 'getElementsByTagName',
'indexOf', 'insertBefore', 'join', 'lastIndexOf', 'length', 'match', 'parentNode', 'pop', 'push', 'random',
'removeEventListener', 'replace', 'reverse', 'round', 'search', 'setInterval', 'setTimeout', 'shift', 'slice',
'sort', 'splice', 'split', 'substr', 'substring', 'test', 'toLowerCase', 'toString', 'toUpperCase', 'unshift',
'valueOf', 'window'], 'common'),
//reserved for future / deprecated
keywords(['class', 'const', 'enum', 'export', 'extends', 'implements', 'import', 'interface', 'let', 'package', 'private',
'protected', 'public', 'static', 'super', 'with', 'yield'], 'future'),
//short words
keywords(['do', 'in'], 'reserved')
)
};
jsonSyntax = {
parse: {
'"(?:[^\\\\"]+|\\\\.)*"?': makeParserFunctionType('string'),
'\\btrue\\b': makeParserFunctionType('global'),
'\\bfalse\\b': makeParserFunctionType('global'),
'\\bnull\\b': makeParserFunctionType('global')
}
};
luaSyntax = {
parse: $.extend({
'--\\[=*\\[': function (c) {
this.open('comment');
this.write(c);
this.updateNoparse('comment', new RegExp(c.replace(/--/, '').replace(/\[/g, '\\]'), 'g'));
},
'--': function (c, text) {
this.open('comment');
this.write(c + text);
this.close('comment');
},
'"(?:[^\\\\"]+|\\\\.)*"?': makeParserFunctionType('string'),
'\'(?:[^\\\\\']+|\\\\.)*\'?': makeParserFunctionType('string'),
'\\[=*\\[': function (s) {
this.open('string');
this.write(s);
this.updateNoparse('string', new RegExp(s.replace(/\[/g, '\\]'), 'g'));
},
'\\[': makeParserFunctionOpen('array'), //index
'\\]': makeParserFunctionClose('array'),
'\\{': makeParserFunctionOpen('array'), //table
'\\}': makeParserFunctionClose('array')
},
keywords(['and', 'break', 'do', 'elseif', 'else', 'end', 'false', 'for', 'function', 'if', 'in', 'local', 'nil', 'not',
'or', 'repeat', 'return', 'then', 'true', 'until', 'while'], 'reserved'),
keywords(['ipairs', 'next', 'pairs', 'select', 'tonumber', 'tostring', 'type', 'unpack', '_VERSION', 'coroutine',
'module', 'require', 'string', 'table', 'math'], 'common')
)
};
function DebugParser () {
}
DebugParser.prototype = {
parse: function (text) {
return [[text, '']];
},
parseWithParen: function (text) {
return [[text, '']];
}
};
function BasicHighlighter (syntax, colors, box, prefix) {
this.parser = syntax ? new Parser(syntax) : new DebugParser();
this.colors = syntax ? colors : {};
this.box = box;
this.prefix = prefix;
this.stylesheet = document.getElementsByTagName('head')[0].appendChild(document.createElement('style'));
this.spanCount = 0;
}
BasicHighlighter.prototype = {
enable: function () {
this.stylesheet.disabled = false;
},
disable: function () {
this.stylesheet.disabled = true;
},
destroy: function () {
this.stylesheet.parentNode.removeChild(this.stylesheet);
},
makeSpans: function (n) {
for (; this.spanCount < n; this.spanCount++) {
this.box.appendChild(document.createElement('span')).id = this.prefix + this.spanCount;
}
},
getColor: function (type) {
if (hasOwn.call(this.colors, type)) {
return this.colors[type];
}
if (type.charAt(0) === '<') {
return this.colors.tag;
}
},
getCSS: function (syntax) {
var lastColor = false, css = [], spans = -1, color, text, i;
for (i = 0; i < syntax.length; i++) {
color = this.getColor(syntax[i][1]);
text = syntax[i][0].replace(/(\\|")/g, '\\$1').replace(/\n/g, '\\A ');
if (color !== lastColor) {
if (lastColor !== false) {
css.push('"}');
}
spans++;
lastColor = color;
if (color) {
color = 'background-color:' + color + ';';
} else {
color = '';
}
css.push('#' + this.prefix + Math.floor(spans / 2) + (spans % 2 === 0 ? ':before' : ':after') + '{' + color + 'content:"');
}
css.push(text);
}
css.push('"}');
this.makeSpans(Math.floor(spans / 2) + 1);
return css.join('');
},
highlight: function (text, pos) {
var css;
if (pos === undefined) {
if (text === this.lastText) {
return;
}
this.lastText = text;
css = this.getCSS(this.parser.parse(text));
} else {
this.lastText = false;
css = this.getCSS(this.parser.parseWithParen(text, pos));
}
if (css === this.lastCSS) {
return;
}
this.lastCSS = css;
this.stylesheet.textContent = css;
}
};
function getStyles (el, styles) {
var computedStyle = window.getComputedStyle(el, null), ret = {}, i;
for (i = 0; i < styles.length; i++) {
ret[styles[i]] = computedStyle[styles[i]];
}
return ret;
}
function setStyles (el, styles) {
var s;
for (s in styles) {
if (hasOwn.call(styles, s)) {
el.style[s] = styles[s];
}
}
}
function addPx (length, d) {
var l = Number(length.replace(/px$/, '')) + d;
if (isNaN(l)) {
return length;
}
return String(l) + 'px';
}
function Highlighter (syntax, colors, textarea, paren) {
var _this = this;
if (!syntax) {
this.debug = true;
}
this.textarea = textarea;
this.initBoxes();
this.basicHighlighter = new BasicHighlighter(syntax, colors, this.highlightbox, textarea.id + '-');
this.onoff = $.noop;
this.reportTime = $.noop;
this.getPos = $.noop;
this.paren = paren;
if (paren) {
this.$textarea = $(this.textarea);
mw.loader.using('jquery.textSelection', function () {
_this.getPos = function () {
return _this.$textarea.textSelection('getCaretPosition');
};
});
}
this.enable();
}
Highlighter.prototype = {
isEnabled: function () {
return this.enabled;
},
enable: function () {
if (this.isEnabled()) {
return;
}
this.bindHandlers();
this.basicHighlighter.enable();
this.enabled = true;
this.highlight();
this.syncScroll();
this.onoff(true);
},
disable: function () {
if (!this.isEnabled()) {
return;
}
this.unbindHandlers();
this.basicHighlighter.disable();
this.enabled = false;
this.onoff(false);
},
destroy: function () {
var scrolltop, focus;
focus = (this.textarea === this.textarea.ownerDocument.activeElement);
scrolltop = this.textarea.scrollTop;
this.disable();
this.basicHighlighter.destroy();
this.container.parentNode.insertBefore(this.textarea, this.container);
this.container.parentNode.removeChild(this.container);
setStyles(this.textarea, this.oldStyle);
this.textarea.scrollTop = scrolltop;
if (focus) {
this.textarea.focus();
}
},
initBoxes: function () {
var scrolltop, focus, style, commonStyle, bugfixStyle = {}, profile = $.client.profile();
this.container = document.createElement('div');
this.oldStyle = getStyles(this.textarea, ['backgroundColor', 'display', 'height', 'left',
'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'overflowX', 'overflowY', 'position',
'resize', 'top', 'whiteSpace', 'width', 'MozBoxSizing', 'WebkitBoxSizing', 'boxSizing']);
focus = (this.textarea === this.textarea.ownerDocument.activeElement);
scrolltop = this.textarea.scrollTop;
this.highlightbox = document.createElement('div');
//make sure "highlightbox" is behind the transparent "textarea" and looks *exactly* the same
commonStyle = {
display: 'block',
height: '100%',
margin: '0px',
overflowX: 'auto',
overflowY: 'scroll',
resize: 'none',
whiteSpace: 'pre-wrap',
width: '100%',
MozBoxSizing: 'border-box',
WebkitBoxSizing: 'border-box',
boxSizing: 'border-box'
};
setStyles(this.textarea, $.extend({
backgroundColor: 'transparent',
position: 'absolute',
left: 0,
top: 0
}, commonStyle));
//set fontSize (workaround for subpixel text positioning in Google Chrome/Opera, cf. https://code.google.com/p/chromium/issues/detail?id=395425) and lineHeight (at least for exotic scripts in Firefox) to pixels
setStyles(this.textarea, getStyles(this.textarea, ['fontSize', 'lineHeight']));
style = getStyles(this.textarea, ['MozAppearance', 'WebkitAppearance', 'borderTopWidth', 'borderRightWidth',
'borderBottomWidth', 'borderLeftWidth', 'direction', 'fontFamily', 'fontSize', 'fontStyle',
'fontVariant', 'fontWeight', 'letterSpacing', 'lineHeight', 'paddingTop', 'paddingRight',
'paddingBottom', 'paddingLeft', 'MozTabSize', 'tabSize', 'textAlign', 'textIndent', 'textTransform',
'unicodeBidi', 'verticalAlign', 'wordSpacing', 'wordWrap']);
//workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=157846
//see style for textarea > .anonymous-div
//in jar:file://*/omni.ja!/chrome/toolkit/res/forms.css resp. https://hg.mozilla.org/mozilla-central/file/*/layout/style/forms.css
if (profile.layout === 'gecko' && profile.versionNumber < 29) {
bugfixStyle.paddingLeft = addPx(style.paddingLeft, 1);
bugfixStyle.paddingRight = addPx(style.paddingRight, 1);
}
setStyles(this.highlightbox, $.extend({
backgroundColor: this.oldStyle.backgroundColor,
borderColor: 'transparent',
borderStyle: 'solid',
color: (this.debug ? 'black' : 'transparent')
}, style, commonStyle, bugfixStyle));
this.highlightbox.lang = this.textarea.lang;
if (this.debug) {
this.oldStyle.color = getStyles(this.textarea, ['color']).color;
setStyles(this.textarea, {
color: this.oldStyle.backgroundColor
});
setStyles(this.highlightbox, getStyles(this.textarea, ['text-rendering']));
}
setStyles(this.container, {
height: this.oldStyle.height,
marginTop: this.oldStyle.marginTop,
marginRight: this.oldStyle.marginRight,
marginBottom: this.oldStyle.marginBottom,
marginLeft: this.oldStyle.marginLeft,
position: 'relative'
});
this.textarea.parentNode.insertBefore(this.container, this.textarea);
this.container.appendChild(this.highlightbox);
this.container.appendChild(this.textarea);
this.textarea.scrollTop = scrolltop;
if (focus) {
this.textarea.focus();
}
},
proxy: function (f) {
var _this = this;
if (!this.proxyCache) {
this.proxyCache = {};
}
if (!hasOwn.call(this.proxyCache, f)) {
this.proxyCache[f] = function () {
_this[f].apply(_this);
};
}
return this.proxyCache[f];
},
bindHandlers: function () {
this.textarea.addEventListener('input', this.proxy('onInput'), false);
if (this.paren) {
this.textarea.addEventListener('keyup', this.proxy('onInput'), false);
}
this.textarea.addEventListener('scroll', this.proxy('syncScroll'), false);
this.intervalID1 = window.setInterval(this.proxy('highlight'), 500);
this.intervalID2 = window.setInterval(this.proxy('syncScroll'), 500);
},
unbindHandlers: function () {
this.textarea.removeEventListener('input', this.proxy('onInput'), false);
if (this.paren) {
this.textarea.removeEventListener('keyup', this.proxy('onInput'), false);
}
this.textarea.removeEventListener('scroll', this.proxy('syncScroll'), false);
window.clearInterval(this.intervalID1);
window.clearInterval(this.intervalID2);
},
syncScroll: function () {
if (this.highlightbox.scrollLeft !== this.textarea.scrollLeft) {
this.highlightbox.scrollLeft = this.textarea.scrollLeft;
}
if (this.highlightbox.scrollTop !== this.textarea.scrollTop) {
this.highlightbox.scrollTop = this.textarea.scrollTop;
}
},
onInput: function () {
window.setTimeout(this.proxy('highlight'), 1);
},
highlight: function () {
var time = $.now();
this.basicHighlighter.highlight(this.textarea.value, this.getPos());
this.reportTime($.now() - time);
}
};
function makeHighlighter (syntax, colors, id, label) {
var textarea = document.getElementById(id), highlighter, $label, $checkbox;
if (!textarea) {
return;
}
highlighter = new Highlighter(syntax, colors, textarea, true);
if (label) {
$label = $('<label>', {
'for': 'syntaxhighlight'
}).text(label);
$checkbox = $('<input id="syntaxhighlight" type="checkbox" checked="checked" />');
highlighter.onoff = function (on) {
$checkbox.prop('checked', on);
};
$checkbox.change(function () {
if ($checkbox.prop('checked')) {
highlighter.enable();
} else {
highlighter.disable();
}
});
$('.editCheckboxes').append('\n').append($checkbox).append('\n').append($label);
}
if (mw.util.getParamValue('logTime')) {
highlighter.reportTime = function (ms) {
if (ms) {
window.console.log(ms + ' ms (#' + id + ')');
}
};
}
return highlighter;
}
defaultWikitextColors = {
bold: '#E5E5E5', //'gray'
'char-emdash': '#FFE6FF', //'pink'
'char-endash': '#E5E5E5', //'gray'
'char-minus': '#FFFFCC', //'yellow'
comment: '#E6FFE6', //'green'
entity: '#E6FFE6', //'green'
externalLink: '#E6FFFF', //'cyan'
italic: '#E5E5E5', //'gray'
heading: '#E5E5E5', //'gray'
hr: '#E5E5E5', //'gray'
listAndIndent: '#E6FFE6', //'green'
magic: '#E5E5E5', //'gray'
'matching-paren': '#FFCCCC',
parameter: '#FFCC66', //'orange'
pre: '#E5E5E5', //'gray'
signature: '#FFCC66', //'orange'
tag: '#FFE6FF', //'pink'
table: '#FFFFCC', //'yellow'
'table-syntax': '#FFCC66', //'orange'
template: '#FFFFCC', //'yellow'
wikilink: '#E6E6FF' //'blue'
};
defaultCssColors = {
at: '#FFE6FF',
attr: '#FFE6FF',
'class': '#FFE6FF',
comment: '#E6FFE6',
decleration: '#FFFFCC',
id: '#E6E6FF',
important: '#E6E6FF',
'matching-paren': '#FFCCCC',
pseudo: '#FFE6FF',
string: '#FFCC66',
value: '#E5E5E5'
};
defaultJsColors = {
array: '#FFFFCC',
comment: '#E6FFE6',
common: '#E6FFFF',
future: '#FFE6FF',
global: '#E6FFE6',
'matching-paren': '#FFCCCC',
regexp: '#FFFFCC',
reserved: '#E6E6FF',
string: '#FFCC66'
};
strongColors = { //I use a misconfigured screen, I cannot see the above colors on it at all
at: 'pink',
array: 'yellow',
attr: 'pink',
bold: 'gray',
'char-emdash': 'pink',
'char-endash': 'gray',
'char-minus': 'yellow',
'class': 'pink',
comment: 'green',
common: 'cyan',
decleration: 'yellow',
entity: 'green',
externalLink: 'cyan',
future: 'pink',
global: 'green',
id: '#55f',
important: '#55f',
italic: 'gray',
heading: 'gray',
hr: 'gray',
listAndIndent: 'green',
magic: 'gray',
'matching-paren': 'red',
parameter: 'orange',
pre: 'gray',
pseudo: 'pink',
regexp: 'yellow',
reserved: '#55f',
signature: 'orange',
string: 'orange',
tag: 'pink',
table: 'yellow',
'table-syntax': 'orange',
template: 'yellow',
value: 'gray',
wikilink: '#55f'
};
function initHighlighter(ext, additional) {
var word, id, debug, colors, syntax, highlighter;
id = mw.config.get('wgCanonicalSpecialPageName') === 'Upload' ? 'wpUploadDescription' : 'wpTextbox1';
debug = !!mw.util.getParamValue('debugSyntaxhighlight');
if (!debug) {
colors = getColors(ext);
syntax = getSyntax(ext);
}
if (!debug && additional) {
for (word in additional) {
if (hasOwn.call(additional, word)) {
colors['additional-' + word] = additional[word];
additional[word] = makeParserFunctionType('additional-' + word);
}
}
syntax = $.extend({}, additional, syntax);
}
highlighter = makeHighlighter(syntax, colors, id, id === 'wpTextbox1' ? mw.msg('schnark-syntaxhighlight-enable') : false);
if (!debug && id === 'wpTextbox1') {
makeHighlighter(syntax, colors, 'wpTextbox2'); //for edit conflicts
}
return function () {
highlighter.destroy();
$('#syntaxhighlight, label[for="syntaxhighlight"]').remove();
};
}
function getColors (ext) {
if (mw.config.get('wgServer') === 'http://localhost') {
return strongColors;
}
if (ext === '') {
//TODO use window.syntaxHighlighterConfig to configure colors, remove Color there
return defaultWikitextColors;
}
if (ext === 'css') {
return defaultCssColors;
}
if (ext === 'js' || ext === 'json' || ext === 'lua') { //TODO separate these
return defaultJsColors;
}
}
function getSyntax (ext) {
if (ext === '') {
return wikiSyntax;
}
if (ext === 'css') {
return cssSyntax;
}
if (ext === 'js') {
return jsSyntax;
}
if (ext === 'json') {
return jsonSyntax;
}
if (ext === 'lua') {
return luaSyntax;
}
}
function allowTabs () {
$(function () {
var $textarea = $('#wpTextbox1'), scrolltop;
$textarea.keypress(function (e) {
if (e.keyCode === 9 && !(e.ctrlKey || e.altKey)) {
e.preventDefault();
var text = $textarea.textSelection('getSelection'),
sel = $textarea.textSelection('getCaretPosition', {startAndEnd: true}),
lines = text.split('\n'), i, len = 0;
if (text === '') {
if (e.shiftKey) {
text = $textarea.textSelection('getContents');
if (text.charAt(sel[0] - 1) !== '\t') {
return;
}
text = text.substring(0, sel[0] - 1) + text.substring(sel[0]);
scrolltop = $textarea[0].scrollTop;
$textarea.textSelection('setContents', text);
$textarea.textSelection('setSelection', {start: sel[0] - 1, end: sel[0] - 1});
$textarea[0].scrollTop = scrolltop;
} else {
$textarea.textSelection('encapsulateSelection', {pre: '\t'});
}
return;
}
for (i = 0; i < lines.length; i++) {
if (e.shiftKey) {
if (lines[i].charAt(0) === '\t') {
lines[i] = lines[i].substr(1);
len--;
}
} else {
if (lines[i] !== '') {
lines[i] = '\t' + lines[i];
len++;
}
}
}
if (len !== 0) {
$textarea.textSelection('encapsulateSelection', {peri: lines.join('\n'), replace: true});
$textarea.textSelection('setSelection', {start: sel[0], end: sel[1] + len});
}
}
});
});
}
//only call this when CodeEditor is disabled!
function createHighlighter (deps, ext) {
mw.loader.using(deps, function () {
$(function () {
window.setTimeout(function () { //make sure we initialize *after* WikiEditor
removeHighlighter = initHighlighter(ext, mw.user.options.get('schnark-syntaxhighlight-additional', false));
}, 0);
});
});
}
//call this before you enable CodeEditor!
function removeHighlighter () {
}
function killCodeEditor () {
mw.config.set('wgCodeEditorCurrentLanguage', false);
$(function () {
try { //FIXME WTF?
var context = $('#wpTextbox1').data('wikiEditorContext');
$('img.tool[rel="codeEditor"]').remove();
context.fn.disableCodeEditor();
if (mw.user.options.get('usecodeeditor') !== '0') {
context.fn.setCodeEditorPreference(false);
}
/* var $textarea = $('#wpTextbox1');
$textarea.wikiEditor('disableCodeEditor');
$textarea.wikiEditor('removeFromToolbar', {section: 'main', group: 'format', tool: 'codeEditor'}); */
} catch (e) {
}
});
}
function handleCodeEditor (deps, ext, ceEnabled) {
var $button = $('img.tool[rel=codeEditor]'), $clone = $button.clone(true).removeClass('tool'); //FIXME very hackish
$button.off('click').click(function () {
if (ceEnabled) {
$clone.click();
ceEnabled = false;
createHighlighter(deps, ext);
} else {
removeHighlighter();
$clone.click();
ceEnabled = true;
}
});
}
function initL10N (l10n) {
var i, chain = mw.language.getFallbackLanguageChain();
for (i = chain.length - 1; i >= 0; i--) {
if (chain[i] in l10n) {
mw.messages.set(l10n[chain[i]]);
}
}
}
function init () {
initL10N({
en: {
'schnark-syntaxhighlight-enable': 'enable syntax highlighter'
},
de: {
'schnark-syntaxhighlight-enable': 'Syntaxhervorhebung aktivieren'
}
});
if (!$.client.test(map) && !mw.util.getParamValue('ignoreBlacklist')) {
return;
}
var modelToExt = {
javascript: 'js',
css: 'css',
Scribunto: 'lua',
wikitext: ''
}, ext, deps = [], hasCodeEditor = 0; //0 - no, 1 - yes, disabled, 2 - yes, enabled
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Upload' && mw.user.options.get('usebetatoolbar')) {
deps.push('ext.wikiEditor.toolbar');
mw.util.addCSS('.tool-select .options {z-index: 5;}');
}
ext = hasOwn.call(modelToExt, mw.config.get('wgPageContentModel')) ? modelToExt[mw.config.get('wgPageContentModel')] : 'json';
if (hasOwn.call(mw.user.options.get('schnark-syntaxhighlight-exclude', {}), ext)) {
return;
}
if (ext) {
mw.loader.using('jquery.textSelection', allowTabs);
}
if (ext && mw.user.options.get('usebetatoolbar')) {
if (mw.user.options.exists('usecodeeditor')) {
if (mw.user.options.get('usecodeeditor') !== '0') {
hasCodeEditor = 2;
} else {
hasCodeEditor = 1;
}
} else {
hasCodeEditor = mw.loader.getState('ext.codeEditor') ? 2 : 0;
}
}
if (hasCodeEditor && mw.user.options.get('userjs-schnark-syntaxhighlight-no-code-editor')) {
killCodeEditor();
hasCodeEditor = 0;
}
if (hasCodeEditor) {
mw.loader.using('ext.codeEditor', function () {
$(function () {
handleCodeEditor(deps, ext, hasCodeEditor === 2);
});
});
}
if (hasCodeEditor !== 2) {
createHighlighter(deps, ext);
}
}
if ($.inArray(mw.config.get('wgAction'), ['edit', 'submit']) !== -1 || mw.config.get('wgCanonicalSpecialPageName') === 'Upload') {
mw.loader.using(['jquery.client', 'mediawiki.util', 'mediawiki.language', 'user.options'], init);
}
if (libs.qunit) {
libs.qunit.Parser = Parser;
libs.qunit.wikiSyntax = wikiSyntax;
libs.qunit.jsSyntax = jsSyntax;
//libs.qunit.jsonSyntax = jsonSyntax;
libs.qunit.cssSyntax = cssSyntax;
libs.qunit.luaSyntax = luaSyntax;
}
})(jQuery, mediaWiki, mediaWiki.libs);