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";
mw.messages.set({
'schnark-syntaxhighlight-enable': 'enable syntax highlighter'
});
if ($.inArray(mw.config.get('wgUserLanguage'), ['bar', 'de', 'de-at', 'de-ch', 'de-formal', 'dsb', 'frr',
'gsw', 'hsb', 'ksh', 'lb', 'nds', 'pdc', 'pdt', 'pfl', 'sli', 'stq', 'vmf']) !== -1) {
mw.messages.set({
'schnark-syntaxhighlight-enable': 'Syntaxhervorhebung aktivieren'
});
}
var map, protocols,
wikiSyntax, cssSyntax, jsSyntax, luaSyntax,
defaultWikitextColors, defaultCssColors, defaultJsColors, strongColors;
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) {
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'),
'^ ': function () {
if (!this.isOpen('template')) {
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('{{');
}
},
'\\{\\|': makeParserFunctionOpen('table'),
'\\}': function (x, text, before) {
var closeTable = (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--; i >= 0) {
if (this.openTags[i] === 'table') {
this.write('}');
this.close('table');
return;
} else if (this.openTags[i] === 'template') {
this.write('}}');
this.close('template');
return;
}
}
},
'~{3,5}': makeParserFunctionType('signature'),
'https?:\/\/': parsePlainLink, //TODO? Should we use mw.config.get('wgUrlProtocols') instead?
'\'\'\'\'\'': function () { //FIXME both '''''bi''b''' and '''''bi'''i'' are ok
var b = this.isOpen('bold'), i = this.isOpen('italic');
if (b && i) {
this.write('\'\'\'\'\'');
this.close('bold');
this.close('italic');
} else if (!b && !i) {
this.open('bold');
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('\'\'\'');
}
},
'\'\'\'': function () {
if (this.isOpen('bold')) {
this.write('\'\'\'');
this.close('bold');
} else {
this.open('bold');
this.write('\'\'\'');
}
},
'\'\'': function () {
if (this.isOpen('italic')) {
this.write('\'\'');
this.close('italic');
} else {
this.open('italic');
this.write('\'\'');
}
},
'&(?:#[xX]?\\d+|\\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(['addEventListener', 'appendChild', 'charAt', 'charCodeAt', 'clearInterval', 'clearTimeout', 'console', 'createElement',
'decodeURIComponent', 'decodeURI', 'document', 'encodeURIComponent', 'encodeURI', 'getElementById', 'getElementsByTagName',
'indexOf', 'insertBefore', 'join', 'length', 'parentNode', 'removeEventListener', 'setInterval', 'setTimeout', 'splice',
'substr', 'substring', 'test', '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')
)
};
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 BasicHighlighter (syntax, colors, box, prefix) {
this.parser = new Parser(syntax);
this.colors = 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 (type in this.colors) {
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) {
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;
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 () {
this.disable();
this.basicHighlighter.destroy();
this.container.parentNode.insertBefore(this.textarea, this.container);
this.container.parentNode.removeChild(this.container);
setStyles(this.textarea, this.oldStyle);
},
initBoxes: function () {
var scrolltop, focus, style, commonStyle, bugfixStyle = {}, profile = $.client.profile();
this.container = document.createElement('div');
this.oldStyle = getStyles(this.textarea, ['backgroundColor', 'marginTop', 'marginRight',
'marginBottom', 'marginLeft', 'overflowX', 'overflowY', 'position', 'resize',
'left', 'top', 'width', 'whiteSpace', '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 = {
margin: 0,
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));
style = getStyles(this.textarea, ['direction', 'fontFamily', 'fontSize', 'lineHeight', 'paddingTop', 'paddingRight',
'paddingBottom', 'paddingLeft', 'height', 'wordWrap', 'verticalAlign', 'unicodeBidi',
'MozAppearance', 'WebkitAppearance']);
//workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=157846, see omni.ja!/chrome/toolkit/res/forms.css: textarea > .anonymous-div
if (profile.layout === 'gecko' && profile.layoutVersion <= 20140101) { //FIXME since which version is this fixed (is it really fixed?)
//Honestly, I have no idea what I'm doing here, but if you select a text in FF24/Win resp. FF17/Linux this seems to put them in sync
if (profile.platform === 'win') {
//bugfixStyle.paddingTop = addPx(style.paddingTop, 1);
bugfixStyle.paddingLeft = addPx(style.paddingLeft, 1); //2
bugfixStyle.paddingRight = addPx(style.paddingRight, 1);
} else {
bugfixStyle.paddingLeft = addPx(style.paddingLeft, 1);
bugfixStyle.paddingRight = addPx(style.paddingRight, 2);
}
}
setStyles(this.highlightbox, $.extend({
backgroundColor: this.oldStyle.backgroundColor,
color: 'transparent'
}, style, commonStyle, bugfixStyle));
setStyles(this.container, {
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 (!(f in this.proxyCache)) {
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();
}
});
/*$checkbox.on('dblclick', function () {
highlighter.destroy();
});*/
$('.editCheckboxes').append('\n').append($checkbox).append('\n').append($label);
}
if (mw.config.get('wgUserName') === 'Schnark') {
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'
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',
template: 'yellow',
value: 'gray',
wikilink: '#55f'
};
function initHighlighter(ext, additional) {
var word, id, colors, syntax, highlighter;
id = mw.config.get('wgCanonicalSpecialPageName') === 'Upload' ? 'wpUploadDescription' : 'wpTextbox1';
colors = getColors(ext);
syntax = getSyntax(ext);
if (additional) {
for (word in additional) {
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 (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 === '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 === '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');
$('#wikiEditor-ui-toolbar .group-format [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 init () {
if (!$.client.test(map) && !mw.util.getParamValue('ignoreBlacklist')) {
return;
}
var modelToExt = {
javascript: 'js',
css: 'css',
Scribunto: 'lua'
}, 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 = modelToExt[mw.config.get('wgPageContentModel')] || '';
if (ext in mw.user.options.get('schnark-syntaxhighlight-exclude', {})) {
return;
}
if (ext) {
if (mw.user.options.exists('usecodeeditor')) {
if (mw.user.options.get('usecodeeditor') !== '0') {
hasCodeEditor = 2;
} else {
hasCodeEditor = 1;
}
}
mw.loader.using('jquery.textSelection', allowTabs);
}
if (hasCodeEditor && mw.user.options.get('userjs-schnark-syntaxhighlight-no-code-editor')) {
killCodeEditor();
hasCodeEditor = 0;
}
if (hasCodeEditor) {
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'], init);
}
if (libs.qunit) {
libs.qunit.Parser = Parser;
libs.qunit.wikiSyntax = wikiSyntax;
libs.qunit.jsSyntax = jsSyntax;
libs.qunit.cssSyntax = cssSyntax;
libs.qunit.luaSyntax = luaSyntax;
}
})(jQuery, mediaWiki, mediaWiki.libs);