Kaldari has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/229616

Change subject: [WIP] Syntax highlighter Code adapted from on-wiki 
Syntax_highlighter.js. Not yet cleaned up, just proof-of-concept for now.
......................................................................

[WIP] Syntax highlighter
Code adapted from on-wiki Syntax_highlighter.js.
Not yet cleaned up, just proof-of-concept for now.

Bug: T101246
Change-Id: I10a0a41689bf981d399e802eba9e6ecfd80385d1
---
M WikiEditor.hooks.php
M extension.json
M i18n/en.json
A modules/ext.wikiEditor.highlight.js
4 files changed, 692 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikiEditor 
refs/changes/16/229616/1

diff --git a/WikiEditor.hooks.php b/WikiEditor.hooks.php
index 25e340f..c2ad6da 100644
--- a/WikiEditor.hooks.php
+++ b/WikiEditor.hooks.php
@@ -101,6 +101,21 @@
                        'modules' => array(
                                'ext.wikiEditor.publish',
                        ),
+               ),
+               'highlight' => array(
+                       'preferences' => array(
+                               'wikieditor-highlight' => array(
+                                       'type' => 'toggle',
+                                       'label-message' => 
'wikieditor-highlight-preference',
+                                       'section' => 'editing/labs',
+                               ),
+                       ),
+                       'requirements' => array(
+                               'wikieditor-highlight' => true,
+                       ),
+                       'modules' => array(
+                               'ext.wikiEditor.highlight',
+                       ),
                )
        );
 
@@ -127,6 +142,8 @@
                        'preview' => array( 'global' => false, 'user' => true ),
                        // Adds a button and dialog for step-by-step publishing
                        'publish' => array( 'global' => false, 'user' => true ),
+                       // Provides syntax highlighing within the editor
+                       'highlight' => array( 'global' => false, 'user' => true 
),
                );
 
                // Eww, do a 2d array merge so we don't wipe out settings
diff --git a/extension.json b/extension.json
index c4d4f75..a2bc915 100644
--- a/extension.json
+++ b/extension.json
@@ -6,7 +6,8 @@
                "Trevor Parscal",
                "Roan Kattouw",
                "Nimish Gautam",
-               "Adam Miller"
+               "Adam Miller",
+               "Remember the dot"
        ],
        "url": "https://www.mediawiki.org/wiki/Extension:WikiEditor";,
        "descriptionmsg": "wikieditor-desc",
@@ -393,6 +394,16 @@
                                "wikieditor-publish-dialog-goback"
                        ]
                },
