From ca32f08966f1b51fcb19460f0996bb0c4048e6fe Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 3 Dec 2011 13:29:22 +0100 Subject: Update to MediaWiki 1.18.0 * also update ArchLinux skin to chagnes in MonoBook * Use only css to hide our menu bar when printing --- extensions/WikiEditor/modules/contentCollector.js | 439 +++++++ extensions/WikiEditor/modules/ext.wikiEditor.css | 23 + .../WikiEditor/modules/ext.wikiEditor.dialogs.js | 15 + .../WikiEditor/modules/ext.wikiEditor.highlight.js | 8 + extensions/WikiEditor/modules/ext.wikiEditor.js | 8 + .../WikiEditor/modules/ext.wikiEditor.preview.js | 8 + .../modules/ext.wikiEditor.previewDialog.js | 8 + .../WikiEditor/modules/ext.wikiEditor.publish.js | 8 + .../modules/ext.wikiEditor.templateEditor.js | 12 + .../WikiEditor/modules/ext.wikiEditor.templates.js | 12 + .../modules/ext.wikiEditor.tests.toolbar.js | 246 ++++ .../WikiEditor/modules/ext.wikiEditor.toc.js | 8 + .../modules/ext.wikiEditor.toolbar.hideSig.js | 14 + .../WikiEditor/modules/ext.wikiEditor.toolbar.js | 14 + .../modules/images/dialogs/insert-link-error.png | Bin 0 -> 672 bytes .../modules/images/dialogs/insert-link-exists.png | Bin 0 -> 335 bytes .../images/dialogs/insert-link-external-rtl.png | Bin 0 -> 279 bytes .../images/dialogs/insert-link-external.png | Bin 0 -> 282 bytes .../modules/images/dialogs/insert-link-invalid.png | Bin 0 -> 607 bytes .../images/dialogs/insert-link-notexists.png | Bin 0 -> 548 bytes .../modules/images/dialogs/loading-small.gif | Bin 0 -> 4046 bytes .../WikiEditor/modules/images/dialogs/loading.gif | Bin 0 -> 4615 bytes .../modules/images/templateEditor/collapse.png | Bin 0 -> 1022 bytes .../images/templateEditor/dialog-collapsed.png | Bin 0 -> 291 bytes .../images/templateEditor/dialog-expanded.png | Bin 0 -> 277 bytes .../modules/images/templateEditor/expand.png | Bin 0 -> 250 bytes .../modules/images/templateEditor/name-base.png | Bin 0 -> 177 bytes .../modules/images/templateEditor/text-base.png | Bin 0 -> 128 bytes .../modules/images/templateEditor/wiki-text.png | Bin 0 -> 255 bytes extensions/WikiEditor/modules/images/toc/close.png | Bin 0 -> 369 bytes extensions/WikiEditor/modules/images/toc/grab.png | Bin 0 -> 131 bytes extensions/WikiEditor/modules/images/toc/grip.png | Bin 0 -> 159 bytes extensions/WikiEditor/modules/images/toc/open.png | Bin 0 -> 370 bytes .../modules/images/toolbar/arrow-down.png | Bin 0 -> 181 bytes .../modules/images/toolbar/arrow-ltr.png | Bin 0 -> 184 bytes .../modules/images/toolbar/arrow-rtl.png | Bin 0 -> 192 bytes .../WikiEditor/modules/images/toolbar/base.png | Bin 0 -> 3632 bytes .../modules/images/toolbar/button-sprite.png | Bin 0 -> 21076 bytes .../modules/images/toolbar/example-image.png | Bin 0 -> 1124 bytes .../modules/images/toolbar/format-big.png | Bin 0 -> 725 bytes .../modules/images/toolbar/format-bold-A.png | Bin 0 -> 633 bytes .../modules/images/toolbar/format-bold-B.png | Bin 0 -> 688 bytes .../modules/images/toolbar/format-bold-F.png | Bin 0 -> 531 bytes .../modules/images/toolbar/format-bold-G.png | Bin 0 -> 637 bytes .../modules/images/toolbar/format-bold-N.png | Bin 0 -> 702 bytes .../modules/images/toolbar/format-bold-P.png | Bin 0 -> 597 bytes .../modules/images/toolbar/format-bold-V.png | Bin 0 -> 588 bytes .../modules/images/toolbar/format-bold-ka.png | Bin 0 -> 1492 bytes .../modules/images/toolbar/format-bold-ru.png | Bin 0 -> 925 bytes .../modules/images/toolbar/format-bold.png | Bin 0 -> 754 bytes .../modules/images/toolbar/format-indent-rtl.png | Bin 0 -> 1019 bytes .../modules/images/toolbar/format-indent.png | Bin 0 -> 1037 bytes .../modules/images/toolbar/format-italic-A.png | Bin 0 -> 556 bytes .../modules/images/toolbar/format-italic-C.png | Bin 0 -> 485 bytes .../modules/images/toolbar/format-italic-D.png | Bin 0 -> 588 bytes .../modules/images/toolbar/format-italic-I.png | Bin 0 -> 406 bytes .../modules/images/toolbar/format-italic-K.png | Bin 0 -> 625 bytes .../modules/images/toolbar/format-italic-ka.png | Bin 0 -> 1247 bytes .../modules/images/toolbar/format-italic.png | Bin 0 -> 644 bytes .../modules/images/toolbar/format-olist-rtl.png | Bin 0 -> 359 bytes .../modules/images/toolbar/format-olist.png | Bin 0 -> 365 bytes .../modules/images/toolbar/format-small.png | Bin 0 -> 713 bytes .../modules/images/toolbar/format-subscript.png | Bin 0 -> 731 bytes .../modules/images/toolbar/format-superscript.png | Bin 0 -> 729 bytes .../modules/images/toolbar/format-ulist-rtl.png | Bin 0 -> 227 bytes .../modules/images/toolbar/format-ulist.png | Bin 0 -> 223 bytes .../modules/images/toolbar/insert-file.png | Bin 0 -> 1386 bytes .../modules/images/toolbar/insert-gallery.png | Bin 0 -> 338 bytes .../modules/images/toolbar/insert-ilink.png | Bin 0 -> 332 bytes .../modules/images/toolbar/insert-link.png | Bin 0 -> 545 bytes .../modules/images/toolbar/insert-newline.png | Bin 0 -> 784 bytes .../modules/images/toolbar/insert-nowiki.png | Bin 0 -> 1557 bytes .../modules/images/toolbar/insert-redirect-rtl.png | Bin 0 -> 1319 bytes .../modules/images/toolbar/insert-redirect.png | Bin 0 -> 1552 bytes .../modules/images/toolbar/insert-reference.png | Bin 0 -> 371 bytes .../modules/images/toolbar/insert-signature.png | Bin 0 -> 851 bytes .../modules/images/toolbar/insert-table.png | Bin 0 -> 343 bytes .../modules/images/toolbar/insert-xlink.png | Bin 0 -> 413 bytes .../modules/images/toolbar/loading-small.gif | Bin 0 -> 3331 bytes .../WikiEditor/modules/images/toolbar/loading.gif | Bin 0 -> 5423 bytes .../modules/images/toolbar/magnify-clip.png | Bin 0 -> 204 bytes .../modules/images/toolbar/png24/arrow-down.png | Bin 0 -> 187 bytes .../modules/images/toolbar/png24/arrow-left.png | Bin 0 -> 192 bytes .../modules/images/toolbar/png24/arrow-right.png | Bin 0 -> 189 bytes .../modules/images/toolbar/png24/format-big.png | Bin 0 -> 740 bytes .../modules/images/toolbar/png24/format-bold-A.png | Bin 0 -> 682 bytes .../modules/images/toolbar/png24/format-bold-B.png | Bin 0 -> 726 bytes .../modules/images/toolbar/png24/format-bold-F.png | Bin 0 -> 576 bytes .../modules/images/toolbar/png24/format-bold-G.png | Bin 0 -> 728 bytes .../modules/images/toolbar/png24/format-bold-N.png | Bin 0 -> 770 bytes .../modules/images/toolbar/png24/format-bold-P.png | Bin 0 -> 642 bytes .../modules/images/toolbar/png24/format-bold-V.png | Bin 0 -> 670 bytes .../modules/images/toolbar/png24/format-bold.png | Bin 0 -> 682 bytes .../images/toolbar/png24/format-italic-A.png | Bin 0 -> 613 bytes .../images/toolbar/png24/format-italic-C.png | Bin 0 -> 551 bytes .../images/toolbar/png24/format-italic-I.png | Bin 0 -> 449 bytes .../images/toolbar/png24/format-italic-K.png | Bin 0 -> 719 bytes .../modules/images/toolbar/png24/format-italic.png | Bin 0 -> 613 bytes .../modules/images/toolbar/png24/format-olist.png | Bin 0 -> 438 bytes .../modules/images/toolbar/png24/format-small.png | Bin 0 -> 688 bytes .../images/toolbar/png24/format-subscript.png | Bin 0 -> 691 bytes .../images/toolbar/png24/format-superscript.png | Bin 0 -> 698 bytes .../modules/images/toolbar/png24/format-ulist.png | Bin 0 -> 389 bytes .../modules/images/toolbar/png24/generate.sh | 12 + .../modules/images/toolbar/png24/insert-file.png | Bin 0 -> 929 bytes .../images/toolbar/png24/insert-gallery.png | Bin 0 -> 1162 bytes .../modules/images/toolbar/png24/insert-link.png | Bin 0 -> 769 bytes .../images/toolbar/png24/insert-newline.png | Bin 0 -> 834 bytes .../modules/images/toolbar/png24/insert-nowiki.png | Bin 0 -> 1585 bytes .../images/toolbar/png24/insert-redirect.png | Bin 0 -> 1244 bytes .../images/toolbar/png24/insert-reference.png | Bin 0 -> 1128 bytes .../images/toolbar/png24/insert-signature.png | Bin 0 -> 673 bytes .../modules/images/toolbar/png24/insert-table.png | Bin 0 -> 284 bytes .../images/toolbar/png24/search-replace.png | Bin 0 -> 1088 bytes .../modules/images/toolbar/search-replace.png | Bin 0 -> 979 bytes .../WikiEditor/modules/jquery.wikiEditor.css | 112 ++ .../modules/jquery.wikiEditor.dialogs.config.css | 204 +++ .../modules/jquery.wikiEditor.dialogs.config.js | 1150 ++++++++++++++++ .../modules/jquery.wikiEditor.dialogs.css | 56 + .../modules/jquery.wikiEditor.dialogs.js | 217 +++ .../modules/jquery.wikiEditor.highlight.js | 357 +++++ .../WikiEditor/modules/jquery.wikiEditor.html | 135 ++ .../WikiEditor/modules/jquery.wikiEditor.iframe.js | 1387 ++++++++++++++++++++ extensions/WikiEditor/modules/jquery.wikiEditor.js | 559 ++++++++ .../modules/jquery.wikiEditor.preview.css | 26 + .../modules/jquery.wikiEditor.preview.js | 164 +++ .../modules/jquery.wikiEditor.previewDialog.css | 35 + .../modules/jquery.wikiEditor.previewDialog.js | 131 ++ .../modules/jquery.wikiEditor.publish.js | 146 +++ .../modules/jquery.wikiEditor.templateEditor.js | 865 ++++++++++++ .../modules/jquery.wikiEditor.templates.js | 69 + .../WikiEditor/modules/jquery.wikiEditor.toc.css | 177 +++ .../WikiEditor/modules/jquery.wikiEditor.toc.js | 667 ++++++++++ .../modules/jquery.wikiEditor.toolbar.config.js | 1091 +++++++++++++++ .../modules/jquery.wikiEditor.toolbar.css | 303 +++++ .../modules/jquery.wikiEditor.toolbar.js | 775 +++++++++++ 136 files changed, 9461 insertions(+) create mode 100644 extensions/WikiEditor/modules/contentCollector.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.css create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.dialogs.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.highlight.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.preview.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.previewDialog.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.publish.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.templateEditor.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.templates.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.tests.toolbar.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.toc.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.toolbar.hideSig.js create mode 100644 extensions/WikiEditor/modules/ext.wikiEditor.toolbar.js create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-error.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-external.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png create mode 100644 extensions/WikiEditor/modules/images/dialogs/loading-small.gif create mode 100644 extensions/WikiEditor/modules/images/dialogs/loading.gif create mode 100644 extensions/WikiEditor/modules/images/templateEditor/collapse.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/expand.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/name-base.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/text-base.png create mode 100644 extensions/WikiEditor/modules/images/templateEditor/wiki-text.png create mode 100644 extensions/WikiEditor/modules/images/toc/close.png create mode 100644 extensions/WikiEditor/modules/images/toc/grab.png create mode 100644 extensions/WikiEditor/modules/images/toc/grip.png create mode 100644 extensions/WikiEditor/modules/images/toc/open.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/arrow-down.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/base.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/button-sprite.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/example-image.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-big.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-A.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-B.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-F.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-G.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-N.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-P.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-V.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-bold.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-indent.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-A.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-C.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-D.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-I.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-K.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-italic.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-olist.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-small.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-subscript.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-superscript.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/format-ulist.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-file.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-gallery.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-ilink.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-link.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-newline.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-redirect.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-reference.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-signature.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-table.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/insert-xlink.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/loading-small.gif create mode 100644 extensions/WikiEditor/modules/images/toolbar/loading.gif create mode 100644 extensions/WikiEditor/modules/images/toolbar/magnify-clip.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-big.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-small.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/generate.sh create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-file.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png create mode 100644 extensions/WikiEditor/modules/images/toolbar/search-replace.png create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.highlight.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.html create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.iframe.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.preview.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.preview.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.publish.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.templates.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.toc.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.toc.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.config.js create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.css create mode 100644 extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.js (limited to 'extensions/WikiEditor/modules') diff --git a/extensions/WikiEditor/modules/contentCollector.js b/extensions/WikiEditor/modules/contentCollector.js new file mode 100644 index 00000000..af8d796a --- /dev/null +++ b/extensions/WikiEditor/modules/contentCollector.js @@ -0,0 +1,439 @@ +// THIS FILE HAS BEEN MODIFIED for use with the mediawiki wikiEditor +// It no longer requires etherpad.collab.ace.easysync2.Changeset +// THIS FILE WAS ORIGINALLY AN APPJET MODULE: etherpad.collab.ace.contentcollector + +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +var _MAX_LIST_LEVEL = 8; + +function sanitizeUnicode(s) { + return s.replace(/[\uffff\ufffe\ufeff\ufdd0-\ufdef\ud800-\udfff]/g, '?'); +} + +function makeContentCollector( browser, domInterface ) { + browser = browser || {}; + + var dom = domInterface || { + isNodeText : function(n) { + return (n.nodeType == 3); + }, + nodeTagName : function(n) { + return n.tagName; + }, + nodeValue : function(n) { + try { + return n.nodeValue; + } catch ( err ) { + return ''; + } + }, + nodeName : function(n) { + return n.nodeName; + }, + nodeNumChildren : function(n) { + return n.childNodes.length; + }, + nodeChild : function(n, i) { + return n.childNodes.item(i); + }, + nodeProp : function(n, p) { + return n[p]; + }, + nodeAttr : function(n, a) { + return n.getAttribute(a); + }, + optNodeInnerHTML : function(n) { + return n.innerHTML; + } + }; + + var _blockElems = { + "div" : 1, + "p" : 1, + "pre" : 1, + "li" : 1 + }; + function isBlockElement(n) { + return !!_blockElems[(dom.nodeTagName(n) || "").toLowerCase()]; + } + function textify(str) { + return sanitizeUnicode(str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, + ' ').replace(/\t/g, ' ')); + } + function getAssoc(node, name) { + return dom.nodeProp(node, "_magicdom_" + name); + } + + var lines = (function() { + var textArray = []; + var self = { + length : function() { + return textArray.length; + }, + atColumnZero : function() { + return textArray[textArray.length - 1] === ""; + }, + startNew : function() { + textArray.push(""); + self.flush(true); + }, + textOfLine : function(i) { + return textArray[i]; + }, + appendText : function(txt, attrString) { + textArray[textArray.length - 1] += txt; + // dmesg(txt+" / "+attrString); + }, + textLines : function() { + return textArray.slice(); + }, + // call flush only when you're done + flush : function(withNewline) { + + } + }; + self.startNew(); + return self; + }()); + var cc = {}; + function _ensureColumnZero(state) { + if (!lines.atColumnZero()) { + _startNewLine(state); + } + } + var selection, startPoint, endPoint; + var selStart = [ -1, -1 ], selEnd = [ -1, -1 ]; + var blockElems = { + "div" : 1, + "p" : 1, + "pre" : 1 + }; + function _isEmpty(node, state) { + // consider clean blank lines pasted in IE to be empty + if (dom.nodeNumChildren(node) == 0) + return true; + if (dom.nodeNumChildren(node) == 1 && getAssoc(node, "shouldBeEmpty") + && dom.optNodeInnerHTML(node) == " " + && !getAssoc(node, "unpasted")) { + if (state) { + var child = dom.nodeChild(node, 0); + _reachPoint(child, 0, state); + _reachPoint(child, 1, state); + } + return true; + } + return false; + } + function _pointHere(charsAfter, state) { + var ln = lines.length() - 1; + var chr = lines.textOfLine(ln).length; + if (chr == 0 && state.listType && state.listType != 'none') { + chr += 1; // listMarker + } + chr += charsAfter; + return [ ln, chr ]; + } + function _reachBlockPoint(nd, idx, state) { + if (!dom.isNodeText(nd)) + _reachPoint(nd, idx, state); + } + function _reachPoint(nd, idx, state) { + if (startPoint && nd == startPoint.node && startPoint.index == idx) { + selStart = _pointHere(0, state); + } + if (endPoint && nd == endPoint.node && endPoint.index == idx) { + selEnd = _pointHere(0, state); + } + } + function _incrementFlag(state, flagName) { + state.flags[flagName] = (state.flags[flagName] || 0) + 1; + } + function _decrementFlag(state, flagName) { + state.flags[flagName]--; + } + function _enterList(state, listType) { + var oldListType = state.listType; + state.listLevel = (state.listLevel || 0) + 1; + if (listType != 'none') { + state.listNesting = (state.listNesting || 0) + 1; + } + state.listType = listType; + return oldListType; + } + function _exitList(state, oldListType) { + state.listLevel--; + if (state.listType != 'none') { + state.listNesting--; + } + state.listType = oldListType; + } + function _produceListMarker(state) { + + } + function _startNewLine(state) { + if (state) { + var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; + if (atBeginningOfLine && state.listType && state.listType != 'none') { + _produceListMarker(state); + } + } + lines.startNew(); + } + cc.notifySelection = function(sel) { + if (sel) { + selection = sel; + startPoint = selection.startPoint; + endPoint = selection.endPoint; + } + }; + cc.collectContent = function(node, state) { + if (!state) { + state = { + flags : {/* name -> nesting counter */} + }; + } + var isBlock = isBlockElement(node); + var isEmpty = _isEmpty(node, state); + if (isBlock) + _ensureColumnZero(state); + var startLine = lines.length() - 1; + _reachBlockPoint(node, 0, state); + if (dom.isNodeText(node)) { + var txt = dom.nodeValue(node); + var rest = ''; + var x = 0; // offset into original text + if (txt.length == 0) { + if (startPoint && node == startPoint.node) { + selStart = _pointHere(0, state); + } + if (endPoint && node == endPoint.node) { + selEnd = _pointHere(0, state); + } + } + while (txt.length > 0) { + var consumed = 0; + if (!browser.firefox || state.flags.preMode) { + var firstLine = txt.split('\n', 1)[0]; + consumed = firstLine.length + 1; + rest = txt.substring(consumed); + txt = firstLine; + } else { /* will only run this loop body once */ + } + if (startPoint && node == startPoint.node + && startPoint.index - x <= txt.length) { + selStart = _pointHere(startPoint.index - x, state); + } + if (endPoint && node == endPoint.node + && endPoint.index - x <= txt.length) { + selEnd = _pointHere(endPoint.index - x, state); + } + var txt2 = txt; + if ((!state.flags.preMode) && /^[\r\n]*$/.exec(txt)) { + // prevents textnodes containing just "\n" from being + // significant + // in safari when pasting text, now that we convert them to + // spaces instead of removing them, because in other cases + // removing "\n" from pasted HTML will collapse words + // together. + txt2 = ""; + } + var atBeginningOfLine = lines.textOfLine(lines.length() - 1).length == 0; + if (atBeginningOfLine) { + // newlines in the source mustn't become spaces at beginning + // of line box + txt2 = txt2.replace(/^\n*/, ''); + } + if (atBeginningOfLine && state.listType + && state.listType != 'none') { + _produceListMarker(state); + } + lines.appendText(textify(txt2)); + + x += consumed; + txt = rest; + if (txt.length > 0) { + _startNewLine(state); + } + } + + } else { + var cls = dom.nodeProp(node, "className"); + var tname = (dom.nodeTagName(node) || "").toLowerCase(); + if (tname == "br") { + _startNewLine(state); + } else if (tname == "script" || tname == "style") { + // ignore + } else if (!isEmpty) { + var styl = dom.nodeAttr(node, "style"); + + var isPre = (tname == "pre"); + if ((!isPre) && browser.safari) { + isPre = (styl && /\bwhite-space:\s*pre\b/i.exec(styl)); + } + if (isPre) + _incrementFlag(state, 'preMode'); + var oldListTypeOrNull = null; + + var nc = dom.nodeNumChildren(node); + for ( var i = 0; i < nc; i++) { + var c = dom.nodeChild(node, i); + //very specific IE case where it inserts which we want to ginore. + //to reproduce copy content from wordpad andpaste into the middle of a line in IE + if ( browser.msie && cls.indexOf('wikiEditor') >= 0 && dom.nodeName(c) == 'SPAN' && dom.nodeAttr(c, 'lang') == "" ) { + continue; + } + cc.collectContent(c, state); + } + + if (isPre) + _decrementFlag(state, 'preMode'); + + if (oldListTypeOrNull) { + _exitList(state, oldListTypeOrNull); + } + } + } + if (!browser.msie) { + _reachBlockPoint(node, 1, state); + } + if (isBlock) { + if (lines.length() - 1 == startLine) { + _startNewLine(state); + } else { + _ensureColumnZero(state); + } + } + + if (browser.msie) { + // in IE, a point immediately after a DIV appears on the next line + //_reachBlockPoint(node, 1, state); + } + }; + // can pass a falsy value for end of doc + cc.notifyNextNode = function(node) { + // an "empty block" won't end a line; this addresses an issue in IE with + // typing into a blank line at the end of the document. typed text + // goes into the body, and the empty line div still looks clean. + // it is incorporated as dirty by the rule that a dirty region has + // to end a line. + if ((!node) || (isBlockElement(node) && !_isEmpty(node))) { + _ensureColumnZero(null); + } + }; + // each returns [line, char] or [-1,-1] + var getSelectionStart = function() { + return selStart; + }; + var getSelectionEnd = function() { + return selEnd; + }; + + // returns array of strings for lines found, last entry will be "" if + // last line is complete (i.e. if a following span should be on a new line). + // can be called at any point + cc.getLines = function() { + return lines.textLines(); + }; + + // cc.applyHints = function(hints) { + // if (hints.pastedLines) { + // + // } + // } + + cc.finish = function() { + lines.flush(); + var lineStrings = cc.getLines(); + + if ( lineStrings.length > 0 && !lineStrings[lineStrings.length - 1] ) { + lineStrings.length--; + } + + var ss = getSelectionStart(); + var se = getSelectionEnd(); + + function fixLongLines() { + // design mode does not deal with with really long lines! + var lineLimit = 2000; // chars + var buffer = 10; // chars allowed over before wrapping + var linesWrapped = 0; + var numLinesAfter = 0; + for ( var i = lineStrings.length - 1; i >= 0; i--) { + var oldString = lineStrings[i]; + if (oldString.length > lineLimit + buffer) { + var newStrings = []; + while (oldString.length > lineLimit) { + // var semiloc = oldString.lastIndexOf(';', + // lineLimit-1); + // var lengthToTake = (semiloc >= 0 ? (semiloc+1) : + // lineLimit); + lengthToTake = lineLimit; + newStrings.push(oldString.substring(0, lengthToTake)); + oldString = oldString.substring(lengthToTake); + + } + if (oldString.length > 0) { + newStrings.push(oldString); + } + function fixLineNumber(lineChar) { + if (lineChar[0] < 0) + return; + var n = lineChar[0]; + var c = lineChar[1]; + if (n > i) { + n += (newStrings.length - 1); + } else if (n == i) { + var a = 0; + while (c > newStrings[a].length) { + c -= newStrings[a].length; + a++; + } + n += a; + } + lineChar[0] = n; + lineChar[1] = c; + } + fixLineNumber(ss); + fixLineNumber(se); + linesWrapped++; + numLinesAfter += newStrings.length; + + newStrings.unshift(i, 1); + lineStrings.splice.apply(lineStrings, newStrings); + + } + } + return { + linesWrapped : linesWrapped, + numLinesAfter : numLinesAfter + }; + } + var wrapData = fixLongLines(); + + return { + selStart : ss, + selEnd : se, + linesWrapped : wrapData.linesWrapped, + numLinesAfter : wrapData.numLinesAfter, + lines : lineStrings + }; + }; + + return cc; +} + + diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.css b/extensions/WikiEditor/modules/ext.wikiEditor.css new file mode 100644 index 00000000..42de5368 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.css @@ -0,0 +1,23 @@ +/* + * CSS for WikiEditor + */ + +/* This ID (#editform) could change in MediaWiki */ +form#editform { + margin: 0; + padding: 0; +} +/* These IDs (#wpSummaryLabel and #wpSummary) could change in MediaWiki */ +#wpSummary, #wpSummaryLabel { + margin-bottom: 1em; +} +/* This ID (#wpTextbox1) could change in MediaWiki */ +.wikiEditor-ui textarea#wpTextbox1 { + border: none; + padding: 0; + margin: -1px; + line-height: 1.5em; +} +.wikiEditor-ui .wikiEditor-ui-text > textarea#wpTextbox1 { + margin: 0; +} diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.dialogs.js b/extensions/WikiEditor/modules/ext.wikiEditor.dialogs.js new file mode 100644 index 00000000..45a19593 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.dialogs.js @@ -0,0 +1,15 @@ +/* + * JavaScript for WikiEditor Dialogs + */ + +$( document ).ready( function() { + if ( !$.wikiEditor.isSupported( $.wikiEditor.modules.dialogs ) ) { + return; + } + + // Replace icons + $.wikiEditor.modules.dialogs.config.replaceIcons( $( '#wpTextbox1' ) ); + + // Add dialogs module + $( '#wpTextbox1' ).wikiEditor( 'addModule', $.wikiEditor.modules.dialogs.config.getDefaultConfig() ); +} ); \ No newline at end of file diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.highlight.js b/extensions/WikiEditor/modules/ext.wikiEditor.highlight.js new file mode 100644 index 00000000..a6b43851 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.highlight.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor Highlighting + */ + +$( document ).ready( function() { + // Add highlight module + $( '#wpTextbox1' ).wikiEditor( 'addModule', 'highlight' ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.js b/extensions/WikiEditor/modules/ext.wikiEditor.js new file mode 100644 index 00000000..6056e63b --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor + */ + +$( document ).ready( function() { + // Initialize wikiEditor + $( '#wpTextbox1' ).wikiEditor(); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.preview.js b/extensions/WikiEditor/modules/ext.wikiEditor.preview.js new file mode 100644 index 00000000..3fbca82d --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.preview.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor Preview module + */ + +$( document ).ready( function() { + // Add preview module + $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'preview' ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.previewDialog.js b/extensions/WikiEditor/modules/ext.wikiEditor.previewDialog.js new file mode 100644 index 00000000..67f97e00 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.previewDialog.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor Preview Dialog + */ + +$( document ).ready( function() { + // Add preview module + $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'previewDialog' ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.publish.js b/extensions/WikiEditor/modules/ext.wikiEditor.publish.js new file mode 100644 index 00000000..d874478c --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.publish.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor Publish module + */ + +$( document ).ready( function() { + // Add publish module + $( '#wpTextbox1' ).wikiEditor( 'addModule', 'publish' ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.templateEditor.js b/extensions/WikiEditor/modules/ext.wikiEditor.templateEditor.js new file mode 100644 index 00000000..650910d2 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.templateEditor.js @@ -0,0 +1,12 @@ +/* + * JavaScript for WikiEditor Template Editor + */ + +$( document ).ready( function() { + // Disable in template namespace + if ( mw.config.get( 'wgNamespaceNumber' ) == 10 ) { + return true; + } + // Add template editor module + $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'templateEditor' ); +}); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.templates.js b/extensions/WikiEditor/modules/ext.wikiEditor.templates.js new file mode 100644 index 00000000..4ac09b54 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.templates.js @@ -0,0 +1,12 @@ +/* + * JavaScript for WikiEditor Templates + */ + +$( document ).ready( function() { + // Disable for template namespace + if ( mw.config.get( 'wgNamespaceNumber' ) == 10 ) { + return true; + } + // Add templates module + $( '#wpTextbox1' ).wikiEditor( 'addModule', 'templates' ); +} ); \ No newline at end of file diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.tests.toolbar.js b/extensions/WikiEditor/modules/ext.wikiEditor.tests.toolbar.js new file mode 100644 index 00000000..de01f34f --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.tests.toolbar.js @@ -0,0 +1,246 @@ +/** + * Test set for the edit toolbar + */ +var textareaId = '#wpTextbox1'; +var wikiEditorTests = { + // Add emoticons section + 'add_sections_toolbar': { + 'call': 'addToToolbar', + 'data': { + 'sections': { + 'emoticons': { + 'type': 'toolbar', + 'label': 'Emoticons' + } + } + }, + 'test': '*[rel=emoticons].section', + 'pre': 0, + 'post': 1 + }, + // Add faces group to emoticons section + 'add_groups': { + 'call': 'addToToolbar', + 'data': { + 'section': 'emoticons', + 'groups': { + 'faces': { + 'label': 'Faces' + } + } + }, + 'test': '*[rel=emoticons].section *[rel=faces].group', + 'pre': 0, + 'post': 1 + }, + // Add smile tool to faces group of emoticons section + 'add_tools': { + 'call': 'addToToolbar', + 'data': { + 'section': 'emoticons', + 'group': 'faces', + 'tools': { + 'smile': { + label: 'Smile!', + type: 'button', + icon: 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Gnome-face-smile.svg/22px-Gnome-face-smile.svg.png', + action: { + type: 'encapsulate', + options: { + pre: ":)" + } + } + } + } + }, + 'test': '*[rel=emoticons].section *[rel=faces].group *[rel=smile].tool', + 'pre': 0, + 'post': 1 + }, + // Add info section + 'add_sections_booklet': { + 'call': 'addToToolbar', + 'data': { + 'sections': { + 'info': { + 'type': 'booklet', + 'label': 'Info' + } + } + }, + 'test': '*[rel=info].section', + 'pre': 0, + 'post': 1 + }, + // Add info section + 'add_pages_table': { + 'call': 'addToToolbar', + 'data': { + 'section': 'info', + 'pages': { + 'colors': { + 'layout': 'table', + 'label': 'Colors', + 'headings': [ + { text: 'Name' }, + { text: 'Temperature' }, + { text: 'Swatch' } + ] + } + } + }, + 'test': '*[rel=info].section *[rel=colors].page', + 'pre': 0, + 'post': 1 + }, + // Add colors rows + 'add_rows': { + 'call': 'addToToolbar', + 'data': { + 'section': 'info', + 'page': 'colors', + 'rows': [ + { + 'name': { text: 'Red' }, + 'temp': { text: 'Warm' }, + 'swatch': { html: '
' } + }, + { + 'name': { text: 'Blue' }, + 'temp': { text: 'Cold' }, + 'swatch': { html: '
' } + }, + { + 'name': { text: 'Silver' }, + 'temp': { text: 'Neutral' }, + 'swatch': { html: '
' } + } + ] + }, + 'test': '*[rel=info].section *[rel=colors].page tr td', + 'pre': 0, + 'post': 9 + }, + // Add + 'add_pages_characters': { + 'call': 'addToToolbar', + 'data': { + 'section': 'info', + 'pages': { + 'emoticons': { + 'layout': 'characters', + 'label': 'Emoticons' + }, + 'removeme': { + 'layout': 'characters', + 'label': 'Remove Me!' + } + } + }, + 'test': '*[rel=info].section *[rel=emoticons].page', + 'pre': 0, + 'post': 1 + }, + // Add + 'add_characters': { + 'call': 'addToToolbar', + 'data': { + 'section': 'info', + 'page': 'emoticons', + 'characters': [ ':)', ':))', ':(', '<3', ';)' ] + }, + 'test': '*[rel=info].section *[rel=emoticons].page *[rel=":)"]', + 'pre': 0, + 'post': 1 + }, + // Remove page + 'remove_page': { + 'call': 'removeFromToolbar', + 'data': { + 'section': 'info', + 'page': 'removeme' + }, + 'test': '*[rel=info].section *[rel=removeme].page', + 'pre': 1, + 'post': 0 + }, + // Remove :)) from emoticon characters + 'remove_character': { + 'call': 'removeFromToolbar', + 'data': { + 'section': 'info', + 'page': 'emoticons', + 'character': ':))' + }, + 'test': '*[rel=info].section *[rel=emoticons].page *[rel=":))"]', + 'pre': 1, + 'post': 0 + }, + // Remove row from colors table of info section + 'remove_row': { + 'call': 'removeFromToolbar', + 'data': { + 'section': 'info', + 'page': 'colors', + 'row': 0 + }, + 'test': '*[rel=info].section *[rel=colors].page tr td', + 'pre': 9, + 'post': 6 + } +}; +$(document).ready( function() { + var button = $( '' ) + .css( { + 'position': 'fixed', + 'bottom': 0, + 'right': 0, + 'width': '100%', + 'backgroundColor': '#333333', + 'opacity': 0.75, + 'color': '#DDDDDD', + 'padding': '0.5em', + 'border': 'none', + 'display': 'none' + } ) + .click( function() { + if ( $(this).attr( 'enabled' ) == 'false' ) { + $(this).slideUp( 'fast' ); + return false; + } + var messages = [ 'Running tests for wikiEditor API' ]; + var $target = $( textareaId ); + var $ui = $target.data( 'wikiEditor-context' ).$ui; + var passes = 0; + var tests = 0; + for ( var test in wikiEditorTests ) { + var pre = $ui.find( wikiEditorTests[test].test ).size() == + wikiEditorTests[test].pre; + messages.push ( test + '-pre: ' + ( pre ? 'PASS' : 'FAIL' ) ); + $target.wikiEditor( + wikiEditorTests[test].call, + wikiEditorTests[test].data + ); + var post = $ui.find( wikiEditorTests[test].test ).size() == + wikiEditorTests[test].post; + messages.push ( test + '-post: ' + ( post ? 'PASS' : 'FAIL' ) ); + if ( pre && post ) { + passes++; + } + tests++; + } + if ( window.console !== undefined ) { + for ( var i = 0; i < messages.length; i++ ) { + console.log( messages[i] ); + } + } + $(this) + .attr( 'title', messages.join( " | " ) ) + .text( passes + ' / ' + tests + ' were successful' ) + .css( 'backgroundColor', passes < tests ? 'red' : 'green' ) + .attr( 'enabled', 'false' ) + .blur(); + } ) + .appendTo( $( 'body' ) ); + setTimeout( function() { button.slideDown( 'fast' ) }, 2000 ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.toc.js b/extensions/WikiEditor/modules/ext.wikiEditor.toc.js new file mode 100644 index 00000000..264e9f86 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.toc.js @@ -0,0 +1,8 @@ +/* + * JavaScript for WikiEditor Table of Contents + */ + +$( document ).ready( function() { + // Add table of contents module + $( '#wpTextbox1' ).wikiEditor( 'addModule', 'toc' ); +} ); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.hideSig.js b/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.hideSig.js new file mode 100644 index 00000000..3bc0f0b4 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.hideSig.js @@ -0,0 +1,14 @@ +/* + * Remove the signature button if the main namespace is edited. + */ +$( document ).ready( function() { + // This module is designed not to depend on ext.wikiEditor or jquery.wikiEditor. + // Removing this dependency fixed various bugs, but it does mean that we have to + // account for the situation where $.wikiEditor is not present + if ( !$.wikiEditor || !$.wikiEditor.isSupported( $.wikiEditor.modules.toolbar ) ) { + return; + } + if ( $( 'body' ).hasClass( 'ns-0' ) ) { + $( '#wpTextbox1' ).wikiEditor( 'removeFromToolbar', { 'section': 'main', 'group': 'insert', 'tool': 'signature' } ); + } +}); diff --git a/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.js b/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.js new file mode 100644 index 00000000..53199375 --- /dev/null +++ b/extensions/WikiEditor/modules/ext.wikiEditor.toolbar.js @@ -0,0 +1,14 @@ +/* + * JavaScript for WikiEditor Toolbar + */ + +$( document ).ready( function() { + if ( !$.wikiEditor.isSupported( $.wikiEditor.modules.toolbar ) ) { + return; + } + // The old toolbar is still in place and needs to be removed so there aren't two toolbars + $( '#toolbar' ).remove(); + // Add toolbar module + // TODO: Implement .wikiEditor( 'remove' ) + $( '#wpTextbox1' ).wikiEditor( 'addModule', $.wikiEditor.modules.toolbar.config.getDefaultConfig() ); +} ); diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-error.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-error.png new file mode 100644 index 00000000..a7def768 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-error.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png new file mode 100644 index 00000000..76abbba5 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png new file mode 100644 index 00000000..74fc1ada Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png new file mode 100644 index 00000000..04a5aa17 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png new file mode 100644 index 00000000..b5521b9d Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png new file mode 100644 index 00000000..84eb9d68 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png differ diff --git a/extensions/WikiEditor/modules/images/dialogs/loading-small.gif b/extensions/WikiEditor/modules/images/dialogs/loading-small.gif new file mode 100644 index 00000000..a8e536b0 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/loading-small.gif differ diff --git a/extensions/WikiEditor/modules/images/dialogs/loading.gif b/extensions/WikiEditor/modules/images/dialogs/loading.gif new file mode 100644 index 00000000..52f78205 Binary files /dev/null and b/extensions/WikiEditor/modules/images/dialogs/loading.gif differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/collapse.png b/extensions/WikiEditor/modules/images/templateEditor/collapse.png new file mode 100644 index 00000000..acb42b91 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/collapse.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png b/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png new file mode 100644 index 00000000..ce703020 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png b/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png new file mode 100644 index 00000000..73832e97 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/expand.png b/extensions/WikiEditor/modules/images/templateEditor/expand.png new file mode 100644 index 00000000..4c96ffd3 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/expand.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/name-base.png b/extensions/WikiEditor/modules/images/templateEditor/name-base.png new file mode 100644 index 00000000..4b103085 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/name-base.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/text-base.png b/extensions/WikiEditor/modules/images/templateEditor/text-base.png new file mode 100644 index 00000000..9cb0c74d Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/text-base.png differ diff --git a/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png b/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png new file mode 100644 index 00000000..ea37e731 Binary files /dev/null and b/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png differ diff --git a/extensions/WikiEditor/modules/images/toc/close.png b/extensions/WikiEditor/modules/images/toc/close.png new file mode 100644 index 00000000..511fc4ff Binary files /dev/null and b/extensions/WikiEditor/modules/images/toc/close.png differ diff --git a/extensions/WikiEditor/modules/images/toc/grab.png b/extensions/WikiEditor/modules/images/toc/grab.png new file mode 100644 index 00000000..a0d5d7ba Binary files /dev/null and b/extensions/WikiEditor/modules/images/toc/grab.png differ diff --git a/extensions/WikiEditor/modules/images/toc/grip.png b/extensions/WikiEditor/modules/images/toc/grip.png new file mode 100644 index 00000000..05203f38 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toc/grip.png differ diff --git a/extensions/WikiEditor/modules/images/toc/open.png b/extensions/WikiEditor/modules/images/toc/open.png new file mode 100644 index 00000000..459aa39b Binary files /dev/null and b/extensions/WikiEditor/modules/images/toc/open.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-down.png b/extensions/WikiEditor/modules/images/toolbar/arrow-down.png new file mode 100644 index 00000000..bf2d4fb4 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/arrow-down.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png b/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png new file mode 100644 index 00000000..c27c9636 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png b/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png new file mode 100644 index 00000000..12ca1837 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/base.png b/extensions/WikiEditor/modules/images/toolbar/base.png new file mode 100644 index 00000000..b55ed07b Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/base.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/button-sprite.png b/extensions/WikiEditor/modules/images/toolbar/button-sprite.png new file mode 100644 index 00000000..bd8989ff Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/button-sprite.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/example-image.png b/extensions/WikiEditor/modules/images/toolbar/example-image.png new file mode 100644 index 00000000..a3853683 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/example-image.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-big.png b/extensions/WikiEditor/modules/images/toolbar/format-big.png new file mode 100644 index 00000000..175ea19d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-big.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png new file mode 100644 index 00000000..22849e62 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png new file mode 100644 index 00000000..45be1de6 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png new file mode 100644 index 00000000..e3264767 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png new file mode 100644 index 00000000..39989b42 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png new file mode 100644 index 00000000..4d34a5c9 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png new file mode 100644 index 00000000..e245e71a Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png new file mode 100644 index 00000000..9dfe649b Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png new file mode 100644 index 00000000..7e37b40f Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png new file mode 100644 index 00000000..e24afd88 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold.png b/extensions/WikiEditor/modules/images/toolbar/format-bold.png new file mode 100644 index 00000000..bd8e294c Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-bold.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png new file mode 100644 index 00000000..3cc6c945 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-indent.png b/extensions/WikiEditor/modules/images/toolbar/format-indent.png new file mode 100644 index 00000000..cb864df2 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-indent.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png new file mode 100644 index 00000000..9010c6b1 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png new file mode 100644 index 00000000..cf191fad Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png new file mode 100644 index 00000000..9120da78 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png new file mode 100644 index 00000000..af5bf947 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png new file mode 100644 index 00000000..d8fd479d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png new file mode 100644 index 00000000..2ca7a136 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic.png b/extensions/WikiEditor/modules/images/toolbar/format-italic.png new file mode 100644 index 00000000..1b47eeed Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-italic.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png new file mode 100644 index 00000000..1f065399 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-olist.png b/extensions/WikiEditor/modules/images/toolbar/format-olist.png new file mode 100644 index 00000000..abaeae5f Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-olist.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-small.png b/extensions/WikiEditor/modules/images/toolbar/format-small.png new file mode 100644 index 00000000..9f031e8c Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-small.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-subscript.png b/extensions/WikiEditor/modules/images/toolbar/format-subscript.png new file mode 100644 index 00000000..b676d89d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-subscript.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-superscript.png b/extensions/WikiEditor/modules/images/toolbar/format-superscript.png new file mode 100644 index 00000000..7220b3c8 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-superscript.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png new file mode 100644 index 00000000..51510157 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/format-ulist.png b/extensions/WikiEditor/modules/images/toolbar/format-ulist.png new file mode 100644 index 00000000..3172d4d3 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/format-ulist.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-file.png b/extensions/WikiEditor/modules/images/toolbar/insert-file.png new file mode 100644 index 00000000..1e5419ad Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-file.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png b/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png new file mode 100644 index 00000000..46a9acc5 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png b/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png new file mode 100644 index 00000000..c4010cfe Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-link.png b/extensions/WikiEditor/modules/images/toolbar/insert-link.png new file mode 100644 index 00000000..a8d2f2f3 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-link.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-newline.png b/extensions/WikiEditor/modules/images/toolbar/insert-newline.png new file mode 100644 index 00000000..67a517b4 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-newline.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png b/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png new file mode 100644 index 00000000..80889f4d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png b/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png new file mode 100644 index 00000000..d0200f6e Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png b/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png new file mode 100644 index 00000000..52931264 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-reference.png b/extensions/WikiEditor/modules/images/toolbar/insert-reference.png new file mode 100644 index 00000000..8c2c4aa7 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-reference.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-signature.png b/extensions/WikiEditor/modules/images/toolbar/insert-signature.png new file mode 100644 index 00000000..49cdc957 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-signature.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-table.png b/extensions/WikiEditor/modules/images/toolbar/insert-table.png new file mode 100644 index 00000000..7897e78d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-table.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png b/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png new file mode 100644 index 00000000..7bf24c46 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/loading-small.gif b/extensions/WikiEditor/modules/images/toolbar/loading-small.gif new file mode 100644 index 00000000..ff0688b9 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/loading-small.gif differ diff --git a/extensions/WikiEditor/modules/images/toolbar/loading.gif b/extensions/WikiEditor/modules/images/toolbar/loading.gif new file mode 100644 index 00000000..fab1bb9d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/loading.gif differ diff --git a/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png b/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png new file mode 100644 index 00000000..00a9cee1 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png new file mode 100644 index 00000000..6fd2d63a Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png new file mode 100644 index 00000000..adcba5bd Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png new file mode 100644 index 00000000..52d04e6a Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png new file mode 100644 index 00000000..5e60a178 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png new file mode 100644 index 00000000..429bff1d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png new file mode 100644 index 00000000..cd4a9993 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png new file mode 100644 index 00000000..9f50b490 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png new file mode 100644 index 00000000..4584a8b2 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png new file mode 100644 index 00000000..7c14e28e Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png new file mode 100644 index 00000000..906ee670 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png new file mode 100644 index 00000000..13b258d7 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png new file mode 100644 index 00000000..429bff1d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png new file mode 100644 index 00000000..ee834ca6 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png new file mode 100644 index 00000000..d2a9182b Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png new file mode 100644 index 00000000..a74215d6 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png new file mode 100644 index 00000000..4e91bcc8 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png new file mode 100644 index 00000000..ee834ca6 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png new file mode 100644 index 00000000..69a3186d Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png new file mode 100644 index 00000000..1b6e22d1 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png new file mode 100644 index 00000000..3fc0dce5 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png new file mode 100644 index 00000000..d5e2d90c Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png new file mode 100644 index 00000000..3ddd46cc Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/generate.sh b/extensions/WikiEditor/modules/images/toolbar/png24/generate.sh new file mode 100644 index 00000000..ca14ee68 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/generate.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +# Compresses all PNGs in the current directory and puts the compressed +# version in the parent directory +# +# Requires pngcrush + +for f in *.png +do + pngcrush $f ../$f +done + diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-file.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-file.png new file mode 100644 index 00000000..cabc613a Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-file.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png new file mode 100644 index 00000000..661689ae Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png new file mode 100644 index 00000000..b16eeaba Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png new file mode 100644 index 00000000..d5ec80b6 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png new file mode 100644 index 00000000..f872244e Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png new file mode 100644 index 00000000..288aa516 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png new file mode 100644 index 00000000..9988dbcc Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png new file mode 100644 index 00000000..0b003916 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png new file mode 100644 index 00000000..e506b928 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png b/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png new file mode 100644 index 00000000..de3a75f8 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png differ diff --git a/extensions/WikiEditor/modules/images/toolbar/search-replace.png b/extensions/WikiEditor/modules/images/toolbar/search-replace.png new file mode 100644 index 00000000..cb70b929 Binary files /dev/null and b/extensions/WikiEditor/modules/images/toolbar/search-replace.png differ diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.css b/extensions/WikiEditor/modules/jquery.wikiEditor.css new file mode 100644 index 00000000..6df44565 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.css @@ -0,0 +1,112 @@ +/* + * CSS for WikiEditor jQuery plugin + */ + +.wikiEditor-ui { + float: left; + position: relative; + clear: both; + width: 100%; + background-color: #E0EEf7; + border: solid silver 1px; +} +.wikiEditor-ui textarea:focus { + outline: none; +} +.wikiEditor-ui .wikiEditor-ui-bottom { + +} +.wikiEditor-ui .wikiEditor-ui-text { + line-height: 0; +} +.wikiEditor-ui .wikiEditor-ui-top { + position: relative; + border-bottom: solid silver 1px; +} +.wikiEditor-ui .wikiEditor-ui-left { + float: left; + width: 100%; +} +.wikiEditor-ui .wikiEditor-ui-right { + float: right; + background: #F3F3F3; + overflow: hidden; +} +.wikiEditor-wikitext { + float: left; + width: 100%; +} +.wikiEditor-ui-controls { + float: left; + width: 100%; + background-color: white; + margin-top: -1px; + border-bottom: solid 1px silver; +} +.wikiEditor-ui-tabs { + float: left; + height: 2.5em; + margin-left: -1px; + background-color: white; + border-left: solid 1px silver; + border-top: solid 1px silver; +} +.wikiEditor-ui-buttons { + float: right; + height: 2.5em; + margin-right: -1px; + background-color: white; + padding-left: 1em; + border-top: solid 1px white; +} +.wikiEditor-ui-buttons button { + margin-left: 0.5em; +} +.wikiEditor-ui-tabs div { + float: left; + height: 2.5em; + background-color: #f3f3f3; + border-right: solid 1px silver; + border-bottom: solid 1px silver; +} +.wikiEditor-ui-tabs div.current { + border-bottom: solid 1px white; + background-color: white; +} +.wikiEditor-ui-tabs div a { + display: inline-block; + padding: 0 0.75em; + line-height: 2.5em; + color: #0645AD; +} +.wikiEditor-ui-tabs div.current a { + color: #333333; +} +.wikiEditor-ui-tabs div.current a:hover { + text-decoration: none; +} + +.wikiEditor-view-wikitext { + line-height: 1em; +} +.wikiEditor-ui-loading { + background: #f3f3f3; + z-index: 10; + position: absolute; + top: 0; + left: 0; + text-align: center; + height: 100%; + width: 100%; + border: 1px solid silver; + margin: -1px; +} +.wikiEditor-ui-loading span { + display: block; + height: 24px; + width: 24px; + /* @embed */ + background: url(images/toolbar/loading.gif) 0 0 no-repeat; + text-indent: -9999px; + margin: 0 auto; +} \ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.css b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.css new file mode 100644 index 00000000..13302ac3 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.css @@ -0,0 +1,204 @@ +/* + * CSS for WikiEditor Dialogs + */ + +/* Table Dialog */ +#wikieditor-toolbar-table-dialog fieldset { + width: 218px; + padding: 0; + float: left; +} +body.rtl #wikieditor-toolbar-table-dialog fieldset { + float: right; +} +#wikieditor-toolbar-table-dialog .wikieditor-toolbar-table-preview-wrapper { + width: 330px; + padding: 0; + float: right; +} +body.rtl #wikieditor-toolbar-table-dialog .wikieditor-toolbar-table-preview-wrapper { + float: left; +} +body.rtl .wikiEditor-toolbar-dialog .wikieditor-toolbar-table-preview-wrapper table { + margin-left: 1em; + margin-right: 0; +} +.wikieditor-toolbar-table-preview-content * { + cursor: default; +} +.wikiEditor-toolbar-dialog .wikieditor-toolbar-table-preview-wrapper table { + width: 100% !important; +} +.wikiEditor-toolbar-dialog .wikieditor-toolbar-table-preview-content table td { + padding: 10px 4px !important; + height: auto !important; +} +.wikiEditor-toolbar-dialog .wikieditor-toolbar-table-preview-content table th { + padding: 7px 3px !important; +} +.wikieditor-toolbar-table-dimension-fields .wikieditor-toolbar-field-wrapper { + float: left; + margin-right: 20px; + vertical-align: bottom; +} +body.rtl .wikieditor-toolbar-table-dimension-fields .wikieditor-toolbar-field-wrapper { + float: right; + margin-right: 0px; + margin-left: 20px; +} +.wikiEditor-toolbar-dialog .ui-dialog-content { + padding: 30px 20px 0 !important; +} +.wikieditor-toolbar-dialog-wrapper { + width: 100%; +} +/* Insert Link Dialog */ +#wikieditor-toolbar-link-int-target-status { + float: right; +} +#wikieditor-toolbar-link-int-target, +#wikieditor-toolbar-link-int-text { + width: 100%; +} +#wikieditor-toolbar-tool-link-int-target-label { + float: left; + line-height: 1.7em; +} +#wikieditor-toolbar-link-int-target-status-exists, +#wikieditor-toolbar-link-int-target-status-notexists, +#wikieditor-toolbar-link-int-target-status-invalid, +#wikieditor-toolbar-link-int-target-status-external { + padding-left: 30px; + background-position: 0 50%; + background-repeat: no-repeat; +} +#wikieditor-toolbar-link-int-target-status-exists { + /* @embed */ + background-image: url(images/dialogs/insert-link-exists.png); +} +#wikieditor-toolbar-link-int-target-status-notexists { + /* @embed */ + background-image: url(images/dialogs/insert-link-notexists.png); +} +#wikieditor-toolbar-link-int-target-status-invalid { + /* @embed */ + background-image: url(images/dialogs/insert-link-invalid.png); +} +#wikieditor-toolbar-link-int-target-status-external { + /* @embed */ + background-image: url(images/dialogs/insert-link-external.png); +} +/* Reference Dialog */ +#wikieditor-toolbar-reference-dialog label { + float: left; + line-height: 1.7em; +} +#wikieditor-toolbar-reference-text { + width: 100%; +} +/* RTL Changes */ +body.rtl .wikiEditor-toolbar-dialog .ui-dialog-buttonpane button { + float: left; + margin: 0.5em 0.4em 0.5em 0 !important; +} +body.rtl .wikiEditor-toolbar-dialog .ui-dialog-titlebar-close { + left: 0.9em; + right: auto; +} +body.rtl .wikiEditor-toolbar-dialog .ui-dialog-title { + float:right; +} +body.rtl #wikieditor-toolbar-link-int-target-status { + float: left; +} +body.rtl #wikieditor-toolbar-tool-link-int-target-label { + float: right; +} +body.rtl .wikieditor-toolbar-floated-field-wrapper { + float: right; + margin-right: 0; + margin-left: 2em; +} +body.rtl #wikieditor-toolbar-link-int-target-status-exists, +body.rtl #wikieditor-toolbar-link-int-target-status-notexists, +body.rtl #wikieditor-toolbar-link-int-target-status-invalid, +body.rtl #wikieditor-toolbar-link-int-target-status-external { + padding-left: 0; + padding-right: 30px; + background-position: 100% 50%; +} +body.rtl #wikieditor-toolbar-link-int-target-status-external { + /* @embed */ + background-image: url(images/dialogs/insert-link-external-rtl.png); +} +body.rtl #wikieditor-toolbar-reference-dialog label { + float: right; +} +/* Template Editor Dialogs */ +.wikiEditor-template-dialog-fields label { + text-transform: capitalize; + float: left; + width: 25%; + line-height: 2.25em; +} +.wikiEditor-template-dialog-fields textarea { + float: right; + width: 70%; + line-height: 1.5em; + height: 1.5em; +} +.wikiEditor-template-dialog-fields .wikiEditor-template-dialog-field-wrapper { + padding: 0.75em 0.33em; + border-bottom: dashed 1px silver; + clear: both; +} +.wikiEditor-template-dialog-fields .wikiEditor-template-dialog-field-wrapper:first-child { + padding-top: 0; +} +.wikiEditor-template-dialog-fields .wikiEditor-template-dialog-field-wrapper:last-child { + border-bottom: none; +} +/* Self Clearing Floats */ +.wikieditor-toolbar-table-dimension-fields:after, +.wikieditor-toolbar-dialog-wrapper:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; +} +.wikieditor-toolbar-table-dimension-fields, +.wikieditor-toolbar-dialog-wrapper { + display: inline-table; +} +/* Hides from IE-mac \*/ +* html .wikieditor-toolbar-table-dimension-fields, +* html .wikieditor-toolbar-dialog-wrapper { + height: 1%; +} +.wikieditor-toolbar-table-dimension-fields, +.wikieditor-toolbar-dialog-wrapper { + display: block; +} +/* End hide from IE-mac */ +.wikiEditor-toolbar-dialog .ui-dialog-buttonpane { + border-top: 1px solid #cccccc !important; +} +.wikiEditor-toolbar-dialog .ui-dialog-content { + padding-bottom: 1em !important; +} +/* Edit dialog */ +.wikiEditor-dialog-editoptions { + margin-top: 15px; +} +/* Publish dialog */ +.wikiEditor-publish-dialog-copywarn { + margin-top: 0.5em; +} +.wikiEditor-publish-dialog-summary { + margin-top: 1.5em; +} +.wikiEditor-publish-dialog-options { + margin-top: 1.5em; +} diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.js b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.js new file mode 100644 index 00000000..4ab6ce46 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.config.js @@ -0,0 +1,1150 @@ +/** + * Configuration of Dialog module for wikiEditor + */ +( function( $ ) { $.wikiEditor.modules.dialogs.config = { + +replaceIcons: function( $textarea ) { + $textarea + .wikiEditor( 'removeFromToolbar', { 'section': 'main', 'group': 'insert', 'tool': 'xlink' } ) + .wikiEditor( 'removeFromToolbar', { 'section': 'main', 'group': 'insert', 'tool': 'ilink' } ) + .wikiEditor( 'removeFromToolbar', { 'section': 'main', 'group': 'insert', 'tool': 'reference' } ) + .wikiEditor( 'removeFromToolbar', { 'section': 'advanced', 'group': 'insert', 'tool': 'table' } ) + .wikiEditor( 'addToToolbar', { + 'section': 'main', + 'group': 'insert', + 'tools': { + 'link': { + 'labelMsg': 'wikieditor-toolbar-tool-link', + 'type': 'button', + 'icon': 'insert-link.png', + 'offset': [2, -1654], + 'action': { + 'type': 'dialog', + 'module': 'insert-link' + } + }, + 'reference': { + 'labelMsg': 'wikieditor-toolbar-tool-reference', + 'filters': [ 'body.ns-subject' ], + 'type': 'button', + 'icon': 'insert-reference.png', + 'offset': [2, -1798], + 'action': { + 'type': 'dialog', + 'module': 'insert-reference' + } + } + } + } ) + .wikiEditor( 'addToToolbar', { + 'section': 'advanced', + 'group': 'insert', + 'tools': { + 'table': { + 'labelMsg': 'wikieditor-toolbar-tool-table', + 'type': 'button', + 'icon': 'insert-table.png', + 'offset': [2, -1942], + 'action': { + 'type': 'dialog', + 'module': 'insert-table' + } + } + } + } ) + .wikiEditor( 'addToToolbar', { + 'section': 'advanced', + 'groups': { + 'search': { + 'tools': { + 'replace': { + 'labelMsg': 'wikieditor-toolbar-tool-replace', + 'type': 'button', + 'icon': 'search-replace.png', + 'offset': [-70, -214], + 'action': { + 'type': 'dialog', + 'module': 'search-and-replace' + } + } + } + } + } + } ); +}, + +getDefaultConfig: function () { + return { 'dialogs': { + 'insert-link': { + titleMsg: 'wikieditor-toolbar-tool-link-title', + id: 'wikieditor-toolbar-link-dialog', + html: '\ +
\ +
\ + \ + \ + \ +
\ +
\ + \ + \ +
\ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ +
', + init: function() { + function isExternalLink( s ) { + // The following things are considered to be external links: + // * Starts a URL protocol + // * Starts with www. + // All of these are potentially valid titles, and the latter two categories match about 6300 + // titles in enwiki's ns0. Out of 6.9M titles, that's 0.09% + if ( typeof arguments.callee.regex == 'undefined' ) { + // Cache the regex + arguments.callee.regex = + new RegExp( "^(" + mw.config.get( 'wgUrlProtocols' ) + "|www\\.)", 'i'); + } + return s.match( arguments.callee.regex ); + } + // Updates the status indicator above the target link + function updateWidget( status ) { + $( '#wikieditor-toolbar-link-int-target-status' ).children().hide(); + $( '#wikieditor-toolbar-link-int-target' ).parent() + .removeClass( + 'status-invalid status-external status-notexists status-exists status-loading' + ); + if ( status ) { + $( '#wikieditor-toolbar-link-int-target-status-' + status ).show(); + $( '#wikieditor-toolbar-link-int-target' ).parent().addClass( 'status-' + status ); + } + if ( status == 'invalid' ) { + $( '.ui-dialog:visible .ui-dialog-buttonpane button:first' ) + .attr( 'disabled', true ) + .addClass( 'disabled' ); + } else { + $( '.ui-dialog:visible .ui-dialog-buttonpane button:first' ) + .removeAttr('disabled') + .removeClass('disabled'); + } + } + // Updates the UI to show if the page title being inputed by the user exists or not + // accepts parameter internal for bypassing external link detection + function updateExistence( internal ) { + // ensure the internal parameter is a boolean + if ( internal != true ) internal = false; + // Abort previous request + var request = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'request' ); + if ( request ) { + request.abort(); + } + var target = $( '#wikieditor-toolbar-link-int-target' ).val(); + var cache = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'existencecache' ); + if ( cache[target] ) { + updateWidget( cache[target] ); + return; + } + if ( target.replace( /^\s+$/,'' ) == '' ) { + // Hide the widget when the textbox is empty + updateWidget( false ); + return; + } + // If the forced internal paremter was not true, check if the target is an external link + if ( !internal && isExternalLink( target ) ) { + updateWidget( 'external' ); + return; + } + if ( target.indexOf( '|' ) != -1 ) { + // Title contains | , which means it's invalid + // but confuses the API. Show invalid and bypass API + updateWidget( 'invalid' ); + return; + } + // Show loading spinner while waiting for the API to respond + updateWidget( 'loading' ); + // Call the API to check page status, saving the request object so it can be aborted if + // necessary + $( '#wikieditor-toolbar-link-int-target-status' ).data( + 'request', + $.ajax( { + url: mw.util.wikiScript( 'api' ), + dataType: 'json', + data: { + 'action': 'query', + 'indexpageids': '', + 'titles': target, + 'converttitles': '', + 'format': 'json' + }, + success: function( data ) { + var status; + if ( !data || typeof data.query == 'undefined' ) { + // This happens in some weird cases + status = false; + } else { + var page = data.query.pages[data.query.pageids[0]]; + status = 'exists'; + if ( typeof page.missing != 'undefined' ) + status = 'notexists'; + else if ( typeof page.invalid != 'undefined' ) + status = 'invalid'; + } + // Cache the status of the link target if the force internal parameter was not + // passed + if ( !internal ) cache[target] = status; + updateWidget( status ); + } + } ) + ); + } + $( '#wikieditor-toolbar-link-type-int, #wikieditor-toolbar-link-type-ext' ).click( function() { + if( $( '#wikieditor-toolbar-link-type-ext' ).is( ':checked' ) ) { + // Abort previous request + var request = $( '#wikieditor-toolbar-link-int-target-status' ).data( 'request' ); + if ( request ) { + request.abort(); + } + updateWidget( 'external' ); + } + if( $( '#wikieditor-toolbar-link-type-int' ).is( ':checked' ) ) + updateExistence( true ); + }); + // Set labels of tabs based on rel values + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + }); + // Set tabindexes on form fields + $.wikiEditor.modules.dialogs.fn.setTabindexes( $(this).find( 'input' ).not( '[tabindex]' ) ); + // Setup the tooltips in the textboxes + $( '#wikieditor-toolbar-link-int-target' ) + .data( 'tooltip', mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-tooltip' ) ); + $( '#wikieditor-toolbar-link-int-text' ) + .data( 'tooltip', mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-text-tooltip' ) ); + $( '#wikieditor-toolbar-link-int-target, #wikieditor-toolbar-link-int-text' ) + .each( function() { + var tooltip = mediaWiki.msg( $( this ).attr( 'id' ) + '-tooltip' ); + if ( $( this ).val() == '' ) + $( this ) + .addClass( 'wikieditor-toolbar-dialog-hint' ) + .val( $( this ).data( 'tooltip' ) ) + .data( 'tooltip-mode', true ); + } ) + .focus( function() { + if( $( this ).val() == $( this ).data( 'tooltip' ) ) { + $( this ) + .val( '' ) + .removeClass( 'wikieditor-toolbar-dialog-hint' ) + .data( 'tooltip-mode', false ); + } + }) + .bind( 'change', function() { + if ( $( this ).val() != $( this ).data( 'tooltip' ) ) { + $( this ) + .removeClass( 'wikieditor-toolbar-dialog-hint' ) + .data( 'tooltip-mode', false ); + } + }) + .bind( 'blur', function() { + if ( $( this ).val() == '' ) { + $( this ) + .addClass( 'wikieditor-toolbar-dialog-hint' ) + .val( $( this ).data( 'tooltip' ) ) + .data( 'tooltip-mode', true ); + } + }); + + // Automatically copy the value of the internal link page title field to the link text field unless the + // user has changed the link text field - this is a convenience thing since most link texts are going to + // be the the same as the page title - Also change the internal/external radio button accordingly + $( '#wikieditor-toolbar-link-int-target' ).bind( 'change keydown paste cut', function() { + // $(this).val() is the old value, before the keypress - Defer this until $(this).val() has + // been updated + setTimeout( function() { + if ( isExternalLink( $( '#wikieditor-toolbar-link-int-target' ).val() ) ) { + $( '#wikieditor-toolbar-link-type-ext' ).attr( 'checked', 'checked' ); + updateWidget( 'external' ); + } else { + $( '#wikieditor-toolbar-link-type-int' ).attr( 'checked', 'checked' ); + updateExistence(); + } + if ( $( '#wikieditor-toolbar-link-int-text' ).data( 'untouched' ) ) + if ( $( '#wikieditor-toolbar-link-int-target' ).val() == + $( '#wikieditor-toolbar-link-int-target' ).data( 'tooltip' ) ) { + $( '#wikieditor-toolbar-link-int-text' ) + .addClass( 'wikieditor-toolbar-dialog-hint' ) + .val( $( '#wikieditor-toolbar-link-int-text' ).data( 'tooltip' ) ) + .change(); + } else { + $( '#wikieditor-toolbar-link-int-text' ) + .val( $( '#wikieditor-toolbar-link-int-target' ).val() ) + .change(); + } + }, 0 ); + }); + $( '#wikieditor-toolbar-link-int-text' ).bind( 'change keydown paste cut', function() { + var oldVal = $(this).val(); + var that = this; + setTimeout( function() { + if ( $(that).val() != oldVal ) + $(that).data( 'untouched', false ); + }, 0 ); + }); + // Add images to the page existence widget, which will be shown mutually exclusively to communicate if + // the page exists, does not exist or the title is invalid (like if it contains a | character) + var existsMsg = mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-status-exists' ); + var notexistsMsg = mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-status-notexists' ); + var invalidMsg = mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-status-invalid' ); + var externalMsg = mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-status-external' ); + var loadingMsg = mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-target-status-loading' ); + $( '#wikieditor-toolbar-link-int-target-status' ) + .append( $( '
' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-exists' ) + .append( existsMsg ) + ) + .append( $( '
' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-notexists' ) + .append( notexistsMsg ) + ) + .append( $( '
' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-invalid' ) + .append( invalidMsg ) + ) + .append( $( '
' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-external' ) + .append( externalMsg ) + ) + .append( $( '
' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-loading' ) + .append( $( '' ).attr( { + 'src': $.wikiEditor.imgPath + 'dialogs/' + 'loading-small.gif', + 'alt': loadingMsg, + 'title': loadingMsg + } ) ) + ) + .data( 'existencecache', {} ) + .children().hide(); + + $( '#wikieditor-toolbar-link-int-target' ) + .bind( 'keyup paste cut', function() { + // Cancel the running timer if applicable + if ( typeof $(this).data( 'timerID' ) != 'undefined' ) { + clearTimeout( $(this).data( 'timerID' ) ); + } + // Delay fetch for a while + // FIXME: Make 120 configurable elsewhere + var timerID = setTimeout( updateExistence, 120 ); + $(this).data( 'timerID', timerID ); + } ) + .change( function() { + // Cancel the running timer if applicable + if ( typeof $(this).data( 'timerID' ) != 'undefined' ) { + clearTimeout( $(this).data( 'timerID' ) ); + } + // Fetch right now + updateExistence(); + } ); + + // Title suggestions + $( '#wikieditor-toolbar-link-int-target' ).data( 'suggcache', {} ).suggestions( { + fetch: function( query ) { + var that = this; + var title = $(this).val(); + + if ( isExternalLink( title ) || title.indexOf( '|' ) != -1 || title == '') { + $(this).suggestions( 'suggestions', [] ); + return; + } + + var cache = $(this).data( 'suggcache' ); + if ( typeof cache[title] != 'undefined' ) { + $(this).suggestions( 'suggestions', cache[title] ); + return; + } + + var request = $.ajax( { + url: mw.util.wikiScript( 'api' ), + data: { + 'action': 'opensearch', + 'search': title, + 'namespace': 0, + 'suggest': '', + 'format': 'json' + }, + dataType: 'json', + success: function( data ) { + cache[title] = data[1]; + $(that).suggestions( 'suggestions', data[1] ); + } + }); + $(this).data( 'request', request ); + }, + cancel: function() { + var request = $(this).data( 'request' ); + if ( request ) + request.abort(); + } + }); + }, + dialog: { + width: 500, + dialogClass: 'wikiEditor-toolbar-dialog', + buttons: { + 'wikieditor-toolbar-tool-link-insert': function() { + function escapeInternalText( s ) { + // FIXME: Should this escape [[ too? Seems to work without that + return s.replace( /(]{2,})/g, '$1' ); + } + function escapeExternalTarget( s ) { + return s.replace( / /g, '%20' ) + .replace( /\[/g, '%5B' ) + .replace( /]/g, '%5D' ); + } + function escapeExternalText( s ) { + // FIXME: Should this escape [ too? Seems to work without that + return s.replace( /(]+)/g, '$1' ); + } + var insertText = ''; + var whitespace = $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace' ); + var target = $( '#wikieditor-toolbar-link-int-target' ).val(); + var text = $( '#wikieditor-toolbar-link-int-text' ).val(); + // check if the tooltips were passed as target or text + if ( $( '#wikieditor-toolbar-link-int-target' ).data( 'tooltip-mode' ) ) + target = ""; + if ( $( '#wikieditor-toolbar-link-int-text' ).data( 'tooltip-mode' ) ) + text = ""; + if ( target == '' ) { + alert( mediaWiki.msg( 'wikieditor-toolbar-tool-link-empty' ) ); + return; + } + if ( $.trim( text ) == '' ) { + // [[Foo| ]] creates an invisible link + // Instead, generate [[Foo|]] + text = ''; + } + if ( $( '#wikieditor-toolbar-link-type-int' ).is( ':checked' ) ) { + // FIXME: Exactly how fragile is this? + if ( $( '#wikieditor-toolbar-link-int-target-status-invalid' ).is( ':visible' ) ) { + // Refuse to add links to invalid titles + alert( mediaWiki.msg( 'wikieditor-toolbar-tool-link-int-invalid' ) ); + return; + } + + if ( target == text || !text.length ) + insertText = '[[' + target + ']]'; + else + insertText = '[[' + target + '|' + escapeInternalText( text ) + ']]'; + } else { + // Prepend http:// if there is no protocol + if ( !target.match( /^[a-z]+:\/\/./ ) ) + target = 'http://' + target; + + // Detect if this is really an internal link in disguise + var match = target.match( $(this).data( 'articlePathRegex' ) ); + if ( match && !$(this).data( 'ignoreLooksInternal' ) ) { + var buttons = { }; + var that = this; + buttons[ mediaWiki.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-int' ) ] = + function() { + $( '#wikieditor-toolbar-link-int-target' ).val( match[1] ).change(); + $(this).dialog( 'close' ); + }; + buttons[ mediaWiki.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal-ext' ) ] = + function() { + $(that).data( 'ignoreLooksInternal', true ); + $(that).closest( '.ui-dialog' ).find( 'button:first' ).click(); + $(that).data( 'ignoreLooksInternal', false ); + $(this).dialog( 'close' ); + }; + $.wikiEditor.modules.dialogs.quickDialog( + mediaWiki.msg( 'wikieditor-toolbar-tool-link-lookslikeinternal', match[1] ), + { buttons: buttons } + ); + return; + } + + var escTarget = escapeExternalTarget( target ); + var escText = escapeExternalText( text ); + + if ( escTarget == escText ) + insertText = escTarget; + else if ( text == '' ) + insertText = '[' + escTarget + ']'; + else + insertText = '[' + escTarget + ' ' + escText + ']'; + } + // Preserve whitespace in selection when replacing + if ( whitespace ) insertText = whitespace[0] + insertText + whitespace[1]; + $(this).dialog( 'close' ); + $.wikiEditor.modules.toolbar.fn.doAction( $(this).data( 'context' ), { + type: 'replace', + options: { + pre: insertText + } + }, $(this) ); + + // Blank form + $( '#wikieditor-toolbar-link-int-target, #wikieditor-toolbar-link-int-text' ).val( '' ); + $( '#wikieditor-toolbar-link-type-int, #wikieditor-toolbar-link-type-ext' ) + .attr( 'checked', '' ); + }, + 'wikieditor-toolbar-tool-link-cancel': function() { + // Clear any saved selection state + var context = $(this).data( 'context' ); + context.fn.restoreCursorAndScrollTop(); + $(this).dialog( 'close' ); + } + }, + open: function() { + // Obtain the server name without the protocol. wgServer may be protocol-relative + var serverName = mw.config.get( 'wgServer' ).replace( /^(https?:)?\/\//, '' ); + // Cache the articlepath regex + $(this).data( 'articlePathRegex', new RegExp( + '^https?://' + $.escapeRE( serverName + mw.config.get( 'wgArticlePath' ) ) + .replace( /\\\$1/g, '(.*)' ) + '$' + ) ); + // Pre-fill the text fields based on the current selection + var context = $(this).data( 'context' ); + // Restore and immediately save selection state, needed for inserting stuff later + context.fn.restoreCursorAndScrollTop(); + context.fn.saveCursorAndScrollTop(); + var selection = context.$textarea.textSelection( 'getSelection' ); + $( '#wikieditor-toolbar-link-int-target' ).focus(); + // Trigger the change event, so the link status indicator is up to date + $( '#wikieditor-toolbar-link-int-target' ).change(); + $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ '', '' ] ); + if ( selection != '' ) { + var target, text, type; + var matches; + if ( ( matches = selection.match( /^(\s*)\[\[([^\]\|]+)(\|([^\]\|]*))?\]\](\s*)$/ ) ) ) { + // [[foo|bar]] or [[foo]] + target = matches[2]; + text = ( matches[4] ? matches[4] : matches[2] ); + type = 'int'; + // Preserve whitespace when replacing + $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[1], matches[5] ] ); + } else if ( ( matches = selection.match( /^(\s*)\[([^\] ]+)( ([^\]]+))?\](\s*)$/ ) ) ) { + // [http://www.example.com foo] or [http://www.example.com] + target = matches[2]; + text = ( matches[4] ? matches[4] : '' ); + type = 'ext'; + // Preserve whitespace when replacing + $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ matches[1], matches[5] ] ); + } else { + // Trim any leading and trailing whitespace from the selection, + // but preserve it when replacing + target = text = $.trim( selection ); + if ( target.length < selection.length ) { + $( '#wikieditor-toolbar-link-dialog' ).data( 'whitespace', [ + selection.substr( 0, selection.indexOf( target.charAt( 0 ) ) ), + selection.substr( + selection.lastIndexOf( target.charAt( target.length - 1 ) ) + 1 + ) ] + ); + } + } + + // Change the value by calling val() doesn't trigger the change event, so let's do that + // ourselves + if ( typeof text != 'undefined' ) + $( '#wikieditor-toolbar-link-int-text' ).val( text ).change(); + if ( typeof target != 'undefined' ) + $( '#wikieditor-toolbar-link-int-target' ).val( target ).change(); + if ( typeof type != 'undefined' ) + $( '#wikieditor-toolbar-link-' + type ).attr( 'checked', 'checked' ); + } + $( '#wikieditor-toolbar-link-int-text' ).data( 'untouched', + $( '#wikieditor-toolbar-link-int-text' ).val() == + $( '#wikieditor-toolbar-link-int-target' ).val() || + $( '#wikieditor-toolbar-link-int-text' ).hasClass( 'wikieditor-toolbar-dialog-hint' ) + ); + $( '#wikieditor-toolbar-link-int-target' ).suggestions(); + + //don't overwrite user's text + if( selection != '' ){ + $( '#wikieditor-toolbar-link-int-text' ).data( 'untouched', false ); + } + + $( '#wikieditor-toolbar-link-int-text, #wikiedit-toolbar-link-int-target' ) + .each( function() { + if ( $(this).val() == '' ) + $(this).parent().find( 'label' ).show(); + }); + + if ( !( $(this).data( 'dialogkeypressset' ) ) ) { + $(this).data( 'dialogkeypressset', true ); + // Execute the action associated with the first button + // when the user presses Enter + $(this).closest( '.ui-dialog' ).keypress( function( e ) { + if ( ( e.keyCode || e.which ) == 13 ) { + var button = $(this).data( 'dialogaction' ) || $(this).find( 'button:first' ); + button.click(); + e.preventDefault(); + } + }); + + // Make tabbing to a button and pressing + // Enter do what people expect + $(this).closest( '.ui-dialog' ).find( 'button' ).focus( function() { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', this ); + }); + } + } + } + }, + 'insert-reference': { + titleMsg: 'wikieditor-toolbar-tool-reference-title', + id: 'wikieditor-toolbar-reference-dialog', + html: '\ +
\ +
\ +
\ + \ + \ +
\ +
\ +
', + init: function() { + // Insert translated strings into labels + $( this ).find( '[rel]' ).each( function() { + $( this ).text( mediaWiki.msg( $( this ).attr( 'rel' ) ) ); + } ); + + }, + dialog: { + dialogClass: 'wikiEditor-toolbar-dialog', + width: 590, + buttons: { + 'wikieditor-toolbar-tool-reference-insert': function() { + var insertText = $( '#wikieditor-toolbar-reference-text' ).val(); + var whitespace = $( '#wikieditor-toolbar-reference-dialog' ).data( 'whitespace' ); + var attributes = $( '#wikieditor-toolbar-reference-dialog' ).data( 'attributes' ); + // Close the dialog + $( this ).dialog( 'close' ); + $.wikiEditor.modules.toolbar.fn.doAction( + $( this ).data( 'context' ), + { + type: 'replace', + options: { + pre: whitespace[0] + '', + peri: insertText, + post: '' + whitespace[1] + } + }, + $( this ) + ); + // Restore form state + $( '#wikieditor-toolbar-reference-text' ).val( "" ); + }, + 'wikieditor-toolbar-tool-reference-cancel': function() { + // Clear any saved selection state + var context = $( this ).data( 'context' ); + context.fn.restoreCursorAndScrollTop(); + $( this ).dialog( 'close' ); + } + }, + open: function() { + // Pre-fill the text fields based on the current selection + var context = $(this).data( 'context' ); + // Restore and immediately save selection state, needed for inserting stuff later + context.fn.restoreCursorAndScrollTop(); + context.fn.saveCursorAndScrollTop(); + var selection = context.$textarea.textSelection( 'getSelection' ); + // set focus + $( '#wikieditor-toolbar-reference-text' ).focus(); + $( '#wikieditor-toolbar-reference-dialog' ) + .data( 'whitespace', [ '', '' ] ) + .data( 'attributes', '' ); + if ( selection != '' ) { + var matches, text; + if ( ( matches = selection.match( /^(\s*)]*)>([^\<]*)<\/ref\>(\s*)$/ ) ) ) { + text = matches[3]; + // Preserve whitespace when replacing + $( '#wikieditor-toolbar-reference-dialog' ) + .data( 'whitespace', [ matches[1], matches[4] ] ); + $( '#wikieditor-toolbar-reference-dialog' ).data( 'attributes', matches[2] ); + } else { + text = selection; + } + $( '#wikieditor-toolbar-reference-text' ).val( text ); + } + if ( !( $( this ).data( 'dialogkeypressset' ) ) ) { + $( this ).data( 'dialogkeypressset', true ); + // Execute the action associated with the first button + // when the user presses Enter + $( this ).closest( '.ui-dialog' ).keypress( function( e ) { + if ( ( e.keyCode || e.which ) == 13 ) { + var button = $( this ).data( 'dialogaction' ) || $( this ).find( 'button:first' ); + button.click(); + e.preventDefault(); + } + } ); + // Make tabbing to a button and pressing + // Enter do what people expect + $( this ).closest( '.ui-dialog' ).find( 'button' ).focus( function() { + $( this ).closest( '.ui-dialog' ).data( 'dialogaction', this ); + } ); + } + } + } + }, + 'insert-table': { + titleMsg: 'wikieditor-toolbar-tool-table-title', + id: 'wikieditor-toolbar-table-dialog', + // FIXME: Localize 'x'? + html: '\ +
\ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ +
\ +
\ + \ +
\ +
\ +
\ + \ +
\ +
\ +
\ +
\ + \ +
\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
\ +
\ +
', + init: function() { + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + }); + // Set tabindexes on form fields + $.wikiEditor.modules.dialogs.fn.setTabindexes( $(this).find( 'input' ).not( '[tabindex]' ) ); + + $( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 ); + $( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 ); + $( '#wikieditor-toolbar-table-wikitable' ).click( function() { + $( '.wikieditor-toolbar-table-preview' ).toggleClass( 'wikitable' ); + }); + + // Hack for sortable preview: dynamically adding + // sortable class doesn't work, so we use a clone + $( '#wikieditor-toolbar-table-preview' ) + .clone() + .attr( 'id', 'wikieditor-toolbar-table-preview2' ) + .addClass( 'sortable' ) + .insertAfter( $( '#wikieditor-toolbar-table-preview' ) ) + .hide(); + + mw.loader.using( 'jquery.tablesorter', function() { + $( '#wikieditor-toolbar-table-preview2' ).tablesorter(); + }); + + $( '#wikieditor-toolbar-table-sortable' ).click( function() { + // Swap the currently shown one clone with the other one + $( '#wikieditor-toolbar-table-preview' ) + .hide() + .attr( 'id', 'wikieditor-toolbar-table-preview3' ); + $( '#wikieditor-toolbar-table-preview2' ) + .attr( 'id', 'wikieditor-toolbar-table-preview' ) + .show(); + $( '#wikieditor-toolbar-table-preview3' ).attr( 'id', 'wikieditor-toolbar-table-preview2' ); + }); + + $( '#wikieditor-toolbar-table-dimensions-header' ).click( function() { + // Instead of show/hiding, switch the HTML around + // We do this because the sortable tables script styles the first row, + // visible or not + var headerHTML = $( '.wikieditor-toolbar-table-preview-header' ).html(); + var hiddenHTML = $( '.wikieditor-toolbar-table-preview-hidden' ).html(); + $( '.wikieditor-toolbar-table-preview-header' ).html( hiddenHTML ); + $( '.wikieditor-toolbar-table-preview-hidden' ).html( headerHTML ); + if ( typeof jQuery.fn.tablesorter == 'function' ) { + $( '#wikieditor-toolbar-table-preview, #wikieditor-toolbar-table-preview2' ) + .filter( '.sortable' ) + .tablesorter(); + } + }); + }, + dialog: { + resizable: false, + dialogClass: 'wikiEditor-toolbar-dialog', + width: 590, + buttons: { + 'wikieditor-toolbar-tool-table-insert': function() { + var rowsVal = $( '#wikieditor-toolbar-table-dimensions-rows' ).val(); + var colsVal = $( '#wikieditor-toolbar-table-dimensions-columns' ).val(); + var rows = parseInt( rowsVal, 10 ); + var cols = parseInt( colsVal, 10 ); + var header = $( '#wikieditor-toolbar-table-dimensions-header' ).is( ':checked' ) ? 1 : 0; + if ( isNaN( rows ) || isNaN( cols ) || rows != rowsVal || cols != colsVal ) { + alert( mediaWiki.msg( 'wikieditor-toolbar-tool-table-invalidnumber' ) ); + return; + } + if ( rows + header == 0 || cols == 0 ) { + alert( mediaWiki.msg( 'wikieditor-toolbar-tool-table-zero' ) ); + return; + } + if ( rows * cols > 1000 ) { + alert( mediaWiki.msg( 'wikieditor-toolbar-tool-table-toomany', 1000 ) ); + return; + } + var headerText = mediaWiki.msg( 'wikieditor-toolbar-tool-table-example-header' ); + var normalText = mediaWiki.msg( 'wikieditor-toolbar-tool-table-example' ); + var table = ""; + for ( var r = 0; r < rows + header; r++ ) { + table += "|-\n"; + for ( var c = 0; c < cols; c++ ) { + var isHeader = ( header && r == 0 ); + var delim = isHeader ? '!' : '|'; + if ( c > 0 ) { + delim += delim; + } + table += delim + ' ' + ( isHeader ? headerText : normalText ) + ' '; + } + // Replace trailing space by newline + // table[table.length - 1] is read-only + table = table.substr( 0, table.length - 1 ) + "\n"; + } + var classes = []; + if ( $( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) + classes.push( 'wikitable' ); + if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) + classes.push( 'sortable' ); + var classStr = classes.length > 0 ? ' class="' + classes.join( ' ' ) + '"' : ''; + $(this).dialog( 'close' ); + $.wikiEditor.modules.toolbar.fn.doAction( + $(this).data( 'context' ), + { + type: 'replace', + options: { + pre: '{|' + classStr + "\n", + peri: table, + post: '|}', + ownline: true + } + }, + $(this) + ); + + // Restore form state + $( '#wikieditor-toolbar-table-dimensions-rows' ).val( 3 ); + $( '#wikieditor-toolbar-table-dimensions-columns' ).val( 3 ); + // Simulate clicks instead of setting values, so the according + // actions are performed + if ( !$( '#wikieditor-toolbar-table-dimensions-header' ).is( ':checked' ) ) + $( '#wikieditor-toolbar-table-dimensions-header' ).click(); + if ( !$( '#wikieditor-toolbar-table-wikitable' ).is( ':checked' ) ) + $( '#wikieditor-toolbar-table-wikitable' ).click(); + if ( $( '#wikieditor-toolbar-table-sortable' ).is( ':checked' ) ) + $( '#wikieditor-toolbar-table-sortable' ).click(); + }, + 'wikieditor-toolbar-tool-table-cancel': function() { + $(this).dialog( 'close' ); + } + }, + open: function() { + $( '#wikieditor-toolbar-table-dimensions-rows' ).focus(); + if ( !( $(this).data( 'dialogkeypressset' ) ) ) { + $(this).data( 'dialogkeypressset', true ); + // Execute the action associated with the first button + // when the user presses Enter + $(this).closest( '.ui-dialog' ).keypress( function( e ) { + if ( ( e.keyCode || e.which ) == 13 ) { + var button = $(this).data( 'dialogaction' ) || $(this).find( 'button:first' ); + button.click(); + e.preventDefault(); + } + }); + + // Make tabbing to a button and pressing + // Enter do what people expect + $(this).closest( '.ui-dialog' ).find( 'button' ).focus( function() { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', this ); + }); + } + } + } + }, + 'search-and-replace': { + 'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': false, + 'firefox': [['>=', 2]], + 'opera': false, + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]] + }, + // Right-to-left languages + 'rtl': { + 'msie': false, + 'firefox': [['>=', 2]], + 'opera': false, + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]] + } + }, + titleMsg: 'wikieditor-toolbar-tool-replace-title', + id: 'wikieditor-toolbar-replace-dialog', + html: '\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
', + init: function() { + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + }); + // Set tabindexes on form fields + $.wikiEditor.modules.dialogs.fn.setTabindexes( $(this).find( 'input' ).not( '[tabindex]' ) ); + + // TODO: Find a cleaner way to share this function + $(this).data( 'replaceCallback', function( mode ) { + $( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); + var searchStr = $( '#wikieditor-toolbar-replace-search' ).val(); + if ( searchStr == '' ) { + $( '#wikieditor-toolbar-replace-emptysearch' ).show(); + return; + } + var replaceStr = $( '#wikieditor-toolbar-replace-replace' ).val(); + var flags = 'm'; + var matchCase = $( '#wikieditor-toolbar-replace-case' ).is( ':checked' ); + var isRegex = $( '#wikieditor-toolbar-replace-regex' ).is( ':checked' ); + if ( !matchCase ) { + flags += 'i'; + } + if ( mode == 'replaceAll' ) { + flags += 'g'; + } + if ( !isRegex ) { + searchStr = $.escapeRE( searchStr ); + } + try { + var regex = new RegExp( searchStr, flags ); + } catch( e ) { + $( '#wikieditor-toolbar-replace-invalidregex' ) + .text( mediaWiki.msg( 'wikieditor-toolbar-tool-replace-invalidregex', + e.message ) ) + .show(); + return; + } + var $textarea = $(this).data( 'context' ).$textarea; + var text = $textarea.textSelection( 'getContents' ); + var match = false; + var offset, s; + if ( mode != 'replaceAll' ) { + offset = $(this).data( 'offset' ); + s = text.substr( offset ); + match = s.match( regex ); + } + if ( !match ) { + // Search hit BOTTOM, continuing at TOP + offset = 0; + s = text; + match = s.match( regex ); + } + + if ( !match ) { + $( '#wikieditor-toolbar-replace-nomatch' ).show(); + } else if ( mode == 'replaceAll' ) { + // Instead of using repetitive .match() calls, we use one .match() call with /g + // and indexOf() followed by substr() to find the offsets. This is actually + // faster because our indexOf+substr loop is faster than a match loop, and the + // /g match is so ridiculously fast that it's negligible. + // FIXME: Repetitively calling encapsulateSelection() is probably the best strategy + // in Firefox/Webkit, but in IE replacing the entire content once is better. + var index; + for ( var i = 0; i < match.length; i++ ) { + index = s.indexOf( match[i] ); + if ( index == -1 ) { + // This shouldn't happen + break; + } + var matchedText = s.substr( index, match[i].length ); + s = s.substr( index + match[i].length ); + + var start = index + offset; + var end = start + match[i].length; + // Make regex placeholder substitution ($1) work + var replace = isRegex ? matchedText.replace( regex, replaceStr ) : replaceStr; + var newEnd = start + replace.length; + $textarea + .textSelection( 'setSelection', { 'start': start, 'end': end } ) + .textSelection( 'encapsulateSelection', { + 'peri': replace, + 'replace': true } ) + .textSelection( 'setSelection', { 'start': start, 'end': newEnd } ); + offset = newEnd; + } + $( '#wikieditor-toolbar-replace-success' ) + .text( mediaWiki.msg( 'wikieditor-toolbar-tool-replace-success', match.length ) ) + .show(); + $(this).data( 'offset', 0 ); + } else { + // Make regex placeholder substitution ($1) work + var replace = isRegex ? match[0].replace( regex, replaceStr ): replaceStr; + var start = match.index + offset; + var end = start + match[0].length; + var newEnd = start + replace.length; + var context = $( this ).data( 'context' ); + $textarea.textSelection( 'setSelection', { 'start': start, + 'end': end } ); + if ( mode == 'replace' ) { + $textarea + .textSelection( 'encapsulateSelection', { + 'peri': replace, + 'replace': true } ) + .textSelection( 'setSelection', { + 'start': start, + 'end': newEnd } ); + } + $textarea.textSelection( 'scrollToCaretPosition' ); + $textarea.textSelection( 'setSelection', { 'start': start, + 'end': mode == 'replace' ? newEnd : end } ); + $( this ).data( 'offset', mode == 'replace' ? newEnd : end ); + var textbox = typeof context.$iframe != 'undefined' ? + context.$iframe[0].contentWindow : $textarea[0]; + textbox.focus(); + } + }); + }, + dialog: { + width: 500, + dialogClass: 'wikiEditor-toolbar-dialog', + buttons: { + 'wikieditor-toolbar-tool-replace-button-findnext': function( e ) { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', e.target ); + $(this).data( 'replaceCallback' ).call( this, 'find' ); + }, + 'wikieditor-toolbar-tool-replace-button-replacenext': function( e ) { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', e.target ); + $(this).data( 'replaceCallback' ).call( this, 'replace' ); + }, + 'wikieditor-toolbar-tool-replace-button-replaceall': function( e ) { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', e.target ); + $(this).data( 'replaceCallback' ).call( this, 'replaceAll' ); + }, + 'wikieditor-toolbar-tool-replace-close': function() { + $(this).dialog( 'close' ); + } + }, + open: function() { + $(this).data( 'offset', 0 ); + $( '#wikieditor-toolbar-replace-search' ).focus(); + $( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); + if ( !( $(this).data( 'onetimeonlystuff' ) ) ) { + $(this).data( 'onetimeonlystuff', true ); + // Execute the action associated with the first button + // when the user presses Enter + $(this).closest( '.ui-dialog' ).keypress( function( e ) { + if ( ( e.keyCode || e.which ) == 13 ) { + var button = $(this).data( 'dialogaction' ) || $(this).find( 'button:first' ); + button.click(); + e.preventDefault(); + } + }); + // Make tabbing to a button and pressing + // Enter do what people expect + $(this).closest( '.ui-dialog' ).find( 'button' ).focus( function() { + $(this).closest( '.ui-dialog' ).data( 'dialogaction', this ); + }); + } + var dialog = $(this).closest( '.ui-dialog' ); + var that = this; + var context = $(this).data( 'context' ); + var textbox = typeof context.$iframe != 'undefined' ? + context.$iframe[0].contentWindow.document : context.$textarea; + + $( textbox ) + .bind( 'keypress.srdialog', function( e ) { + if ( ( e.keyCode || e.which ) == 13 ) { + // Enter + var button = dialog.data( 'dialogaction' ) || dialog.find( 'button:first' ); + button.click(); + e.preventDefault(); + } else if ( ( e.keyCode || e.which ) == 27 ) { + // Escape + $(that).dialog( 'close' ); + } + }); + }, + close: function() { + var context = $(this).data( 'context' ); + var textbox = typeof context.$iframe != 'undefined' ? + context.$iframe[0].contentWindow.document : context.$textarea; + $( textbox ).unbind( 'keypress.srdialog' ); + $(this).closest( '.ui-dialog' ).data( 'dialogaction', false ); + } + } + } + } }; +} + +}; } ) ( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.css b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.css new file mode 100644 index 00000000..d1b68ab3 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.css @@ -0,0 +1,56 @@ +/* + * CSS for WikiEditor Dialogs jQuery plugin + */ + +.wikiEditor-toolbar-dialog table { + margin-top: 0.75em; +} +.wikiEditor-toolbar-dialog table td { + padding: 0.5em; + height: 3em; + overflow: visible; +} +/* Put suggestions (default z-index 99) on top of dialogs (z-index 1002) */ +div.suggestions { + z-index: 1099; +} +.wikiEditor-toolbar-dialog table td { + padding: 0 !important; +} +.wikiEditor-toolbar-dialog .ui-dialog-content fieldset { + border: none !important; + margin: 0 !important; + padding: 0 !important; +} +.wikiEditor-toolbar-dialog .ui-widget-header { + border-bottom:1px solid #6bc8f3 !important; +} +.wikiEditor-toolbar-dialog .ui-dialog-content input[type=text] { + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -webkit-box-sizing: border-box; + -khtml-box-sizing: border-box; +} +.wikiEditor-toolbar-dialog .ui-dialog-content input[type="radio"], +.wikiEditor-toolbar-dialog .ui-dialog-content input[type="checkbox"] { + margin-left: 0; +} +.wikiEditor-toolbar-dialog .ui-dialog-titlebar-close { + padding: 0; +} +body.ltr .wikiEditor-toolbar-dialog .ui-dialog-titlebar-close { + right: 0.9em; +} +.wikieditor-toolbar-field-wrapper { + padding: 0 0 25px 0; +} +.wikieditor-toolbar-floated-field-wrapper { + float: left; + margin-right: 2em; +} +.wikieditor-toolbar-dialog-hint { + color: #999999; +} +.wikiEditor-toolbar-dialog { + border: none; +} \ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.js b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.js new file mode 100644 index 00000000..9b291466 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.dialogs.js @@ -0,0 +1,217 @@ +/** + * Dialog Module for wikiEditor + */ +( function( $ ) { $.wikiEditor.modules.dialogs = { + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 7]], + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 + 'firefox': [ + ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4'] + ], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]] + }, + // Right-to-left languages + 'rtl': { + 'msie': [['>=', 7]], + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 + 'firefox': [ + ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4'] + ], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]] + } +}, +/** + * API accessible functions + */ +api: { + addDialog: function( context, data ) { + $.wikiEditor.modules.dialogs.fn.create( context, data ) + }, + openDialog: function( context, module ) { + if ( module in $.wikiEditor.modules.dialogs.modules ) { + var mod = $.wikiEditor.modules.dialogs.modules[module]; + var $dialog = $( '#' + mod.id ); + if ( $dialog.length == 0 ) { + $.wikiEditor.modules.dialogs.fn.reallyCreate( context, mod, module ); + $dialog = $( '#' + mod.id ); + } + + // Workaround for bug in jQuery UI: close button in top right retains focus + $dialog.closest( '.ui-dialog' ) + .find( '.ui-dialog-titlebar-close' ) + .removeClass( 'ui-state-focus' ); + + $dialog.dialog( 'open' ); + } + }, + closeDialog: function( context, module ) { + if ( module in $.wikiEditor.modules.dialogs.modules ) { + $( '#' + $.wikiEditor.modules.dialogs.modules[module].id ).dialog( 'close' ); + } + } +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates a dialog module within a wikiEditor + * + * @param {Object} context Context object of editor to create module in + * @param {Object} config Configuration object to create module from + */ + create: function( context, config ) { + // Defer building of modules, but do check whether they need the iframe rightaway + for ( var mod in config ) { + var module = config[mod]; + // Only create the dialog if it's supported, isn't filtered and doesn't exist yet + var filtered = false; + if ( typeof module.filters != 'undefined' ) { + for ( var i = 0; i < module.filters.length; i++ ) { + if ( $( module.filters[i] ).length == 0 ) { + filtered = true; + break; + } + } + } + // If the dialog already exists, but for another textarea, simply remove it + var $existingDialog = $( '#' + module.id ); + if ( $existingDialog.length > 0 && $existingDialog.data( 'context' ).$textarea != context.$textarea ) { + $existingDialog.remove(); + } + // Re-select from the DOM, we might have removed the dialog just now + $existingDialog = $( '#' + module.id ); + if ( !filtered && $.wikiEditor.isSupported( module ) && $existingDialog.size() === 0 ) { + $.wikiEditor.modules.dialogs.modules[mod] = module; + // If this dialog requires the iframe, set it up + if ( typeof context.$iframe == 'undefined' && $.wikiEditor.isRequired( module, 'iframe' ) ) { + context.fn.setupIframe(); + } + context.$textarea.trigger( 'wikiEditor-dialogs-setup-' + mod ); + // If this dialog requires immediate creation, create it now + if ( typeof module.immediateCreate !== 'undefined' && module.immediateCreate ) { + $.wikiEditor.modules.dialogs.fn.reallyCreate( context, module, mod ); + } + } + } + }, + /** + * Build the actual dialog. This done on-demand rather than in create() + * @param {Object} context Context object of editor dialog belongs to + * @param {Object} module Dialog module object + * @param {String} name Dialog name (key in $.wikiEditor.modules.dialogs.modules) + */ + reallyCreate: function( context, module, name ) { + var configuration = module.dialog; + // Add some stuff to configuration + configuration.bgiframe = true; + configuration.autoOpen = false; + configuration.modal = true; + configuration.title = $.wikiEditor.autoMsg( module, 'title' ); + // Transform messages in keys + // Stupid JS won't let us do stuff like + // foo = { mediaWiki.msg( 'bar' ): baz } + configuration.newButtons = {}; + for ( var msg in configuration.buttons ) + configuration.newButtons[mediaWiki.msg( msg )] = configuration.buttons[msg]; + configuration.buttons = configuration.newButtons; + // Create the dialog
+ var dialogDiv = $( '
' ) + .attr( 'id', module.id ) + .html( module.html ) + .data( 'context', context ) + .appendTo( $( 'body' ) ) + .each( module.init ) + .dialog( configuration ); + // Set tabindexes on buttons added by .dialog() + $.wikiEditor.modules.dialogs.fn.setTabindexes( dialogDiv.closest( '.ui-dialog' ) + .find( 'button' ).not( '[tabindex]' ) ); + if ( !( 'resizeme' in module ) || module.resizeme ) { + dialogDiv + .bind( 'dialogopen', $.wikiEditor.modules.dialogs.fn.resize ) + .find( '.ui-tabs' ).bind( 'tabsshow', function() { + $(this).closest( '.ui-dialog-content' ).each( + $.wikiEditor.modules.dialogs.fn.resize ); + }); + } + dialogDiv.bind( 'dialogclose', function() { + context.fn.restoreSelection(); + } ); + + // Let the outside world know we set up this dialog + context.$textarea.trigger( 'wikiEditor-dialogs-loaded-' + name ); + }, + /** + * Resize a dialog so its contents fit + * + * Usage: dialog.each( resize ); or dialog.bind( 'blah', resize ); + * NOTE: This function assumes $.ui.dialog has already been loaded + */ + resize: function() { + var wrapper = $(this).closest( '.ui-dialog' ); + var oldWidth = wrapper.width(); + // Make sure elements don't wrapped so we get an accurate idea of whether they really fit. Also temporarily show + // hidden elements. Work around jQuery bug where
inside a dialog is both + // :visible and :hidden + var oldHidden = $(this).find( '*' ).not( ':visible' ); + // Save the style attributes of the hidden elements to restore them later. Calling hide() after show() messes up + // for elements hidden with a class + oldHidden.each( function() { + $(this).data( 'oldstyle', $(this).attr( 'style' ) ); + }); + oldHidden.show(); + var oldWS = $(this).css( 'white-space' ); + $(this).css( 'white-space', 'nowrap' ); + if ( wrapper.width() <= $(this).get(0).scrollWidth ) { + var thisWidth = $(this).data( 'thisWidth' ) ? $(this).data( 'thisWidth' ) : 0; + thisWidth = Math.max( $(this).get(0).width, thisWidth ); + $(this).width( thisWidth ); + $(this).data( 'thisWidth', thisWidth ); + var wrapperWidth = $(this).data( 'wrapperWidth' ) ? $(this).data( 'wrapperWidth' ) : 0; + wrapperWidth = Math.max( wrapper.get(0).scrollWidth, wrapperWidth ); + wrapper.width( wrapperWidth ); + $(this).data( 'wrapperWidth', wrapperWidth ); + $(this).dialog( { 'width': wrapper.width() } ); + wrapper.css( 'left', parseInt( wrapper.css( 'left' ) ) - ( wrapper.width() - oldWidth ) / 2 ); + } + $(this).css( 'white-space', oldWS ); + oldHidden.each( function() { + $(this).attr( 'style', $(this).data( 'oldstyle' ) ); + }); + }, + /** + * Set the right tabindexes on elements in a dialog + * @param $elements Elements to set tabindexes on. If they already have tabindexes, this function can behave a bit weird + */ + setTabindexes: function( $elements ) { + // Get the highest tab index + var tabIndex = $( document ).lastTabIndex() + 1; + $elements.each( function() { + $(this).attr( 'tabindex', tabIndex++ ); + } ); + } +}, +// This stuff is just hanging here, perhaps we could come up with a better home for this stuff +modules: {}, +quickDialog: function( body, settings ) { + $( '
' ) + .text( body ) + .appendTo( $( 'body' ) ) + .dialog( $.extend( { + bgiframe: true, + modal: true + }, settings ) ) + .dialog( 'open' ); +} + +}; } ) ( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.highlight.js b/extensions/WikiEditor/modules/jquery.wikiEditor.highlight.js new file mode 100644 index 00000000..c804b3aa --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.highlight.js @@ -0,0 +1,357 @@ +/* Highlight module for wikiEditor */ + +( function( $ ) { $.wikiEditor.modules.highlight = { + +/** + * Core Requirements + */ +'req': [ 'iframe' ], +/** + * Configuration + */ +'cfg': { + 'styleVersion': 3 +}, +/** + * Internally used event handlers + */ +'evt': { + 'delayedChange': function( context, event ) { + if ( event.data.scope == 'realchange' ) { + $.wikiEditor.modules.highlight.fn.scan( context ); + $.wikiEditor.modules.highlight.fn.mark( context, event.data.scope ); + } + }, + 'ready': function( context, event ) { + $.wikiEditor.modules.highlight.fn.scan( context ); + $.wikiEditor.modules.highlight.fn.mark( context, 'ready' ); + } +}, +/** + * Internally used functions + */ +'fn': { + /** + * Creates a highlight module within a wikiEditor + * + * @param config Configuration object to create module from + */ + 'create': function( context, config ) { + context.modules.highlight.markersStr = ''; + }, + /** + * Scans text division for tokens + * + * @param division + */ + 'scan': function( context, division ) { + // Remove all existing tokens + var tokenArray = context.modules.highlight.tokenArray = []; + // Scan text for new tokens + var text = context.fn.getContents(); + // Perform a scan for each module which provides any expressions to scan for + // FIXME: This traverses the entire string once for every regex. Investigate + // whether |-concatenating regexes then traversing once is faster. + for ( var module in context.modules ) { + if ( module in $.wikiEditor.modules && 'exp' in $.wikiEditor.modules[module] ) { + for ( var exp in $.wikiEditor.modules[module].exp ) { + // Prepare configuration + var regex = $.wikiEditor.modules[module].exp[exp].regex; + var label = $.wikiEditor.modules[module].exp[exp].label; + var markAfter = $.wikiEditor.modules[module].exp[exp].markAfter || false; + // Search for tokens + var offset = 0, left, right, match; + while ( ( match = text.substr( offset ).match( regex ) ) != null ) { + right = ( left = offset + match.index ) + match[0].length; + tokenArray[tokenArray.length] = { + 'offset': markAfter ? right : left, + 'label': label, + 'tokenStart': left, + 'match': match + }; + // Move to the right of this match + offset = right; + } + } + } + } + // Sort by start + tokenArray.sort( function( a, b ) { return a.tokenStart - b.tokenStart; } ); + // Let the world know, a scan just happened! + context.fn.trigger( 'scan' ); + }, + /** + * Marks up text with HTML + * + * @param division + * @param tokens + */ + // FIXME: What do division and tokens do? + // TODO: Document the scan() and mark() APIs somewhere + 'mark': function( context, division, tokens ) { + // Reset markers + var markers = []; + + // Recycle markers that will be skipped in this run + if ( context.modules.highlight.markers && division != '' ) { + for ( var i = 0; i < context.modules.highlight.markers.length; i++ ) { + if ( context.modules.highlight.markers[i].skipDivision == division ) { + markers.push( context.modules.highlight.markers[i] ); + } + } + } + context.modules.highlight.markers = markers; + + // Get all markers + context.fn.trigger( 'mark' ); + markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } ); + + // Serialize the markers array to a string and compare it with the one stored in the previous run - if they're + // equal, there's no markers to change + var markersStr = ''; + for ( var i = 0; i < markers.length; i++ ) { + markersStr += markers[i].start + ',' + markers[i].end + ',' + markers[i].type + ','; + } + if ( context.modules.highlight.markersStr == markersStr ) { + // No change, bail out + return; + } + context.modules.highlight.markersStr = markersStr; + + // Traverse the iframe DOM, inserting markers where they're needed - store visited markers here so we know which + // markers should be removed + var visited = [], v = 0; + for ( var i = 0; i < markers.length; i++ ) { + if ( typeof markers[i].skipDivision !== 'undefined' && ( division == markers[i].skipDivision ) ) { + continue; + } + + // We want to isolate each marker, so we may need to split textNodes if a marker starts or ends halfway one. + var start = markers[i].start; + var s = context.fn.getOffset( start ); + if ( !s ) { + // This shouldn't happen + continue; + } + var startNode = s.node; + + // Don't wrap leading BRs, produces undesirable results + // FIXME: It's also possible that the offset is a bit high because getOffset() has incremented .length to + // fake the newline caused by startNode being in a P. In this case, prevent the textnode splitting below + // from making startNode an empty textnode, IE barfs on that + while ( startNode.nodeName == 'BR' || s.offset == startNode.nodeValue.length ) { + start++; + s = context.fn.getOffset( start ); + startNode = s.node; + } + + // The next marker starts somewhere in this textNode or at this BR + if ( s.offset > 0 && s.node.nodeName == '#text' ) { + // Split off the prefix - this leaves the prefix in the current node and puts the rest in a new node + // which is our start node + var newStartNode = startNode.splitText( s.offset < s.node.nodeValue.length ? + s.offset : s.node.nodeValue.length - 1 + ); + var oldStartNode = startNode; + startNode = newStartNode; + // Update offset objects. We don't need purgeOffsets(), simply manipulating the existing offset objects + // will suffice + // FIXME: This manipulates context.offsets directly, which is ugly, but the performance improvement vs. + // purgeOffsets() is worth it - this code doesn't set lastTextNode to newStartNode for offset objects + // with lastTextNode == oldStartNode, but that doesn't really matter + var subtracted = s.offset; + var oldLength = s.length; + + var j, o; + // Update offset objects referring to oldStartNode + for ( j = start - subtracted; j < start; j++ ) { + if ( j in context.offsets ) { + o = context.offsets[j]; + o.node = oldStartNode; + o.length = subtracted; + } + } + // Update offset objects referring to newStartNode + for ( j = start; j < start - subtracted + oldLength; j++ ) { + if ( j in context.offsets ) { + o = context.offsets[j]; + o.node = newStartNode; + o.offset -= subtracted; + o.length -= subtracted; + o.lastTextNode = oldStartNode; + } + } + } + var end = markers[i].end; + // To avoid ending up at the first char of the next node, we grab the offset for end - 1 and add one to the + // offset + var e = context.fn.getOffset( end - 1 ); + if ( !e ) { + // This shouldn't happen + continue; + } + var endNode = e.node; + if ( e.offset + 1 < e.length - 1 && endNode.nodeName == '#text' ) { + // Split off the suffix. This puts the suffix in a new node and leaves the rest in endNode + var oldEndNode = endNode; + var newEndNode = endNode.splitText( e.offset + 1 ); + // Update offset objects + var subtracted = e.offset + 1; + var oldLength = e.length; + var j, o; + // Update offset objects referring to oldEndNode + for ( j = end - subtracted; j < end; j++ ) { + if ( j in context.offsets ) { + o = context.offsets[j]; + o.node = oldEndNode; + o.length = subtracted; + } + } + // We have to insert this one, as it might not exist: we didn't call getOffset( end ) + context.offsets[end] = { + 'node': newEndNode, + 'offset': 0, + 'length': oldLength - subtracted, + 'lastTextNode': oldEndNode + }; + // Update offset objects referring to newEndNode + for ( j = end + 1; j < end - subtracted + oldLength; j++ ) { + if ( j in context.offsets ) { + o = context.offsets[j]; + o.node = newEndNode; + o.offset -= subtracted; + o.length -= subtracted; + o.lastTextNode = oldEndNode; + } + } + } + // Don't wrap trailing BRs, doing that causes weird issues + if ( endNode.nodeName == 'BR' ) { + endNode = e.lastTextNode; + } + // If startNode and endNode have different parents, we need to pull endNode and all textnodes in between + // into startNode's parent and replace

with
+ if ( startNode.parentNode != endNode.parentNode ) { + var startP = $( startNode ).closest( 'p' ).get( 0 ); + var t = new context.fn.rawTraverser( startNode, startP, context.$content.get( 0 ), false ); + var afterStart = startNode.nextSibling; + var lastP = startP; + var nextT = t.next(); + while ( nextT && t.node != endNode ) { + t = nextT; + nextT = t.next(); + // If t.node has a different parent, merge t.node.parentNode with startNode.parentNode + if ( t.node.parentNode != startNode.parentNode ) { + var oldParent = t.node.parentNode; + if ( afterStart ) { + if ( lastP != t.inP ) { + // We're entering a new

, insert a
+ startNode.parentNode.insertBefore( + startNode.ownerDocument.createElement( 'br' ), + afterStart + ); + } + // A

with just a
in it is an empty line, so let's not bother with unwrapping it + if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) { + // Move all children of oldParent into startNode's parent + while ( oldParent.firstChild ) { + startNode.parentNode.insertBefore( oldParent.firstChild, afterStart ); + } + } + } else { + if ( lastP != t.inP ) { + // We're entering a new

, insert a
+ startNode.parentNode.appendChild( + startNode.ownerDocument.createElement( 'br' ) + ); + } + // A

with just a
in it is an empty line, so let's not bother with unwrapping it + if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) { + // Move all children of oldParent into startNode's parent + while ( oldParent.firstChild ) { + startNode.parentNode.appendChild( oldParent.firstChild ); + } + } + } + // Remove oldParent, which is now empty + oldParent.parentNode.removeChild( oldParent ); + } + lastP = t.inP; + } + // Moving nodes around like this invalidates offset objects + // TODO: Update offset objects ourselves for performance. Requires rewriting this code block to be + // offset-based rather than traverser-based + } + // Now wrap everything between startNode and endNode (may be equal). + var ca1 = startNode, ca2 = endNode; + if ( ca1 && ca2 && ca1.parentNode ) { + var anchor = markers[i].getAnchor( ca1, ca2 ); + if ( !anchor ) { + var commonAncestor = ca1.parentNode; + if ( markers[i].anchor == 'wrap') { + // We have to store things like .parentNode and .nextSibling because appendChild() changes these + var newNode = ca1.ownerDocument.createElement( 'span' ); + var nextNode = ca2.nextSibling; + // Append all nodes between ca1 and ca2 (inclusive) to newNode + var n = ca1; + while ( n != nextNode ) { + var ns = n.nextSibling; + newNode.appendChild( n ); + n = ns; + } + // Insert newNode in the right place + if ( nextNode ) { + commonAncestor.insertBefore( newNode, nextNode ); + } else { + commonAncestor.appendChild( newNode ); + } + anchor = newNode; + } else if ( markers[i].anchor == 'tag' ) { + anchor = commonAncestor; + } + $( anchor ).data( 'marker', markers[i] ).addClass( 'wikiEditor-highlight' ); + // Allow the module adding this marker to manipulate it + markers[i].afterWrap( anchor, markers[i] ); + + } else { + // Update the marker object + $( anchor ).data( 'marker', markers[i] ); + if ( typeof markers[i].onSkip == 'function' ) { + markers[i].onSkip( anchor ); + } + } + visited[v++] = anchor; + } + } + // Remove markers that were previously inserted but weren't passed to this function - visited[] contains the + // visited elements in order and find() and each() preserve order + var j = 0; + context.$content.find( '.wikiEditor-highlight' ).each( function() { + if ( visited[j] == this ) { + // This marker is legit, leave it in + j++; + return true; + } + // Remove this marker + var marker = $(this).data( 'marker' ); + if ( marker && typeof marker.skipDivision != 'undefined' && ( division == marker.skipDivision ) ) { + // Don't remove these either + return true; + } + if ( marker && typeof marker.beforeUnwrap == 'function' ) + marker.beforeUnwrap( this ); + if ( ( marker && marker.anchor == 'tag' ) || $(this).is( 'p' ) ) { + // Remove all classes + $(this).removeAttr( 'class' ); + } else { + // Assume anchor == 'wrap' + $(this).replaceWith( this.childNodes ); + } + context.fn.purgeOffsets(); + }); + + } +} + +}; })( jQuery ); + diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.html b/extensions/WikiEditor/modules/jquery.wikiEditor.html new file mode 100644 index 00000000..f11521a3 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.html @@ -0,0 +1,135 @@ + + + + WikiEditor + + + + + + diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.iframe.js b/extensions/WikiEditor/modules/jquery.wikiEditor.iframe.js new file mode 100644 index 00000000..5a0cc153 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.iframe.js @@ -0,0 +1,1387 @@ +/* IFrame extension for wikiEditor */ + +( function( $ ) { $.wikiEditor.extensions.iframe = function( context ) { + +/* + * Event Handlers + * + * These act as filters returning false if the event should be ignored or returning true if it should be passed + * on to all modules. This is also where we can attach some extra information to the events. + */ +context.evt = $.extend( context.evt, { + /** + * Filters change events, which occur when the user interacts with the contents of the iframe. The goal of this + * function is to both classify the scope of changes as 'division' or 'character' and to prevent further + * processing of events which did not actually change the content of the iframe. + */ + 'keydown': function( event ) { + switch ( event.which ) { + case 90: // z + case 89: // y + if ( event.which == 89 && !$.browser.msie ) { + // only handle y events for IE + return true; + } else if ( ( event.ctrlKey || event.metaKey ) && context.history.length ) { + // HistoryPosition is a negative number between -1 and -context.history.length, in other words + // it's the number of steps backwards from the latest state. + var newPosition; + if ( event.shiftKey || event.which == 89 ) { + // Redo + newPosition = context.historyPosition + 1; + } else { + // Undo + newPosition = context.historyPosition - 1; + } + // Only act if we are switching to a valid state + if ( newPosition >= ( context.history.length * -1 ) && newPosition < 0 ) { + // Make sure we run the history storing code before we make this change + context.fn.updateHistory( context.oldDelayedHTML != context.$content.html() ); + context.oldDelayedHistoryPosition = context.historyPosition; + context.historyPosition = newPosition; + // Change state + // FIXME: Destroys event handlers, will be a problem with template folding + context.$content.html( + context.history[context.history.length + context.historyPosition].html + ); + context.fn.purgeOffsets(); + if( context.history[context.history.length + context.historyPosition].sel ) { + context.fn.setSelection( { + start: context.history[context.history.length + context.historyPosition].sel[0], + end: context.history[context.history.length + context.historyPosition].sel[1] + } ); + } + } + // Prevent the browser from jumping in and doing its stuff + return false; + } + break; + // Intercept all tab events to provide consisten behavior across browsers + // Webkit browsers insert tab characters by default into the iframe rather than changing input focus + case 9: //tab + // if any modifier keys are pressed, allow the browser to do it's thing + if ( event.ctrlKey || event.altKey || event.shiftKey ) { + return true; + } else { + var $tabindexList = $( '[tabindex]:visible' ).sort( function( a, b ) { + return a.tabIndex - b.tabIndex; + } ); + for( var i=0; i < $tabindexList.length; i++ ) { + if( $tabindexList.eq( i ).attr( 'id' ) == context.$iframe.attr( 'id' ) ) { + $tabindexList.get( i + 1 ).focus(); + break; + } + } + return false; + } + break; + case 86: //v + if ( event.ctrlKey && $.browser.msie && 'paste' in context.evt ) { + //paste, intercepted for IE + context.evt.paste( event ); + } + break; + } + return true; + }, + 'change': function( event ) { + event.data.scope = 'division'; + var newHTML = context.$content.html(); + if ( context.oldHTML != newHTML ) { + context.fn.purgeOffsets(); + context.oldHTML = newHTML; + event.data.scope = 'realchange'; + } + // Never let the body be totally empty + if ( context.$content.children().length == 0 ) { + context.$content.append( '

' ); + } + return true; + }, + 'delayedChange': function( event ) { + event.data.scope = 'division'; + var newHTML = context.$content.html(); + if ( context.oldDelayedHTML != newHTML ) { + context.oldDelayedHTML = newHTML; + event.data.scope = 'realchange'; + // Surround by

if it does not already have it + var cursorPos = context.fn.getCaretPosition(); + var t = context.fn.getOffset( cursorPos[0] ); + if ( ! $.browser.msie && t && t.node.nodeName == '#text' && t.node.parentNode.nodeName.toLowerCase() == 'body' ) { + $( t.node ).wrap( "

" ); + context.fn.purgeOffsets(); + context.fn.setSelection( { start: cursorPos[0], end: cursorPos[1] } ); + } + } + context.fn.updateHistory( event.data.scope == 'realchange' ); + return true; + }, + 'cut': function( event ) { + setTimeout( function() { + context.$content.find( 'br' ).each( function() { + if ( $(this).parent().is( 'body' ) ) { + $(this).wrap( $( '

' ) ); + } + } ); + }, 100 ); + return true; + }, + 'paste': function( event ) { + // Save the cursor position to restore it after all this voodoo + var cursorPos = context.fn.getCaretPosition(); + var oldLength = context.fn.getContents().length; + var positionFromEnd = oldLength - cursorPos[1]; + + //give everything the wikiEditor class so that we can easily pick out things without that class as pasted + context.$content.find( '*' ).addClass( 'wikiEditor' ); + if ( $.layout.name !== 'webkit' ) { + context.$content.addClass( 'pasting' ); + } + + setTimeout( function() { + // Kill stuff we know we don't want + context.$content.find( 'script,style,img,input,select,textarea,hr,button,link,meta' ).remove(); + var nodeToDelete = []; + var pastedContent = []; + var firstDirtyNode; + var $lastDirtyNode; + var elementAtCursor; + if ( $.browser.msie && !context.offsets ) { + elementAtCursor = null; + } else { + elementAtCursor = context.fn.getOffset( cursorPos[0] ); + } + if ( elementAtCursor == null || elementAtCursor.node == null ) { + context.$content.prepend( '

' ); + firstDirtyNode = context.$content.children()[0]; + } else { + firstDirtyNode = elementAtCursor.node; + } + + //this is ugly but seems like the best way to handle the case where we select and replace all editor contents + try { + firstDirtyNode.parentNode; + } catch ( err ) { + context.$content.prepend( '

' ); + firstDirtyNode = context.$content.children()[0]; + } + + while ( firstDirtyNode != null ) { + //we're going to replace the contents of the entire parent node. + while ( firstDirtyNode.parentNode && firstDirtyNode.parentNode.nodeName != 'BODY' + && ! $( firstDirtyNode ).hasClass( 'wikiEditor' ) + ) { + firstDirtyNode = firstDirtyNode.parentNode; + } + //go back till we find the first pasted node + while ( firstDirtyNode.previousSibling != null + && ! $( firstDirtyNode.previousSibling ).hasClass( 'wikiEditor' ) + ) { + + if ( $( firstDirtyNode.previousSibling ).hasClass( '#comment' ) ) { + $( firstDirtyNode ).remove(); + } else { + firstDirtyNode = firstDirtyNode.previousSibling; + } + } + + if ( firstDirtyNode.previousSibling != null ) { + $lastDirtyNode = $( firstDirtyNode.previousSibling ); + } else { + $lastDirtyNode = $( firstDirtyNode ); + } + + var cc = makeContentCollector( $.browser, null ); + while ( firstDirtyNode != null ) { + cc.collectContent(firstDirtyNode); + cc.notifyNextNode(firstDirtyNode.nextSibling); + + nodeToDelete.push( firstDirtyNode ); + + firstDirtyNode = firstDirtyNode.nextSibling; + if ( $( firstDirtyNode ).hasClass( 'wikiEditor' ) ) { + break; + } + } + + var ccData = cc.finish(); + pastedContent = ccData.lines; + var pastedPretty = ''; + for ( var i = 0; i < pastedContent.length; i++ ) { + //escape html + pastedPretty = pastedContent[i].replace(/&/g, '&').replace(//g, '>').replace(/\r?\n/g, '\\n'); + //replace leading white spaces with   + match = pastedContent[i].match(/^[\s]+[^\s]/); + if ( match != null && match.length > 0 ) { + index = match[0].length; + leadingSpace = match[0].replace(/[\s]/g, ' '); + pastedPretty = leadingSpace + pastedPretty.substring(index, pastedPretty.length); + } + + + if( !pastedPretty && $.browser.msie && i == 0 ) { + continue; + } + $newElement = $( '

' ); + if ( pastedPretty ) { + $newElement.html( pastedPretty ); + } else { + $newElement.html( '
' ); + } + $newElement.insertAfter( $lastDirtyNode ); + + $lastDirtyNode = $newElement; + + } + + //now delete all the original nodes that we prettified already + while ( nodeToDelete.length > 0 ) { + $deleteNode = $( nodeToDelete.pop() ); + $deleteNode.remove(); + } + + //anything without wikiEditor class was pasted. + $selection = context.$content.find( ':not(.wikiEditor)' ); + if ( $selection.length == 0 ) { + break; + } else { + firstDirtyNode = $selection.eq( 0 )[0]; + } + } + context.$content.find( '.wikiEditor' ).removeClass( 'wikiEditor' ); + + //now place the cursor at the end of pasted content + var newLength = context.fn.getContents().length; + var newPos = newLength - positionFromEnd; + + context.fn.purgeOffsets(); + context.fn.setSelection( { start: newPos, end: newPos } ); + + context.fn.scrollToCaretPosition(); + }, 0 ); + return true; + }, + 'ready': function( event ) { + // Initialize our history queue + if ( context.$content ) { + context.history.push( { 'html': context.$content.html(), 'sel': context.fn.getCaretPosition() } ); + } else { + context.history.push( { 'html': '', 'sel': context.fn.getCaretPosition() } ); + } + return true; + } +} ); + +/** + * Internally used functions + */ +context.fn = $.extend( context.fn, { + 'highlightLine': function( $element, mode ) { + if ( !$element.is( 'p' ) ) { + $element = $element.closest( 'p' ); + } + $element.css( 'backgroundColor', '#AACCFF' ); + setTimeout( function() { $element.animate( { 'backgroundColor': 'white' }, 'slow' ); }, 100 ); + setTimeout( function() { $element.css( 'backgroundColor', 'white' ); }, 1000 ); + }, + 'htmlToText': function( html ) { + // This function is slow for large inputs, so aggressively cache input/output pairs + if ( html in context.htmlToTextMap ) { + return context.htmlToTextMap[html]; + } + var origHTML = html; + + // We use this elaborate trickery for cross-browser compatibility + // IE does overzealous whitespace collapsing for $( '
' ).html( html );
+		// We also do 
and easy cases for

conversion here, complicated cases are handled later + html = html + .replace( /\r?\n/g, "" ) // IE7 inserts newlines before block elements + .replace( / /g, " " ) // We inserted these to prevent IE from collapsing spaces + .replace( /\]*\>\<\/p\>/gi, '

' ) // Remove trailing
from

+ .replace( /\<\/p\>\s*\]*\>/gi, "\n" ) // Easy case for

conversion + .replace( /\]*\>/gi, "\n" ) //
conversion + .replace( /\<\/p\>(\n*)\]*\>/gi, "$1\n" ) + // Un-nest

tags + .replace( /\]*\>]*\>/gi, '

' ) + .replace( /\<\/p\><\/p\>/gi, '

' ); + // Save leading and trailing whitespace now and restore it later. IE eats it all, and even Firefox + // won't leave everything alone + var leading = html.match( /^\s*/ )[0]; + var trailing = html.match( /\s*$/ )[0]; + html = html.substr( leading.length, html.length - leading.length - trailing.length ); + var $pre = $( '
' + html + '
' ); + $pre.find( '.wikiEditor-noinclude' ).each( function() { $( this ).remove(); } ); + // Convert tabs,

s and
s back + $pre.find( '.wikiEditor-tab' ).each( function() { $( this ).text( "\t" ); } ); + $pre.find( 'br' ).each( function() { $( this ).replaceWith( "\n" ); } ); + // Converting

s is wrong if there's nothing before them, so check that. + // .find( '* + p' ) isn't good enough because textnodes aren't considered + $pre.find( 'p' ).each( function() { + var text = $( this ).text(); + // If this

is preceded by some text, add a \n at the beginning, and if + // it's followed by a textnode, add a \n at the end + // We need the traverser because there can be other weird stuff in between + + // Check for preceding text + var t = new context.fn.rawTraverser( this.firstChild, this, $pre.get( 0 ), true ).prev(); + while ( t && t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { + t = t.prev(); + } + if ( t ) { + text = "\n" + text; + } + + // Check for following text + t = new context.fn.rawTraverser( this.lastChild, this, $pre.get( 0 ), true ).next(); + while ( t && t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { + t = t.next(); + } + if ( t && !t.inP && t.node.nodeName == '#text' && t.node.nodeValue.charAt( 0 ) != '\n' + && t.node.nodeValue.charAt( 0 ) != '\r' ) { + text += "\n"; + } + $( this ).text( text ); + } ); + var retval; + if ( $.browser.msie ) { + // IE aggressively collapses whitespace in .text() after having done DOM manipulation, + // but for some crazy reason this does work. Also convert \r back to \n + retval = $( '

' + $pre.html() + '
' ).text().replace( /\r/g, '\n' ); + } else { + retval = $pre.text(); + } + return context.htmlToTextMap[origHTML] = leading + retval + trailing; + }, + /** + * Get the first element before the selection that's in a certain class + * @param classname Class to match. Defaults to '', meaning any class + * @param strict If true, the element the selection starts in cannot match (default: false) + * @return jQuery object or null if unknown + */ + 'beforeSelection': function( classname, strict ) { + if ( typeof classname == 'undefined' ) { + classname = ''; + } + var e = null, offset = null; + if ( $.browser.msie && !context.$iframe[0].contentWindow.document.body ) { + return null; + } + if ( context.$iframe[0].contentWindow.getSelection ) { + // Firefox and Opera + var selection = context.$iframe[0].contentWindow.getSelection(); + // On load, webkit seems to not have a valid selection + if ( selection.baseNode !== null ) { + // Start at the selection's start and traverse the DOM backwards + // This is done by traversing an element's children first, then the element itself, then its parent + e = selection.getRangeAt( 0 ).startContainer; + offset = selection.getRangeAt( 0 ).startOffset; + } else { + return null; + } + + // When the cursor is on an empty line, Opera gives us a bogus range object with + // startContainer=endContainer=body and startOffset=endOffset=1 + var body = context.$iframe[0].contentWindow.document.body; + if ( $.browser.opera && e == body && offset == 1 ) { + return null; + } + } + if ( !e && context.$iframe[0].contentWindow.document.selection ) { + // IE + // Because there's nothing like range.startContainer in IE, we need to do a DOM traversal + // to find the element the start of the selection is in + var range = context.$iframe[0].contentWindow.document.selection.createRange(); + // Set range2 to the text before the selection + var range2 = context.$iframe[0].contentWindow.document.body.createTextRange(); + // For some reason this call throws errors in certain cases, e.g. when the selection is + // not in the iframe + try { + range2.setEndPoint( 'EndToStart', range ); + } catch ( ex ) { + return null; + } + var seekPos = context.fn.htmlToText( range2.htmlText ).length; + var offset = context.fn.getOffset( seekPos ); + e = offset ? offset.node : null; + offset = offset ? offset.offset : null; + if ( !e ) { + return null; + } + } + if ( e.nodeName != '#text' ) { + // The selection is not in a textnode, but between two non-text nodes + // (usually inside the between two
s). Go to the rightmost + // child of the node just before the selection + var newE = e.firstChild; + for ( var i = 0; i < offset - 1 && newE; i++ ) { + newE = newE.nextSibling; + } + while ( newE && newE.lastChild ) { + newE = newE.lastChild; + } + e = newE || e; + } + + // We'd normally use if( $( e ).hasClass( class ) in the while loop, but running the jQuery + // constructor thousands of times is very inefficient + var classStr = ' ' + classname + ' '; + while ( e ) { + if ( !strict && ( !classname || ( ' ' + e.className + ' ' ).indexOf( classStr ) != -1 ) ) { + return $( e ); + } + var next = e.previousSibling; + while ( next && next.lastChild ) { + next = next.lastChild; + } + e = next || e.parentNode; + strict = false; + } + return $( [] ); + }, + /** + * Object used by traverser(). Don't use this unless you know what you're doing + */ + 'rawTraverser': function( node, inP, ancestor, skipNoinclude ) { + this.node = node; + this.inP = inP; + this.ancestor = ancestor; + this.skipNoinclude = skipNoinclude; + this.next = function() { + var p = this.node; + var nextInP = this.inP; + while ( p && !p.nextSibling ) { + p = p.parentNode; + if ( p == this.ancestor ) { + // We're back at the ancestor, stop here + p = null; + } + if ( p && p.nodeName == "P" ) { + nextInP = null; + } + } + p = p ? p.nextSibling : null; + if ( p && p.nodeName == "P" ) { + nextInP = p; + } + do { + // Filter nodes with the wikiEditor-noinclude class + // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because + // $() is slow in a tight loop + if ( this.skipNoinclude ) { + while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { + p = p.nextSibling; + } + } + if ( p && p.firstChild ) { + p = p.firstChild; + if ( p.nodeName == "P" ) { + nextInP = p; + } + } + } while ( p && p.firstChild ); + // Instead of calling the rawTraverser constructor, inline it. This avoids function call overhead + return p ? { 'node': p, 'inP': nextInP, 'ancestor': this.ancestor, + 'skipNoinclude': this.skipNoinclude, 'next': this.next, 'prev': this.prev } : null; + }; + this.prev = function() { + var p = this.node; + var prevInP = this.inP; + while ( p && !p.previousSibling ) { + p = p.parentNode; + if ( p == this.ancestor ) { + // We're back at the ancestor, stop here + p = null; + } + if ( p && p.nodeName == "P" ) { + prevInP = null; + } + } + p = p ? p.previousSibling : null; + if ( p && p.nodeName == "P" ) { + prevInP = p; + } + do { + // Filter nodes with the wikiEditor-noinclude class + // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because + // $() is slow in a tight loop + if ( this.skipNoinclude ) { + while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { + p = p.previousSibling; + } + } + if ( p && p.lastChild ) { + p = p.lastChild; + if ( p.nodeName == "P" ) { + prevInP = p; + } + } + } while ( p && p.lastChild ); + // Instead of calling the rawTraverser constructor, inline it. This avoids function call overhead + return p ? { 'node': p, 'inP': prevInP, 'ancestor': this.ancestor, + 'skipNoinclude': this.skipNoinclude, 'next': this.next, 'prev': this.prev } : null; + }; + }, + /** + * Get an object used to traverse the leaf nodes in the iframe DOM. This traversal skips leaf nodes + * inside an element with the wikiEditor-noinclude class. This basically wraps rawTraverser + * + * @param start Node to start at + * @return Traverser object, use .next() or .prev() to get a traverser object referring to the + * previous/next node + */ + 'traverser': function( start ) { + // Find the leftmost leaf node in the tree + var startNode = start.jquery ? start.get( 0 ) : start; + var node = startNode; + var inP = node.nodeName == "P" ? node : null; + do { + // Filter nodes with the wikiEditor-noinclude class + // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because + // $() is slow in a tight loop + while ( node && ( ' ' + node.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { + node = node.nextSibling; + } + if ( node && node.firstChild ) { + node = node.firstChild; + if ( node.nodeName == "P" ) { + inP = node; + } + } + } while ( node && node.firstChild ); + return new context.fn.rawTraverser( node, inP, startNode, true ); + }, + 'getOffset': function( offset ) { + if ( !context.offsets ) { + context.fn.refreshOffsets(); + } + if ( offset in context.offsets ) { + return context.offsets[offset]; + } + // Our offset is not pre-cached. Find the highest offset below it and interpolate + // We need to traverse the entire object because for() doesn't traverse in order + // We don't do in-order traversal because the object is sparse + var lowerBound = -1; + for ( var o in context.offsets ) { + var realO = parseInt( o ); + if ( realO < offset && realO > lowerBound) { + lowerBound = realO; + } + } + if ( !( lowerBound in context.offsets ) ) { + // Weird edge case: either offset is too large or the document is empty + return null; + } + var base = context.offsets[lowerBound]; + return context.offsets[offset] = { + 'node': base.node, + 'offset': base.offset + offset - lowerBound, + 'length': base.length, + 'lastTextNode': base.lastTextNode + }; + }, + 'purgeOffsets': function() { + context.offsets = null; + }, + 'refreshOffsets': function() { + context.offsets = [ ]; + var t = context.fn.traverser( context.$content ); + var pos = 0, lastTextNode = null; + while ( t ) { + if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) { + t = t.next(); + continue; + } + var nextPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1; + var nextT = t.next(); + var leavingP = t.node.nodeName == '#text' && t.inP && nextT && ( !nextT.inP || nextT.inP != t.inP ); + context.offsets[pos] = { + 'node': t.node, + 'offset': 0, + 'length': nextPos - pos + ( leavingP ? 1 : 0 ), + 'lastTextNode': lastTextNode + }; + if ( leavingP ) { + //

Foo

looks like "Foo\n", make it quack like it too + // Basically we're faking the \n character much like we're treating
s + context.offsets[nextPos] = { + 'node': t.node, + 'offset': nextPos - pos, + 'length': nextPos - pos + 1, + 'lastTextNode': lastTextNode + }; + } + pos = nextPos + ( leavingP ? 1 : 0 ); + if ( t.node.nodeName == '#text' ) { + lastTextNode = t.node; + } + t = nextT; + } + }, + 'saveCursorAndScrollTop': function() { + // Stub out textarea behavior + return; + }, + 'restoreCursorAndScrollTop': function() { + // Stub out textarea behavior + return; + }, + 'saveSelection': function() { + if ( $.client.profile().name === 'msie' ) { + context.$iframe[0].contentWindow.focus(); + context.savedSelection = context.$iframe[0].contentWindow.document.selection.createRange(); + } + }, + 'restoreSelection': function() { + if ( $.client.profile().name === 'msie' && context.savedSelection !== null ) { + context.$iframe[0].contentWindow.focus(); + context.savedSelection.select(); + context.savedSelection = null; + } + }, + /** + * Update the history queue + * + * @param htmlChange pass true or false to inidicate if there was a text change that should potentially + * be given a new history state. + */ + 'updateHistory': function( htmlChange ) { + var newHTML = context.$content.html(); + var newSel = context.fn.getCaretPosition(); + // Was text changed? Was it because of a REDO or UNDO action? + if ( + context.history.length == 0 || + ( htmlChange && context.oldDelayedHistoryPosition == context.historyPosition ) + ) { + context.oldDelayedSel = newSel; + // Do we need to trim extras from our history? + // FIXME: this should really be happing on change, not on the delay + if ( context.historyPosition < -1 ) { + //clear out the extras + context.history.splice( context.history.length + context.historyPosition + 1 ); + context.historyPosition = -1; + } + context.history.push( { 'html': newHTML, 'sel': newSel } ); + // If the history has grown longer than 10 items, remove the earliest one + while ( context.history.length > 10 ) { + context.history.shift(); + } + } else if ( context.oldDelayedSel != newSel ) { + // If only the selection was changed, update it + context.oldDelayedSel = newSel; + context.history[context.history.length + context.historyPosition].sel = newSel; + } + // synch our old delayed history position until the next undo/redo action + context.oldDelayedHistoryPosition = context.historyPosition; + }, + /** + * Sets up the iframe in place of the textarea to allow more advanced operations + */ + 'setupIframe': function() { + context.$iframe = $( '' ) + .attr( { + 'frameBorder': 0, + 'border': 0, + 'tabindex': 1, + 'src': mw.config.get( 'wgExtensionAssetsPath' ) + '/WikiEditor/modules/jquery.wikiEditor.html?' + + 'instance=' + context.instance + '&ts=' + ( new Date() ).getTime() + '&is=content', + 'id': 'wikiEditor-iframe-' + context.instance + } ) + .css( { + 'backgroundColor': 'white', + 'width': '100%', + 'height': context.$textarea.height(), + 'display': 'none', + 'overflow-y': 'scroll', + 'overflow-x': 'hidden' + } ) + .insertAfter( context.$textarea ) + .load( function() { + // Internet Explorer will reload the iframe once we turn on design mode, so we need to only turn it + // on during the first run, and then bail + if ( !this.isSecondRun ) { + // Turn the document's design mode on + context.$iframe[0].contentWindow.document.designMode = 'on'; + // Let the rest of this function happen next time around + if ( $.browser.msie ) { + this.isSecondRun = true; + return; + } + } + // Get a reference to the content area of the iframe + context.$content = $( context.$iframe[0].contentWindow.document.body ); + // Add classes to the body to influence the styles based on what's enabled + for ( module in context.modules ) { + context.$content.addClass( 'wikiEditor-' + module ); + } + // If we just do "context.$content.text( context.$textarea.val() )", Internet Explorer will strip + // out the whitespace charcters, specifically "\n" - so we must manually encode text and append it + // TODO: Refactor this into a textToHtml() function + var html = context.$textarea.val() + // We're gonna use &esc; as an escape sequence + .replace( /&esc;/g, '&esc;esc;' ) + // Escape existing uses of

,

,   and + .replace( /\/g, '&esc;<p>' ) + .replace( /\<\/p\>/g, '&esc;</p>' ) + .replace( + /\\<\/span\>/g, + '&esc;<span class="wikiEditor-tab"></span>' + ) + .replace( / /g, '&esc;&nbsp;' ); + // We must do some extra processing on IE to avoid dirty diffs, specifically IE will collapse + // leading spaces - browser sniffing is not ideal, but executing this code on a non-broken browser + // doesn't cause harm + if ( $.browser.msie ) { + html = html.replace( /\t/g, '' ); + if ( $.browser.versionNumber <= 7 ) { + // Replace all spaces matching   - IE <= 7 needs this because of its overzealous + // whitespace collapsing + html = html.replace( / /g, " " ); + } else { + // IE8 is happy if we just convert the first leading space to   + html = html.replace( /(^|\n) /g, "$1 " ); + } + } + // Use a dummy div to escape all entities + // This'll also escape
, and   , so we unescape those after + // We also need to unescape the doubly-escaped things mentioned above + html = $( '
' ).text( '

' + html.replace( /\r?\n/g, '

' ) + '

' ).html() + .replace( /&nbsp;/g, ' ' ) + // Allow

tags to survive encoding + .replace( /<p>/g, '

' ) + .replace( /<\/p>/g, '

' ) + // And too + .replace( + /<span( | )class=("|")wikiEditor-tab("|")><\/span>/g, + '' + ) + // Empty

tags need
tags in them + .replace( /

<\/p>/g, '


' ) + // Unescape &esc; stuff + .replace( /&esc;&amp;nbsp;/g, '&nbsp;' ) + .replace( /&esc;&lt;p&gt;/g, '<p>' ) + .replace( /&esc;&lt;\/p&gt;/g, '</p>' ) + .replace( + /&esc;&lt;span&nbsp;class=&quot;wikiEditor-tab&quot;&gt;&lt;\/span&gt;/g, + '<span class="wikiEditor-tab"><\/span>' + ) + .replace( /&esc;esc;/g, '&esc;' ); + context.$content.html( html ); + + // Reflect direction of parent frame into child + if ( $( 'body' ).is( '.rtl' ) ) { + context.$content.addClass( 'rtl' ).attr( 'dir', 'rtl' ); + } + // Activate the iframe, encoding the content of the textarea and copying it to the content of iframe + context.$textarea.attr( 'disabled', true ); + context.$textarea.hide(); + context.$iframe.show(); + // Let modules know we're ready to start working with the content + context.fn.trigger( 'ready' ); + // Only save HTML now: ready handlers may have modified it + context.oldHTML = context.oldDelayedHTML = context.$content.html(); + //remove our temporary loading + /* Disaling our loading div for now + $( '.wikiEditor-ui-loading' ).fadeOut( 'fast', function() { + $( this ).remove(); + } ); + */ + // Setup event handling on the iframe + $( context.$iframe[0].contentWindow.document ) + .bind( 'keydown', function( event ) { + event.jQueryNode = context.fn.getElementAtCursor(); + return context.fn.trigger( 'keydown', event ); + + } ) + .bind( 'keyup', function( event ) { + event.jQueryNode = context.fn.getElementAtCursor(); + return context.fn.trigger( 'keyup', event ); + } ) + .bind( 'keypress', function( event ) { + event.jQueryNode = context.fn.getElementAtCursor(); + return context.fn.trigger( 'keypress', event ); + } ) + .bind( 'paste', function( event ) { + return context.fn.trigger( 'paste', event ); + } ) + .bind( 'cut', function( event ) { + return context.fn.trigger( 'cut', event ); + } ) + .bind( 'keyup paste mouseup cut encapsulateSelection', function( event ) { + return context.fn.trigger( 'change', event ); + } ) + .delayedBind( 250, 'keyup paste mouseup cut encapsulateSelection', function( event ) { + context.fn.trigger( 'delayedChange', event ); + } ); + } ); + // Attach a submit handler to the form so that when the form is submitted the content of the iframe gets + // decoded and copied over to the textarea + context.$textarea.closest( 'form' ).submit( function() { + context.$textarea.attr( 'disabled', false ); + context.$textarea.val( context.$textarea.textSelection( 'getContents' ) ); + } ); + /* FIXME: This was taken from EditWarning.js - maybe we could do a jquery plugin for this? */ + // Attach our own handler for onbeforeunload which respects the current one + context.fallbackWindowOnBeforeUnload = window.onbeforeunload; + window.onbeforeunload = function() { + context.$textarea.val( context.$textarea.textSelection( 'getContents' ) ); + if ( context.fallbackWindowOnBeforeUnload ) { + return context.fallbackWindowOnBeforeUnload(); + } + }; + }, + + /* + * Compatibility with the $.textSelection jQuery plug-in. When the iframe is in use, these functions provide + * equivilant functionality to the otherwise textarea-based functionality. + */ + + 'getElementAtCursor': function() { + if ( context.$iframe[0].contentWindow.getSelection ) { + // Firefox and Opera + var selection = context.$iframe[0].contentWindow.getSelection(); + if ( selection.rangeCount == 0 ) { + // We don't know where the cursor is + return $( [] ); + } + var sc = selection.getRangeAt( 0 ).startContainer; + if ( sc.nodeName == "#text" ) sc = sc.parentNode; + return $( sc ); + } else if ( context.$iframe[0].contentWindow.document.selection ) { // should come last; Opera! + // IE + var selection = context.$iframe[0].contentWindow.document.selection.createRange(); + return $( selection.parentElement() ); + } + }, + + /** + * Gets the complete contents of the iframe (in plain text, not HTML) + */ + 'getContents': function() { + // For

, .html() returns

 

in IE + // This seems to convince IE while not affecting display + if ( !context.$content ) { + return ''; + } + var html; + if ( $.browser.msie ) { + // Don't manipulate the iframe DOM itself, causes cursor jumping issues + var $c = $( context.$content.get( 0 ).cloneNode( true ) ); + $c.find( 'p' ).each( function() { + if ( $(this).html() == '' ) { + $(this).replaceWith( '

' ); + } + } ); + html = $c.html(); + } else { + html = context.$content.html(); + } + return context.fn.htmlToText( html ); + }, + /** + * Gets the currently selected text in the content + * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead + */ + 'getSelection': function() { + var retval; + if ( context.$iframe[0].contentWindow.getSelection ) { + // Firefox and Opera + retval = context.$iframe[0].contentWindow.getSelection(); + if ( $.browser.opera ) { + // Opera strips newlines in getSelection(), so we need something more sophisticated + if ( retval.rangeCount > 0 ) { + retval = context.fn.htmlToText( $( '
' )
+							.append( retval.getRangeAt( 0 ).cloneContents() )
+							.html()
+					);
+				} else {
+					retval = '';
+				}
+			}
+		} else if ( context.$iframe[0].contentWindow.document.selection ) { // should come last; Opera!
+			// IE
+			retval = context.$iframe[0].contentWindow.document.selection.createRange();
+		}
+		if ( typeof retval.text != 'undefined' ) {
+			// In IE8, retval.text is stripped of newlines, so we need to process retval.htmlText
+			// to get a reliable answer. IE7 does get this right though
+			// Run this fix for all IE versions anyway, it doesn't hurt
+			retval = context.fn.htmlToText( retval.htmlText );
+		} else if ( typeof retval.toString != 'undefined' ) {
+			retval = retval.toString();
+		}
+		return retval;
+	},
+	/**
+	 * Inserts text at the begining and end of a text selection, optionally inserting text at the caret when
+	 * selection is empty.
+	 * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead
+	 */
+	'encapsulateSelection': function( options ) {
+		var selText = $(this).textSelection( 'getSelection' );
+		var selTextArr;
+		var collapseToEnd = false;
+		var selectAfter = false;
+		var setSelectionTo = null;
+		var pre = options.pre, post = options.post;
+		if ( !selText ) {
+			selText = options.peri;
+			selectAfter = true;
+		} else if ( options.peri == selText.replace( /\s+$/, '' ) ) {
+			// Probably a successive button press
+			// strip any extra white space from selText
+			selText = selText.replace( /\s+$/, '' );
+			// set the collapseToEnd flag to ensure our selection is collapsed to the end before any insertion is done
+			collapseToEnd = true;
+			// set selectAfter to true since we know we'll be populating with our default text
+			selectAfter = true;
+		} else if ( options.replace ) {
+			selText = options.peri;
+		} else if ( selText.charAt( selText.length - 1 ) == ' ' ) {
+			// Exclude ending space char
+			// FIXME: Why?
+			selText = selText.substring( 0, selText.length - 1 );
+			post += ' ';
+		}
+		if ( options.splitlines ) {
+			selTextArr = selText.split( /\n/ );
+		}
+
+		if ( context.$iframe[0].contentWindow.getSelection ) {
+			// Firefox and Opera
+			var range = context.$iframe[0].contentWindow.getSelection().getRangeAt( 0 );
+			// if our test above indicated that this was a sucessive button press, we need to collapse the 
+			// selection to the end to avoid replacing text 
+			if ( collapseToEnd ) {
+				// Make sure we're not collapsing ourselves into a BR tag
+				if ( range.endContainer.nodeName == 'BR' ) {
+					range.setEndBefore( range.endContainer );
+				}
+				range.collapse( false );
+			}
+			if ( options.ownline ) {
+				// We need to figure out if the cursor is at the start or end of a line
+				var atStart = false, atEnd = false;
+				var body = context.$content.get( 0 );
+				if ( range.startOffset == 0 ) {
+					// Start of a line
+					// FIXME: Not necessarily the case with syntax highlighting or
+					// template collapsing
+					atStart = true;
+				} else if ( range.startContainer == body ) {
+					// Look up the node just before the start of the selection
+					// If it's a 
, we're at the start of a line that starts with a + // block element; if not, we're at the end of a line + var n = body.firstChild; + for ( var i = 0; i < range.startOffset - 1 && n; i++ ) { + n = n.nextSibling; + } + if ( n && n.nodeName == 'BR' ) { + atStart = true; + } else { + atEnd = true; + } + } + if ( ( range.endOffset == 0 && range.endContainer.nodeValue == null ) || + ( range.endContainer.nodeName == '#text' && + range.endOffset == range.endContainer.nodeValue.length ) || + ( range.endContainer.nodeName == 'P' && range.endContainer.nodeValue == null ) ) { + atEnd = true; + } + if ( !atStart ) { + pre = "\n" + options.pre; + } + if ( !atEnd ) { + post += "\n"; + } + } + var insertText = ""; + if ( options.splitlines ) { + for( var j = 0; j < selTextArr.length; j++ ) { + insertText = insertText + pre + selTextArr[j] + post; + if( j != selTextArr.length - 1 ) { + insertText += "\n"; + } + } + } else { + insertText = pre + selText + post; + } + var insertLines = insertText.split( "\n" ); + range.extractContents(); + // Insert the contents one line at a time - insertNode() inserts at the beginning, so this has to happen + // in reverse order + // Track the first and last inserted node, and if we need to also track where the text we need to select + // afterwards starts and ends + var firstNode = null, lastNode = null; + var selSC = null, selEC = null, selSO = null, selEO = null, offset = 0; + for ( var i = insertLines.length - 1; i >= 0; i-- ) { + firstNode = context.$iframe[0].contentWindow.document.createTextNode( insertLines[i] ); + range.insertNode( firstNode ); + lastNode = lastNode || firstNode; + var newOffset = offset + insertLines[i].length; + if ( !selEC && post.length <= newOffset ) { + selEC = firstNode; + selEO = selEC.nodeValue.length - ( post.length - offset ); + } + if ( selEC && !selSC && pre.length >= insertText.length - newOffset ) { + selSC = firstNode; + selSO = pre.length - ( insertText.length - newOffset ); + } + offset = newOffset; + if ( i > 0 ) { + firstNode = context.$iframe[0].contentWindow.document.createElement( 'br' ); + range.insertNode( firstNode ); + newOffset = offset + 1; + if ( !selEC && post.length <= newOffset ) { + selEC = firstNode; + selEO = 1 - ( post.length - offset ); + } + if ( selEC && !selSC && pre.length >= insertText.length - newOffset ) { + selSC = firstNode; + selSO = pre.length - ( insertText.length - newOffset ); + } + offset = newOffset; + } + } + if ( firstNode ) { + context.fn.scrollToTop( $( firstNode.parentNode ) ); + } + if ( selectAfter ) { + setSelectionTo = { + startContainer: selSC, + endContainer: selEC, + start: selSO, + end: selEO + }; + } else if ( lastNode ) { + setSelectionTo = { + startContainer: lastNode, + endContainer: lastNode, + start: lastNode.nodeValue.length, + end: lastNode.nodeValue.length + }; + } + } else if ( context.$iframe[0].contentWindow.document.selection ) { + // IE + context.$iframe[0].contentWindow.focus(); + var range = context.$iframe[0].contentWindow.document.selection.createRange(); + if ( options.ownline && range.moveStart ) { + // Check if we're at the start of a line + // If not, prepend a newline + var range2 = context.$iframe[0].contentWindow.document.selection.createRange(); + range2.collapse(); + range2.moveStart( 'character', -1 ); + // FIXME: Which check is correct? + if ( range2.text != "\r" && range2.text != "\n" && range2.text != "" ) { + pre = "\n" + pre; + } + + // Check if we're at the end of a line + // If not, append a newline + var range3 = context.$iframe[0].contentWindow.document.selection.createRange(); + range3.collapse( false ); + range3.moveEnd( 'character', 1 ); + if ( range3.text != "\r" && range3.text != "\n" && range3.text != "" ) { + post += "\n"; + } + } + // if our test above indicated that this was a sucessive button press, we need to collapse the + // selection to the end to avoid replacing text + if ( collapseToEnd ) { + range.collapse( false ); + } + // TODO: Clean this up. Duplicate code due to the pre-existing browser specific structure of this + // function + var insertText = ""; + if ( options.splitlines ) { + for( var j = 0; j < selTextArr.length; j++ ) { + insertText = insertText + pre + selTextArr[j] + post; + if( j != selTextArr.length - 1 ) { + insertText += "\n"; + } + } + } else { + insertText = pre + selText + post; + } + // TODO: Maybe find a more elegant way of doing this like the Firefox code above? + range.pasteHTML( insertText + .replace( /\/g, '>' ) + .replace( /\r?\n/g, '
' ) + ); + if ( selectAfter ) { + range.moveStart( 'character', -post.length - selText.length ); + range.moveEnd( 'character', -post.length ); + range.select(); + } + } + + if ( setSelectionTo ) { + context.fn.setSelection( setSelectionTo ); + } + // Trigger the encapsulateSelection event (this might need to get named something else/done differently) + $( context.$iframe[0].contentWindow.document ).trigger( + 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ] + ); + return context.$textarea; + }, + /** + * Gets the position (in resolution of bytes not nessecarily characters) in a textarea + * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead + */ + 'getCaretPosition': function( options ) { + var startPos = null, endPos = null; + if ( context.$iframe[0].contentWindow.getSelection ) { + var selection = context.$iframe[0].contentWindow.getSelection(); + if ( selection.rangeCount == 0 ) { + // We don't know where the cursor is + return [ 0, 0 ]; + } + var sc = selection.getRangeAt( 0 ).startContainer, ec = selection.getRangeAt( 0 ).endContainer; + var so = selection.getRangeAt( 0 ).startOffset, eo = selection.getRangeAt( 0 ).endOffset; + if ( sc.nodeName == 'BODY' ) { + // Grab the node just before the start of the selection + var n = sc.firstChild; + for ( var i = 0; i < so - 1 && n; i++ ) { + n = n.nextSibling; + } + sc = n; + so = 0; + } + if ( ec.nodeName == 'BODY' ) { + var n = ec.firstChild; + for ( var i = 0; i < eo - 1 && n; i++ ) { + n = n.nextSibling; + } + ec = n; + eo = 0; + } + + // Make sure sc and ec are leaf nodes + while ( sc.firstChild ) { + sc = sc.firstChild; + } + while ( ec.firstChild ) { + ec = ec.firstChild; + } + // Make sure the offsets are regenerated if necessary + context.fn.getOffset( 0 ); + var o; + for ( o in context.offsets ) { + if ( startPos === null && context.offsets[o].node == sc ) { + // For some wicked reason o is a string, even though + // we put it in as an integer. Use ~~ to coerce it too an int + startPos = ~~o + so - context.offsets[o].offset; + } + if ( startPos !== null && context.offsets[o].node == ec ) { + endPos = ~~o + eo - context.offsets[o].offset; + break; + } + } + } else if ( context.$iframe[0].contentWindow.document.selection ) { + // IE + // FIXME: This is mostly copypasted from the textSelection plugin + var d = context.$iframe[0].contentWindow.document; + var postFinished = false; + var periFinished = false; + var postFinished = false; + var preText, rawPreText, periText; + var rawPeriText, postText, rawPostText; + // Depending on the document state, and if the cursor has ever been manually placed within the document + // the following call such as setEndPoint can result in nasty errors. These cases are always cases + // in which the start and end points can safely be assumed to be 0, so we will just try our best to do + // the full process but fall back to 0. + try { + // Create range containing text in the selection + var periRange = d.selection.createRange().duplicate(); + // Create range containing text before the selection + var preRange = d.body.createTextRange(); + // Move the end where we need it + preRange.setEndPoint( "EndToStart", periRange ); + // Create range containing text after the selection + var postRange = d.body.createTextRange(); + // Move the start where we need it + postRange.setEndPoint( "StartToEnd", periRange ); + // Load the text values we need to compare + preText = rawPreText = preRange.text; + periText = rawPeriText = periRange.text; + postText = rawPostText = postRange.text; + /* + * Check each range for trimmed newlines by shrinking the range by 1 + * character and seeing if the text property has changed. If it has + * not changed then we know that IE has trimmed a \r\n from the end. + */ + do { + if ( !postFinished ) { + if ( preRange.compareEndPoints( "StartToEnd", preRange ) == 0 ) { + postFinished = true; + } else { + preRange.moveEnd( "character", -1 ); + if ( preRange.text == preText ) { + rawPreText += "\r\n"; + } else { + postFinished = true; + } + } + } + if ( !periFinished ) { + if ( periRange.compareEndPoints( "StartToEnd", periRange ) == 0 ) { + periFinished = true; + } else { + periRange.moveEnd( "character", -1 ); + if ( periRange.text == periText ) { + rawPeriText += "\r\n"; + } else { + periFinished = true; + } + } + } + if ( !postFinished ) { + if ( postRange.compareEndPoints("StartToEnd", postRange) == 0 ) { + postFinished = true; + } else { + postRange.moveEnd( "character", -1 ); + if ( postRange.text == postText ) { + rawPostText += "\r\n"; + } else { + postFinished = true; + } + } + } + } while ( ( !postFinished || !periFinished || !postFinished ) ); + startPos = rawPreText.replace( /\r\n/g, "\n" ).length; + endPos = startPos + rawPeriText.replace( /\r\n/g, "\n" ).length; + } catch( e ) { + startPos = endPos = 0; + } + } + return [ startPos, endPos ]; + }, + /** + * Sets the selection of the content + * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead + * + * @param start Character offset of selection start + * @param end Character offset of selection end + * @param startContainer Element in iframe to start selection in. If not set, start is a character offset + * @param endContainer Element in iframe to end selection in. If not set, end is a character offset + */ + 'setSelection': function( options ) { + var sc = options.startContainer, ec = options.endContainer; + sc = sc && sc.jquery ? sc[0] : sc; + ec = ec && ec.jquery ? ec[0] : ec; + if ( context.$iframe[0].contentWindow.getSelection ) { + // Firefox and Opera + var start = options.start, end = options.end; + if ( !sc || !ec ) { + var s = context.fn.getOffset( start ); + var e = context.fn.getOffset( end ); + sc = s ? s.node : null; + ec = e ? e.node : null; + start = s ? s.offset : null; + end = e ? e.offset : null; + // Don't try to set the selection past the end of a node, causes errors + // Just put the selection at the end of the node in this case + if ( sc != null && sc.nodeName == '#text' && start > sc.nodeValue.length ) { + start = sc.nodeValue.length - 1; + } + if ( ec != null && ec.nodeName == '#text' && end > ec.nodeValue.length ) { + end = ec.nodeValue.length - 1; + } + } + if ( !sc || !ec ) { + // The requested offset isn't in the offsets array + // Give up + return context.$textarea; + } + + var sel = context.$iframe[0].contentWindow.getSelection(); + while ( sc.firstChild && sc.nodeName != '#text' ) { + sc = sc.firstChild; + } + while ( ec.firstChild && ec.nodeName != '#text' ) { + ec = ec.firstChild; + } + var range = context.$iframe[0].contentWindow.document.createRange(); + range.setStart( sc, start ); + range.setEnd( ec, end ); + sel.removeAllRanges(); + sel.addRange( range ); + context.$iframe[0].contentWindow.focus(); + } else if ( context.$iframe[0].contentWindow.document.body.createTextRange ) { + // IE + var range = context.$iframe[0].contentWindow.document.body.createTextRange(); + if ( sc ) { + range.moveToElementText( sc ); + } + range.collapse(); + range.moveEnd( 'character', options.start ); + + var range2 = context.$iframe[0].contentWindow.document.body.createTextRange(); + if ( ec ) { + range2.moveToElementText( ec ); + } + range2.collapse(); + range2.moveEnd( 'character', options.end ); + + // IE does newline emulation for

s:

foo

bar

becomes foo\nbar just fine + // but

foo



bar

becomes foo\n\n\n\nbar , one \n too many + // Correct for this + var matches, counted = 0; + // while ( matches = range.htmlText.match( regex ) && matches.length <= counted ) doesn't work + // because the assignment side effect hasn't happened yet when the second term is evaluated + while ( matches = range.htmlText.match( /\<\/p\>(\]*\>)+\/gi ) ) { + if ( matches.length <= counted ) + break; + range.moveEnd( 'character', matches.length ); + counted += matches.length; + } + range2.moveEnd( 'character', counted ); + while ( matches = range2.htmlText.match( /\<\/p\>(\]*\>)+\/gi ) ) { + if ( matches.length <= counted ) + break; + range2.moveEnd( 'character', matches.length ); + counted += matches.length; + } + + range2.setEndPoint( 'StartToEnd', range ); + range2.select(); + } + return context.$textarea; + }, + /** + * Scroll a textarea to the current cursor position. You can set the cursor position with setSelection() + * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead + */ + 'scrollToCaretPosition': function( options ) { + context.fn.scrollToTop( context.fn.getElementAtCursor(), true ); + }, + /** + * Scroll an element to the top of the iframe + * DO NOT CALL THIS DIRECTLY, use $.textSelection( 'functionname', options ) instead + * + * @param $element jQuery object containing an element in the iframe + * @param force If true, scroll the element even if it's already visible + */ + 'scrollToTop': function( $element, force ) { + var html = context.$content.closest( 'html' ), + body = context.$content.closest( 'body' ), + parentHtml = $( 'html' ), + parentBody = $( 'body' ); + var y = $element.offset().top; + if ( !$.browser.msie && ! $element.is( 'body' ) ) { + y = parentHtml.scrollTop() > 0 ? y + html.scrollTop() - parentHtml.scrollTop() : y; + y = parentBody.scrollTop() > 0 ? y + body.scrollTop() - parentBody.scrollTop() : y; + } + var topBound = html.scrollTop() > body.scrollTop() ? html.scrollTop() : body.scrollTop(), + bottomBound = topBound + context.$iframe.height(); + if ( force || y < topBound || y > bottomBound ) { + html.scrollTop( y ); + body.scrollTop( y ); + } + $element.trigger( 'scrollToTop' ); + } +} ); + +/* Setup the IFrame */ +context.fn.setupIframe(); + +} } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.js b/extensions/WikiEditor/modules/jquery.wikiEditor.js new file mode 100644 index 00000000..1f3d1fa5 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.js @@ -0,0 +1,559 @@ +/** + * This plugin provides a way to build a wiki-text editing user interface around a textarea. + * + * @example To intialize without any modules: + * $( 'div#edittoolbar' ).wikiEditor(); + * + * @example To initialize with one or more modules, or to add modules after it's already been initialized: + * $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'toolbar', { ... config ... } ); + * + */ +( function( $ ) { + +/** + * Global static object for wikiEditor that provides generally useful functionality to all modules and contexts. + */ +$.wikiEditor = { + /** + * For each module that is loaded, static code shared by all instances is loaded into this object organized by + * module name. The existance of a module in this object only indicates the module is available. To check if a + * module is in use by a specific context check the context.modules object. + */ + 'modules': {}, + /** + * A context can be extended, such as adding iframe support, on a per-wikiEditor instance basis. + */ + 'extensions': {}, + /** + * In some cases like with the iframe's HTML file, it's convienent to have a lookup table of all instances of the + * WikiEditor. Each context contains an instance field which contains a key that corrosponds to a reference to the + * textarea which the WikiEditor was build around. This way, by passing a simple integer you can provide a way back + * to a specific context. + */ + 'instances': [], + /** + * For each browser name, an array of conditions that must be met are supplied in [operaton, value]-form where + * operation is a string containing a JavaScript compatible binary operator and value is either a number to be + * compared with $.browser.versionNumber or a string to be compared with $.browser.version. If a browser is not + * specifically mentioned, we just assume things will work. + */ + 'browsers': { + // Left-to-right languages + 'ltr': { + // The toolbar layout is broken in IE6 + 'msie': [['>=', 7]], + // Layout issues in FF < 2 + 'firefox': [['>=', 2]], + // Text selection bugs galore - this may be a different situation with the new iframe-based solution + 'opera': [['>=', 9.6]], + // jQuery minimums + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false + }, + // Right-to-left languages + 'rtl': { + // The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode + 'msie': [['>=', 8]], + // Layout issues in FF < 2 + 'firefox': [['>=', 2]], + // Text selection bugs galore - this may be a different situation with the new iframe-based solution + 'opera': [['>=', 9.6]], + // jQuery minimums + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false + } + }, + /** + * Path to images - this is a bit messy, and it would need to change if this code (and images) gets moved into the + * core - or anywhere for that matter... + */ + 'imgPath' : mw.config.get( 'wgExtensionAssetsPath' ) + '/WikiEditor/modules/images/', + /** + * Checks the current browser against the browsers object to determine if the browser has been black-listed or not. + * Because these rules are often very complex, the object contains configurable operators and can check against + * either the browser version number or string. This process also involves checking if the current browser is amung + * those which we have configured as compatible or not. If the browser was not configured as comptible we just go on + * assuming things will work - the argument here is to prevent the need to update the code when a new browser comes + * to market. The assumption here is that any new browser will be built on an existing engine or be otherwise so + * similar to another existing browser that things actually do work as expected. The merrits of this argument, which + * is essentially to blacklist rather than whitelist are debateable, but at this point we've decided it's the more + * "open-web" way to go. + * @param module Module object, defaults to $.wikiEditor + */ + 'isSupported': function( module ) { + // Fallback to the wikiEditor browser map if no special map is provided in the module + var mod = module && 'browsers' in module ? module : $.wikiEditor; + // Check for and make use of cached value and early opportunities to bail + if ( typeof mod.supported !== 'undefined' ) { + // Cache hit + return mod.supported; + } + // Run a browser support test and then cache and return the result + return mod.supported = $.client.test( mod.browsers ); + }, + /** + * Checks if a module has a specific requirement + * @param module Module object + * @param requirement String identifying requirement + */ + 'isRequired': function( module, requirement ) { + if ( typeof module['req'] !== 'undefined' ) { + for ( var req in module['req'] ) { + if ( module['req'][req] == requirement ) { + return true; + } + } + } + return false; + }, + /** + * Provides a way to extract messages from objects. Wraps the mediaWiki.msg() function, which + * may eventually become a wrapper for some kind of core MW functionality. + * + * @param object Object to extract messages from + * @param property String of name of property which contains the message. This should be the base name of the + * property, which means that in the case of the object { this: 'that', fooMsg: 'bar' }, passing property as 'this' + * would return the raw text 'that', while passing property as 'foo' would return the internationalized message + * with the key 'bar'. + */ + 'autoMsg': function( object, property ) { + // Accept array of possible properties, of which the first one found will be used + if ( typeof property == 'object' ) { + for ( var i in property ) { + if ( property[i] in object || property[i] + 'Msg' in object ) { + property = property[i]; + break; + } + } + } + if ( property in object ) { + return object[property]; + } else if ( property + 'Msg' in object ) { + var p = object[property + 'Msg']; + if ( $.isArray( p ) && p.length >= 2 ) { + return mediaWiki.msg.apply( mediaWiki.msg, p ); + } else { + return mediaWiki.msg( p ); + } + } else { + return ''; + } + }, + /** + * Provides a way to extract a property of an object in a certain language, falling back on the property keyed as + * 'default' or 'default-rtl'. If such key doesn't exist, the object itself is considered the actual value, which + * should ideally be the case so that you may use a string or object of any number of strings keyed by language + * with a default. + * + * @param object Object to extract property from + * @param lang Language code, defaults to wgUserLanguage + */ + 'autoLang': function( object, lang ) { + var defaultKey = $( 'body' ).hasClass( 'rtl' ) ? 'default-rtl' : 'default'; + return object[lang || mw.config.get( 'wgUserLanguage' )] || object[defaultKey] || object['default'] || object; + }, + /** + * Provides a way to extract the path of an icon in a certain language, automatically appending a version number for + * caching purposes and prepending an image path when icon paths are relative. + * + * @param icon Icon object from e.g. toolbar config + * @param path Default icon path, defaults to $.wikiEditor.imgPath + * @param lang Language code, defaults to wgUserLanguage + */ + 'autoIcon': function( icon, path, lang ) { + var src = $.wikiEditor.autoLang( icon, lang ); + path = path || $.wikiEditor.imgPath; + // Prepend path if src is not absolute + if ( src.substr( 0, 7 ) != 'http://' && src.substr( 0, 8 ) != 'https://' && src[0] != '/' ) { + src = path + src; + } + return src + '?' + mw.loader.version( 'jquery.wikiEditor' ); + }, + /** + * Get the sprite offset for a language if available, icon for a language if available, or the default offset or icon, + * in that order of preference. + * @param icon Icon object, see autoIcon() + * @param offset Offset object + * @param path Icon path, see autoIcon() + * @param lang Language code, defaults to wgUserLanguage + */ + 'autoIconOrOffset': function( icon, offset, path, lang ) { + lang = lang || mw.config.get( 'wgUserLanguage' ); + if ( typeof offset == 'object' && lang in offset ) { + return offset[lang]; + } else if ( typeof icon == 'object' && lang in icon ) { + return $.wikiEditor.autoIcon( icon, undefined, lang ); + } else { + return $.wikiEditor.autoLang( offset, lang ); + } + } +}; + +/** + * jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea. + */ +$.fn.wikiEditor = function() { + +// Skip any further work when running in browsers that are unsupported +if ( !$.wikiEditor.isSupported() ) { + return $(this); +} + +/* Initialization */ + +// The wikiEditor context is stored in the element's data, so when this function gets called again we can pick up right +// where we left off +var context = $(this).data( 'wikiEditor-context' ); +// On first call, we need to set things up, but on all following calls we can skip right to the API handling +if ( !context || typeof context == 'undefined' ) { + + // Star filling the context with useful data - any jQuery selections, as usual should be named with a preceding $ + context = { + // Reference to the textarea element which the wikiEditor is being built around + '$textarea': $(this), + // Container for any number of mutually exclusive views that are accessible by tabs + 'views': {}, + // Container for any number of module-specific data - only including data for modules in use on this context + 'modules': {}, + // General place to shouve bits of data into + 'data': {}, + // Unique numeric ID of this instance used both for looking up and differentiating instances of wikiEditor + 'instance': $.wikiEditor.instances.push( $(this) ) - 1, + // Array mapping elements in the textarea to character offsets + 'offsets': null, + // Cache for context.fn.htmlToText() + 'htmlToTextMap': {}, + // The previous HTML of the iframe, stored to detect whether something really changed. + 'oldHTML': null, + // Same for delayedChange() + 'oldDelayedHTML': null, + // The previous selection of the iframe, stored to detect whether the selection has changed + 'oldDelayedSel': null, + // Saved selection state for IE + 'savedSelection': null, + // Stack of states in { html: [string] } form + 'history': [], + // Current history state position - this is number of steps backwards, so it's always -1 or less + 'historyPosition': -1, + /// The previous historyPosition, stored to detect if change events were due to an undo or redo action + 'oldDelayedHistoryPosition': -1, + // List of extensions active on this context + 'extensions': [] + }; + + /* + * Externally Accessible API + * + * These are available using calls to $(selection).wikiEditor( call, data ) where selection is a jQuery selection + * of the textarea that the wikiEditor instance was built around. + */ + + context.api = { + /** + * Activates a module on a specific context with optional configuration data. + * + * @param data Either a string of the name of a module to add without any additional configuration parameters, + * or an object with members keyed with module names and valued with configuration objects. + */ + 'addModule': function( context, data ) { + var modules = {}; + if ( typeof data == 'string' ) { + modules[data] = {}; + } else if ( typeof data == 'object' ) { + modules = data; + } + for ( var module in modules ) { + // Check for the existance of an available / supported module with a matching name and a create function + if ( typeof module == 'string' && typeof $.wikiEditor.modules[module] !== 'undefined' && + $.wikiEditor.isSupported( $.wikiEditor.modules[module] ) ) + { + // Extend the context's core API with this module's own API calls + if ( 'api' in $.wikiEditor.modules[module] ) { + for ( var call in $.wikiEditor.modules[module].api ) { + // Modules may not overwrite existing API functions - first come, first serve + if ( !( call in context.api ) ) { + context.api[call] = $.wikiEditor.modules[module].api[call]; + } + } + } + // Activate the module on this context + if ( 'fn' in $.wikiEditor.modules[module] && 'create' in $.wikiEditor.modules[module].fn ) { + // Add a place for the module to put it's own stuff + context.modules[module] = {}; + // Tell the module to create itself on the context + $.wikiEditor.modules[module].fn.create( context, modules[module] ); + } + } + } + } + }; + + /* + * Event Handlers + * + * These act as filters returning false if the event should be ignored or returning true if it should be passed + * on to all modules. This is also where we can attach some extra information to the events. + */ + + context.evt = { + /* Empty until extensions add some; see jquery.wikiEditor.iframe.js for examples. */ + }; + + /* Internal Functions */ + + context.fn = { + /** + * Executes core event filters as well as event handlers provided by modules. + */ + 'trigger': function( name, event ) { + // Event is an optional argument, but from here on out, at least the type field should be dependable + if ( typeof event == 'undefined' ) { + event = { 'type': 'custom' }; + } + // Ensure there's a place for extra information to live + if ( typeof event.data == 'undefined' ) { + event.data = {}; + } + + // Allow filtering to occur + if ( name in context.evt ) { + if ( !context.evt[name]( event ) ) { + return false; + } + } + var returnFromModules = null; //they return null by default + // Pass the event around to all modules activated on this context + + for ( var module in context.modules ) { + if ( + module in $.wikiEditor.modules && + 'evt' in $.wikiEditor.modules[module] && + name in $.wikiEditor.modules[module].evt + ) { + var ret = $.wikiEditor.modules[module].evt[name]( context, event ); + if (ret != null) { + //if 1 returns false, the end result is false + if( returnFromModules == null ) { + returnFromModules = ret; + } else { + returnFromModules = returnFromModules && ret; + } + } + } + } + if ( returnFromModules != null ) { + return returnFromModules; + } else { + return true; + } + }, + /** + * Adds a button to the UI + */ + 'addButton': function( options ) { + // Ensure that buttons and tabs are visible + context.$controls.show(); + context.$buttons.show(); + return $( '