+               "ext.wikiEditor.highlight": {
+                       "group": "ext.wikiEditor",
+                       "scripts": "ext.wikiEditor.highlight.js",
+                       "dependencies": [
+                               "ext.wikiEditor"
+                       ],
+                       "messages": [
+                               "wikieditor-highlight-disabled"
+                       ]
+               },
                "ext.wikiEditor.toolbar": {
                        "group": "ext.wikiEditor",
                        "scripts": "ext.wikiEditor.toolbar.js",
diff --git a/i18n/en.json b/i18n/en.json
index cca3585..b5216af 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -21,6 +21,8 @@
     "wikieditor-publish-dialog-watch": "Watch this page",
     "wikieditor-publish-dialog-publish": "Publish",
     "wikieditor-publish-dialog-goback": "Go back",
+    "wikieditor-highlight-preference": "Enable syntax highlighting",
+    "wikieditor-highlight-disabled": "Syntax highlighting on this page was 
disabled because it took too long. The maximum allowed highlighting time is 
$1ms, and your computer took $2ms.",
     "wikieditor-toolbar": "Editing toolbar",
     "wikieditor-toolbar-desc": "Edit page toolbar with enhanced usability",
     "wikieditor-toolbar-preference": "Enable enhanced editing toolbar",
diff --git a/modules/ext.wikiEditor.highlight.js 
b/modules/ext.wikiEditor.highlight.js
new file mode 100644
index 0000000..16204af
--- /dev/null
+++ b/modules/ext.wikiEditor.highlight.js
@@ -0,0 +1,661 @@
+/**
+ * Load syntax highlighting
+ *
+ * Adapted from:
+ * https://www.mediawiki.org/wiki/User:Remember_the_dot/Syntax_highlighter.js
+ * Copyright Remember the dot, 2012–2015
+ */
+
+/* This file may be used under the terms of any of the following
+   licenses, as well as any later version of the same licenses:
+
+   GNU General Public License 2.0
+   <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
+
+   Creative Commons Attribution-ShareAlike 3.0 Unported License
+   <https://creativecommons.org/licenses/by-sa/3.0/>
+
+   GNU Free Documentation License 1.2
+   <https://www.gnu.org/licenses/old-licenses/fdl-1.2.html>
+*/
+
+( function ( mw, $ ) {
+       "use strict";
+       
+       //variables that are preserved between function calls
+       var wpTextbox0;
+       var wpTextbox1;
+       var syntaxStyleTextNode;
+       var lastText;
+       var maxSpanNumber = -1; //the number of the last span available, used 
to tell if creating additional spans is necessary
+       var highlightSyntaxIfNeededIntervalID;
+       var attributeObserver;
+       var sourceTagRegex;
+       var nowikiTagRegex;
+       
+       /* Define context-specific regexes, one for every common token that 
ends the
+          current context.
+          
+          An attempt has been made to search for the most common syntaxes 
first,
+          thus maximizing performance. Syntaxes that begin with the same 
character
+          are searched for at the same time.
+          
+          Supported wiki syntaxes from most common to least common:
+                  [[internal link]] [http:// named external link]
+                  {{template}} {{{template parameter}}} {| table |}
+                  <tag> <!-- comment -->
+                  http:// bare external link
+                  =Heading= * unordered list # ordered list : indent ; small 
heading ---- horizontal line
+                  ''italic'' '''bold'''
+                  three tildes username four tildes signature five tildes 
timestamp
+                  &entity;
+          
+          The tag-matching regex follows the XML standard closely so that users
+          won't feel like they have to escape sequences that MediaWiki will 
never
+          consider to be tags.
+          
+          Only entities for characters which need to be escaped or cannot be
+          unambiguously represented in a monospace font are highlighted, such 
as
+          Greek letters that strongly resemble Latin letters. Use of other 
entities
+          is discouraged as a matter of style. For the same reasons, numeric
+          entities should be in hexadecimal (giving character codes in decimal 
only
+          adds confusion).
+          
+          Newlines are sucked up into ending tokens (including comments, bare
+          external links, lists, horizontal lines, signatures, entities, etc.) 
to
+          avoid creating spans with nothing but newlines in them.
+          
+          Flags: g for global search, m for make ^ match the beginning of each 
line
+          and $ the end of each line
+       */
+       var breakerRegexBase = 
"\\[(?:\\[|(?:https?:|ftp:)?//|mailto:)|\\{(?:\\{\\{?|\\|)|<(?:[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD-\\.\u00B7\u0300-\u036F\u203F-\u203F-\u2040]*(?=/?>|
 
|\n)|!--[^]*?-->\n*)|(?:https?://|ftp://|mailto:)[^\\s\"<>[\\]{-}]*[^\\s\",\\.:;<>[\\]{-}]\n*|^(?:=|[*#:;]+\n*|-{4,}\n*)|\\\\'\\\\'(?:\\\\')?|~{3,5}\n*";
+       var entityRegexBase = 
"&(?:(?:n(?:bsp|dash)|m(?:dash|inus)|lt|e[mn]sp|thinsp|amp|quot|gt|shy|zwn?j|lrm|rlm|Alpha|Beta|Epsilon|Zeta|Eta|Iota|Kappa|[Mm]u|micro|Nu|[Oo]micron|[Rr]ho|Tau|Upsilon|Chi)|#x[0-9a-fA-F]+);\n*";
+       function breakerRegexWithPrefix(prefix)
+       {
+               //the stop token has to be at the beginning of the regex so 
that it takes precedence over substrings of itself.
+               return new RegExp("(" + prefix + ")\n*|" + breakerRegexBase + 
"|" + entityRegexBase, "gm");
+       }
+       function nowikiTagBreakerRegex(tagName)
+       {
+               return new RegExp("(</" + tagName + ">)\n*|" + entityRegexBase, 
"gm");
+       }
+       var defaultBreakerRegex            = new RegExp(breakerRegexBase, "gm");
+       var wikilinkBreakerRegex                  = 
breakerRegexWithPrefix("]][a-zA-Z]*");
+       var namedExternalLinkBreakerRegex = breakerRegexWithPrefix("]");
+       var parameterBreakerRegex                = 
breakerRegexWithPrefix("}}}");
+       var templateBreakerRegex                  = 
breakerRegexWithPrefix("}}");
+       var tableBreakerRegex                    = 
breakerRegexWithPrefix("\\|}");
+       var headingBreakerRegex            = breakerRegexWithPrefix("\n");
+       var tagBreakerRegexCache                  = {};
+       var nowikiTagBreakerRegexCache  = {};
+       
+       function highlightSyntax()
+       {
+               lastText = wpTextbox1.value;
+               /* Backslashes and apostrophes are CSS-escaped at the beginning 
and all
+                  parsing regexes and functions are designed to match. On the 
other hand,
+                  newlines are not escaped until written so that in the 
regexes ^ and $
+                  work for both newlines and the beginning or end of the 
string. */
+               var text = lastText.replace(/['\\]/g, "\\$&") + "\n"; //add a 
newline to fix scrolling and parsing issues
+               var i = 0; //the location of the parser as it goes through var 
text
+               
+               var css = "";
+               var spanNumber = 0;
+               var lastColor;
+               var before = true;
+               
+               //writes text into to-be-created span elements of wpTextbox0 
using :before and :after pseudo-elements
+               //both :before and :after are used because using two 
pseudo-elements per span is significantly faster than doubling the number of 
spans required
+               function writeText(text, color)
+               {
+                       //no need to use another span if using the same color
+                       if (color != lastColor)
+                       {
+                               //whitespace is omitted in the hope of 
increasing performance
+                               css += "'}#s" + spanNumber; //spans will be 
created with IDs s0 through sN
+                               if (before)
+                               {
+                                       css += ":before{";
+                                       before = false;
+                               }
+                               else
+                               {
+                                       css += ":after{";
+                                       before = true;
+                                       ++spanNumber;
+                               }
+                               if (color)
+                               {
+                                       //"background-color" is 6 characters 
longer than "background" but the browser processes it faster
+                                       css += "background-color:" + color + 
";";
+                               }
+                               css += "content:'";
+                               lastColor = color;
+                       }
+                       css += text;
+               }
+               
+               /* About assumedBold and assumedItalic:
+                  
+                  Highlighting bold or italic markup presents a special 
challenge
+                  because the actual MediaWiki parser uses multiple passes to 
determine
+                  which ticks represent start tags and which represent end 
tags.
+                  Because that would be too slow for us here, we instead keep 
track of
+                  what kinds of unclosed opening ticks have been encountered 
and use
+                  that to make a good guess as to whether the next ticks 
encountered
+                  are an opening tag or a closing tag.
+                  
+                  The major downsides to this method are that '''apostrophe 
italic''
+                  and ''italic apostrophe''' are not highlighted correctly, 
and bold
+                  and italic are both highlighted in the same color.
+                  
+                  To permit ''The best<ref>''Reference Title''</ref> book 
ever'',
+                  assumedBold and assumedItalic are saved on the stack and 
reset to
+                  undefined (essentially, false) when recursing into a new 
block. */
+               
+               function highlightBlock(color, breakerRegex, assumedBold, 
assumedItalic)
+               {
+                       var match;
+                       
+                       for (breakerRegex.lastIndex = i; match = 
breakerRegex.exec(text); breakerRegex.lastIndex = i)
+                       {
+                               if (match[1])
+                               {
+                                       //end token found
+                                       writeText(text.substring(i, 
breakerRegex.lastIndex), color);
+                                       i = breakerRegex.lastIndex;
+                                       return;
+                               }
+                               
+                               var endIndexOfLastColor = 
breakerRegex.lastIndex - match[0].length;
+                               if (i < endIndexOfLastColor) //avoid calling 
writeText with text == "" to improve performance
+                               {
+                                       writeText(text.substring(i, 
endIndexOfLastColor), color);
+                               }
+                               
+                               i = breakerRegex.lastIndex;
+                               
+                               switch (match[0].charAt(0)) //cases in this 
switch should be arranged from most common to least common
+                               {
+                                       case "[":
+                                               if (match[0].charAt(1) == "[")
+                                               {
+                                                       //wikilink
+                                                       writeText("[[", 
syntaxHighlighterConfig.wikilinkColor || color);
+                                                       
highlightBlock(syntaxHighlighterConfig.wikilinkColor || color, 
wikilinkBreakerRegex);
+                                               }
+                                               else
+                                               {
+                                                       //named external link
+                                                       writeText(match[0], 
syntaxHighlighterConfig.externalLinkColor || color);
+                                                       
highlightBlock(syntaxHighlighterConfig.externalLinkColor || color, 
namedExternalLinkBreakerRegex);
+                                               }
+                                               break;
+                                       case "{":
+                                               if (match[0].charAt(1) == "{")
+                                               {
+                                                       if (match[0].length == 
3)
+                                                       {
+                                                               //parameter
+                                                               
writeText("{{{", syntaxHighlighterConfig.parameterColor || color);
+                                                               
highlightBlock(syntaxHighlighterConfig.parameterColor || color, 
parameterBreakerRegex);
+                                                       }
+                                                       else
+                                                       {
+                                                               //template
+                                                               writeText("{{", 
syntaxHighlighterConfig.templateColor || color);
+                                                               
highlightBlock(syntaxHighlighterConfig.templateColor || color, 
templateBreakerRegex);
+                                                       }
+                                               }
+                                               else //|
+                                               {
+                                                       //table
+                                                       writeText("{|", 
syntaxHighlighterConfig.tableColor || color);
+                                                       
highlightBlock(syntaxHighlighterConfig.tableColor || color, tableBreakerRegex);
+                                               }
+                                               break;
+                                       case "<":
+                                               if (match[0].charAt(1) == "!")
+                                               {
+                                                       //comment tag
+                                                       writeText(match[0], 
syntaxHighlighterConfig.commentColor || color);
+                                                       break;
+                                               }
+                                               else
+                                               {
+                                                       //some other kind of 
tag, search for its end
+                                                       //the search is made 
easier because XML attributes may not contain the character ">"
+                                                       var tagEnd = 
text.indexOf(">", i) + 1;
+                                                       if (tagEnd === 0)
+                                                       {
+                                                               //not a tag, 
just a "<" with some text after it
+                                                               writeText("<", 
color);
+                                                               i = i - 
match[0].length + 1;
+                                                               break;
+                                                       }
+                                                       
+                                                       if (text.charAt(tagEnd 
- 2) == "/")
+                                                       {
+                                                               //empty tag
+                                                               
writeText(text.substring(i - match[0].length, tagEnd), 
syntaxHighlighterConfig.tagColor || color);
+                                                               i = tagEnd;
+                                                       }
+                                                       else
+                                                       {
+                                                               var tagName = 
match[0].substring(1);
+                                                               
+                                                               if 
(sourceTagRegex.test(tagName))
+                                                               {
+                                                                       //tag 
that contains text in a different programming language
+                                                                       var 
stopAfter = "</" + tagName + ">";
+                                                                       var 
endIndex = text.indexOf(stopAfter, i);
+                                                                       if 
(endIndex == -1)
+                                                                       {
+                                                                               
endIndex = text.length;
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               
endIndex += stopAfter.length;
+                                                                       }
+                                                                       
writeText(text.substring(i - match[0].length, endIndex), 
syntaxHighlighterConfig.tagColor || color);
+                                                                       i = 
endIndex;
+                                                               }
+                                                               else if 
(nowikiTagRegex.test(tagName))
+                                                               {
+                                                                       //tag 
that can contain only HTML entities
+                                                                       
writeText(text.substring(i - match[0].length, tagEnd), 
syntaxHighlighterConfig.tagColor || color);
+                                                                       i = 
tagEnd;
+                                                                       
highlightBlock(syntaxHighlighterConfig.tagColor || color, 
nowikiTagBreakerRegexCache[tagName]);
+                                                               }
+                                                               else
+                                                               {
+                                                                       
//ordinary tag
+                                                                       
writeText(text.substring(i - match[0].length, tagEnd), 
syntaxHighlighterConfig.tagColor || color);
+                                                                       i = 
tagEnd;
+                                                                       if 
(!tagBreakerRegexCache[tagName])
+                                                                       {
+                                                                               
tagBreakerRegexCache[tagName] = breakerRegexWithPrefix("</" + tagName + ">");
+                                                                       }
+                                                                       
highlightBlock(syntaxHighlighterConfig.tagColor || color, 
tagBreakerRegexCache[tagName]);
+                                                               }
+                                                       }
+                                               }
+                                               break;
+                                       case "h":
+                                       case "f":
+                                       case "m":
+                                               //bare external link
+                                               writeText(match[0], 
syntaxHighlighterConfig.externalLinkColor || color);
+                                               break;
+                                       case "=":
+                                               if 
(/[^=]=+$/.test(text.substring(i, text.indexOf("\n", i)))) //the line begins 
and ends with an equals sign and has something else in the middle
+                                               {
+                                                       //heading
+                                                       writeText("=", 
syntaxHighlighterConfig.headingColor || color);
+                                                       
highlightBlock(syntaxHighlighterConfig.headingColor || color, 
headingBreakerRegex);
+                                               }
+                                               else
+                                               {
+                                                       writeText("=", color); 
//move on, process this line as regular wikitext
+                                               }
+                                               break;
+                                       case "*":
+                                       case "#":
+                                       case ":":
+                                               //unordered list, ordered list, 
indent, small heading
+                                               //just highlight the marker
+                                               writeText(match[0], 
syntaxHighlighterConfig.listOrIndentColor || color);
+                                               break;
+                                       case ";":
+                                               //small heading
+                                               writeText(";", 
syntaxHighlighterConfig.headingColor || color);
+                                               
highlightBlock(syntaxHighlighterConfig.headingColor || color, 
headingBreakerRegex);
+                                               break;
+                                       case "-":
+                                               //horizontal line
+                                               writeText(match[0], 
syntaxHighlighterConfig.hrColor || color);
+                                               break;
+                                       case "\\":
+                                               writeText(match[0], 
syntaxHighlighterConfig.boldOrItalicColor || color);
+                                               if (match[0].length == 6)
+                                               {
+                                                       //bold
+                                                       if (assumedBold)
+                                                       {
+                                                               //end tag
+                                                               if 
(assumedItalic)
+                                                               {
+                                                                       //end 
of bold part of bold-italic block
+                                                                       //block 
is now italic-only
+                                                                       
assumedBold = false;
+                                                               }
+                                                               else
+                                                               {
+                                                                       //end 
of bold block
+                                                                       return;
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               //start tag
+                                                               if 
(assumedItalic)
+                                                               {
+                                                                       //start 
of bold part of previously italic-only block
+                                                                       //block 
is now bold-italic
+                                                                       
assumedBold = true;
+                                                               }
+                                                               else
+                                                               {
+                                                                       //start 
of bold block
+                                                                       
highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, 
defaultBreakerRegex, true, false);
+                                                               }
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       //italic
+                                                       if (assumedItalic)
+                                                       {
+                                                               //end tag
+                                                               if (assumedBold)
+                                                               {
+                                                                       //end 
of italic part of bold-italic block
+                                                                       //block 
is now bold-only
+                                                                       
assumedItalic = false;
+                                                               }
+                                                               else
+                                                               {
+                                                                       //end 
of italic block
+                                                                       return;
+                                                               }
+                                                       }
+                                                       else
+                                                       {
+                                                               //start tag
+                                                               if (assumedBold)
+                                                               {
+                                                                       //start 
of italic part of previously bold-only block
+                                                                       //block 
is now bold-italic
+                                                                       
assumedItalic = true;
+                                                               }
+                                                               else
+                                                               {
+                                                                       //start 
of italic block
+                                                                       
highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, 
defaultBreakerRegex, false, true);
+                                                               }
+                                                       }
+                                               }
+                                               break;
+                                       case "&":
+                                               //entity
+                                               writeText(match[0], 
syntaxHighlighterConfig.entityColor || color);
+                                               break;
+                                       case "~":
+                                               //username, signature, timestamp
+                                               writeText(match[0], 
syntaxHighlighterConfig.signatureColor || color);
+                               }
+                       }
+               }
+               
+               
+               //start!
+               var startTime = Date.now();
+               highlightBlock("", defaultBreakerRegex);
+               
+               //output the leftovers (if any) to make sure whitespace etc. 
matches
+               if (i < text.length)
+               {
+                       writeText(text.substring(i), "");
+               }
+               
+               //if highlighting took too long, disable it.
+               var endTime = Date.now();
+               var execTime = endTime - startTime;
+               if (execTime > syntaxHighlighterConfig.timeout)
+               {
+                       clearInterval(highlightSyntaxIfNeededIntervalID);
+                       wpTextbox1.removeEventListener("input", 
highlightSyntax);
+                       wpTextbox1.removeEventListener("scroll", syncScrollX);
+                       wpTextbox1.removeEventListener("scroll", syncScrollY);
+                       attributeObserver.disconnect();
+                       syntaxStyleTextNode.nodeValue = "";
+
+                       wpTextbox1.style.backgroundColor = "";
+                       wpTextbox1.style.position = "";
+                       wpTextbox0.removeAttribute("dir");
+                       wpTextbox0.removeAttribute("lang");
+                       wpTextbox0.style = "color:red; font-size:small;";
+
+                       wpTextbox0.textContent = mw.message( 
'wikieditor-highlight-disabled', syntaxHighlighterConfig.timeout, execTime 
).plain();
+                       return;
+               }
+               
+               //do we have enough span elements to match the generated CSS?
+               //this step isn't included in the above benchmark because it 
takes a highly variable amount of time
+               if (maxSpanNumber < spanNumber)
+               {
+                       var fragment = document.createDocumentFragment();
+                       
+                       do
+                       {
+                               var spanID = maxSpanNumber++;
+                               
fragment.appendChild(document.createElement("span")).id = "s" + spanID;
+                       }
+                       while (maxSpanNumber < spanNumber);
+                       wpTextbox0.appendChild(fragment);
+               }
+               
+               /* finish CSS: move the extra '} from the beginning to the end 
and CSS-
+                  escape newlines. CSS ignores the space after the hex code of 
the
+                  escaped character */
+               syntaxStyleTextNode.nodeValue = css.substring(2).replace(/\n/g, 
"\\A ") + "'}";
+       }
+       
+       function syncScrollX()
+       {
+               wpTextbox0.scrollLeft = wpTextbox1.scrollLeft;
+       }
+       
+       function syncScrollY()
+       {
+               wpTextbox0.scrollTop = wpTextbox1.scrollTop;
+       }
+       
+       function syncTextDirection()
+       {
+               wpTextbox0.dir = wpTextbox1.dir;
+       }
+       
+       //this function runs once every 500ms to detect changes to wpTextbox1's 
text that the input event does not catch
+       //this happens when another script changes the text without knowing 
that the syntax highlighter needs to be informed
+       function highlightSyntaxIfNeeded()
+       {
+               if (wpTextbox1.value != lastText)
+               {
+                       highlightSyntax();
+               }
+               if (wpTextbox1.scrollLeft != wpTextbox0.scrollLeft)
+               {
+                       syncScrollX();
+               }
+               if (wpTextbox1.scrollTop != wpTextbox0.scrollTop)
+               {
+                       syncScrollY();
+               }
+               if (wpTextbox1.offsetHeight != wpTextbox0.offsetHeight)
+               {
+                       wpTextbox0.style.height = wpTextbox1.offsetHeight + 
"px";
+               }
+       }
+       
+       function setup()
+       {
+               function configureColor(parameterName, hardcodedFallback)
+               {
+                       if (typeof(syntaxHighlighterConfig[parameterName]) == 
"undefined")
+                       {
+                               syntaxHighlighterConfig[parameterName] = 
syntaxHighlighterSiteConfig[parameterName];
+                       }
+                       
+                       if (syntaxHighlighterConfig[parameterName] == "normal")
+                       {
+                               syntaxHighlighterConfig[parameterName] = 
hardcodedFallback;
+                       }
+                       else if (typeof(syntaxHighlighterConfig[parameterName]) 
!= "undefined")
+                       {
+                               return;
+                       }
+                       else if (typeof(syntaxHighlighterConfig.defaultColor) 
!= "undefined")
+                       {
+                               syntaxHighlighterConfig[parameterName] = 
syntaxHighlighterConfig.defaultColor;
+                       }
+                       else
+                       {
+                               syntaxHighlighterConfig[parameterName] = 
hardcodedFallback;
+                       }
+               }
+               
+               function tagNamesToRegex(tagNames)
+               {
+                       
//https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/resources%2Fsrc%2Fmediawiki%2Fmediawiki.RegExp.js
+                       return new RegExp("^(?:" + tagNames.map(function(str) { 
return str.replace(/([\\{}()|.?*+\-\^$\[\]])/g, '\\$1'); }).join("|") + ")$");
+               }
+               
+               window.syntaxHighlighterSiteConfig = 
window.syntaxHighlighterSiteConfig || {};
+               window.syntaxHighlighterConfig = window.syntaxHighlighterConfig 
|| {};
+               
+               //use 3-digit colors instead of 6-digit colors for performance
+               configureColor("boldOrItalicColor",  "#EEE"); //gray
+               configureColor("commentColor",     "#EFE"); //green
+               configureColor("entityColor",           "#DFD"); //green
+               configureColor("externalLinkColor",  "#EFF"); //cyan
+               configureColor("headingColor",     "#EEE"); //gray
+               configureColor("hrColor",                       "#EEE"); //gray
+               configureColor("listOrIndentColor",  "#EFE"); //green
+               configureColor("parameterColor",         "#FC6"); //orange
+               configureColor("signatureColor",         "#FC6"); //orange
+               configureColor("tagColor",                 "#FEF"); //pink
+               configureColor("tableColor",             "#FFC"); //yellow
+               configureColor("templateColor",   "#FFC"); //yellow
+               configureColor("wikilinkColor",   "#EEF"); //blue
+               
+               //tag lists are ordered from most common to least common
+               syntaxHighlighterConfig.nowikiTags = 
syntaxHighlighterConfig.nowikiTags || syntaxHighlighterSiteConfig.nowikiTags || 
["nowiki", "pre"];
+               syntaxHighlighterConfig.sourceTags = 
syntaxHighlighterConfig.sourceTags || syntaxHighlighterSiteConfig.sourceTags || 
["math", "syntaxhighlight", "source", "timeline", "hiero"];
+               syntaxHighlighterConfig.timeout = 
syntaxHighlighterConfig.timeout || syntaxHighlighterSiteConfig.timeout || 50;
+               
+               nowikiTagRegex = 
tagNamesToRegex(syntaxHighlighterConfig.nowikiTags);
+               sourceTagRegex = 
tagNamesToRegex(syntaxHighlighterConfig.sourceTags);
+               
+               syntaxHighlighterConfig.nowikiTags.forEach(function(tagName) {
+                       nowikiTagBreakerRegexCache[tagName] = 
nowikiTagBreakerRegex(tagName);
+               });
+               
+               var textboxContainer = document.createElement("div");
+               wpTextbox0 = document.createElement("div");
+               wpTextbox1 = document.getElementById("wpTextbox1");
+               
+               var syntaxStyleElement = document.createElement("style");
+               syntaxStyleTextNode = 
syntaxStyleElement.appendChild(document.createTextNode(""));
+               
+               //the styling of the textbox and the background div must be 
kept very similar
+               var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
+               var scrollTop = wpTextbox1.scrollTop;
+               var focus = (document.activeElement == wpTextbox1);
+               
+               wpTextbox0.dir                             = wpTextbox1.dir;
+               wpTextbox0.lang                           = wpTextbox1.lang; 
//lang determines which font "monospace" is
+               wpTextbox0.style.backgroundColor = 
wpTextbox1Style.backgroundColor;
+               wpTextbox0.style.border           = "1px solid transparent";
+               wpTextbox0.style.boxSizing         = "border-box";
+               wpTextbox0.style.color             = "transparent"; //makes it 
look just a little bit smoother
+               wpTextbox0.style.fontFamily       = wpTextbox1Style.fontFamily;
+               wpTextbox0.style.fontSize               = 
wpTextbox1Style.fontSize;
+               wpTextbox0.style.lineHeight       = "normal";
+               wpTextbox0.style.marginBottom   = wpTextbox1Style.marginBottom;
+               wpTextbox0.style.marginLeft       = "0";
+               wpTextbox0.style.marginRight     = "0";
+               wpTextbox0.style.marginTop         = wpTextbox1Style.marginTop;
+               wpTextbox0.style.overflowX         = "auto";
+               wpTextbox0.style.overflowY         = "scroll";
+               //horizontal resize would look horribly choppy, better to make 
the user resize the browser window instead
+               wpTextbox0.style.resize           = (wpTextbox1Style.resize == 
"vertical" || wpTextbox1Style.resize == "both" ? "vertical" : "none");
+               wpTextbox0.style.tabSize                 = 
wpTextbox1Style.tabSize;
+               wpTextbox0.style.whiteSpace       = "pre-wrap";
+               wpTextbox0.style.width             = "100%";
+               wpTextbox0.style.wordWrap               = "normal"; //see below
+               
+               wpTextbox1.style.backgroundColor = "transparent";
+               wpTextbox1.style.border           = "1px inset gray";
+               wpTextbox1.style.boxSizing         = "border-box";
+               wpTextbox1.style.fontSize               = 
wpTextbox1Style.fontSize; //resolves alignment problems on mobile chrome
+               wpTextbox1.style.lineHeight       = "normal";
+               wpTextbox1.style.left                   = "0";
+               wpTextbox1.style.margin           = "0";
+               wpTextbox1.style.overflowX         = "auto";
+               wpTextbox1.style.overflowY         = "scroll";
+               wpTextbox1.style.padding                 = "0";
+               wpTextbox1.style.position               = "absolute";
+               wpTextbox1.style.resize           = wpTextbox0.style.resize;
+               wpTextbox1.style.top                     = "0";
+               wpTextbox1.style.width             = "100%";
+               wpTextbox1.style.wordWrap               = "normal"; //overall 
more visually appealing
+               
+               //lock both heights to pixel values so that the browser zoom 
feature works better
+               wpTextbox0.style.height           = wpTextbox1.offsetHeight + 
"px";
+               wpTextbox1.style.height           = wpTextbox0.style.height;
+               
+               textboxContainer.style.clear     = "both";
+               textboxContainer.style.position  = "relative";
+               
+               wpTextbox1.parentNode.insertBefore(textboxContainer, 
wpTextbox1);
+               textboxContainer.appendChild(wpTextbox1);
+               textboxContainer.appendChild(wpTextbox0);
+               
+               //changing the parent resets scrollTop to 0 and removes focus, 
so we have to bring that back
+               wpTextbox0.scrollTop = scrollTop;
+               wpTextbox1.scrollTop = scrollTop;
+               if (focus) wpTextbox1.focus();
+               
+               //fix drop-downs in editing toolbar
+               $('.tool-select *').css({zIndex: 5});
+               
+               document.head.appendChild(syntaxStyleElement);
+               
+               wpTextbox1.addEventListener("input", highlightSyntax);
+               wpTextbox1.addEventListener("scroll", syncScrollX);
+               wpTextbox1.addEventListener("scroll", syncScrollY);
+               attributeObserver = new MutationObserver(syncTextDirection);
+               attributeObserver.observe(wpTextbox1, {attributes: true});
+               highlightSyntaxIfNeededIntervalID = 
setInterval(highlightSyntaxIfNeeded, 500);
+               highlightSyntax();
+       }
+       
+       function queueSetup()
+       {
+               setTimeout(setup, 0);
+       }
+       
+       
+       //enable the highlighter only when editing wikitext pages
+       //in the future a separate parser could be added for CSS and JS pages
+       //blacklist Internet Explorer, it's just too broken
+       var wgAction = mw.config.get("wgAction");
+       if ((wgAction == "edit" || wgAction == "submit") && 
mw.config.get("wgPageContentModel") == "wikitext" && $.client.profile().layout 
!= "trident")
+       {
+               /* The highlighter has to run after any other script (such as 
the
+                  editing toolbar) that reparents wpTextbox1. We make sure that
+                  everything else has run by waiting for the page to 
completely load
+                  and then adding a call to the setup function to the end of 
the event
+                  queue, so that the setup function runs after any other 
triggers set
+                  on the load event. */
+               if (document.readyState == "complete")
+               {
+                       queueSetup();
+               }
+               else
+               {
+                       $(window).load(queueSetup);
+               }
+       }
+}( mediaWiki, jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/229616
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I10a0a41689bf981d399e802eba9e6ecfd80385d1
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikiEditor
Gerrit-Branch: master
Gerrit-Owner: Kaldari <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to