diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2011-12-03 13:29:22 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2011-12-03 13:29:22 +0100 |
commit | ca32f08966f1b51fcb19460f0996bb0c4048e6fe (patch) | |
tree | ec04cc15b867bc21eedca904cea9af0254531a11 /extensions/WikiEditor/modules | |
parent | a22fbfc60f36f5f7ee10d5ae6fe347340c2ee67c (diff) |
Update to MediaWiki 1.18.0
* also update ArchLinux skin to chagnes in MonoBook
* Use only css to hide our menu bar when printing
Diffstat (limited to 'extensions/WikiEditor/modules')
136 files changed, 9461 insertions, 0 deletions
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 <span lang="en"> 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: '<div style="width:10px;height:10px;background-color:red;">' } + }, + { + 'name': { text: 'Blue' }, + 'temp': { text: 'Cold' }, + 'swatch': { html: '<div style="width:10px;height:10px;background-color:blue;">' } + }, + { + 'name': { text: 'Silver' }, + 'temp': { text: 'Neutral' }, + 'swatch': { html: '<div style="width:10px;height:10px;background-color:silver;">' } + } + ] + }, + '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 = $( '<button>Run wikiEditor Tests!</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 Binary files differnew file mode 100644 index 00000000..a7def768 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-error.png diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png Binary files differnew file mode 100644 index 00000000..76abbba5 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-exists.png diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png Binary files differnew file mode 100644 index 00000000..74fc1ada --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-external-rtl.png diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png Binary files differnew file mode 100644 index 00000000..04a5aa17 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-external.png diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png Binary files differnew file mode 100644 index 00000000..b5521b9d --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-invalid.png diff --git a/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png b/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png Binary files differnew file mode 100644 index 00000000..84eb9d68 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/insert-link-notexists.png diff --git a/extensions/WikiEditor/modules/images/dialogs/loading-small.gif b/extensions/WikiEditor/modules/images/dialogs/loading-small.gif Binary files differnew file mode 100644 index 00000000..a8e536b0 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/loading-small.gif diff --git a/extensions/WikiEditor/modules/images/dialogs/loading.gif b/extensions/WikiEditor/modules/images/dialogs/loading.gif Binary files differnew file mode 100644 index 00000000..52f78205 --- /dev/null +++ b/extensions/WikiEditor/modules/images/dialogs/loading.gif diff --git a/extensions/WikiEditor/modules/images/templateEditor/collapse.png b/extensions/WikiEditor/modules/images/templateEditor/collapse.png Binary files differnew file mode 100644 index 00000000..acb42b91 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/collapse.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png b/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png Binary files differnew file mode 100644 index 00000000..ce703020 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/dialog-collapsed.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png b/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png Binary files differnew file mode 100644 index 00000000..73832e97 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/dialog-expanded.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/expand.png b/extensions/WikiEditor/modules/images/templateEditor/expand.png Binary files differnew file mode 100644 index 00000000..4c96ffd3 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/expand.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/name-base.png b/extensions/WikiEditor/modules/images/templateEditor/name-base.png Binary files differnew file mode 100644 index 00000000..4b103085 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/name-base.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/text-base.png b/extensions/WikiEditor/modules/images/templateEditor/text-base.png Binary files differnew file mode 100644 index 00000000..9cb0c74d --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/text-base.png diff --git a/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png b/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png Binary files differnew file mode 100644 index 00000000..ea37e731 --- /dev/null +++ b/extensions/WikiEditor/modules/images/templateEditor/wiki-text.png diff --git a/extensions/WikiEditor/modules/images/toc/close.png b/extensions/WikiEditor/modules/images/toc/close.png Binary files differnew file mode 100644 index 00000000..511fc4ff --- /dev/null +++ b/extensions/WikiEditor/modules/images/toc/close.png diff --git a/extensions/WikiEditor/modules/images/toc/grab.png b/extensions/WikiEditor/modules/images/toc/grab.png Binary files differnew file mode 100644 index 00000000..a0d5d7ba --- /dev/null +++ b/extensions/WikiEditor/modules/images/toc/grab.png diff --git a/extensions/WikiEditor/modules/images/toc/grip.png b/extensions/WikiEditor/modules/images/toc/grip.png Binary files differnew file mode 100644 index 00000000..05203f38 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toc/grip.png diff --git a/extensions/WikiEditor/modules/images/toc/open.png b/extensions/WikiEditor/modules/images/toc/open.png Binary files differnew file mode 100644 index 00000000..459aa39b --- /dev/null +++ b/extensions/WikiEditor/modules/images/toc/open.png diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-down.png b/extensions/WikiEditor/modules/images/toolbar/arrow-down.png Binary files differnew file mode 100644 index 00000000..bf2d4fb4 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/arrow-down.png diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png b/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png Binary files differnew file mode 100644 index 00000000..c27c9636 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/arrow-ltr.png diff --git a/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png b/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png Binary files differnew file mode 100644 index 00000000..12ca1837 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/arrow-rtl.png diff --git a/extensions/WikiEditor/modules/images/toolbar/base.png b/extensions/WikiEditor/modules/images/toolbar/base.png Binary files differnew file mode 100644 index 00000000..b55ed07b --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/base.png diff --git a/extensions/WikiEditor/modules/images/toolbar/button-sprite.png b/extensions/WikiEditor/modules/images/toolbar/button-sprite.png Binary files differnew file mode 100644 index 00000000..bd8989ff --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/button-sprite.png diff --git a/extensions/WikiEditor/modules/images/toolbar/example-image.png b/extensions/WikiEditor/modules/images/toolbar/example-image.png Binary files differnew file mode 100644 index 00000000..a3853683 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/example-image.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-big.png b/extensions/WikiEditor/modules/images/toolbar/format-big.png Binary files differnew file mode 100644 index 00000000..175ea19d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-big.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png Binary files differnew file mode 100644 index 00000000..22849e62 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-A.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png Binary files differnew file mode 100644 index 00000000..45be1de6 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-B.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png Binary files differnew file mode 100644 index 00000000..e3264767 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-F.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png Binary files differnew file mode 100644 index 00000000..39989b42 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-G.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png Binary files differnew file mode 100644 index 00000000..4d34a5c9 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-N.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png Binary files differnew file mode 100644 index 00000000..e245e71a --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-P.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png Binary files differnew file mode 100644 index 00000000..9dfe649b --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-V.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png Binary files differnew file mode 100644 index 00000000..7e37b40f --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-ka.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png b/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png Binary files differnew file mode 100644 index 00000000..e24afd88 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold-ru.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-bold.png b/extensions/WikiEditor/modules/images/toolbar/format-bold.png Binary files differnew file mode 100644 index 00000000..bd8e294c --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-bold.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png Binary files differnew file mode 100644 index 00000000..3cc6c945 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-indent-rtl.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-indent.png b/extensions/WikiEditor/modules/images/toolbar/format-indent.png Binary files differnew file mode 100644 index 00000000..cb864df2 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-indent.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png Binary files differnew file mode 100644 index 00000000..9010c6b1 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-A.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png Binary files differnew file mode 100644 index 00000000..cf191fad --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-C.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png Binary files differnew file mode 100644 index 00000000..9120da78 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-D.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png Binary files differnew file mode 100644 index 00000000..af5bf947 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-I.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png Binary files differnew file mode 100644 index 00000000..d8fd479d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-K.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png b/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png Binary files differnew file mode 100644 index 00000000..2ca7a136 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic-ka.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-italic.png b/extensions/WikiEditor/modules/images/toolbar/format-italic.png Binary files differnew file mode 100644 index 00000000..1b47eeed --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-italic.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png Binary files differnew file mode 100644 index 00000000..1f065399 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-olist-rtl.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-olist.png b/extensions/WikiEditor/modules/images/toolbar/format-olist.png Binary files differnew file mode 100644 index 00000000..abaeae5f --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-olist.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-small.png b/extensions/WikiEditor/modules/images/toolbar/format-small.png Binary files differnew file mode 100644 index 00000000..9f031e8c --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-small.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-subscript.png b/extensions/WikiEditor/modules/images/toolbar/format-subscript.png Binary files differnew file mode 100644 index 00000000..b676d89d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-subscript.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-superscript.png b/extensions/WikiEditor/modules/images/toolbar/format-superscript.png Binary files differnew file mode 100644 index 00000000..7220b3c8 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-superscript.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png b/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png Binary files differnew file mode 100644 index 00000000..51510157 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-ulist-rtl.png diff --git a/extensions/WikiEditor/modules/images/toolbar/format-ulist.png b/extensions/WikiEditor/modules/images/toolbar/format-ulist.png Binary files differnew file mode 100644 index 00000000..3172d4d3 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/format-ulist.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-file.png b/extensions/WikiEditor/modules/images/toolbar/insert-file.png Binary files differnew file mode 100644 index 00000000..1e5419ad --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-file.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png b/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png Binary files differnew file mode 100644 index 00000000..46a9acc5 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-gallery.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png b/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png Binary files differnew file mode 100644 index 00000000..c4010cfe --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-ilink.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-link.png b/extensions/WikiEditor/modules/images/toolbar/insert-link.png Binary files differnew file mode 100644 index 00000000..a8d2f2f3 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-link.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-newline.png b/extensions/WikiEditor/modules/images/toolbar/insert-newline.png Binary files differnew file mode 100644 index 00000000..67a517b4 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-newline.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png b/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png Binary files differnew file mode 100644 index 00000000..80889f4d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-nowiki.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png b/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png Binary files differnew file mode 100644 index 00000000..d0200f6e --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-redirect-rtl.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png b/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png Binary files differnew file mode 100644 index 00000000..52931264 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-redirect.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-reference.png b/extensions/WikiEditor/modules/images/toolbar/insert-reference.png Binary files differnew file mode 100644 index 00000000..8c2c4aa7 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-reference.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-signature.png b/extensions/WikiEditor/modules/images/toolbar/insert-signature.png Binary files differnew file mode 100644 index 00000000..49cdc957 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-signature.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-table.png b/extensions/WikiEditor/modules/images/toolbar/insert-table.png Binary files differnew file mode 100644 index 00000000..7897e78d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-table.png diff --git a/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png b/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png Binary files differnew file mode 100644 index 00000000..7bf24c46 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/insert-xlink.png diff --git a/extensions/WikiEditor/modules/images/toolbar/loading-small.gif b/extensions/WikiEditor/modules/images/toolbar/loading-small.gif Binary files differnew file mode 100644 index 00000000..ff0688b9 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/loading-small.gif diff --git a/extensions/WikiEditor/modules/images/toolbar/loading.gif b/extensions/WikiEditor/modules/images/toolbar/loading.gif Binary files differnew file mode 100644 index 00000000..fab1bb9d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/loading.gif diff --git a/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png b/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png Binary files differnew file mode 100644 index 00000000..00a9cee1 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/magnify-clip.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png Binary files differnew file mode 100644 index 00000000..6fd2d63a --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-down.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png Binary files differnew file mode 100644 index 00000000..adcba5bd --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-left.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png Binary files differnew file mode 100644 index 00000000..52d04e6a --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/arrow-right.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png Binary files differnew file mode 100644 index 00000000..5e60a178 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-big.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png Binary files differnew file mode 100644 index 00000000..429bff1d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-A.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png Binary files differnew file mode 100644 index 00000000..cd4a9993 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-B.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png Binary files differnew file mode 100644 index 00000000..9f50b490 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-F.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png Binary files differnew file mode 100644 index 00000000..4584a8b2 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-G.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png Binary files differnew file mode 100644 index 00000000..7c14e28e --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-N.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png Binary files differnew file mode 100644 index 00000000..906ee670 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-P.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png Binary files differnew file mode 100644 index 00000000..13b258d7 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold-V.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png Binary files differnew file mode 100644 index 00000000..429bff1d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-bold.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png Binary files differnew file mode 100644 index 00000000..ee834ca6 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-A.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png Binary files differnew file mode 100644 index 00000000..d2a9182b --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-C.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png Binary files differnew file mode 100644 index 00000000..a74215d6 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-I.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png Binary files differnew file mode 100644 index 00000000..4e91bcc8 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic-K.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png Binary files differnew file mode 100644 index 00000000..ee834ca6 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-italic.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png Binary files differnew file mode 100644 index 00000000..69a3186d --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-olist.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png Binary files differnew file mode 100644 index 00000000..1b6e22d1 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-small.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png Binary files differnew file mode 100644 index 00000000..3fc0dce5 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-subscript.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png Binary files differnew file mode 100644 index 00000000..d5e2d90c --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-superscript.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png b/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png Binary files differnew file mode 100644 index 00000000..3ddd46cc --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/format-ulist.png 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 Binary files differnew file mode 100644 index 00000000..cabc613a --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-file.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png Binary files differnew file mode 100644 index 00000000..661689ae --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-gallery.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png Binary files differnew file mode 100644 index 00000000..b16eeaba --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-link.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png Binary files differnew file mode 100644 index 00000000..d5ec80b6 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-newline.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png Binary files differnew file mode 100644 index 00000000..f872244e --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-nowiki.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png Binary files differnew file mode 100644 index 00000000..288aa516 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-redirect.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png Binary files differnew file mode 100644 index 00000000..9988dbcc --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-reference.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png Binary files differnew file mode 100644 index 00000000..0b003916 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-signature.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png b/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png Binary files differnew file mode 100644 index 00000000..e506b928 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/insert-table.png diff --git a/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png b/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png Binary files differnew file mode 100644 index 00000000..de3a75f8 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/png24/search-replace.png diff --git a/extensions/WikiEditor/modules/images/toolbar/search-replace.png b/extensions/WikiEditor/modules/images/toolbar/search-replace.png Binary files differnew file mode 100644 index 00000000..cb70b929 --- /dev/null +++ b/extensions/WikiEditor/modules/images/toolbar/search-replace.png 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: '\ + <fieldset>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-link-int-target" rel="wikieditor-toolbar-tool-link-int-target" id="wikieditor-toolbar-tool-link-int-target-label"></label>\ + <div id="wikieditor-toolbar-link-int-target-status"></div>\ + <input type="text" id="wikieditor-toolbar-link-int-target" />\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-link-int-text" rel="wikieditor-toolbar-tool-link-int-text"></label>\ + <input type="text" id="wikieditor-toolbar-link-int-text" />\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <div class="wikieditor-toolbar-floated-field-wrapper">\ + <input type="radio" id="wikieditor-toolbar-link-type-int" name="wikieditor-toolbar-link-type" selected />\ + <label for="wikieditor-toolbar-link-type-int" rel="wikieditor-toolbar-tool-link-int"></label>\ + </div>\ + <div class="wikieditor-toolbar-floated-field-wrapper">\ + <input type="radio" id="wikieditor-toolbar-link-type-ext" name="wikieditor-toolbar-link-type" />\ + <label for="wikieditor-toolbar-link-type-ext" rel="wikieditor-toolbar-tool-link-ext"></label>\ + </div>\ + </div>\ + </fieldset>', + 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( $( '<div />' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-exists' ) + .append( existsMsg ) + ) + .append( $( '<div />' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-notexists' ) + .append( notexistsMsg ) + ) + .append( $( '<div />' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-invalid' ) + .append( invalidMsg ) + ) + .append( $( '<div />' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-external' ) + .append( externalMsg ) + ) + .append( $( '<div />' ) + .attr( 'id', 'wikieditor-toolbar-link-int-target-status-loading' ) + .append( $( '<img />' ).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, '<nowiki>$1</nowiki>' ); + } + 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, '<nowiki>$1</nowiki>' ); + } + 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: '\ + <div class="wikieditor-toolbar-dialog-wrapper">\ + <fieldset><div class="wikieditor-toolbar-table-form">\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-reference-text"\ + rel="wikieditor-toolbar-tool-reference-text"></label>\ + <input type="text" id="wikieditor-toolbar-reference-text" />\ + </div>\ + </div></fieldset>\ + </div>', + 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] + '<ref' + attributes + '>', + peri: insertText, + post: '</ref>' + 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([^\>]*)>([^\<]*)<\/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: '\ + <div class="wikieditor-toolbar-dialog-wrapper">\ + <fieldset><div class="wikieditor-toolbar-table-form">\ + <div class="wikieditor-toolbar-field-wrapper">\ + <input type="checkbox" id="wikieditor-toolbar-table-dimensions-header" checked />\ + <label for="wikieditor-toolbar-table-dimensions-header"\ + rel="wikieditor-toolbar-tool-table-dimensions-header"></label>\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <input type="checkbox" id="wikieditor-toolbar-table-wikitable" checked />\ + <label for="wikieditor-toolbar-table-wikitable" rel="wikieditor-toolbar-tool-table-wikitable"></label>\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <input type="checkbox" id="wikieditor-toolbar-table-sortable" />\ + <label for="wikieditor-toolbar-table-sortable" rel="wikieditor-toolbar-tool-table-sortable"></label>\ + </div>\ + <div class="wikieditor-toolbar-table-dimension-fields">\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-table-dimensions-rows"\ + rel="wikieditor-toolbar-tool-table-dimensions-rows"></label><br />\ + <input type="text" id="wikieditor-toolbar-table-dimensions-rows" size="4" />\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-table-dimensions-columns"\ + rel="wikieditor-toolbar-tool-table-dimensions-columns"></label><br />\ + <input type="text" id="wikieditor-toolbar-table-dimensions-columns" size="4" />\ + </div>\ + </div>\ + </div></fieldset>\ + <div class="wikieditor-toolbar-table-preview-wrapper" >\ + <span rel="wikieditor-toolbar-tool-table-example"></span>\ + <div class="wikieditor-toolbar-table-preview-content">\ + <table id="wikieditor-toolbar-table-preview" class="wikieditor-toolbar-table-preview wikitable">\ + <thead>\ + <tr class="wikieditor-toolbar-table-preview-header">\ + <th rel="wikieditor-toolbar-tool-table-example-header"></th>\ + <th rel="wikieditor-toolbar-tool-table-example-header"></th>\ + <th rel="wikieditor-toolbar-tool-table-example-header"></th>\ + </tr>\ + </thead><tbody>\ + <tr class="wikieditor-toolbar-table-preview-hidden" style="display: none;">\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + </tr><tr>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + </tr><tr>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + </tr><tr>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + <td rel="wikieditor-toolbar-tool-table-example-cell-text"></td>\ + </tr>\ + </tbody>\ + </table>\ + </div>\ + </div></div>', + 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: '\ + <div id="wikieditor-toolbar-replace-message">\ + <div id="wikieditor-toolbar-replace-nomatch" rel="wikieditor-toolbar-tool-replace-nomatch"></div>\ + <div id="wikieditor-toolbar-replace-success"></div>\ + <div id="wikieditor-toolbar-replace-emptysearch" rel="wikieditor-toolbar-tool-replace-emptysearch"></div>\ + <div id="wikieditor-toolbar-replace-invalidregex"></div>\ + </div>\ + <fieldset>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-replace-search" rel="wikieditor-toolbar-tool-replace-search"></label>\ + <input type="text" id="wikieditor-toolbar-replace-search" style="width: 100%;" />\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <label for="wikieditor-toolbar-replace-replace" rel="wikieditor-toolbar-tool-replace-replace"></label>\ + <input type="text" id="wikieditor-toolbar-replace-replace" style="width: 100%;" />\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <input type="checkbox" id="wikieditor-toolbar-replace-case" />\ + <label for="wikieditor-toolbar-replace-case" rel="wikieditor-toolbar-tool-replace-case"></label>\ + </div>\ + <div class="wikieditor-toolbar-field-wrapper">\ + <input type="checkbox" id="wikieditor-toolbar-replace-regex" />\ + <label for="wikieditor-toolbar-replace-regex" rel="wikieditor-toolbar-tool-replace-regex"></label>\ + </div>\ + </fieldset>', + 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 <div> + var dialogDiv = $( '<div />' ) + .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 <div style="display:inline;" /> 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 ) { + $( '<div />' ) + .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 </p><p> with <br> + 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 <p>, insert a <br> + startNode.parentNode.insertBefore( + startNode.ownerDocument.createElement( 'br' ), + afterStart + ); + } + // A <p> with just a <br> 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 <p>, insert a <br> + startNode.parentNode.appendChild( + startNode.ownerDocument.createElement( 'br' ) + ); + } + // A <p> with just a <br> 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 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html> +<head> + <title>WikiEditor</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <!--[if gte IE 8]> + <style> + /* IE8 ONLY - This is how we are fixing the double-height of BR tags when they are alone in a P tag */ + p > br { + display: none; + } + p > br + br { + display: block; + } + </style> + <![endif]--> + <style> + body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + font-family: monospace; + font-size: 9.5pt; + line-height: 1.5em; + overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + } + body.pasting { + white-space: normal; + } + p { + margin: 0; + padding: 0; + } + /* General WikiEditor stuff */ + .wikiEditor-nodisplay { + display: none !important; + } + .wikiEditor-tab { + padding-left: 4em; + } + /* WikiEditor Templates */ + .wikiEditor-templates .wikiEditor-template { + color: silver; + } + /* WikiEditor TemplateEditor */ + .wikiEditor-templateEditor .wikiEditor-template-text-shrunken{ + height: 1px !important; + width: 1px !important; + overflow: hidden; + float: right; + } + .wikiEditor-templateEditor .wikiEditor-template-text-visible{ + padding: 0.5em 4px; + padding-top: 1.25em; + margin-top: -0.95em; + background: #F3F3F3 url(images/templateEditor/text-base.png) repeat-x scroll center top; + display: block; + width: 100%; + border-bottom: solid 1px #cccccc; + } + .wikiEditor-templateEditor .wikiEditor-template { + display: inline-block; + font-size: 12px; + } + .wikiEditor-templateEditor .wikiEditor-template-name { + cursor: pointer; + vertical-align: -2px; + display: inline-block; + height: 16px; + margin-bottom: -1px; + margin-right: 2px; + overflow: hidden; + background: url(images/templateEditor/name-base.png) 0 0 repeat-x #e8e8e8; + color: #000000; + font-family: monospace; + text-decoration: none; + padding-left: 0.33em; + line-height: 16px; + } + .wikiEditor-templateEditor .wikiEditor-template-expand { + cursor: pointer; + vertical-align: -2px; + display: inline-block; + margin-left: 2px; + height: 16px; + margin-bottom: -1px; + line-height: 16px; + overflow: hidden; + width: 13px; + background-position: 50%; + } + .wikiEditor-templateEditor .wikiEditor-template-dialog { + cursor: pointer; + vertical-align: -18%; + display: inline-block; + height: 16px; + overflow: hidden; + width: 22px; + background-position: 50%; + } + .wikiEditor-templateEditor .wikiEditor-template-name:hover { + text-decoration: underline; + } + .wikiEditor-templateEditor .wikiEditor-template-expanded .wikiEditor-template-expand { + background-image: url(images/templateEditor/collapse.png); + } + .wikiEditor-templateEditor .wikiEditor-template-expanded .wikiEditor-template-dialog { + background-image: url(images/templateEditor/dialog-expanded.png); + } + .wikiEditor-templateEditor .wikiEditor-template-collapsed .wikiEditor-template-expand { + background-image: url(images/templateEditor/expand.png); + } + .wikiEditor-templateEditor .wikiEditor-template-collapsed .wikiEditor-template-dialog { + background-image: url(images/templateEditor/dialog-collapsed.png); + } + .wikiEditor-templateEditor .wikiEditor-template-expanded { + display: block; + } + .wikiEditor-templateEditor .wikiEditor-template .wikiEditor-template-text { + + } + .wikiEditor-templateEditor .wikiEditor-template-end, .wikiEditor-template-start { + color: blue; + cursor: pointer; + } + </style> +</head> +<body></body> +</html> 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( '<p></p>' ); + } + 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 <p> 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( "<p></p>" ); + 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( $( '<p></p>' ) ); + } + } ); + }, 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( '<p class = wikiEditor></p>' ); + 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( '<p class = wikiEditor></p>' ); + 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(/>/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 = $( '<p class="wikiEditor pasted" ></p>' ); + if ( pastedPretty ) { + $newElement.html( pastedPretty ); + } else { + $newElement.html( '<br class="wikiEditor">' ); + } + $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 $( '<pre />' ).html( html ); + // We also do <br> and easy cases for <p> 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( /\<br[^\>]*\>\<\/p\>/gi, '</p>' ) // Remove trailing <br> from <p> + .replace( /\<\/p\>\s*\<p[^\>]*\>/gi, "\n" ) // Easy case for <p> conversion + .replace( /\<br[^\>]*\>/gi, "\n" ) // <br> conversion + .replace( /\<\/p\>(\n*)\<p[^\>]*\>/gi, "$1\n" ) + // Un-nest <p> tags + .replace( /\<p[^\>]*\><p[^\>]*\>/gi, '<p>' ) + .replace( /\<\/p\><\/p\>/gi, '</p>' ); + // 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 = $( '<pre>' + html + '</pre>' ); + $pre.find( '.wikiEditor-noinclude' ).each( function() { $( this ).remove(); } ); + // Convert tabs, <p>s and <br>s back + $pre.find( '.wikiEditor-tab' ).each( function() { $( this ).text( "\t" ); } ); + $pre.find( 'br' ).each( function() { $( this ).replaceWith( "\n" ); } ); + // Converting <p>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 <p> 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>' + $pre.html() + '</pre>' ).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 <body> between two <br>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 ) { + // <p>Foo</p> looks like "Foo\n", make it quack like it too + // Basically we're faking the \n character much like we're treating <br>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 = $( '<iframe></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 <p>, </p>, and <span class="wikiEditor-tab"></span> + .replace( /\<p\>/g, '&esc;<p>' ) + .replace( /\<\/p\>/g, '&esc;</p>' ) + .replace( + /\<span class="wikiEditor-tab"\>\<\/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, '<span class="wikiEditor-tab"></span>' ); + 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 <br>, <span> and , so we unescape those after + // We also need to unescape the doubly-escaped things mentioned above + html = $( '<div />' ).text( '<p>' + html.replace( /\r?\n/g, '</p><p>' ) + '</p>' ).html() + .replace( /&nbsp;/g, ' ' ) + // Allow <p> tags to survive encoding + .replace( /<p>/g, '<p>' ) + .replace( /<\/p>/g, '</p>' ) + // And <span class="wikiEditor-tab"></span> too + .replace( + /<span( | )class=("|")wikiEditor-tab("|")><\/span>/g, + '<span class="wikiEditor-tab"></span>' + ) + // Empty <p> tags need <br> tags in them + .replace( /<p><\/p>/g, '<p><br></p>' ) + // 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 <p></p>, .html() returns <p> </p> 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( '<p></p>' ); + } + } ); + 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( $( '<pre />' ) + .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 <BR>, 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( />/g, '>' ) + .replace( /\r?\n/g, '<br />' ) + ); + 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 <p>s: <p>foo</p><p>bar</p> becomes foo\nbar just fine + // but <p>foo</p><br><br><p>bar</p> 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\>(\<br[^\>]*\>)+\<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\>(\<br[^\>]*\>)+\<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 $( '<button />' ) + .text( $.wikiEditor.autoMsg( options, 'caption' ) ) + .click( options.action ) + .appendTo( context.$buttons ); + }, + /** + * Adds a view to the UI, which is accessed using a set of tabs. Views are mutually exclusive and by default a + * wikitext view will be present. Only when more than one view exists will the tabs will be visible. + */ + 'addView': function( options ) { + // Adds a tab + function addTab( options ) { + // Ensure that buttons and tabs are visible + context.$controls.show(); + context.$tabs.show(); + // Return the newly appended tab + return $( '<div></div>' ) + .attr( 'rel', 'wikiEditor-ui-view-' + options.name ) + .addClass( context.view == options.name ? 'current' : null ) + .append( $( '<a></a>' ) + .attr( 'href', '#' ) + .mousedown( function() { + // No dragging! + return false; + } ) + .click( function( event ) { + context.$ui.find( '.wikiEditor-ui-view' ).hide(); + context.$ui.find( '.' + $(this).parent().attr( 'rel' ) ).show(); + context.$tabs.find( 'div' ).removeClass( 'current' ); + $(this).parent().addClass( 'current' ); + $(this).blur(); + if ( 'init' in options && typeof options.init == 'function' ) { + options.init( context ); + } + event.preventDefault(); + return false; + } ) + .text( $.wikiEditor.autoMsg( options, 'title' ) ) + ) + .appendTo( context.$tabs ); + } + // Automatically add the previously not-needed wikitext tab + if ( !context.$tabs.children().size() ) { + addTab( { 'name': 'wikitext', 'titleMsg': 'wikieditor-wikitext-tab' } ); + } + // Add the tab for the view we were actually asked to add + addTab( options ); + // Return newly appended view + return $( '<div></div>' ) + .addClass( 'wikiEditor-ui-view wikiEditor-ui-view-' + options.name ) + .hide() + .appendTo( context.$ui ); + }, + /** + * Save scrollTop and cursor position for IE + */ + 'saveCursorAndScrollTop': function() { + if ( $.client.profile().name === 'msie' ) { + var IHateIE = { + 'scrollTop' : context.$textarea.scrollTop(), + 'pos': context.$textarea.textSelection( 'getCaretPosition', { startAndEnd: true } ) + }; + context.$textarea.data( 'IHateIE', IHateIE ); + } + }, + /** + * Restore scrollTo and cursor position for IE + */ + 'restoreCursorAndScrollTop': function() { + if ( $.client.profile().name === 'msie' ) { + var IHateIE = context.$textarea.data( 'IHateIE' ); + if ( IHateIE ) { + context.$textarea.scrollTop( IHateIE.scrollTop ); + context.$textarea.textSelection( 'setSelection', { start: IHateIE.pos[0], end: IHateIE.pos[1] } ); + context.$textarea.data( 'IHateIE', null ); + } + } + }, + /** + * Save text selection for IE + */ + 'saveSelection': function() { + if ( $.client.profile().name === 'msie' ) { + context.$textarea.focus(); + context.savedSelection = document.selection.createRange(); + } + }, + /** + * Restore text selection for IE + */ + 'restoreSelection': function() { + if ( $.client.profile().name === 'msie' && context.savedSelection !== null ) { + context.$textarea.focus(); + context.savedSelection.select(); + context.savedSelection = null; + } + } + }; + + /* + * Base UI Construction + * + * The UI is built from several containers, the outer-most being a div classed as "wikiEditor-ui". These containers + * provide a certain amount of "free" layout, but in some situations procedural layout is needed, which is performed + * as a response to the "resize" event. + */ + + // Assemble a temporary div to place over the wikiEditor while it's being constructed + /* Disabling our loading div for now + var $loader = $( '<div></div>' ) + .addClass( 'wikiEditor-ui-loading' ) + .append( $( '<span>' + mediaWiki.msg( 'wikieditor-loading' ) + '</span>' ) + .css( 'marginTop', context.$textarea.height() / 2 ) ); + */ + // Encapsulate the textarea with some containers for layout + context.$textarea + /* Disabling our loading div for now + .after( $loader ) + .add( $loader ) + */ + .wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui' ) ) + .wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-view wikiEditor-ui-view-wikitext' ) ) + .wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) ) + .wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) ) + .wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) ); + // Get references to some of the newly created containers + context.$ui = context.$textarea.parent().parent().parent().parent().parent(); + context.$wikitext = context.$textarea.parent().parent().parent().parent(); + // Add in tab and button containers + context.$wikitext + .before( + $( '<div></div>' ).addClass( 'wikiEditor-ui-controls' ) + .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-tabs' ).hide() ) + .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) ) + ) + .before( $( '<div style="clear:both;"></div>' ) ); + // Get references to some of the newly created containers + context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide(); + context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' ); + context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' ); + // Clear all floating after the UI + context.$ui.after( $( '<div style="clear:both;"></div>' ) ); + // Attach a right container + context.$wikitext.append( $( '<div></div>' ).addClass( 'wikiEditor-ui-right' ) ); + // Attach a top container to the left pane + context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) ); + // Setup the intial view + context.view = 'wikitext'; + // Trigger the "resize" event anytime the window is resized + $( window ).resize( function( event ) { context.fn.trigger( 'resize', event ); } ); +} + +/* API Execution */ + +// Since javascript gives arguments as an object, we need to convert them so they can be used more easily +var args = $.makeArray( arguments ); + +// Dynamically setup core extensions for modules that are required +if ( args[0] == 'addModule' && typeof args[1] != 'undefined' ) { + var modules = args[1]; + if ( typeof modules != "object" ) { + modules = {}; + modules[args[1]] = ''; + } + for ( var module in modules ) { + // Only allow modules which are supported (and thus actually being turned on) affect the decision to extend + if ( module in $.wikiEditor.modules && $.wikiEditor.isSupported( $.wikiEditor.modules[module] ) ) { + // Activate all required core extensions on context + for ( var e in $.wikiEditor.extensions ) { + if ( + $.wikiEditor.isRequired( $.wikiEditor.modules[module], e ) && + $.inArray( e, context.extensions ) === -1 + ) { + context.extensions[context.extensions.length] = e; + $.wikiEditor.extensions[e]( context ); + } + } + break; + } + } +} + +// There would need to be some arguments if the API is being called +if ( args.length > 0 ) { + // Handle API calls + var call = args.shift(); + if ( call in context.api ) { + context.api[call]( context, typeof args[0] == 'undefined' ? {} : args[0] ); + } +} + +// Store the context for next time, and support chaining +return $(this).data( 'wikiEditor-context', context ); + +}; } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.preview.css b/extensions/WikiEditor/modules/jquery.wikiEditor.preview.css new file mode 100644 index 00000000..7eb455b5 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.preview.css @@ -0,0 +1,26 @@ +/* + * CSS for WikiEditor Preview jQuery plugin + */ + +.wikiEditor-preview-loading { + padding: 1em; + background-color: white; +} +.wikiEditor-preview-loading span { + color: #666666; +} +.wikiEditor-preview-spinner { + padding-right: 1em; +} +.wikiEditor-preview-contents { + padding: 1em; + background-color: white; +} +/* FIXME: This only works for the first wikiEditor on the page! */ +#wikiEditor-0-preview-dialog .wikiEditor-ui-loading { + overflow: hidden; + border: none; +} +.ui-dialog .ui-dialog-buttonpane { + margin: 0 !important; +}
\ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.preview.js b/extensions/WikiEditor/modules/jquery.wikiEditor.preview.js new file mode 100644 index 00000000..634672a8 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.preview.js @@ -0,0 +1,164 @@ +/* Preview module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.preview = { + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 7]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + } +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates a preview module within a wikiEditor + * @param context Context object of editor to create module in + * @param config Configuration object to create module from + */ + create: function( context, config ) { + if ( 'initialized' in context.modules.preview ) { + return; + } + context.modules.preview = { + 'initialized': true, + 'previewText': null, + 'changesText': null + }; + context.modules.preview.$preview = context.fn.addView( { + 'name': 'preview', + 'titleMsg': 'wikieditor-preview-tab', + 'init': function( context ) { + // Gets the latest copy of the wikitext + var wikitext = context.$textarea.textSelection( 'getContents' ); + // Aborts when nothing has changed since the last preview + if ( context.modules.preview.previewText == wikitext ) { + return; + } + context.modules.preview.$preview.find( '.wikiEditor-preview-contents' ).empty(); + context.modules.preview.$preview.find( '.wikiEditor-preview-loading' ).show(); + $.post( + mw.util.wikiScript( 'api' ), + { + 'action': 'parse', + 'title': mw.config.get( 'wgPageName' ), + 'text': wikitext, + 'prop': 'text', + 'pst': '', + 'format': 'json' + }, + function( data ) { + if ( + typeof data.parse == 'undefined' || + typeof data.parse.text == 'undefined' || + typeof data.parse.text['*'] == 'undefined' + ) { + return; + } + context.modules.preview.previewText = wikitext; + context.modules.preview.$preview.find( '.wikiEditor-preview-loading' ).hide(); + context.modules.preview.$preview.find( '.wikiEditor-preview-contents' ) + .html( data.parse.text['*'] ) + .find( 'a:not([href^=#])' ).click( function() { return false; } ); + }, + 'json' + ); + } + } ); + + context.$changesTab = context.fn.addView( { + 'name': 'changes', + 'titleMsg': 'wikieditor-preview-changes-tab', + 'init': function( context ) { + // Gets the latest copy of the wikitext + var wikitext = context.$textarea.textSelection( 'getContents' ); + // Aborts when nothing has changed since the last time + if ( context.modules.preview.changesText == wikitext ) { + return; + } + context.$changesTab.find( 'table.diff tbody' ).empty(); + context.$changesTab.find( '.wikiEditor-preview-loading' ).show(); + + // Call the API. First PST the input, then diff it + var postdata = { + 'action': 'parse', + 'onlypst': '', + 'text': wikitext, + 'format': 'json' + }; + + $.post( mw.util.wikiScript( 'api' ), postdata, function( data ) { + try { + var postdata2 = { + 'action': 'query', + 'indexpageids': '', + 'prop': 'revisions', + 'titles': mw.config.get( 'wgPageName' ), + 'rvdifftotext': data.parse.text['*'], + 'rvprop': '', + 'format': 'json' + }; + var section = $( '[name=wpSection]' ).val(); + if ( section != '' ) + postdata2['rvsection'] = section; + + $.post( mw.util.wikiScript( 'api' ), postdata2, function( data ) { + // Add diff CSS + mw.loader.load( 'mediawiki.action.history.diff' ); + try { + var diff = data.query.pages[data.query.pageids[0]] + .revisions[0].diff['*']; + context.$changesTab.find( 'table.diff tbody' ) + .html( diff ); + context.$changesTab + .find( '.wikiEditor-preview-loading' ).hide(); + context.modules.preview.changesText = wikitext; + } catch ( e ) { } // "blah is undefined" error, ignore + }, 'json' + ); + } catch( e ) { } // "blah is undefined" error, ignore + }, 'json' ); + } + } ); + + var loadingMsg = mediaWiki.msg( 'wikieditor-preview-loading' ); + context.modules.preview.$preview + .add( context.$changesTab ) + .append( $( '<div />' ) + .addClass( 'wikiEditor-preview-loading' ) + .append( $( '<img />' ) + .addClass( 'wikiEditor-preview-spinner' ) + .attr( { + 'src': $.wikiEditor.imgPath + 'dialogs/loading.gif', + 'valign': 'absmiddle', + 'alt': loadingMsg, + 'title': loadingMsg + } ) + ) + .append( + $( '<span></span>' ).text( loadingMsg ) + ) + ) + .append( $( '<div />' ) + .addClass( 'wikiEditor-preview-contents' ) + ); + context.$changesTab.find( '.wikiEditor-preview-contents' ) + .html( '<table class="diff"><col class="diff-marker" /><col class="diff-content" />' + + '<col class="diff-marker" /><col class="diff-content" /><tbody /></table>' ); + } +} + +}; } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.css b/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.css new file mode 100644 index 00000000..7a27f594 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.css @@ -0,0 +1,35 @@ +/* + * CSS for WikiEditor Preview Dialog jQuery plugin + */ + +/* FIXME: This only works for the first wikiEditor on the page! */ +#wikiEditor-0-preview-dialog .wikiEditor-ui-loading { + background: #f3f3f3; + z-index: 10; + position: absolute; + left: 0; + text-align: center; + height: 100%; + width: 100%; + overflow: hidden; + border: none; +} +/* FIXME: This only works for the first wikiEditor on the page! */ +#wikiEditor-0-preview-dialog .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: 50px auto; +} +.ui-dialog .ui-dialog-buttonpane { + margin: 0 !important; +} +.wikiEditor-preview-dialog-contents { + font-size: 0.9em !important; +} +.wikiEditor-preview-dialog-contents #firstHeading { + font-size: 2.1em; +}
\ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.js b/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.js new file mode 100644 index 00000000..72003055 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.previewDialog.js @@ -0,0 +1,131 @@ +/* Publish module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.previewDialog = { + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 7]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + } +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates a publish module within a wikiEditor + * @param context Context object of editor to create module in + * @param config Configuration object to create module from + */ + create: function( context, config ) { + // Build the dialog behind the Publish button + var dialogID = 'wikiEditor-' + context.instance + '-preview-dialog'; + $.wikiEditor.modules.dialogs.fn.create( + context, + { + preview: { + id: dialogID, + titleMsg: 'wikieditor-preview-tab', + html: '\ + <div class="wikiEditor-ui-loading"><span></span></div>\ + <div class="wikiEditor-preview-dialog-contents"></div>\ + ', + init: function() { + }, + dialog: { + buttons: { + 'wikieditor-publish-dialog-publish': function() { + var minorChecked = $( '#wikiEditor-' + context.instance + + '-dialog-minor' ).is( ':checked' ) ? + 'checked' : ''; + var watchChecked = $( '#wikiEditor-' + context.instance + + '-dialog-watch' ).is( ':checked' ) ? + 'checked' : ''; + $( '#wpMinoredit' ).attr( 'checked', minorChecked ); + $( '#wpWatchthis' ).attr( 'checked', watchChecked ); + $( '#wpSummary' ).val( $( '#wikiEditor-' + context.instance + + '-dialog-summary' ).val() ); + $( '#editform' ).submit(); + }, + 'wikieditor-publish-dialog-goback': function() { + $(this).dialog( 'close' ); + } + }, + resizable: false, + height: $( 'body' ).height() - 100, + width: $( 'body' ).width() - 300, + position: ['center', 'top'], + open: function() { + // Gets the latest copy of the wikitext + var wikitext = context.fn.getContents(); + var $dialog = $( '#' + dialogID ); + $dialog + .css( 'position', 'relative' ) + .css( 'height', $( 'body' ).height() - 200 ) + .parent() + .css( 'top', '25px' ); + // $dialog.dialog( 'option', 'width', $( 'body' ).width() - 300 ); + // Aborts when nothing has changed since the last preview + if ( context.modules.preview.previewText == wikitext ) { + return; + } + + $dialog.find( '.wikiEditor-preview-dialog-contents' ).empty(); + $dialog.find( '.wikiEditor-ui-loading' ).show(); + $.post( + mw.util.wikiScript( 'api' ), + { + 'action': 'parse', + 'title': mw.config.get( 'wgPageName' ), + 'text': wikitext, + 'prop': 'text', + 'pst': '', + 'format': 'json' + }, + function( data ) { + if ( + typeof data.parse == 'undefined' || + typeof data.parse.text == 'undefined' || + typeof data.parse.text['*'] == 'undefined' + ) { + return; + } + context.modules.preview.previewText = wikitext; + $dialog.find( '.wikiEditor-ui-loading' ).hide(); + $dialog.find( '.wikiEditor-preview-dialog-contents' ) + .html( '<h1 class="firstHeading" id="firstHeading">' + + mw.config.get( 'wgTitle' ) + '</h1>' + + data.parse.text['*'] ) + .find( 'a:not([href^=#])' ).click( function() { return false; } ); + }, + 'json' + ); + } + }, + resizeme: false + } + } + ); + context.fn.addButton( { + 'captionMsg': 'wikieditor-preview-tab', + 'action': function() { + context.$textarea.wikiEditor( 'openDialog', 'preview'); + return false; + } + } ); + } +} + +}; } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.publish.js b/extensions/WikiEditor/modules/jquery.wikiEditor.publish.js new file mode 100644 index 00000000..5a758e43 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.publish.js @@ -0,0 +1,146 @@ +/* Publish module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.publish = { + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 7]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 4]] + } +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates a publish module within a wikiEditor + * @param context Context object of editor to create module in + * @param config Configuration object to create module from + */ + create: function( context, config ) { + // Build the dialog behind the Publish button + var dialogID = 'wikiEditor-' + context.instance + '-dialog'; + $.wikiEditor.modules.dialogs.fn.create( + context, + { + previewsave: { + id: dialogID, + titleMsg: 'wikieditor-publish-dialog-title', + html: '\ + <div class="wikiEditor-publish-dialog-copywarn"></div>\ + <div class="wikiEditor-publish-dialog-editoptions">\ + <form id="wikieditor-' + context.instance + '-publish-dialog-form">\ + <div class="wikiEditor-publish-dialog-summary">\ + <label for="wikiEditor-' + context.instance + '-dialog-summary"\ + rel="wikieditor-publish-dialog-summary"></label>\ + <br />\ + <input type="text" id="wikiEditor-' + context.instance + '-dialog-summary"\ + style="width: 100%;" />\ + </div>\ + <div class="wikiEditor-publish-dialog-options">\ + <input type="checkbox"\ + id="wikiEditor-' + context.instance + '-dialog-minor" />\ + <label for="wikiEditor-' + context.instance + '-dialog-minor"\ + rel="wikieditor-publish-dialog-minor"></label>\ + <input type="checkbox"\ + id="wikiEditor-' + context.instance + '-dialog-watch" />\ + <label for="wikiEditor-' + context.instance + '-dialog-watch"\ + rel="wikieditor-publish-dialog-watch"></label>\ + </div>\ + </form>\ + </div>', + init: function() { + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + }); + + /* REALLY DIRTY HACK! */ + // Reformat the copyright warning stuff + var copyWarnHTML = $( '#editpage-copywarn p' ).html(); + // TODO: internationalize by splitting on other characters that end statements + var copyWarnStatements = copyWarnHTML.split( '. ' ); + var newCopyWarnHTML = '<ul>'; + for ( var i = 0; i < copyWarnStatements.length; i++ ) { + if ( copyWarnStatements[i] != '' ) { + var copyWarnStatement = $.trim( copyWarnStatements[i] ).replace( /\.*$/, '' ); + newCopyWarnHTML += '<li>' + copyWarnStatement + '.</li>'; + } + } + newCopyWarnHTML += '</ul>'; + // No list if there's only one element + $(this).find( '.wikiEditor-publish-dialog-copywarn' ).html( + copyWarnStatements.length > 1 ? newCopyWarnHTML : copyWarnHTML + ); + /* END OF REALLY DIRTY HACK */ + + if ( $( '#wpMinoredit' ).size() == 0 ) + $( '#wikiEditor-' + context.instance + '-dialog-minor' ).hide(); + else if ( $( '#wpMinoredit' ).is( ':checked' ) ) + $( '#wikiEditor-' + context.instance + '-dialog-minor' ) + .attr( 'checked', 'checked' ); + if ( $( '#wpWatchthis' ).size() == 0 ) + $( '#wikiEditor-' + context.instance + '-dialog-watch' ).hide(); + else if ( $( '#wpWatchthis' ).is( ':checked' ) ) + $( '#wikiEditor-' + context.instance + '-dialog-watch' ) + .attr( 'checked', 'checked' ); + + $(this).find( 'form' ).submit( function( e ) { + $(this).closest( '.ui-dialog' ).find( 'button:first' ).click(); + e.preventDefault(); + }); + }, + dialog: { + buttons: { + 'wikieditor-publish-dialog-publish': function() { + var minorChecked = $( '#wikiEditor-' + context.instance + + '-dialog-minor' ).is( ':checked' ) ? + 'checked' : ''; + var watchChecked = $( '#wikiEditor-' + context.instance + + '-dialog-watch' ).is( ':checked' ) ? + 'checked' : ''; + $( '#wpMinoredit' ).attr( 'checked', minorChecked ); + $( '#wpWatchthis' ).attr( 'checked', watchChecked ); + $( '#wpSummary' ).val( $( '#wikiEditor-' + context.instance + + '-dialog-summary' ).val() ); + $( '#editform' ).submit(); + }, + 'wikieditor-publish-dialog-goback': function() { + $(this).dialog( 'close' ); + } + }, + open: function() { + $( '#wikiEditor-' + context.instance + '-dialog-summary' ).focus(); + }, + width: 500 + }, + resizeme: false + } + } + ); + context.fn.addButton( { + 'captionMsg': 'wikieditor-publish-button-publish', + 'action': function() { + $( '#' + dialogID ).dialog( 'open' ); + return false; + } + } ); + context.fn.addButton( { + 'captionMsg': 'wikieditor-publish-button-cancel', + 'action': function() { } + } ); + } +} + +}; } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js b/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js new file mode 100644 index 00000000..794bddc7 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js @@ -0,0 +1,865 @@ +/* TemplateEditor module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.templateEditor = { +/** + * Name mappings, dirty hack which will be removed once "TemplateInfo" extension is more fully supported + */ +'nameMappings': { //keep these all lowercase to navigate web of redirects + "infobox skyscraper": "building_name", + "infobox settlement": "official_name" +}, + + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': false, + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]] + } +}, +/** + * Core Requirements + */ +'req': [ 'iframe' ], +/** + * Event handlers + */ +evt: { + + mark: function( context, event ) { + // The markers returned by this function are skipped on realchange, so don't regenerate them in that case + if ( context.modules.highlight.currentScope == 'realchange' ) { + return; + } + + // Get references to the markers and tokens from the current context + var markers = context.modules.highlight.markers; + var tokenArray = context.modules.highlight.tokenArray; + // Collect matching level 0 template call boundaries from the tokenArray + var level = 0; + var tokenIndex = 0; + while ( tokenIndex < tokenArray.length ){ + while ( tokenIndex < tokenArray.length && tokenArray[tokenIndex].label != 'TEMPLATE_BEGIN' ) { + tokenIndex++; + } + //open template + if ( tokenIndex < tokenArray.length ) { + var beginIndex = tokenIndex; + var endIndex = -1; //no match found + var openTemplates = 1; + var templatesMatched = false; + while ( tokenIndex < tokenArray.length - 1 && endIndex == -1 ) { + tokenIndex++; + if ( tokenArray[tokenIndex].label == 'TEMPLATE_BEGIN' ) { + openTemplates++; + } else if ( tokenArray[tokenIndex].label == 'TEMPLATE_END' ) { + openTemplates--; + if ( openTemplates == 0 ) { + endIndex = tokenIndex; + } //we can stop looping + } + }//while finding template ending + if ( endIndex != -1 ) { + markers.push( { + start: tokenArray[beginIndex].offset, + end: tokenArray[endIndex].offset, + type: 'template', + anchor: 'wrap', + afterWrap: function( node ) { + // Generate model + var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) ); + if ( model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) ); + $.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) ); + } else { + $( node ).addClass( 'wikiEditor-template-text' ); + } + }, + beforeUnwrap: function( node ) { + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) { + $.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) ); + } + }, + onSkip: function( node ) { + if ( $( node ).html() == $( node ).data( 'oldHTML' ) ) { + // No change + return; + } + + // Text changed, regenerate model + var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) ); + + // Update template name if needed + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) { + var $label = $( node ).parent().find( '.wikiEditor-template-label' ); + var displayName = $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ); + if ( $label.text() != displayName ) { + $label.text( displayName ); + } + } + + // Wrap or unwrap the template if needed + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) && + !model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) ); + } else if ( !$( node ).parent().hasClass( 'wikiEditor-template' ) && + model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) ); + $.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) ); + } + }, + getAnchor: function( ca1, ca2 ) { + return $( ca1.parentNode ).is( 'span.wikiEditor-template-text' ) ? + ca1.parentNode : null; + }, + context: context, + skipDivision: 'realchange' + } ); + } else { //else this was an unmatched opening + tokenArray[beginIndex].label = 'TEMPLATE_FALSE_BEGIN'; + tokenIndex = beginIndex; + } + }//if opentemplates + } + }, //mark + + keydown: function( context, event ) { + // Reset our ignoreKeypress variable if it's set to true + if ( context.$iframe.data( 'ignoreKeypress' ) ) { + context.$iframe.data( 'ignoreKeypress', false ); + } + var $evtElem = event.jQueryNode; + if ( $evtElem.hasClass( 'wikiEditor-template-label' ) ) { + // Allow anything if the command or control key are depressed + if ( event.ctrlKey || event.metaKey ) return true; + switch ( event.which ) { + case 13: // Enter + $evtElem.click(); + event.preventDefault(); + return false; + case 32: // Space + $evtElem.parent().siblings( '.wikiEditor-template-expand' ).click(); + event.preventDefault(); + return false; + case 37:// Left + case 38:// Up + case 39:// Right + case 40: //Down + return true; + default: + // Set the ignroreKeypress variable so we don't allow typing if the key is held + context.$iframe.data( 'ignoreKeypress', true ); + // Can't type in a template name + event.preventDefault(); + return false; + } + } else if ( $evtElem.hasClass( 'wikiEditor-template-text' ) ) { + switch ( event.which ) { + case 13: // Enter + // Ensure that the user can't break this by holding in the enter key + context.$iframe.data( 'ignoreKeypress', true ); + // FIXME: May be a more elegant way to do this, but this works too + context.fn.encapsulateSelection( { 'pre': '\n', 'peri': '', 'post': '' } ); + event.preventDefault(); + return false; + default: return true; + } + } + }, + keyup: function( context, event ) { + // Rest our ignoreKeypress variable if it's set to true + if ( context.$iframe.data( 'ignoreKeypress' ) ) { + context.$iframe.data( 'ignoreKeypress', false ); + } + return true; + }, + keypress: function( context, event ) { + // If this event is from a keydown event which we want to block, ignore it + return ( context.$iframe.data( 'ignoreKeypress' ) ? false : true ); + } +}, +/** + * Regular expressions that produce tokens + */ +exp: [ + { 'regex': /{{/, 'label': "TEMPLATE_BEGIN" }, + { 'regex': /}}/, 'label': "TEMPLATE_END", 'markAfter': true } +], +/** + * Configuration + */ +cfg: { +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates template form module within wikieditor + * @param context Context object of editor to create module in + * @param config Configuration object to create module from + */ + create: function( context, config ) { + // Initialize module within the context + context.modules.templateEditor = {}; + }, + /** + * Turns a simple template wrapper (really just a <span>) into a complex one + * @param $wrapper Wrapping <span> + */ + wrapTemplate: function( $wrapper ) { + var model = $wrapper.data( 'model' ); + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper + .wrap( '<span class="wikiEditor-template"></span>' ) + .addClass( 'wikiEditor-template-text wikiEditor-template-text-shrunken' ) + .parent() + .addClass( 'wikiEditor-template-collapsed' ) + .prepend( + '<span class="wikiEditor-template-expand wikiEditor-noinclude"></span>' + + '<span class="wikiEditor-template-name wikiEditor-noinclude">' + + '<span class="wikiEditor-template-label wikiEditor-noinclude">' + + $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ) + '</span>' + + '<span class="wikiEditor-template-dialog wikiEditor-noinclude"></span>' + + '</span>' + ); + }, + /** + * Turn a complex template wrapper back into a simple one + * @param $wrapper Wrapping <span> + */ + unwrapTemplate: function( $wrapper ) { + $wrapper.parent().replaceWith( $wrapper ); + }, + /** + * Bind events to a template + * @param $wrapper Original wrapper for the template to bind events to + */ + bindTemplateEvents: function( $wrapper ) { + var $template = $wrapper.parent( '.wikiEditor-template' ); + + if ( typeof ( opera ) == "undefined" ) { + $template.parent().attr('contentEditable', 'false'); + } + + $template.click( function(event) {event.preventDefault(); return false;} ); + + $template.find( '.wikiEditor-template-name' ) + .click( function( event ) { + $.wikiEditor.modules.templateEditor.fn.createDialog( $wrapper ); + event.stopPropagation(); + return false; + } ) + .mousedown( function( event ) { event.stopPropagation(); return false; } ); + $template.find( '.wikiEditor-template-expand' ) + .click( function( event ) { + $.wikiEditor.modules.templateEditor.fn.toggleWikiTextEditor( $wrapper ); + event.stopPropagation(); + return false; + } ) + .mousedown( function( event ) { event.stopPropagation(); return false; } ); + }, + /** + * Toggle the visisbilty of the wikitext for a given template + * @param $wrapper The origianl wrapper we want expand/collapse + */ + toggleWikiTextEditor: function( $wrapper ) { + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper.parent( '.wikiEditor-template' ); + context.fn.purgeOffsets(); + $template + .toggleClass( 'wikiEditor-template-expanded' ) + .toggleClass( 'wikiEditor-template-collapsed' ) ; + + var $templateText = $template.find( '.wikiEditor-template-text' ); + $templateText.toggleClass( 'wikiEditor-template-text-shrunken' ); + $templateText.toggleClass( 'wikiEditor-template-text-visible' ); + if( $templateText.hasClass('wikiEditor-template-text-shrunken') ){ + //we just closed the template + + // Update the model if we need to + if ( $templateText.html() != $templateText.data( 'oldHTML' ) ) { + var templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText ); + + //this is the only place the template name can be changed; keep the template name in sync + var $tLabel = $template.find( '.wikiEditor-template-label' ); + $tLabel.text( $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( templateModel ) ); + } + + } + }, + /** + * Create a dialog for editing a given template and open it + * @param $wrapper The origianl wrapper for which to create the dialog + */ + createDialog: function( $wrapper ) { + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper.parent( '.wikiEditor-template' ); + var dialog = { + 'titleMsg': 'wikieditor-template-editor-dialog-title', + 'id': 'wikiEditor-template-dialog', + 'html': '\ + <fieldset>\ + <div class="wikiEditor-template-dialog-title" />\ + <div class="wikiEditor-template-dialog-fields" />\ + </fieldset>', + init: function() { + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + } ); + }, + immediateCreate: true, + dialog: { + width: 600, + height: 400, + dialogClass: 'wikiEditor-toolbar-dialog', + buttons: { + 'wikieditor-template-editor-dialog-submit': function() { + // More user feedback + var $templateDiv = $( this ).data( 'templateDiv' ); + context.fn.highlightLine( $templateDiv ); + + var $templateText = $templateDiv.children( '.wikiEditor-template-text' ); + var templateModel = $templateText.data( 'model' ); + $( this ).find( '.wikiEditor-template-dialog-field-wrapper textarea' ).each( function() { + // Update the value + templateModel.setValue( $( this ).data( 'name' ), $( this ).val() ); + }); + //keep text consistent + $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText, templateModel ); + + $( this ).dialog( 'close' ); + }, + 'wikieditor-template-editor-dialog-cancel': function() { + $(this).dialog( 'close' ); + } + }, + open: function() { + var $templateDiv = $( this ).data( 'templateDiv' ); + var $templateText = $templateDiv.children( '.wikiEditor-template-text' ); + var templateModel = $templateText.data( 'model' ); + // Update the model if we need to + if ( $templateText.html() != $templateText.data( 'oldHTML' ) ) { + templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText ); + } + + // Build the table + // TODO: Be smart and recycle existing table + var params = templateModel.getAllInitialParams(); + var $fields = $( this ).find( '.wikiEditor-template-dialog-fields' ); + // Do some bookkeeping so we can recycle existing rows + var $rows = $fields.find( '.wikiEditor-template-dialog-field-wrapper' ); + for ( var paramIndex in params ) { + var param = params[paramIndex]; + if ( typeof param.name == 'undefined' ) { + // param is the template name, skip it + continue; + } + var paramText = typeof param == 'string' ? + param.name.replace( /[\_\-]/g, ' ' ) : + param.name; + var paramVal = templateModel.getValue( param.name ); + if ( $rows.length > 0 ) { + // We have another row to recycle + var $row = $rows.eq( 0 ); + $row.children( 'label' ).text( paramText ); + $row.children( 'textarea' ) + .data( 'name', param.name ) + .val( paramVal ) + .each( function() { + $(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' ); + } ); + $rows = $rows.not( $row ); + } else { + // Create a new row + var $paramRow = $( '<div />' ) + .addClass( 'wikiEditor-template-dialog-field-wrapper' ); + $( '<label />' ) + .text( paramText ) + .appendTo( $paramRow ); + $( '<textarea />' ) + .data( 'name', param.name ) + .val( paramVal ) + .each( function() { + $(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' ); + } ) + .data( 'expanded', false ) + .bind( 'cut paste keypress click change', function( e ) { + // If this was fired by a tab keypress, let it go + if ( e.keyCode == '9' ) return true; + var $this = $( this ); + setTimeout( function() { + var expanded = $this.data( 'expanded' ); + if ( $this.val().indexOf( '\n' ) != -1 || $this.val().length > 24 ) { + if ( !expanded ) { + $this.animate( { 'height': '4.5em' }, 'fast' ); + $this.data( 'expanded', true ); + } + } else { + if ( expanded ) { + $this.animate( { 'height': '1.5em' }, 'fast' ); + $this.data( 'expanded', false ); + } + } + }, 0 ); + } ) + .appendTo( $paramRow ); + $paramRow + .append( '<div style="clear:both"></div>' ) + .appendTo( $fields ); + } + } + + // Remove any leftover rows + $rows.remove(); + $fields.find( 'label' ).autoEllipsis(); + // Ensure our close button doesn't recieve the ui-state-focus class + $( this ).parent( '.ui-dialog' ).find( '.ui-dialog-titlebar-close' ) + .removeClass( 'ui-state-focus' ); + + // Set tabindexes on form fields if needed + // First unset the tabindexes on the buttons and existing form fields + // so the order doesn't get messed up + var $needTabindex = $( this ).closest( '.ui-dialog' ).find( 'button, textarea' ); + if ( $needTabindex.not( '[tabindex]' ).length ) { + // Only do this if there actually are elements missing a tabindex + $needTabindex.removeAttr( 'tabindex' ); + $.wikiEditor.modules.dialogs.fn.setTabindexes( $needTabindex ); + } + } + } + }; + // Lazy-create the dialog at this time + context.$textarea.wikiEditor( 'addDialog', { 'templateEditor': dialog } ); + $( '#' + dialog.id ) + .data( 'templateDiv', $template ) + .dialog( 'open' ); + }, + /** + * Update a template's model and HTML + * @param $templateText Wrapper <span> containing the template text + * @param model Template model to use, will be generated if not set + * @return model object + */ + updateModel: function( $templateText, model ) { + var context = $templateText.data( 'marker' ).context; + var text; + if ( typeof model == 'undefined' ) { + text = context.fn.htmlToText( $templateText.html() ); + } else { + text = model.getText(); + } + // To keep stuff simple but not break it, we need to do encode newlines as <br>s + $templateText.text( text ); + $templateText.html( $templateText.html().replace( /\n/g, '<br />' ) ); + $templateText.data( 'oldHTML', $templateText.html() ); + if ( typeof model == 'undefined' ) { + model = new $.wikiEditor.modules.templateEditor.fn.model( text ); + $templateText.data( 'model', model ); + } + return model; + }, + + /** + * Gets template display name + */ + getTemplateDisplayName: function ( model ) { + var tName = model.getName(); + if( model.getValue( 'name' ) != '' ) { + return tName + ': ' + model.getValue( 'name' ); + } else if( model.getValue( 'Name' ) != '' ) { + return tName + ': ' + model.getValue( 'Name' ); + } else if( tName.toLowerCase() in $.wikiEditor.modules.templateEditor.nameMappings ) { + return tName + ': ' + model.getValue( $.wikiEditor.modules.templateEditor.nameMappings[tName.toLowerCase()] ); + } + return tName; + }, + + /** + * Builds a template model from given wikitext representation, allowing object-oriented manipulation of the contents + * of the template while preserving whitespace and formatting. + * + * @param wikitext String of wikitext content + */ + model: function( wikitext ) { + + /* Private members */ + + var collapsible = true; + + /* Private Functions */ + + /** + * Builds a Param object. + * + * @param name + * @param value + * @param number + * @param nameIndex + * @param equalsIndex + * @param valueIndex + */ + function Param( name, value, number, nameIndex, equalsIndex, valueIndex ) { + this.name = name; + this.value = value; + this.number = number; + this.nameIndex = nameIndex; + this.equalsIndex = equalsIndex; + this.valueIndex = valueIndex; + } + /** + * Builds a Range object. + * + * @param begin + * @param end + */ + function Range( begin, end ) { + this.begin = begin; + this.end = end; + } + /** + * Set 'original' to true if you want the original value irrespective of whether the model's been changed + * + * @param name + * @param value + * @param original + */ + function getSetValue( name, value, original ) { + var valueRange; + var rangeIndex; + var retVal; + if ( isNaN( name ) ) { + // It's a string! + if ( typeof paramsByName[name] == 'undefined' ) { + // Does not exist + return ""; + } + rangeIndex = paramsByName[name]; + } else { + // It's a number! + rangeIndex = parseInt( name ); + } + if ( typeof params[rangeIndex] == 'undefined' ) { + // Does not exist + return ""; + } + valueRange = ranges[params[rangeIndex].valueIndex]; + if ( typeof valueRange.newVal == 'undefined' || original ) { + // Value unchanged, return original wikitext + retVal = wikitext.substring( valueRange.begin, valueRange.end ); + } else { + // New value exists, return new value + retVal = valueRange.newVal; + } + if ( value != null ) { + ranges[params[rangeIndex].valueIndex].newVal = value; + } + return retVal; + }; + + /* Public Functions */ + + /** + * Get template name + */ + this.getName = function() { + if( typeof ranges[templateNameIndex].newVal == 'undefined' ) { + return wikitext.substring( ranges[templateNameIndex].begin, ranges[templateNameIndex].end ); + } else { + return ranges[templateNameIndex].newVal; + } + }; + /** + * Set template name (if we want to support this) + * + * @param name + */ + this.setName = function( name ) { + ranges[templateNameIndex].newVal = name; + }; + /** + * Set value for a given param name / number + * + * @param name + * @param value + */ + this.setValue = function( name, value ) { + return getSetValue( name, value, false ); + }; + /** + * Get value for a given param name / number + * + * @param name + */ + this.getValue = function( name ) { + return getSetValue( name, null, false ); + }; + /** + * Get original value of a param + * + * @param name + */ + this.getOriginalValue = function( name ) { + return getSetValue( name, null, true ); + }; + /** + * Get a list of all param names (numbers for the anonymous ones) + */ + this.getAllParamNames = function() { + return paramsByName; + }; + /** + * Get the initial params + */ + this.getAllInitialParams = function(){ + return params; + }; + /** + * Get original template text + */ + this.getOriginalText = function() { + return wikitext; + }; + /** + * Get modified template text + */ + this.getText = function() { + newText = ""; + for ( i = 0 ; i < ranges.length; i++ ) { + if( typeof ranges[i].newVal == 'undefined' ) { + newText += wikitext.substring( ranges[i].begin, ranges[i].end ); + } else { + newText += ranges[i].newVal; + } + } + return newText; + }; + + this.isCollapsible = function() { + return collapsible; + }; + + /** + * Update ranges if there's been a change in one or more 'segments' of the template. + * Removes adjustment function so adjustment is only made once ever. + */ + + this.updateRanges = function() { + var adjustment = 0; + for (var i = 0 ; i < ranges.length; i++ ) { + ranges[i].begin += adjustment; + if( typeof ranges[i].adjust != 'undefined' ) { + adjustment += ranges[i].adjust(); + // NOTE: adjust should be a function that has the information necessary to calculate the length of + // this 'segment' + delete ranges[i].adjust; + } + ranges[i].end += adjustment; + } + }; + + // Whitespace* {{ whitespace* nonwhitespace: + if ( wikitext.match( /\s*{{\s*[^\s|]*:/ ) ) { + collapsible = false; // is a parser function + } + /* + * Take all template-specific characters that are not particular to the template we're looking at, namely {|=}, + * and convert them into something harmless, in this case 'X' + */ + // Get rid of first {{ with whitespace + var sanatizedStr = wikitext.replace( /{{/, " " ); + // Replace end + endBraces = sanatizedStr.match( /}}\s*$/ ); + if ( endBraces ) { + sanatizedStr = sanatizedStr.substring( 0, endBraces.index ) + " " + + sanatizedStr.substring( endBraces.index + 2 ); + } + + + //treat HTML comments like whitespace + while ( sanatizedStr.indexOf( '<!' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '<!' ); + endIndex = sanatizedStr.indexOf('-->') + 3; + if( endIndex < 3 ){ + break; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /\S/g , ' ' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + + // Match the open braces we just found with equivalent closing braces note, works for any level of braces + while ( sanatizedStr.indexOf( '{{' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '{{' ) + 1; + openBraces = 2; + endIndex = startIndex; + while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) { + var brace = sanatizedStr[++endIndex]; + openBraces += brace == '}' ? -1 : brace == '{' ? 1 : 0; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[{}|=]/g , 'X' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + //links, images, etc, which also can nest + while ( sanatizedStr.indexOf( '[[' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '[[' ) + 1; + openBraces = 2; + endIndex = startIndex; + while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) { + var brace = sanatizedStr[++endIndex]; + openBraces += brace == ']' ? -1 : brace == '[' ? 1 : 0; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[\[\]|=]/g , 'X' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + + /* + * Parse 1 param at a time + */ + var ranges = []; + var params = []; + var templateNameIndex = 0; + var doneParsing = false; + oldDivider = 0; + divider = sanatizedStr.indexOf( '|', oldDivider ); + if ( divider == -1 ) { + divider = sanatizedStr.length; + doneParsing = true; + collapsible = false; //zero params + } + nameMatch = sanatizedStr.substring( 0, divider ).match( /[^\s]/ ); + if ( nameMatch != null ) { + ranges.push( new Range( 0 ,nameMatch.index ) ); //whitespace and squiggles upto the name + nameEndMatch = sanatizedStr.substring( 0 , divider ).match( /[^\s]\s*$/ ); //last nonwhitespace character + templateNameIndex = ranges.push( new Range( nameMatch.index, + nameEndMatch.index + 1 ) ); + templateNameIndex--; //push returns 1 less than the array + ranges[templateNameIndex].old = wikitext.substring( ranges[templateNameIndex].begin, + ranges[templateNameIndex].end ); + } else { + ranges.push(new Range(0,0)); + ranges[templateNameIndex].old = ""; + } + params.push( ranges[templateNameIndex].old ); //put something in params (0) + /* + * Start looping over params + */ + var currentParamNumber = 0; + var valueEndIndex = ranges[templateNameIndex].end; + var paramsByName = []; + while ( !doneParsing ) { + currentParamNumber++; + oldDivider = divider; + divider = sanatizedStr.indexOf( '|', oldDivider + 1 ); + if ( divider == -1 ) { + divider = sanatizedStr.length; + doneParsing = true; + } + currentField = sanatizedStr.substring( oldDivider+1, divider ); + if ( currentField.indexOf( '=' ) == -1 ) { + // anonymous field, gets a number + + //default values, since we'll allow empty values + valueBeginIndex = oldDivider + 1; + valueEndIndex = oldDivider + 1; + + valueBegin = currentField.match( /\S+/ ); //first nonwhitespace character + if( valueBegin != null ){ + valueBeginIndex = valueBegin.index + oldDivider+1; + valueEnd = currentField.match( /[^\s]\s*$/ ); //last nonwhitespace character + if( valueEnd == null ){ //ie + continue; + } + valueEndIndex = valueEnd.index + oldDivider + 2; + } + ranges.push( new Range( ranges[ranges.length-1].end, + valueBeginIndex ) ); //all the chars upto now + nameIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1; + equalsIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1; + valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1; + params.push( new Param( + currentParamNumber, + wikitext.substring( ranges[valueIndex].begin, ranges[valueIndex].end ), + currentParamNumber, + nameIndex, + equalsIndex, + valueIndex + ) ); + paramsByName[currentParamNumber] = currentParamNumber; + } else { + // There's an equals, could be comment or a value pair + currentName = currentField.substring( 0, currentField.indexOf( '=' ) ); + // Still offset by oldDivider - first nonwhitespace character + nameBegin = currentName.match( /\S+/ ); + if ( nameBegin == null ) { + // This is a comment inside a template call / parser abuse. let's not encourage it + currentParamNumber--; + continue; + } + nameBeginIndex = nameBegin.index + oldDivider + 1; + // Last nonwhitespace and non } character + nameEnd = currentName.match( /[^\s]\s*$/ ); + if( nameEnd == null ){ //ie + continue; + } + nameEndIndex = nameEnd.index + oldDivider + 2; + // All the chars upto now + ranges.push( new Range( ranges[ranges.length-1].end, nameBeginIndex ) ); + nameIndex = ranges.push( new Range( nameBeginIndex, nameEndIndex ) ) - 1; + currentValue = currentField.substring( currentField.indexOf( '=' ) + 1); + oldDivider += currentField.indexOf( '=' ) + 1; + + //default values, since we'll allow empty values + valueBeginIndex = oldDivider + 1; + valueEndIndex = oldDivider + 1; + + // First nonwhitespace character + valueBegin = currentValue.match( /\S+/ ); + if( valueBegin != null ){ + valueBeginIndex = valueBegin.index + oldDivider + 1; + // Last nonwhitespace and non } character + valueEnd = currentValue.match( /[^\s]\s*$/ ); + if( valueEnd == null ){ //ie + continue; + } + valueEndIndex = valueEnd.index + oldDivider + 2; + } + // All the chars upto now + equalsIndex = ranges.push( new Range( ranges[ranges.length-1].end, valueBeginIndex) ) - 1; + valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1; + params.push( new Param( + wikitext.substring( nameBeginIndex, nameEndIndex ), + wikitext.substring( valueBeginIndex, valueEndIndex ), + currentParamNumber, + nameIndex, + equalsIndex, + valueIndex + ) ); + paramsByName[wikitext.substring( nameBeginIndex, nameEndIndex )] = currentParamNumber; + } + } + // The rest of the string + ranges.push( new Range( valueEndIndex, wikitext.length ) ); + + // Save vars + this.ranges = ranges; + this.wikitext = wikitext; + this.params = params; + this.paramsByName = paramsByName; + this.templateNameIndex = templateNameIndex; + } //model +} +}; } )( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.templates.js b/extensions/WikiEditor/modules/jquery.wikiEditor.templates.js new file mode 100644 index 00000000..b303e3fa --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.templates.js @@ -0,0 +1,69 @@ +/* Templates Module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.templates = { + +/** + * Core Requirements + */ +'req': [ 'iframe' ], +/** + * Object Templates + */ +'tpl': { + 'marker': { + 'type': 'template', + 'anchor': 'wrap', + 'skipDivision': 'realchange', + 'afterWrap': function( node ) { + $( node ).addClass( 'wikiEditor-template' ); + }, + 'getAnchor': function( ca1, ca2 ) { + return $( ca1.parentNode ).is( '.wikiEditor-template' ) ? ca1.parentNode : null; + } + } +}, +/** + * Event handlers + */ +'evt': { + 'mark': function( context, event ) { + // The markers returned by this function are skipped on realchange, so don't regenerate them in that case + if ( context.modules.highlight.currentScope == 'realchange' ) { + return; + } + // Get references to the markers and tokens from the current context + var markers = context.modules.highlight.markers; + var tokens = context.modules.highlight.tokenArray; + // Use depth-tracking to extract top-level templates from tokens + var depth = 0, bias, start; + for ( var i in tokens ) { + depth += ( bias = tokens[i].label == 'TEMPLATE_BEGIN' ? 1 : ( tokens[i].label == 'TEMPLATE_END' ? -1 : 0 ) ); + if ( bias > 0 && depth == 1 ) { + // Top-level opening - use offset as start + start = tokens[i].offset; + } else if ( bias < 0 && depth == 0 ) { + // Top-level closing - use offset as end + markers[markers.length] = $.extend( + { 'context': context, 'start': start, 'end': tokens[i].offset }, + $.wikiEditor.modules.templates.tpl.marker + ); + } + if ( depth < 0 ) { + depth = 0; + } + } + } +}, +'exp': [ + { 'regex': /{{/, 'label': "TEMPLATE_BEGIN" }, + { 'regex': /}}/, 'label': "TEMPLATE_END", 'markAfter': true } +], +/** + * Internally used functions + */ +'fn': { + 'create': function( context, config ) { + // Do some stuff here... + } +} + +}; } ) ( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.toc.css b/extensions/WikiEditor/modules/jquery.wikiEditor.toc.css new file mode 100644 index 00000000..bb1e8775 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.toc.css @@ -0,0 +1,177 @@ +/* + * CSS for WikiEditor Table of Contents jQuery plugin + */ + +.wikiEditor-ui-toc { + /* height and width are set dynamically */ + /*float: right;*/ + padding: 0; + overflow: auto; + overflow-x: hidden; +} +.wikiEditor-ui-toc { + border-left: solid silver 1px; +} +.wikiEditor-ui-toc ul { + padding: 0; + margin: 0; + list-style: none; + /* IE needs to be told in great detail how to act, or it misbehaves */ + list-style-image: none; + list-style-position: outside; + list-style-type: none; + width: 100%; +} +.tab-toc { + /* Should match the toolbar */ + /* @embed */ + background-image: url(images/toolbar/base.png); + background-position: left top; + background-repeat: repeat-x; + height: 26px; + padding: 3px 0; + line-height: 26px; + padding-left: 1em; + border-bottom: solid 1px silver; + white-space: nowrap; + overflow: hidden; +} +.tab-toc a { + outline: none; +} +.wikiEditor-ui-toc li { + padding: 0; + margin: 0; +} +.wikiEditor-ui-toc ul ul { + padding: 0; + margin: 0; + margin-bottom: 0 !important; + margin-top: 0 !important; + list-style: none; + background-image: none; +} +.wikiEditor-ui-toc ul li div { + display: block; + font-size: 0.9em; + cursor: pointer; + color: #0645ad; +} +.wikiEditor-ui-toc ul li div { + padding: 0.125em; + padding-left: 1em; +} +.wikiEditor-ui-toc ul ul li div { + padding-left: 2em; +} +.wikiEditor-ui-toc ul ul ul li div { + padding-left: 3em; +} +.wikiEditor-ui-toc ul ul ul ul li div { + padding-left: 4em; +} +.wikiEditor-ui-toc ul ul ul ul ul li div { + padding-left: 5em; +} +.wikiEditor-ui-toc ul ul ul ul ul ul li div { + padding-left: 6em; +} +.wikiEditor-ui-toc ul li div.current { + background-color: #FAFAFA; + color: #333333; +} +.wikiEditor-ui-toc ul li div.section-0 { + font-size: 1em; + padding-top: 0.5em; + padding-bottom: 0.5em; + border-bottom: solid 1px #DDDDDD; +} +/* Collapsing changes */ +.wikiEditor-ui-toc { + overflow-y: hidden; + position: relative; +} +.wikiEditor-ui-toc ul { + overflow-y: auto; + overflow-x: hidden; + height: 100%; + margin-bottom: 0 !important; + +} +.wikiEditor-ui-toc ul ul { + float: none; + height: auto; +} +#wikiEditor-ui-toc-collapse { + height: 100%; + width: 18px; + position: absolute; + top: 0; + left: 0; +} +.wikiEditor-ui-toc-collapse-open { + /* @embed */ + background: #f3f3f3 url(images/toc/close.png) 4px 50% no-repeat; + border-left: 1px solid #DDDDDD; +} +.wikiEditor-ui-toc-collapse-closed { + /* @embed */ + background: #f3f3f3 url(images/toc/open.png) 4px 50% no-repeat; +} +/* Resizing Changes */ +.wikiEditor-ui-toc-resize-vertical, +.ui-resizable-w { + width: 4px; + position: absolute; + top: 0; + left: 0; + height: 100%; + cursor: ew-resize; +} +.wikiEditor-ui .wikiEditor-ui-right { + overflow: visible; +} +.wikiEditor-ui-right .ui-resizable-w { + left: 0px !important; + z-index: 0; +} +.wikiEditor-ui-right .wikiEditor-ui-toc-resize-grip { + width: 5px; + height: 12px; + padding: 3px; + position: absolute; + top: 7px; + left: -12px !important; + cursor: ew-resize; + /* @embed */ + background: url(images/toc/grip.png) 50% 50% no-repeat; + z-index: 0; +} +.wikiEditor-ui-toolbar .tab-toc { + float: right; + margin: 3px 16px 3px 3px; + line-height: 26px; +} +.wikiEditor-ui-toc-expandControl { + position: absolute; + z-index: 2; + top: 0px; + right: 10px; + height: 26px; + padding: 3px 0; + line-height: 26px; + padding-right: 1em; + white-space: nowrap; + overflow: hidden; +} +.wikiEditor-ui-text textarea { + resize: none; +} +.wikiEditor-ui-text textarea:focus { + outline: none; +} +/* Self Clearing for the wikiText view */ +.wikiEditor-ui-view-wikiText { + overflow: auto; + width: 100%; +}
\ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.toc.js b/extensions/WikiEditor/modules/jquery.wikiEditor.toc.js new file mode 100644 index 00000000..a01335e2 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.toc.js @@ -0,0 +1,667 @@ +/* TOC Module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.toc = { + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 7]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]], + 'chrome': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]], + 'chrome': [['>=', 4]] + } +}, +/** + * Core Requirements + */ +'req': [ 'iframe' ], +/** + * Configuration + */ +cfg: { + // Default width of table of contents + defaultWidth: '166px', + // Minimum width to allow resizing to before collapsing the table of contents - used when resizing and collapsing + minimumWidth: '70px', + // Minimum width of the wikiText area + textMinimumWidth: '450px', + // The style property to be used for positioning the flexible module in regular mode + flexProperty: 'marginRight', + // Boolean var indicating text direction + rtl: false +}, +/** + * API accessible functions + */ +api: { + // +}, +/** + * Event handlers + */ +evt: { + change: function( context, event ) { + $.wikiEditor.modules.toc.fn.update( context ); + }, + ready: function( context, event ) { + // Add the TOC to the document + $.wikiEditor.modules.toc.fn.build( context ); + if ( !context.$content ) { + return; + } + context.$content.parent() + .blur( function() { + var context = event.data.context; + $.wikiEditor.modules.toc.fn.unhighlight( context ); + }); + $.wikiEditor.modules.toc.fn.improveUI(); + $.wikiEditor.modules.toc.evt.resize( context ); + }, + resize: function( context, event ) { + var availableWidth = context.$wikitext.width() - parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ), + totalMinWidth = parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ) + + parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ); + context.$ui.find( '.wikiEditor-ui-right' ) + .resizable( 'option', 'maxWidth', availableWidth ); + if ( context.modules.toc.$toc.data( 'positionMode' ) != 'disabled' && + context.$wikitext.width() < totalMinWidth ) { + $.wikiEditor.modules.toc.fn.disable( context ); + } else if ( context.modules.toc.$toc.data( 'positionMode' ) == 'disabled' && + context.$wikitext.width() > totalMinWidth ) { + $.wikiEditor.modules.toc.fn.enable( context ); + } else if ( context.modules.toc.$toc.data( 'positionMode' ) == 'regular' && + context.$ui.find( '.wikiEditor-ui-right' ).width() > availableWidth ) { + //switch mode + $.wikiEditor.modules.toc.fn.switchLayout( context ); + } else if ( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' && + context.modules.toc.$toc.data( 'previousWidth' ) < context.$wikitext.width() ) { + //switch mode + $.wikiEditor.modules.toc.fn.switchLayout( context ); + } + if ( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + context.modules.toc.$toc.find( 'div' ).autoEllipsis( + { 'position': 'right', 'tooltip': true, 'restoreText': true } + ); + } + // reset the height of the TOC + if ( !context.modules.toc.$toc.data( 'collapsed' ) ){ + context.modules.toc.$toc.height( + context.$ui.find( '.wikiEditor-ui-left' ).height() - + context.$ui.find( '.tab-toc' ).outerHeight() + ); + } + + // store the width of the view for comparison on next resize + context.modules.toc.$toc.data( 'previousWidth', context.$wikitext.width() ); + }, + mark: function( context, event ) { + var hash = ''; + var markers = context.modules.highlight.markers; + var tokenArray = context.modules.highlight.tokenArray; + var outline = context.data.outline = []; + var h = 0; + for ( var i = 0; i < tokenArray.length; i++ ) { + if ( tokenArray[i].label != 'TOC_HEADER' ) { + continue; + } + h++; + markers.push( { + index: h, + start: tokenArray[i].tokenStart, + end: tokenArray[i].offset, + type: 'toc', + anchor: 'tag', + afterWrap: function( node ) { + var marker = $( node ).data( 'marker' ); + $( node ).addClass( 'wikiEditor-toc-header' ) + .addClass( 'wikiEditor-toc-section-' + marker.index ) + .data( 'section', marker.index ); + }, + beforeUnwrap: function( node ) { + $( node ).removeClass( 'wikiEditor-toc-header' ) + .removeClass( 'wikiEditor-toc-section-' + $( node ).data( 'section' ) ); + }, + onSkip: function( node ) { + var marker = $( node ).data( 'marker' ); + if ( $( node ).data( 'section' ) != marker.index ) { + $( node ) + .removeClass( 'wikiEditor-toc-section-' + $( node ).data( 'section' ) ) + .addClass( 'wikiEditor-toc-section-' + marker.index ) + .data( 'section', marker.index ); + } + }, + getAnchor: function( ca1, ca2 ) { + return $( ca1.parentNode ).is( '.wikiEditor-toc-header' ) ? + ca1.parentNode : null; + } + } ); + hash += tokenArray[i].match[2] + '\n'; + outline.push ( { + 'text': tokenArray[i].match[2], + 'level': tokenArray[i].match[1].length, + 'index': h + } ); + } + // Only update the TOC if it's been changed - we do this by comparing a hash of the headings this time to last + if ( typeof context.modules.toc.lastHash == 'undefined' || context.modules.toc.lastHash !== hash ) { + $.wikiEditor.modules.toc.fn.build( context ); + $.wikiEditor.modules.toc.fn.update( context ); + // Remember the changed version + context.modules.toc.lastHash = hash; + } + } +}, +exp: [ + { 'regex': /^(={1,6})([^\r\n]+?)\1\s*$/m, 'label': 'TOC_HEADER', 'markAfter': true } +], +/** + * Internally used functions + */ +fn: { + /** + * Creates a table of contents 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 ) { + if ( '$toc' in context.modules.toc ) { + return; + } + $.wikiEditor.modules.toc.cfg.rtl = $( 'body' ).is( '.rtl' ); + $.wikiEditor.modules.toc.cfg.flexProperty = $.wikiEditor.modules.toc.cfg.rtl ? 'marginLeft' : 'marginRight'; + var height = context.$ui.find( '.wikiEditor-ui-left' ).height(); + context.modules.toc.$toc = $( '<div />' ) + .addClass( 'wikiEditor-ui-toc' ) + .data( 'context', context ) + .data( 'positionMode', 'regular' ) + .data( 'collapsed', false ); + context.$ui.find( '.wikiEditor-ui-right' ) + .append( context.modules.toc.$toc ); + context.modules.toc.$toc.height( + context.$ui.find( '.wikiEditor-ui-left' ).height() + ); + $.wikiEditor.modules.toc.fn.redraw( context, $.wikiEditor.modules.toc.cfg.defaultWidth ); + }, + redraw: function( context, fixedWidth ) { + var fixedWidth = parseFloat( fixedWidth ); + if( context.modules.toc.$toc.data( 'positionMode' ) == 'regular' ) { + context.$ui.find( '.wikiEditor-ui-right' ) + .css( 'width', fixedWidth + 'px' ); + context.$ui.find( '.wikiEditor-ui-left' ) + .css( $.wikiEditor.modules.toc.cfg.flexProperty, ( -1 * fixedWidth ) + 'px' ) + .children() + .css( $.wikiEditor.modules.toc.cfg.flexProperty, fixedWidth + 'px' ); + } else if( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + context.$ui.find( '.wikiEditor-ui-left' ) + .css( 'width', fixedWidth ); + context.$ui.find( '.wikiEditor-ui-right' ) + .css( $.wikiEditor.modules.toc.cfg.rtl ? 'right': 'left', fixedWidth ); + context.$wikitext.css( 'height', context.$ui.find( '.wikiEditor-ui-right' ).height() ); + } + }, + switchLayout: function( context ) { + var width, + height = context.$ui.find( '.wikiEditor-ui-right' ).height(); + if( context.modules.toc.$toc.data( 'positionMode' ) == 'regular' + && !context.modules.toc.$toc.data( 'collapsed' ) ) { + // store position mode + context.modules.toc.$toc.data( 'positionMode', 'goofy' ); + // store the width of the TOC, to ensure we dont allow it to be larger than this when switching back + context.modules.toc.$toc.data( 'positionModeChangeAt', + context.$ui.find( '.wikiEditor-ui-right' ).width() ); + width = $.wikiEditor.modules.toc.cfg.textMinimumWidth; + // set our styles for goofy mode + context.$ui.find( '.wikiEditor-ui-left' ) + .css( $.wikiEditor.modules.toc.cfg.flexProperty, '') + .css( { 'position': 'absolute', 'float': 'none', + 'left': $.wikiEditor.modules.toc.cfg.rtl ? 'auto': 0, + 'right' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto' } ) + .children() + .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ); + context.$ui.find( '.wikiEditor-ui-right' ) + .css( { 'width': 'auto', 'position': 'absolute', 'float': 'none', + 'right': $.wikiEditor.modules.toc.cfg.rtl ? 'auto': 0, + 'left' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto' } ); + context.$wikitext + .css( 'position', 'relative' ); + } else if ( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + // store position mode + context.modules.toc.$toc.data( 'positionMode', 'regular' ); + // set width + width = context.$wikitext.width() - context.$ui.find( '.wikiEditor-ui-left' ).width(); + if ( width > context.modules.toc.$toc.data( 'positionModeChangeAt' ) ) { + width = context.modules.toc.$toc.data( 'positionModeChangeAt' ); + } + // set our styles for regular mode + context.$wikitext + .css( { 'position': '', 'height': '' } ); + context.$ui.find( '.wikiEditor-ui-right' ) + .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ) + .css( { 'position': '', 'left': '', 'right': '', 'float': '', 'top': '', 'height': '' } ); + context.$ui.find( '.wikiEditor-ui-left' ) + .css( { 'width': '', 'position': '', 'left': '', 'float': '', 'right': '' } ); + } + $.wikiEditor.modules.toc.fn.redraw( context, width ); + }, + disable: function( context ) { + if ( context.modules.toc.$toc.data( 'collapsed' ) ) { + context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).hide(); + } else { + if( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + $.wikiEditor.modules.toc.fn.switchLayout( context ); + } + context.$ui.find( '.wikiEditor-ui-right' ).hide(); + context.$ui.find( '.wikiEditor-ui-left' ) + .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ) + .children() + .css( $.wikiEditor.modules.toc.cfg.flexProperty, '' ); + } + context.modules.toc.$toc.data( 'positionMode', 'disabled' ); + }, + enable: function( context ) { + context.modules.toc.$toc.data( 'positionMode', 'regular' ); + if ( context.modules.toc.$toc.data( 'collapsed' ) ) { + context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).show(); + } else { + context.$ui.find( '.wikiEditor-ui-right' ).show(); + $.wikiEditor.modules.toc.fn.redraw( context, $.wikiEditor.modules.toc.cfg.minimumWidth ); + context.modules.toc.$toc.find( 'div' ).autoEllipsis( + { 'position': 'right', 'tooltip': true, 'restoreText': true } + ); + } + }, + unhighlight: function( context ) { + // FIXME: For some reason, IE calls this function twice, the first time with context undefined + // Investigate this when you have time please! In the meantime, the user interaction is working just + // fine because the second call is valid + if ( context ) { + context.modules.toc.$toc.find( 'div' ).removeClass( 'current' ); + } + }, + /** + * Highlight the section the cursor is currently within + * + * @param {Object} context + */ + update: function( context ) { + //temporarily commenting this out because it is causing all kinds of cursor + //and text jumping issues in IE. WIll get back to this --pdhanda + /* + var div = context.fn.beforeSelection( 'wikiEditor-toc-header' ); + if ( div === null ) { + // beforeSelection couldn't figure it out, keep the old highlight state + return; + } + + $.wikiEditor.modules.toc.fn.unhighlight( context ); + var section = div.data( 'section' ) || 0; + if ( context.data.outline.length > 0 ) { + var sectionLink = context.modules.toc.$toc.find( 'div.section-' + section ); + sectionLink.addClass( 'current' ); + + // Scroll the highlighted link into view if necessary + var relTop = sectionLink.offset().top - context.modules.toc.$toc.offset().top; + + var scrollTop = context.modules.toc.$toc.scrollTop(); + var divHeight = context.modules.toc.$toc.height(); + var sectionHeight = sectionLink.height(); + if ( relTop < 0 ) + // Scroll up + context.modules.toc.$toc.scrollTop( scrollTop + relTop ); + else if ( relTop + sectionHeight > divHeight ) + // Scroll down + context.modules.toc.$toc.scrollTop( scrollTop + relTop + sectionHeight - divHeight ); + } + */ + }, + + /** + * Collapse the contents module + * + * @param {Object} event Event object with context as data + */ + collapse: function( event ) { + var $this = $( this ), + context = $this.data( 'context' ); + if( context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + $.wikiEditor.modules.toc.fn.switchLayout( context ); + } + var pT = $this.parent().position().top - 1; + context.modules.toc.$toc.data( 'collapsed', true ); + var leftParam = {}, leftChildParam = {}; + leftParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = '-1px'; + leftChildParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = '1px'; + context.$ui.find( '.wikiEditor-ui-left' ) + .animate( leftParam, 'fast', function() { + $( this ).css( $.wikiEditor.modules.toc.cfg.flexProperty, 0 ); + } ) + .children() + .animate( leftChildParam, 'fast', function() { + $( this ).css( $.wikiEditor.modules.toc.cfg.flexProperty, 0 ); + } ); + context.$ui.find( '.wikiEditor-ui-right' ) + .css( { + 'marginTop' : '1px', + 'position' : 'absolute', + 'left' : $.wikiEditor.modules.toc.cfg.rtl ? 0 : 'auto', + 'right' : $.wikiEditor.modules.toc.cfg.rtl ? 'auto' : 0, + 'top' : pT } ) + .fadeOut( 'fast', function() { + $( this ).hide() + .css( { 'marginTop': '0', 'width': '1px' } ); + context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).fadeIn( 'fast' ); + // Let the UI know things have moved around + context.fn.trigger( 'tocCollapse' ); + context.fn.trigger( 'resize' ); + } ); + + $.cookie( 'wikiEditor-' + context.instance + '-toc-width', 0 ); + return false; + }, + + /** + * Expand the contents module + * + * @param {Object} event Event object with context as data + */ + expand: function( event ) { + var $this = $( this ), + context = $this.data( 'context' ), + openWidth = parseFloat( context.modules.toc.$toc.data( 'openWidth' ) ), + availableSpace = context.$wikitext.width() - parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ); + if ( availableSpace < $.wikiEditor.modules.toc.cfg.textMinmumWidth ) return false; + context.modules.toc.$toc.data( 'collapsed', false ); + // check if we've got enough room to open to our stored width + if ( availableSpace < openWidth ) openWidth = availableSpace; + context.$ui.find( '.wikiEditor-ui-toc-expandControl' ).hide(); + var leftParam = {}, leftChildParam = {}; + leftParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = parseFloat( openWidth ) * -1; + leftChildParam[ $.wikiEditor.modules.toc.cfg.flexProperty ] = openWidth; + context.$ui.find( '.wikiEditor-ui-left' ) + .animate( leftParam, 'fast' ) + .children() + .animate( leftChildParam, 'fast' ); + context.$ui.find( '.wikiEditor-ui-right' ) + .show() + .css( 'marginTop', '1px' ) + .animate( { 'width' : openWidth }, 'fast', function() { + context.$content.trigger( 'mouseup' ); + $( this ).css( { + 'marginTop' : '0', + 'position' : 'relative', + 'right' : 'auto', + 'left' : 'auto', + 'top': 'auto' } ); + context.fn.trigger( 'tocExpand' ); + context.fn.trigger( 'resize' ); + } ); + $.cookie( 'wikiEditor-' + context.instance + '-toc-width', + context.modules.toc.$toc.data( 'openWidth' ) ); + return false; + }, + /** + * Builds table of contents + * + * @param {Object} context + */ + build: function( context ) { + /** + * Builds a structured outline from flat outline + * + * @param {Object} outline Array of objects with level fields + */ + function buildStructure( outline, offset, level ) { + if ( offset == undefined ) offset = 0; + if ( level == undefined ) level = 1; + var sections = []; + for ( var i = offset; i < outline.length; i++ ) { + if ( outline[i].nLevel == level ) { + var sub = buildStructure( outline, i + 1, level + 1 ); + if ( sub.length ) { + outline[i].sections = sub; + } + sections[sections.length] = outline[i]; + } else if ( outline[i].nLevel < level ) { + break; + } + } + return sections; + } + /** + * Builds unordered list HTML object from structured outline + * + * @param {Object} structure Structured outline + */ + function buildList( structure ) { + var list = $( '<ul />' ); + for ( var i = 0; i < structure.length; i++ ) { + var div = $( '<div />' ) + .addClass( 'section-' + structure[i].index ) + .data( 'index', structure[i].index ) + .mousedown( function() { + // No dragging! + return false; + } ) + .click( function( event ) { + var wrapper = context.$content.find( + '.wikiEditor-toc-section-' + $( this ).data( 'index' ) ); + if ( wrapper.size() == 0 ) + wrapper = context.$content; + context.fn.scrollToTop( wrapper, true ); + context.$textarea.textSelection( 'setSelection', { + 'start': 0, + 'startContainer': wrapper + } ); + // Bring user's eyes to the point we've now jumped to + context.fn.highlightLine( $( wrapper ) ); + // Highlight the clicked link + //remove highlighting of toc after a second. Temporary hack till the highlight works --pdhanda + //$.wikiEditor.modules.toc.fn.unhighlight( context ); + $( this ).addClass( 'current' ); + //$( this ).removeClass( 'current' ); + setTimeout( function() { $.wikiEditor.modules.toc.fn.unhighlight( context ) }, 1000 ); + + if ( typeof $.trackAction != 'undefined' ) + $.trackAction( 'ntoc.heading' ); + event.preventDefault(); + } ) + .text( structure[i].text ); + if ( structure[i].text == '' ) + div.html( ' ' ); + var item = $( '<li />' ).append( div ); + if ( structure[i].sections !== undefined ) { + item.append( buildList( structure[i].sections ) ); + } + list.append( item ); + } + return list; + } + /** + * Builds controls for collapsing and expanding the TOC + * + */ + function buildCollapseControls( ) { + var $collapseControl = $( '<div />' ), $expandControl = $( '<div />' ); + $collapseControl + .addClass( 'tab' ) + .addClass( 'tab-toc' ) + .append( '<a href="#" />' ) + .mousedown( function( e ) { + // No dragging! + e.preventDefault(); + return false; + } ) + .bind( 'click.wikiEditor-toc', function( e ) { + context.modules.toc.$toc.trigger( 'collapse.wikiEditor-toc' ); + // No dragging! + e.preventDefault(); + return false; + } ) + .find( 'a' ) + .text( mediaWiki.msg( 'wikieditor-toc-hide' ) ); + $expandControl + .addClass( 'wikiEditor-ui-toc-expandControl' ) + .append( '<a href="#" />' ) + .mousedown( function( e ) { + // No dragging! + e.preventDefault(); + return false; + } ) + .bind( 'click.wikiEditor-toc', function( e ) { + context.modules.toc.$toc.trigger( 'expand.wikiEditor-toc' ); + // No dragging! + e.preventDefault(); + return false; + } ) + .hide() + .find( 'a' ) + .text( mediaWiki.msg( 'wikieditor-toc-show' ) ); + $collapseControl.insertBefore( context.modules.toc.$toc ); + context.$ui.find( '.wikiEditor-ui-left .wikiEditor-ui-top' ).append( $expandControl ); + } + /** + * Initializes resizing controls on the TOC and sets the width of + * the TOC based on it's previous state + * + */ + function buildResizeControls( ) { + context.$ui + .data( 'resizableDone', true ) + .find( '.wikiEditor-ui-right' ) + .data( 'wikiEditor-ui-left', context.$ui.find( '.wikiEditor-ui-left' ) ) + .resizable( { handles: 'w,e', preventPositionLeftChange: true, + minWidth: parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ), + start: function( e, ui ) { + var $this = $( this ); + // Toss a transparent cover over our iframe + $( '<div />' ) + .addClass( 'wikiEditor-ui-resize-mask' ) + .css( { + 'position': 'absolute', + 'z-index': 2, + 'left': 0, + 'top': 0, + 'bottom': 0, + 'right': 0 + } ) + .appendTo( context.$ui.find( '.wikiEditor-ui-left' ) ); + $this.resizable( 'option', 'maxWidth', $this.parent().width() - + parseFloat( $.wikiEditor.modules.toc.cfg.textMinimumWidth ) ); + if(context.modules.toc.$toc.data( 'positionMode' ) == 'goofy' ) { + $.wikiEditor.modules.toc.fn.switchLayout( context ); + } + }, + resize: function( e, ui ) { + // for some odd reason, ui.size.width seems a step ahead of what the *actual* width of + // the resizable is + $( this ).css( { 'width': ui.size.width, 'top': 'auto', 'height': 'auto' } ) + .data( 'wikiEditor-ui-left' ) + .css( $.wikiEditor.modules.toc.cfg.flexProperty, ( -1 * ui.size.width ) ) + .children().css( $.wikiEditor.modules.toc.cfg.flexProperty, ui.size.width ); + // Let the UI know things have moved around + context.fn.trigger( 'resize' ); + }, + stop: function ( e, ui ) { + context.$ui.find( '.wikiEditor-ui-resize-mask' ).remove(); + context.$content.trigger( 'mouseup' ); + if( ui.size.width <= parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ) ) { + context.modules.toc.$toc.trigger( 'collapse.wikiEditor-toc' ); + } else { + context.modules.toc.$toc.find( 'div' ).autoEllipsis( + { 'position': 'right', 'tooltip': true, 'restoreText': true } + ); + context.modules.toc.$toc.data( 'openWidth', ui.size.width ); + $.cookie( 'wikiEditor-' + context.instance + '-toc-width', ui.size.width ); + } + // Let the UI know things have moved around + context.fn.trigger( 'resize' ); + } + }); + // Convert our east resize handle into a secondary west resize handle + var handle = $.wikiEditor.modules.toc.cfg.rtl ? 'w' : 'e'; + context.$ui.find( '.ui-resizable-' + handle ) + .removeClass( 'ui-resizable-' + handle ) + .addClass( 'ui-resizable-' + ( handle == 'w' ? 'e' : 'w' ) ) + .addClass( 'wikiEditor-ui-toc-resize-grip' ); + // Bind collapse and expand event handlers to the TOC + context.modules.toc.$toc + .bind( 'collapse.wikiEditor-toc', $.wikiEditor.modules.toc.fn.collapse ) + .bind( 'expand.wikiEditor-toc', $.wikiEditor.modules.toc.fn.expand ); + context.modules.toc.$toc.data( 'openWidth', $.wikiEditor.modules.toc.cfg.defaultWidth ); + // If the toc-width cookie is set, reset the widths based upon that + if ( $.cookie( 'wikiEditor-' + context.instance + '-toc-width' ) == 0 ) { + context.modules.toc.$toc.trigger( 'collapse.wikiEditor-toc', { data: context } ); + } else if ( $.cookie( 'wikiEditor-' + context.instance + '-toc-width' ) > 0 ) { + var initialWidth = $.cookie( 'wikiEditor-' + context.instance + '-toc-width' ); + if( initialWidth < parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ) ) + initialWidth = parseFloat( $.wikiEditor.modules.toc.cfg.minimumWidth ) + 1; + context.modules.toc.$toc.data( 'openWidth', initialWidth + 'px' ); + $.wikiEditor.modules.toc.fn.redraw( context, initialWidth ); + } + } + + // Normalize heading levels for list creation + // This is based on Linker::generateTOC(), so it should behave like the + // TOC on rendered articles does - which is considdered to be correct + // at this point in time. + if ( context.data.outline ) { + var outline = context.data.outline; + var lastLevel = 0; + var nLevel = 0; + for ( var i = 0; i < outline.length; i++ ) { + if ( outline[i].level > lastLevel ) { + nLevel++; + } + else if ( outline[i].level < lastLevel ) { + nLevel -= Math.max( 1, lastLevel - outline[i].level ); + } + if ( nLevel <= 0 ) { + nLevel = 1; + } + outline[i].nLevel = nLevel; + lastLevel = outline[i].level; + } + // Recursively build the structure and add special item for + // section 0, if needed + var structure = buildStructure( outline ); + if ( $( 'input[name=wpSection]' ).val() == '' ) { + structure.unshift( { 'text': mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ), 'level': 1, 'index': 0 } ); + } + context.modules.toc.$toc.html( buildList( structure ) ); + + if ( !context.$ui.data( 'resizableDone' ) ) { + buildResizeControls(); + buildCollapseControls(); + } + context.modules.toc.$toc.find( 'div' ).autoEllipsis( + { 'position': 'right', 'tooltip': true, 'restoreText': true } + ); + } + }, + improveUI: function() { + /* + * Extending resizable to allow west resizing without altering the left position attribute + */ + $.ui.plugin.add( "resizable", "preventPositionLeftChange", { + resize: function( event, ui ) { + $( this ).data( "resizable" ).position.left = 0; + } + } ); + } +} + +}; + +} ) ( jQuery ); diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.config.js b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.config.js new file mode 100644 index 00000000..ab92d173 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.config.js @@ -0,0 +1,1091 @@ +/** + * Configuration of Toolbar module for wikiEditor + */ +( function( $ ) { $.wikiEditor.modules.toolbar.config = { + +getDefaultConfig: function() { + var fileNamespace = mw.config.get( 'wgFormattedNamespaces' )[6]; + return { 'toolbar': { + // Main section + 'main': { + 'type': 'toolbar', + 'groups': { + 'format': { + 'tools': { + 'bold': { + 'labelMsg': 'wikieditor-toolbar-tool-bold', + 'type': 'button', + 'offset': { + 'default': [2, -574], + 'en': [2, -142], + 'cs': [2, -142], + 'de': [2, -214], + 'fr': [2, -286], + 'es': [2, -358], + 'he': [2, -142], + 'hu': [2, -214], + 'it': [2, -286], + 'nl': [2, -502], + 'pt': [2, -358], + 'pt-br': [2, -358], + 'pl': [2, -142], + 'ml': [2, -142] + }, + 'icon': { + 'default': 'format-bold.png', + 'en': 'format-bold-B.png', + 'cs': 'format-bold-B.png', + 'de': 'format-bold-F.png', + 'fr': 'format-bold-G.png', + 'es': 'format-bold-N.png', + 'he': 'format-bold-B.png', + 'hu': 'format-bold-F.png', + 'it': 'format-bold-G.png', + 'ka': 'format-bold-ka.png', + 'nl': 'format-bold-V.png', + 'pt': 'format-bold-N.png', + 'pt-br': 'format-bold-N.png', + 'pl': 'format-bold-B.png', + 'ru': 'format-bold-ru.png', + 'ml': 'format-bold-B.png' + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "'''", + 'periMsg': 'wikieditor-toolbar-tool-bold-example', + 'post': "'''" + } + } + }, + 'italic': { + 'section': 'main', + 'group': 'format', + 'id': 'italic', + 'labelMsg': 'wikieditor-toolbar-tool-italic', + 'type': 'button', + 'offset': { + 'default': [2, -718], + 'en': [2, -862], + 'cs': [2, -862], + 'de': [2, -934], + 'fr': [2, -862], + 'es': [2, -790], + 'he': [2, -862], + 'it': [2, -790], + 'nl': [2, -790], + 'pt': [2, -862], + 'pt-br': [2, -862], + 'pl': [2, -862], + 'ru': [2, -934], + 'ml': [2, -862] + }, + 'icon': { + 'default': 'format-italic.png', + 'en': 'format-italic-I.png', + 'cs': 'format-italic-I.png', + 'de': 'format-italic-K.png', + 'fr': 'format-italic-I.png', + 'es': 'format-italic-C.png', + 'he': 'format-italic-I.png', + 'hu': 'format-italic-D.png', + 'it': 'format-italic-C.png', + 'ka': 'format-italic-ka.png', + 'nl': 'format-italic-C.png', + 'pt': 'format-italic-I.png', + 'pt-br': 'format-italic-I.png', + 'pl': 'format-italic-I.png', + 'ru': 'format-italic-K.png', + 'ml': 'format-italic-I.png' + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "''", + 'periMsg': 'wikieditor-toolbar-tool-italic-example', + 'post': "''" + } + } + } + } + }, + 'insert': { + 'tools': { + 'xlink': { + 'labelMsg': 'wikieditor-toolbar-tool-xlink', + 'type': 'button', + 'icon': 'insert-xlink.png', + 'offset': [-70, 2], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "[", + 'periMsg': 'wikieditor-toolbar-tool-xlink-example', + 'post': "]" + } + } + }, + 'ilink': { + 'labelMsg': 'wikieditor-toolbar-tool-ilink', + 'type': 'button', + 'icon': 'insert-ilink.png', + 'offset': [2, -1582], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "[[", + 'periMsg': 'wikieditor-toolbar-tool-ilink-example', + 'post': "]]" + } + } + }, + 'file': { + 'labelMsg': 'wikieditor-toolbar-tool-file', + 'type': 'button', + 'icon': 'insert-file.png', + 'offset': [2, -1438], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': '[[' + fileNamespace + ':', + 'periMsg': 'wikieditor-toolbar-tool-file-example', + 'post': "]]" + } + } + }, + 'reference': { + 'labelMsg': 'wikieditor-toolbar-tool-reference', + 'filters': [ 'body.ns-subject' ], + 'type': 'button', + 'offset': [2, -1798], + 'icon': 'insert-reference.png', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<ref>", + 'periMsg': 'wikieditor-toolbar-tool-reference-example', + 'post': "</ref>" + } + } + }, + 'signature': { + 'labelMsg': 'wikieditor-toolbar-tool-signature', + 'type': 'button', + 'offset': [2, -1872], + 'icon': 'insert-signature.png', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "--~~~~" + } + } + } + } + } + } + }, + // Format section + 'advanced': { + 'labelMsg': 'wikieditor-toolbar-section-advanced', + 'type': 'toolbar', + 'groups': { + 'heading': { + 'tools': { + 'heading': { + 'labelMsg': 'wikieditor-toolbar-tool-heading', + 'type': 'select', + 'list': { + 'heading-2' : { + 'labelMsg': 'wikieditor-toolbar-tool-heading-2', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': '== ', + 'periMsg': 'wikieditor-toolbar-tool-heading-example', + 'post': ' ==', + 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, + 'regexReplace': "\$1==\$3==\$4", + 'ownline': true + } + } + }, + 'heading-3' : { + 'labelMsg': 'wikieditor-toolbar-tool-heading-3', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': '=== ', + 'periMsg': 'wikieditor-toolbar-tool-heading-example', + 'post': ' ===', + 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, + 'regexReplace': "\$1===\$3===\$4", + 'ownline': true + } + } + }, + 'heading-4' : { + 'labelMsg': 'wikieditor-toolbar-tool-heading-4', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': '==== ', + 'periMsg': 'wikieditor-toolbar-tool-heading-example', + 'post': ' ====', + 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, + 'regexReplace': "\$1====\$3====\$4", + 'ownline': true + } + } + }, + 'heading-5' : { + 'labelMsg': 'wikieditor-toolbar-tool-heading-5', + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': '===== ', + 'periMsg': 'wikieditor-toolbar-tool-heading-example', + 'post': ' =====', + 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, + 'regexReplace': "\$1=====\$3=====\$4", + 'ownline': true + } + } + } + } + } + } + }, + 'format': { + 'labelMsg': 'wikieditor-toolbar-group-format', + 'tools': { + 'ulist': { + 'labelMsg': 'wikieditor-toolbar-tool-ulist', + 'type': 'button', + 'icon': { + 'default': 'format-ulist.png', + 'default-rtl': 'format-ulist-rtl.png' + }, + 'offset': { + 'default': [2, -1366], + 'default-rtl': [-70, -286] + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "* ", + 'periMsg': 'wikieditor-toolbar-tool-ulist-example', + 'post': "", + 'ownline': true, + 'splitlines': true + } + } + }, + 'olist': { + 'labelMsg': 'wikieditor-toolbar-tool-olist', + 'type': 'button', + 'icon': { + 'default': 'format-olist.png', + 'default-rtl': 'format-olist-rtl.png' + }, + 'offset': { + 'default': [2, -1078], + 'default-rtl': [-70, -358] + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "# ", + 'periMsg': 'wikieditor-toolbar-tool-olist-example', + 'post': "", + 'ownline': true, + 'splitlines': true + } + } + }, + 'indent': { + 'labelMsg': 'wikieditor-toolbar-tool-indent', + 'type': 'button', + 'icon': { + 'default': 'format-indent.png', + 'default-rtl': 'format-indent-rtl.png' + }, + 'offset': { + 'default': [2, -646], + 'default-rtl': [-70, -430] + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': ":", + 'periMsg': 'wikieditor-toolbar-tool-indent-example', + 'post': "", + 'ownline': true, + 'splitlines': true + } + } + }, + 'nowiki': { + 'labelMsg': 'wikieditor-toolbar-tool-nowiki', + 'type': 'button', + 'icon': 'insert-nowiki.png', + 'offset': [-70, -70], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<nowiki>", + 'periMsg': 'wikieditor-toolbar-tool-nowiki-example', + 'post': "</nowiki>" + } + } + }, + 'newline': { + 'labelMsg': 'wikieditor-toolbar-tool-newline', + 'type': 'button', + 'icon': 'insert-newline.png', + 'offset': [2, -1726], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<br />\n" + } + } + } + } + }, + 'size': { + 'tools': { + 'big': { + 'labelMsg': 'wikieditor-toolbar-tool-big', + 'type': 'button', + 'icon': 'format-big.png', + 'offset': [2, 2], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<big>", + 'periMsg': 'wikieditor-toolbar-tool-big-example', + 'post': "</big>" + } + } + }, + 'small': { + 'labelMsg': 'wikieditor-toolbar-tool-small', + 'type': 'button', + 'icon': 'format-small.png', + 'offset': [2, -1150], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<small>", + 'periMsg': 'wikieditor-toolbar-tool-small-example', + 'post': "</small>" + } + } + }, + 'superscript': { + 'labelMsg': 'wikieditor-toolbar-tool-superscript', + 'type': 'button', + 'icon': 'format-superscript.png', + 'offset': [2, -1294], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<sup>", + 'periMsg': 'wikieditor-toolbar-tool-superscript-example', + 'post': "</sup>" + } + } + }, + 'subscript': { + 'labelMsg': 'wikieditor-toolbar-tool-subscript', + 'type': 'button', + 'icon': 'format-subscript.png', + 'offset': [2, -1222], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<sub>", + 'periMsg': 'wikieditor-toolbar-tool-subscript-example', + 'post': "</sub>" + } + } + } + } + }, + 'insert': { + 'labelMsg': 'wikieditor-toolbar-group-insert', + 'tools': { + 'gallery': { + 'labelMsg': 'wikieditor-toolbar-tool-gallery', + 'type': 'button', + 'icon': 'insert-gallery.png', + 'offset': [2, -1510], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "<gallery>\n", + 'periMsg': [ + 'wikieditor-toolbar-tool-gallery-example', fileNamespace + ], + 'post': "\n</gallery>", + 'ownline': true + } + } + }, + 'table': { + 'labelMsg': 'wikieditor-toolbar-tool-table', + 'type': 'button', + 'icon': 'insert-table.png', + 'offset': [2, -1942], + 'filters': [ '#wpTextbox1:not(.toolbar-dialogs)' ], + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "{| class=\"wikitable\" border=\"1\"\n|", + 'periMsg': 'wikieditor-toolbar-tool-table-example-old', + 'post': "\n|}", + 'ownline': true + } + } + }, + 'redirect': { + 'labelMsg': 'wikieditor-toolbar-tool-redirect', + 'type': 'button', + 'icon': { + 'default': 'insert-redirect.png', + 'default-rtl': 'insert-redirect-rtl.png' + }, + 'offset': { + 'default': [-70, -142], + 'default-rtl': [-70, -502] + }, + 'action': { + 'type': 'encapsulate', + 'options': { + 'pre': "#REDIRECT [[", + 'periMsg': 'wikieditor-toolbar-tool-redirect-example', + 'post': "]]", + 'ownline': true + } + } + } + } + } + } + }, + 'characters': { + 'labelMsg': 'wikieditor-toolbar-section-characters', + 'type': 'booklet', + 'deferLoad': true, + 'pages': { + 'latin': { + 'labelMsg': 'wikieditor-toolbar-characters-page-latin', + 'layout': 'characters', + 'characters': [ + "\u00c1", "\u00e1", "\u00c0", "\u00e0", "\u00c2", "\u00e2", "\u00c4", "\u00e4", "\u00c3", + "\u00e3", "\u01cd", "\u01ce", "\u0100", "\u0101", "\u0102", "\u0103", "\u0104", "\u0105", + "\u00c5", "\u00e5", "\u0106", "\u0107", "\u0108", "\u0109", "\u00c7", "\u00e7", "\u010c", + "\u010d", "\u010a", "\u010b", "\u0110", "\u0111", "\u010e", "\u010f", "\u00c9", "\u00e9", + "\u00c8", "\u00e8", "\u00ca", "\u00ea", "\u00cb", "\u00eb", "\u011a", "\u011b", "\u0112", + "\u0113", "\u0114", "\u0115", "\u0116", "\u0117", "\u0118", "\u0119", "\u011c", "\u011d", + "\u0122", "\u0123", "\u011e", "\u011f", "\u0120", "\u0121", "\u0124", "\u0125", "\u0126", + "\u0127", "\u00cd", "\u00ed", "\u00cc", "\u00ec", "\u00ce", "\u00ee", "\u00cf", "\u00ef", + "\u0128", "\u0129", "\u01cf", "\u01d0", "\u012a", "\u012b", "\u012c", "\u012d", "\u0130", + "\u0131", "\u012e", "\u012f", "\u0134", "\u0135", "\u0136", "\u0137", "\u0139", "\u013a", + "\u013b", "\u013c", "\u013d", "\u013e", "\u0141", "\u0142", "\u013f", "\u0140", "\u0143", + "\u0144", "\u00d1", "\u00f1", "\u0145", "\u0146", "\u0147", "\u0148", "\u00d3", "\u00f3", + "\u00d2", "\u00f2", "\u00d4", "\u00f4", "\u00d6", "\u00f6", "\u00d5", "\u00f5", "\u01d1", + "\u01d2", "\u014c", "\u014d", "\u014e", "\u014f", "\u01ea", "\u01eb", "\u0150", "\u0151", + "\u0154", "\u0155", "\u0156", "\u0157", "\u0158", "\u0159", "\u015a", "\u015b", "\u015c", + "\u015d", "\u015e", "\u015f", "\u0160", "\u0161", "\u0162", "\u0163", "\u0164", "\u0165", + "\u00da", "\u00fa", "\u00d9", "\u00f9", "\u00db", "\u00fb", "\u00dc", "\u00fc", "\u0168", + "\u0169", "\u016e", "\u016f", "\u01d3", "\u01d4", "\u016a", "\u016b", "\u01d6", "\u01d8", + "\u01da", "\u01dc", "\u016c", "\u016d", "\u0172", "\u0173", "\u0170", "\u0171", "\u0174", + "\u0175", "\u00dd", "\u00fd", "\u0176", "\u0177", "\u0178", "\u00ff", "\u0232", "\u0233", + "\u0179", "\u017a", "\u017d", "\u017e", "\u017b", "\u017c", "\u00c6", "\u00e6", "\u01e2", + "\u01e3", "\u00d8", "\u00f8", "\u0152", "\u0153", "\u00df", "\u00f0", "\u00de", "\u00fe", + "\u018f", "\u0259" + ] + }, + 'latinextended': { + 'labelMsg': 'wikieditor-toolbar-characters-page-latinextended', + 'layout': 'characters', + 'characters': [ + "\u1e00", "\u1e01", "\u1e9a", "\u1ea0", "\u1ea1", "\u1ea2", "\u1ea3", "\u1ea4", "\u1ea5", + "\u1ea6", "\u1ea7", "\u1ea8", "\u1ea9", "\u1eaa", "\u1eab", "\u1eac", "\u1ead", "\u1eae", + "\u1eaf", "\u1eb0", "\u1eb1", "\u1eb2", "\u1eb3", "\u1eb4", "\u1eb5", "\u1eb6", "\u1eb7", + "\u1e02", "\u1e03", "\u1e04", "\u1e05", "\u1e06", "\u1e07", "\u1e08", "\u1e09", "\u1e0a", + "\u1e0b", "\u1e0c", "\u1e0d", "\u1e0e", "\u1e0f", "\u1e10", "\u1e11", "\u1e12", "\u1e13", + "\u1e14", "\u1e15", "\u1e16", "\u1e17", "\u1e18", "\u1e19", "\u1e1a", "\u1e1b", "\u1e1c", + "\u1e1d", "\u1eb8", "\u1eb9", "\u1eba", "\u1ebb", "\u1ebc", "\u1ebd", "\u1ebe", "\u1ebf", + "\u1ec0", "\u1ec1", "\u1ec2", "\u1ec3", "\u1ec4", "\u1ec5", "\u1ec6", "\u1ec7", "\u1e1e", + "\u1e1f", "\u1e20", "\u1e21", "\u1e22", "\u1e23", "\u1e24", "\u1e25", "\u1e26", "\u1e27", + "\u1e28", "\u1e29", "\u1e2a", "\u1e2b", "\u1e96", "\u1e2c", "\u1e2d", "\u1e2e", "\u1e2f", + "\u1ec8", "\u1ec9", "\u1eca", "\u1ecb", "\u1e30", "\u1e31", "\u1e32", "\u1e33", "\u1e34", + "\u1e35", "\u1e36", "\u1e37", "\u1e38", "\u1e39", "\u1e3a", "\u1e3b", "\u1e3c", "\u1e3d", + "\u1efa", "\u1efb", "\u1e3e", "\u1e3f", "\u1e40", "\u1e41", "\u1e42", "\u1e43", "\u1e44", + "\u1e45", "\u1e46", "\u1e47", "\u1e48", "\u1e49", "\u1e4a", "\u1e4b", "\u1e4c", "\u1e4d", + "\u1e4e", "\u1e4f", "\u1e50", "\u1e51", "\u1e52", "\u1e53", "\u1ecc", "\u1ecd", "\u1ece", + "\u1ecf", "\u1ed0", "\u1ed1", "\u1ed2", "\u1ed3", "\u1ed4", "\u1ed5", "\u1ed6", "\u1ed7", + "\u1ed8", "\u1ed9", "\u1eda", "\u1edb", "\u1edc", "\u1edd", "\u1ede", "\u1edf", "\u1ee0", + "\u1ee1", "\u1ee2", "\u1ee3", "\u1e54", "\u1e55", "\u1e56", "\u1e57", "\u1e58", "\u1e59", + "\u1e5a", "\u1e5b", "\u1e5c", "\u1e5d", "\u1e5e", "\u1e5f", "\u1e60", "\u1e61", "\u1e9b", + "\u1e62", "\u1e63", "\u1e64", "\u1e65", "\u1e66", "\u1e67", "\u1e68", "\u1e69", "\u1e9c", + "\u1e9d", "\u1e6a", "\u1e6b", "\u1e6c", "\u1e6d", "\u1e6e", "\u1e6f", "\u1e70", "\u1e71", + "\u1e97", "\u1e72", "\u1e73", "\u1e74", "\u1e75", "\u1e76", "\u1e77", "\u1e78", "\u1e79", + "\u1e7a", "\u1e7b", "\u1ee4", "\u1ee5", "\u1ee6", "\u1ee7", "\u1ee8", "\u1ee9", "\u1eea", + "\u1eeb", "\u1eec", "\u1eed", "\u1eee", "\u1eef", "\u1ef0", "\u1ef1", "\u1e7c", "\u1e7d", + "\u1e7e", "\u1e7f", "\u1efc", "\u1efd", "\u1e80", "\u1e81", "\u1e82", "\u1e83", "\u1e84", + "\u1e85", "\u1e86", "\u1e87", "\u1e88", "\u1e89", "\u1e98", "\u1e8a", "\u1e8b", "\u1e8c", + "\u1e8d", "\u1e8e", "\u1e8f", "\u1e99", "\u1ef2", "\u1ef3", "\u1ef4", "\u1ef5", "\u1ef6", + "\u1ef7", "\u1ef8", "\u1ef9", "\u1efe", "\u1eff", "\u1e90", "\u1e91", "\u1e92", "\u1e93", + "\u1e94", "\u1e95", "\u1e9e", "\u1e9f" + ] + }, + 'ipa': { + 'labelMsg': 'wikieditor-toolbar-characters-page-ipa', + 'layout': 'characters', + 'characters': [ + "p", "t\u032a", "t", "\u0288", "c", "k", "q", "\u02a1", "\u0294", "b","d\u032a", "d", "\u0256", + "\u025f", "\u0261", "\u0262", "\u0253", "\u0257", "\u0284", "\u0260", "\u029b", "t\u0361s", + "t\u0361\u0283", "t\u0361\u0255", "d\u0361z", "d\u0361\u0292", "d\u0361\u0291", "\u0278", "f", + "\u03b8", "s", "\u0283", "\u0285", "\u0286", "\u0282", "\u0255", "\u00e7", "\u0267", "x", + "\u03c7", "\u0127", "\u029c", "h", "\u03b2", "v", "\u028d", "\u00f0", "z", "\u0292", "\u0293", + "\u0290", "\u0291", "\u029d", "\u0263", "\u0281", "\u0295", "\u0296", "\u02a2", "\u0266", + "\u026c", "\u026e", "m", "m\u0329", "\u0271", "\u0271\u0329", "\u0271\u030d", "n\u032a", + "n\u032a\u030d", "n", "n\u0329", "\u0273", "\u0273\u0329", "\u0272", "\u0272\u0329", "\u014b", + "\u014b\u030d", "\u014b\u0329", "\u0274", "\u0274\u0329", "\u0299", "\u0299\u0329", "r", + "r\u0329", "\u0280", "\u0280\u0329", "\u027e", "\u027d", "\u027f", "\u027a", "l\u032a", + "l\u032a\u0329", "l", "l\u0329", "\u026b", "\u026b\u0329", "\u026d", "\u026d\u0329", "\u028e", + "\u028e\u0329", "\u029f", "\u029f\u0329", "w", "\u0265", "\u028b", "\u0279", "\u027b", "j", + "\u0270", "\u0298", "\u01c2", "\u01c0", "!", "\u01c1", "\u02b0", "\u02b1", "\u02b7", "\u02b8", + "\u02b2", "\u02b3", "\u207f", "\u02e1", "\u02b4", "\u02b5", "\u02e2", "\u02e3", "\u02e0", + "\u02b6", "\u02e4", "\u02c1", "\u02c0", "\u02bc", "i", "i\u032f", "\u0129", "y", "y\u032f", + "\u1ef9", "\u026a", "\u026a\u032f", "\u026a\u0303", "\u028f", "\u028f\u032f", "\u028f\u0303", + "\u0268", "\u0268\u032f", "\u0268\u0303", "\u0289", "\u0289\u032f", "\u0289\u0303", "\u026f", + "\u026f\u032f", "\u026f\u0303", "u", "u\u032f", "\u0169", "\u028a", "\u028a\u032f", + "\u028a\u0303", "e", "e\u032f", "\u1ebd", "\u00f8", "\u00f8\u032f", "\u00f8\u0303", "\u0258", + "\u0258\u032f", "\u0258\u0303", "\u0275", "\u0275\u032f", "\u0275\u0303", "\u0264", + "\u0264\u032f", "\u0264\u0303", "o", "o\u032f", "\u00f5", "\u025b", "\u025b\u032f", + "\u025b\u0303", "\u0153", "\u0153\u032f", "\u0153\u0303", "\u025c", "\u025c\u032f", + "\u025c\u0303", "\u0259", "\u0259\u032f", "\u0259\u0303", "\u025e", "\u025e\u032f", + "\u025e\u0303", "\u028c", "\u028c\u032f", "\u028c\u0303", "\u0254", "\u0254\u032f", + "\u0254\u0303", "\u00e6", "\u00e6\u032f", "\u00e6\u0303", "\u0276", "\u0276\u032f", + "\u0276\u0303", "a", "a\u032f", "\u00e3", "\u0250", "\u0250\u032f", "\u0250\u0303", "\u0251", + "\u0251\u032f", "\u0251\u0303", "\u0252", "\u0252\u032f", "\u0252\u0303", "\u02c8", "\u02cc", + "\u02d0", "\u02d1", "\u02d8", ".", "\u203f", "|", "\u2016" + ] + }, + 'symbols': { + 'labelMsg': 'wikieditor-toolbar-characters-page-symbols', + 'layout': 'characters', + 'characters': [ + "~", "|", "\u00a1", "\u00bf", "\u2020", "\u2021", "\u2194", "\u2191", "\u2193", "\u2022", + "\u00b6", "#", "\u00bd", "\u2153", "\u2154", "\u00bc", "\u00be", "\u215b", "\u215c", "\u215d", + "\u215e", "\u221e", "\u2018", "\u2019", + { + 'label': "\u201c\u201d", + 'action': { + 'type': 'encapsulate', 'options': { 'pre': "\u201c", 'post': "\u201d" } + } + }, + { + 'label': "\u201e\u201c", + 'action': { + 'type': 'encapsulate', 'options': { 'pre': "\u201e", 'post': "\u201c" } + } + }, + { + 'label': "\u201e\u201d", + 'action': { + 'type': 'encapsulate', 'options': { 'pre': "\u201e", 'post': "\u201d" } + } + }, + { + 'label': "\u00ab\u00bb", + 'action': { + 'type': 'encapsulate', 'options': { 'pre': "\u00ab", 'post': "\u00bb" } + } + }, + "\u00a4", "\u20b3", "\u0e3f", "\u20b5", "\u00a2", "\u20a1", "\u20a2", "$", "\u20ab", "\u20af", + "\u20ac", "\u20a0", "\u20a3", "\u0192", "\u20b4", "\u20ad", "\u20a4", "\u2133", "\u20a5", + "\u20a6", "\u2116", "\u20a7", "\u20b0", "\u00a3", "\u17db", "\u20a8", "\u20aa", "\u09f3", + "\u20ae", "\u20a9", "\u00a5", "\u2660", "\u2663", "\u2665", "\u2666", "m\u00b2", "m\u00b3", + "\u2013", "\u2014", "\u2026", "\u2018", "\u2019", "\u201c", "\u201d", "\u00b0", "\u2032", + "\u2033", "\u2248", "\u2260", "\u2264", "\u2265", "\u00b1", "\u2212", "\u00d7", "\u00f7", + "\u2190", "\u2192", "\u00b7", "\u00a7" + ] + }, + 'greek': { + 'labelMsg': 'wikieditor-toolbar-characters-page-greek', + 'layout': 'characters', + 'language': 'hl', + 'characters': [ + "\u0391", "\u0386", "\u03b1", "\u03ac", "\u0392", "\u03b2", "\u0393", "\u03b3", "\u0394", + "\u03b4", "\u0395", "\u0388", "\u03b5", "\u03ad", "\u0396", "\u03b6", "\u0397", "\u0389", + "\u03b7", "\u03ae", "\u0398", "\u03b8", "\u0399", "\u038a", "\u03b9", "\u03af", "\u039a", + "\u03ba", "\u039b", "\u03bb", "\u039c", "\u03bc", "\u039d", "\u03bd", "\u039e", "\u03be", + "\u039f", "\u038c", "\u03bf", "\u03cc", "\u03a0", "\u03c0", "\u03a1", "\u03c1", "\u03a3", + "\u03c3", "\u03c2", "\u03a4", "\u03c4", "\u03a5", "\u038e", "\u03c5", "\u03cd", "\u03a6", + "\u03c6", "\u03a7", "\u03c7", "\u03a8", "\u03c8", "\u03a9", "\u038f", "\u03c9", "\u03ce" + ] + }, + 'cyrillic': { + 'labelMsg': 'wikieditor-toolbar-characters-page-cyrillic', + 'layout': 'characters', + 'characters': [ + "\u0410", "\u0430", "\u04d8", "\u04d9", "\u0411", "\u0431", "\u0412", "\u0432", "\u0413", + "\u0433", "\u0490", "\u0491", "\u0403", "\u0453", "\u0492", "\u0493", "\u0414", "\u0434", + "\u0402", "\u0452", "\u0415", "\u0435", "\u0404", "\u0454", "\u0401", "\u0451", "\u0416", + "\u0436", "\u0417", "\u0437", "\u0405", "\u0455", "\u0418", "\u0438", "\u0406", "\u0456", + "\u0407", "\u0457", "\u04c0", "\u0419", "\u0439", "\u04e2", "\u04e3", "\u0408", "\u0458", + "\u041a", "\u043a", "\u040c", "\u045c", "\u049a", "\u049b", "\u041b", "\u043b", "\u0409", + "\u0459", "\u041c", "\u043c", "\u041d", "\u043d", "\u040a", "\u045a", "\u04a2", "\u04a3", + "\u041e", "\u043e", "\u04e8", "\u04e9", "\u041f", "\u043f", "\u0420", "\u0440", "\u0421", + "\u0441", "\u0422", "\u0442", "\u040b", "\u045b", "\u0423", "\u0443", "\u040e", "\u045e", + "\u04ee", "\u04ef", "\u04b0", "\u04b1", "\u04ae", "\u04af", "\u0424", "\u0444", "\u0425", + "\u0445", "\u04b2", "\u04b3", "\u04ba", "\u04bb", "\u0426", "\u0446", "\u0427", "\u0447", + "\u04b6", "\u04b7", "\u040f", "\u045f", "\u0428", "\u0448", "\u0429", "\u0449", "\u042a", + "\u044a", "\u042b", "\u044b", "\u042c", "\u044c", "\u042d", "\u044d", "\u042e", "\u044e", + "\u042f", "\u044f" + ] + }, + // The core 28-letter alphabet, special letters for the Arabic language, + // vowels, punctuation, digits. + // Names of letters are written as in the Unicode charts. + 'arabic': { + 'labelMsg': 'wikieditor-toolbar-characters-page-arabic', + 'layout': 'characters', + 'language': 'ar', + 'direction': 'rtl', + 'characters': [ + // core alphabet + "\u0627", "\u0628", "\u062a", "\u062b", "\u062c", "\u062d", "\u062e", "\u062f", + "\u0630", "\u0631", "\u0632", "\u0633", "\u0634", "\u0635", "\u0636", "\u0637", + "\u0638", "\u0639", "\u063a", "\u0641", "\u0642", "\u0643", "\u0644", "\u0645", + "\u0646", "\u0647", "\u0648", "\u064a", + // special letters for the Arabic language + "\u0621", // Hamza + "\u0622", "\u0623", "\u0625", "\u0671", // Alef + "\u0624", // Waw hamza + "\u0626", // Yeh hamza + "\u0649", // Alef maksura + "\u0629", // Teh marbuta + // vowels + "\u064E", "\u064F", "\u0650", "\u064B", "\u064C", "\u064D", "\u0651", "\u0652", + "\u0670", + // punctuation + "\u060c", "\u061b", "\u061f", "\u0640", + // digits + "\u0660", "\u0661", "\u0662", "\u0663", "\u0664", "\u0665", "\u0666", "\u0667", + "\u0668", "\u0669", "\u066A", "\u066B", "\u066C", "\u066D" + ] + }, + // Characters for languages other than Arabic. + 'arabicextended': { + 'labelMsg': 'wikieditor-toolbar-characters-page-arabicextended', + 'layout': 'characters', + 'language': 'ar', + 'direction': 'rtl', + 'characters': [ + // Alef + "\u0672", "\u0673", "\u0674", "\u0675", "\u0773", "\u0774", + // Beh + "\u066E", "\u067B", "\u067E", "\u0680", "\u0750", "\u0751", "\u0752", "\u0753", + "\u0754", "\u0755", "\u0756", + // Teh + "\u0679", "\u067A", "\u067C", "\u067D", "\u067F", + // Jeem + "\u0681", "\u0682", "\u0683", "\u0684", "\u0685", "\u0686", "\u0687", "\u06BF", + // Hah + "\u0757", "\u0758", "\u076E", "\u076F", "\u0772", "\u077C", + // Dal + "\u0688", "\u0689", "\u068A", "\u068B", "\u068C", "\u068D", "\u068E", "\u068F", + "\u0690", "\u06EE", "\u0759", "\u075A", + // Reh + "\u0691", "\u0692", "\u0693", "\u0694", "\u0695", "\u0696", "\u0697", "\u0698", + "\u0699", "\u06EF", "\u075B", "\u076B", "\u076C", "\u0771", + // Seen + "\u069A", "\u069B", "\u069C", "\u077D", + // Sheen + "\u06FA", "\u075C", "\u076D", "\u0770", "\u077E", + // Sad + "\u069D", "\u069E", + // Dad + "\u06FB", + // Tah + "\u069F", + // Ain + "\u06A0", "\u075D", "\u075E", "\u075F", + // Ghain + "\u06FC", + // Feh + "\u06A1", "\u06A2", "\u06A3", "\u06A4", "\u06A5", "\u06A6", "\u0760", "\u0761", + // Qaf + "\u066F", "\u06A7", "\u06A8", + // Kaf + "\u063B", "\u063C", "\u06A9", "\u06AA", "\u06AB", "\u06AC", "\u06AD", "\u06AE", + "\u06AF", "\u06B0", "\u06B1", "\u06B2", "\u06B3", "\u06B4", "\u0762", "\u0763", + "\u0764", "\u077F", + // Lam + "\u06B5", "\u06B6", "\u06B7", "\u06B8", "\u076A", + // Meem + "\u0765", "\u0766", + // Noon + "\u06B9", "\u06BA", "\u06BB", "\u06BC", "\u06BD", "\u0767", "\u0768", "\u0769", + // Heh + "\u06BE", "\u06C0", "\u06C1", "\u06C2", "\u06C3", "\u06D5", "\u06FF", + // Waw + "\u0676", "\u0677", "\u06C4", "\u06C5", "\u06C6", "\u06C7", "\u06C8", "\u06C9", + "\u06CA", "\u06CB", "\u06CF", "\u0778", "\u0779", + // Yeh + "\u0620", "\u063D", "\u063E", "\u063F", "\u0678", "\u06CC", "\u06CD", "\u06CE", + "\u06D0", "\u06D1", "\u06D2", "\u06D3", "\u0775", "\u0776", "\u0777", "\u077A", + "\u077B", + // diacritics + "\u0656", "\u0657", "\u0658", "\u0659", "\u065A", "\u065B", "\u065C", "\u065D", + "\u065E", "\u065F", + // special punctuation + "\u06D4", "\u06FD", "\u06FE", + // special digits + "\u06F0", "\u06F1", "\u06F2", "\u06F3", "\u06F4", "\u06F5", "\u06F6", "\u06F7", + "\u06F8", "\u06F9", + ] + }, + 'hebrew': { + 'labelMsg': 'wikieditor-toolbar-characters-page-hebrew', + 'layout': 'characters', + 'direction': 'rtl', + 'characters': [ + "\u05d0", "\u05d1", "\u05d2", "\u05d3", "\u05d4", "\u05d5", "\u05d6", "\u05d7", "\u05d8", + "\u05d9", "\u05db", "\u05da", "\u05dc", "\u05de", "\u05dd", "\u05e0", "\u05df", "\u05e1", + "\u05e2", "\u05e4", "\u05e3", "\u05e6", "\u05e5", "\u05e7", "\u05e8", "\u05e9", "\u05ea", + "\u05f3", "\u05f4", "\u05f0", "\u05f1", "\u05f2", "\u05be", + [ "\u05b0\u25cc", "\u05b0" ], [ "\u05b1\u25cc", "\u05b1" ], [ "\u05b2\u25cc", "\u05b2" ], + [ "\u05b3\u25cc", "\u05b3" ], [ "\u05b4\u25cc", "\u05b4" ], [ "\u05b5\u25cc", "\u05b5" ], + [ "\u05b6\u25cc", "\u05b6" ], [ "\u05b7\u25cc", "\u05b7" ], [ "\u05b8\u25cc", "\u05b8" ], + [ "\u05b9\u25cc", "\u05b9" ], [ "\u05bb\u25cc", "\u05bb" ], [ "\u05bc\u25cc", "\u05bc" ], + [ "\u05c1\u25cc", "\u05c1" ], [ "\u05c2\u25cc", "\u05c2" ], [ "\u05c7\u25cc", "\u05c7" ], + [ "\u0591\u25cc", "\u0591" ], [ "\u0592\u25cc", "\u0592" ], [ "\u0593\u25cc", "\u0593" ], + [ "\u0594\u25cc", "\u0594" ], [ "\u0595\u25cc", "\u0595" ], [ "\u0596\u25cc", "\u0596" ], + [ "\u0597\u25cc", "\u0597" ], [ "\u0598\u25cc", "\u0598" ], [ "\u0599\u25cc", "\u0599" ], + [ "\u059a\u25cc", "\u059a" ], [ "\u059b\u25cc", "\u059b" ], [ "\u059c\u25cc", "\u059c" ], + [ "\u059d\u25cc", "\u059d" ], [ "\u059e\u25cc", "\u059e" ], [ "\u059f\u25cc", "\u059f" ], + [ "\u05a0\u25cc", "\u05a0" ], [ "\u05a1\u25cc", "\u05a1" ], [ "\u05a2\u25cc", "\u05a2" ], + [ "\u05a3\u25cc", "\u05a3" ], [ "\u05a4\u25cc", "\u05a4" ], [ "\u05a5\u25cc", "\u05a5" ], + [ "\u05a6\u25cc", "\u05a6" ], [ "\u05a7\u25cc", "\u05a7" ], [ "\u05a8\u25cc", "\u05a8" ], + [ "\u05a9\u25cc", "\u05a9" ], [ "\u05aa\u25cc", "\u05aa" ], [ "\u05ab\u25cc", "\u05ab" ], + [ "\u05ac\u25cc", "\u05ac" ], [ "\u05ad\u25cc", "\u05ad" ], [ "\u05ae\u25cc", "\u05ae" ], + [ "\u05af\u25cc", "\u05af" ], [ "\u05bf\u25cc", "\u05bf" ], [ "\u05c0\u25cc", "\u05c0" ], + [ "\u05c3\u25cc", "\u05c3" ] + ] + }, + 'bangla': { + 'labelMsg': 'wikieditor-toolbar-characters-page-bangla', + 'language': 'bn', + 'layout': 'characters', + 'characters': [ + "\u0985", "\u0986", "\u0987", "\u0988", "\u0989", "\u098a", "\u098b", "\u098f", "\u0990", + "\u0993", "\u0994", "\u09be", "\u09bf", "\u09c0", "\u09c1", "\u09c2", "\u09c3", "\u09c7", + "\u09c8", "\u09cb", "\u09cc", "\u0995", "\u0996", "\u0997", "\u0998", "\u0999", "\u099a", + "\u099b", "\u099c", "\u099d", "\u099e", "\u099f", "\u09a0", "\u09a1", "\u09a2", "\u09a3", + "\u09a4", "\u09a5", "\u09a6", "\u09a7", "\u09a8", "\u09aa", "\u09ab", "\u09ac", "\u09ad", + "\u09ae", "\u09af", "\u09b0", "\u09b2", "\u09b6", "\u09b7", "\u09b8", "\u09b9", "\u09a1\u09bc", + "\u09a2\u09bc", "\u09af\u09bc", "\u09ce", "\u0982", "\u0983", "\u0981", "\u09cd", "\u09e7", + "\u09e8", "\u09e9", "\u09ea", "\u09eb", "\u09ec", "\u09ed", "\u09ee", "\u09ef", "\u09e6" + ] + }, + 'telugu': { + 'labelMsg': 'wikieditor-toolbar-characters-page-telugu', + 'language': 'te', + 'layout': 'characters', + 'characters': [ + "\u0c01", "\u0c02", "\u0c03", "\u0c05", "\u0c06", "\u0c07", "\u0c08", "\u0c09", "\u0c0a", + "\u0c0b", "\u0c60", "\u0c0c", "\u0c61", "\u0c0e", "\u0c0f", "\u0c10", "\u0c12", "\u0c13", + "\u0c14", "\u0c15", "\u0c16", "\u0c17", "\u0c18", "\u0c19", "\u0c1a", "\u0c1b", "\u0c1c", + "\u0c1d", "\u0c1e", "\u0c1f", "\u0c20", "\u0c21", "\u0c22", "\u0c23", "\u0c24", "\u0c25", + "\u0c26", "\u0c27", "\u0c28", "\u0c2a", "\u0c2b", "\u0c2c", "\u0c2d", "\u0c2e", "\u0c2f", + "\u0c30", "\u0c31", "\u0c32", "\u0c33", "\u0c35", "\u0c36", "\u0c37", "\u0c38", "\u0c39", + "\u0c3e", "\u0c3f", "\u0c40", "\u0c41", "\u0c42", "\u0c43", "\u0c44", "\u0c46", "\u0c47", + "\u0c48", "\u0c4a", "\u0c4b", "\u0c4c", "\u0c4d", "\u0c62", "\u0c63", "\u0c58", "\u0c59", + "\u0c66", "\u0c67", "\u0c68", "\u0c69", "\u0c6a", "\u0c6b", "\u0c6c", "\u0c6d", "\u0c6e", + "\u0c6f", "\u0c3d", "\u0c78", "\u0c79", "\u0c7a", "\u0c7b", "\u0c7c", "\u0c7d", "\u0c7e", + "\u0c7f" + ] + }, + 'sinhala': { + 'labelMsg': 'wikieditor-toolbar-characters-page-sinhala', + 'language': 'si', + 'layout': 'characters', + 'characters': [ + "\u0d85", "\u0d86", "\u0d87", "\u0d88", "\u0d89", "\u0d8a", "\u0d8b", "\u0d8c", "\u0d8d", + "\u0d8e", "\u0d8f", "\u0d90", "\u0d91", "\u0d92", "\u0d93", "\u0d94", "\u0d95", "\u0d96", + "\u0d9a", "\u0d9b", "\u0d9c", "\u0d9d", "\u0d9e", "\u0d9f", "\u0da0", "\u0da1", "\u0da2", + "\u0da3", "\u0da4", "\u0da5", "\u0da6", "\u0da7", "\u0da8", "\u0da9", "\u0daa", "\u0dab", + "\u0dac", "\u0dad", "\u0dae", "\u0daf", "\u0db0", "\u0db1", "\u0db3", "\u0db4", "\u0db5", + "\u0db6", "\u0db7", "\u0db8", "\u0db9", "\u0dba", "\u0dbb", "\u0dbd", "\u0dc0", "\u0dc1", + "\u0dc2", "\u0dc3", "\u0dc4", "\u0dc5", "\u0dc6", + [ "\u25cc\u0dcf", "\u0dcf" ], [ "\u25cc\u0dd0", "\u0dd0" ], [ "\u25cc\u0dd1", "\u0dd1" ], + [ "\u25cc\u0dd2", "\u0dd2" ], [ "\u25cc\u0dd3", "\u0dd3" ], [ "\u25cc\u0dd4", "\u0dd4" ], + [ "\u25cc\u0dd6", "\u0dd6" ], [ "\u25cc\u0dd8", "\u0dd8" ], [ "\u25cc\u0df2", "\u0df2" ], + [ "\u25cc\u0ddf", "\u0ddf" ], [ "\u25cc\u0df3", "\u0df3" ], [ "\u25cc\u0dd9", "\u0dd9" ], + [ "\u25cc\u0dda", "\u0dda" ], [ "\u25cc\u0ddc", "\u0ddc" ], [ "\u25cc\u0ddd", "\u0ddd" ], + [ "\u25cc\u0dde", "\u0dde" ], [ "\u25cc\u0dca", "\u0dca" ] + ] + }, + 'gujarati': { + 'labelMsg': 'wikieditor-toolbar-characters-page-gujarati', + 'language': 'gu', + 'layout': 'characters', + 'characters': [ + "\u0ad0", "\u0a85", "\u0a86", "\u0a87", "\u0a88", "\u0a89", "\u0a8a", "\u0a8b", "\u0ae0", + "\u0a8c", "\u0ae1", "\u0a8d", "\u0a8f", "\u0a90", "\u0a91", "\u0a93", "\u0a94", "\u0a95", + "\u0a96", "\u0a97", "\u0a98", "\u0a99", "\u0a9a", "\u0a9b", "\u0a9c", "\u0a9d", "\u0a9e", + "\u0a9f", "\u0aa0", "\u0aa1", "\u0aa2", "\u0aa3", "\u0aa4", "\u0aa5", "\u0aa6", "\u0aa7", + "\u0aa8", "\u0aaa", "\u0aab", "\u0aac", "\u0aad", "\u0aae", "\u0aaf", "\u0ab0", "\u0ab2", + "\u0ab5", "\u0ab6", "\u0ab7", "\u0ab8", "\u0ab9", "\u0ab3", "\u0abd", + [ "\u25cc\u0abe", "\u0abe" ], [ "\u25cc\u0abf", "\u0abf" ], [ "\u25cc\u0ac0", "\u0ac0" ], + [ "\u25cc\u0ac1", "\u0ac1" ], [ "\u25cc\u0ac2", "\u0ac2" ], [ "\u25cc\u0ac3", "\u0ac3" ], + [ "\u25cc\u0ac4", "\u0ac4" ], [ "\u25cc\u0ae2", "\u0ae2" ], [ "\u25cc\u0ae3", "\u0ae3" ], + [ "\u25cc\u0ac5", "\u0ac5" ], [ "\u25cc\u0ac7", "\u0ac7" ], [ "\u25cc\u0ac8", "\u0ac8" ], + [ "\u25cc\u0ac9", "\u0ac9" ], [ "\u25cc\u0acb", "\u0acb" ], [ "\u25cc\u0acc", "\u0acc" ], + [ "\u25cc\u0acd", "\u0acd" ] + ] + }, + 'thai': { + 'labelMsg': 'wikieditor-toolbar-characters-page-thai', + 'language': 'th', + 'layout': 'characters', + 'characters': [ + "\u0e01", "\u0e02", "\u0e03", "\u0e04", "\u0e05", "\u0e06", "\u0e07", "\u0e08", "\u0e09", + "\u0e0a", "\u0e0b", "\u0e0c", "\u0e0d", "\u0e0e", "\u0e0f", "\u0e10", "\u0e11", "\u0e12", + "\u0e13", "\u0e14", "\u0e15", "\u0e16", "\u0e17", "\u0e18", "\u0e19", "\u0e1a", "\u0e1b", + "\u0e1c", "\u0e1d", "\u0e1e", "\u0e1f", "\u0e20", "\u0e21", "\u0e22", "\u0e23", "\u0e24", + "\u0e25", "\u0e26", "\u0e27", "\u0e28", "\u0e29", "\u0e2a", "\u0e2b", "\u0e2c", "\u0e2d", + "\u0e2e", "\u0e30", "\u0e31", "\u0e32", "\u0e45", "\u0e33", "\u0e34", "\u0e35", "\u0e36", + "\u0e37", "\u0e38", "\u0e39", "\u0e40", "\u0e41", "\u0e42", "\u0e43", "\u0e44", "\u0e47", + "\u0e48", "\u0e49", "\u0e4a", "\u0e4b", "\u0e4c", "\u0e4d", "\u0e3a", "\u0e4e", "\u0e50", + "\u0e51", "\u0e52", "\u0e53", "\u0e54", "\u0e55", "\u0e56", "\u0e57", "\u0e58", "\u0e59", + "\u0e3f", "\u0e46", "\u0e2f", "\u0e5a", "\u0e4f", "\u0e5b" + ] + }, + 'lao': { + 'labelMsg': 'wikieditor-toolbar-characters-page-lao', + 'language': 'lo', + 'layout': 'characters', + 'characters': [ + "\u0e81", "\u0e82", "\u0e84", "\u0e87", "\u0e88", "\u0eaa", "\u0e8a", "\u0e8d", "\u0e94", + "\u0e95", "\u0e96", "\u0e97", "\u0e99", "\u0e9a", "\u0e9b", "\u0e9c", "\u0e9d", "\u0e9e", + "\u0e9f", "\u0ea1", "\u0ea2", "\u0ea5", "\u0ea7", "\u0eab", "\u0ead", "\u0eae", "\u0ea3", + "\u0edc", "\u0edd", "\u0ebc", "\u0ebd", "\u0eb0", "\u0eb1", "\u0eb2", "\u0eb3", "\u0eb4", + "\u0eb5", "\u0eb6", "\u0eb7", "\u0eb8", "\u0eb9", "\u0ebb", "\u0ec0", "\u0ec1", "\u0ec2", + "\u0ec3", "\u0ec4", "\u0ec8", "\u0ec9", "\u0eca", "\u0ecb", "\u0ecc", "\u0ecd", "\u0ed0", + "\u0ed1", "\u0ed2", "\u0ed3", "\u0ed4", "\u0ed5", "\u0ed6", "\u0ed7", "\u0ed8", "\u0ed9", + "\u20ad", "\u0ec6", "\u0eaf" + ] + }, + 'khmer': { + 'labelMsg': 'wikieditor-toolbar-characters-page-khmer', + 'language': 'km', + 'layout': 'characters', + 'characters': [ + "\u1780", "\u1781", "\u1782", "\u1783", "\u1784", "\u1785", "\u1786", "\u1787", "\u1788", + "\u1789", "\u178a", "\u178b", "\u178c", "\u178d", "\u178e", "\u178f", "\u1790", "\u1791", + "\u1792", "\u1793", "\u1794", "\u1795", "\u1796", "\u1797", "\u1798", "\u1799", "\u179a", + "\u179b", "\u179c", "\u179f", "\u17a0", "\u17a1", "\u17a2", "\u17a3", "\u17a4", "\u17a5", + "\u17a6", "\u17a7", "\u17a8", "\u17a9", "\u17aa", "\u17ab", "\u17ac", "\u17ad", "\u17ae", + "\u17af", "\u17b0", "\u17b1", "\u17b2", "\u17b3", "\u17d2", "\u17b4", "\u17b5", "\u17b6", + "\u17b7", "\u17b8", "\u17b9", "\u17ba", "\u17bb", "\u17bc", "\u17bd", "\u17be", "\u17bf", + "\u17c0", "\u17c1", "\u17c2", "\u17c3", "\u17c4", "\u17c5", "\u17c6", "\u17c7", "\u17c8", + "\u17c9", "\u17ca", "\u17cb", "\u17cc", "\u17cd", "\u17ce", "\u17cf", "\u17d0", "\u17d1", + "\u17d3", "\u17dd", "\u17dc", "\u17e0", "\u17e1", "\u17e2", "\u17e3", "\u17e4", "\u17e5", + "\u17e6", "\u17e7", "\u17e8", "\u17e9", "\u17db", "\u17d4", "\u17d5", "\u17d6", "\u17d7", + "\u17d8", "\u17d9", "\u17da", "\u17f0", "\u17f1", "\u17f2", "\u17f3", "\u17f4", "\u17f5", + "\u17f6", "\u17f7", "\u17f8", "\u17f9", "\u19e0", "\u19e1", "\u19e2", "\u19e3", "\u19e4", + "\u19e5", "\u19e6", "\u19e7", "\u19e8", "\u19e9", "\u19ea", "\u19eb", "\u19ec", "\u19ed", + "\u19ee", "\u19ef", "\u19f0", "\u19f1", "\u19f2", "\u19f3", "\u19f4", "\u19f5", "\u19f6", + "\u19f7", "\u19f8", "\u19f9", "\u19fa", "\u19fb", "\u19fc", "\u19fd", "\u19fe", "\u19ff" + ] + } + } + }, + 'help': { + 'labelMsg': 'wikieditor-toolbar-section-help', + 'type': 'booklet', + 'deferLoad': true, + 'pages': { + 'format': { + 'labelMsg': 'wikieditor-toolbar-help-page-format', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-italic-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-italic-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-italic-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-bold-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-bold-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-bold-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-bolditalic-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-bolditalic-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-bolditalic-result' } + } + ] + }, + 'link': { + 'labelMsg': 'wikieditor-toolbar-help-page-link', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-ilink-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-ilink-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-ilink-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-xlink-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-xlink-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-xlink-result' } + } + ] + }, + 'heading': { + 'labelMsg': 'wikieditor-toolbar-help-page-heading', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading2-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading2-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading2-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading3-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading3-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading3-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading4-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading4-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading4-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading5-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading5-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-heading5-result' } + } + ] + }, + 'list': { + 'labelMsg': 'wikieditor-toolbar-help-page-list', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-ulist-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-ulist-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-ulist-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-olist-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-olist-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-olist-result' } + } + ] + }, + 'file': { + 'labelMsg': 'wikieditor-toolbar-help-page-file', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-file-description' }, + 'syntax': { 'htmlMsg': [ 'wikieditor-toolbar-help-content-file-syntax', fileNamespace ] }, + 'result': { 'htmlMsg': [ 'wikieditor-toolbar-help-content-file-result', mw.config.get( 'stylepath' ) ] } + } + ] + }, + 'reference': { + 'labelMsg': 'wikieditor-toolbar-help-page-reference', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-reference-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-reference-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-reference-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-rereference-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-rereference-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-rereference-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-showreferences-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-showreferences-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-showreferences-result' } + } + ] + }, + 'discussion': { + 'labelMsg': 'wikieditor-toolbar-help-page-discussion', + 'layout': 'table', + 'headings': [ + { 'textMsg': 'wikieditor-toolbar-help-heading-description' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-syntax' }, + { 'textMsg': 'wikieditor-toolbar-help-heading-result' } + ], + 'rows': [ + { + 'description': { + 'htmlMsg': 'wikieditor-toolbar-help-content-signaturetimestamp-description' + }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-signaturetimestamp-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-signaturetimestamp-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-signature-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-signature-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-signature-result' } + }, + { + 'description': { 'htmlMsg': 'wikieditor-toolbar-help-content-indent-description' }, + 'syntax': { 'htmlMsg': 'wikieditor-toolbar-help-content-indent-syntax' }, + 'result': { 'htmlMsg': 'wikieditor-toolbar-help-content-indent-result' } + } + ] + } + } + } + } }; +} + +}; } ) ( jQuery );
\ No newline at end of file diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.css b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.css new file mode 100644 index 00000000..aab08ca8 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.css @@ -0,0 +1,303 @@ +/* + * CSS for WikiEditor Toolbar jQuery plugin + */ + +.wikiEditor-ui-toolbar { + position: relative; + width: 100%; +} +/* Expandable Sections */ +.wikiEditor-ui-toolbar .sections { + float: left; + width: 100%; + clear: both; + height: 0; +} +.wikiEditor-ui-toolbar .sections .section { + display: none; + float: left; + width: 100%; + border-top: solid 1px #DDDDDD; + background-color: #E0EEf7; +} +.wikiEditor-ui-toolbar { + /* @embed */ + background-image: url(images/toolbar/base.png); + background-position: left top; + background-repeat: repeat-x; +} +/* Gets overridden when the section div is in class loading - see below */ +.wikiEditor-ui-toolbar .sections div .spinner { + display: none; +} +.wikiEditor-ui-toolbar .sections .loading .spinner { + display: block; + float: left; + /* @embed */ + background-image: url(images/toolbar/loading.gif); + background-position: left center; + background-repeat: no-repeat; + padding-left: 32px; + margin-left: 0.5em; + height: 32px; + color: #666666; +} +/* Top Level Containers */ +.wikiEditor-ui-toolbar .tabs, +.wikiEditor-ui-toolbar .section-main { + position: relative; + float: left; + height: 26px; +} +/* Groups */ +.wikiEditor-ui-toolbar .group { + float: left; + height: 26px; + padding-right: 6px; + border-right: solid 1px #DDDDDD; + margin: 3px; +} +.wikiEditor-ui-toolbar .group-search { + float: right; + padding: 0 0 0 6px; + border-right: none; + border-left: 1px solid #DDDDDD; +} +.wikiEditor-ui-toolbar .group-insert { + border-right: none; +} +/* Sprited Buttons */ +.wikiEditor-toolbar-spritedButton { + /* @embed */ + background: url(images/toolbar/button-sprite.png) 0 0 no-repeat; + display: block; + float: left; + height: 22px; + text-indent: -9999px; + width: 22px; + padding: 2px; + cursor: pointer; + overflow: hidden; +} +/* Tabs */ +.wikiEditor-ui-toolbar .tabs { + list-style: none; + margin: 3px; +} +.wikiEditor-ui-toolbar .tabs span.tab { + display: block; + float: left; + line-height: 26px; +} +.wikiEditor-ui-toolbar .tabs span.tab a, +.wikiEditor-ui-toolbar .tabs span.tab a:visited { + display: inline-block; + float: left; + padding-left: 18px; + padding-right: 12px; + height: 26px; + cursor: pointer; + color: #0645ad; + /* @embed */ + background-image: url(images/toolbar/arrow-ltr.png); + background-position: left center; + background-repeat: no-repeat; +} +.wikiEditor-ui-toolbar .tabs span.tab a.current, +.wikiEditor-ui-toolbar .tabs span.tab a.current:visited { + color: #333333; + /* @embed */ + background-image: url(images/toolbar/arrow-down.png); +} +.wikiEditor-ui-toolbar .tabs span.tab a.current:hover { + text-decoration: none; +} +.wikiEditor-ui-toolbar .tabs span.tab a.loading { + /* @embed */ + background-image: url(images/toolbar/loading-small.gif); +} +/* Toolbar */ +.wikiEditor-ui-toolbar .group .label { + float: left; + border: 0px; + height: 22px; + line-height: 22px; + margin: 2px; + margin-left: 5px; + margin-right: 8px; + color: #777777; + cursor: default; +} +.wikiEditor-ui-toolbar .group img.tool { + float: left; + border: 0px; + height: 22px; + width: 22px; + padding: 2px; + cursor: pointer; +} +.wikiEditor-ui-toolbar .group .tool-select { + float: left; + margin: 2px; + height: 22px; + cursor: pointer; + border: solid 1px silver; + padding: 0; + margin-right: 0; + cursor: pointer; + background-color: #ffffff; +} +.wikiEditor-ui-toolbar .group .tool-select .label { + /* @embed */ + background-image: url(images/toolbar/arrow-down.png); + background-position: center right; + background-repeat: no-repeat; + padding: 0; + margin: 0; + padding-left: 4px; + padding-right: 22px; + margin-right: 4px; + cursor: pointer; + text-decoration: none; + color: #333333; +} +.wikiEditor-ui-toolbar .group .tool-select .menu .options { + position: absolute; + display: none; + margin-left: -1px; + margin-top: 22px; + border: solid 1px silver; + background-color: #ffffff; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option { + display: block; + padding: 0.5em; + text-decoration: none; + color: black; + white-space: nowrap; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option:hover { + background-color: #E0EEf7; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option[rel=heading-2] { + font-size: 150%; + font-weight: normal; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option[rel=heading-3] { + font-size: 132%; + font-weight: normal; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option[rel=heading-4] { + font-size: 116%; + font-weight: normal; +} +.wikiEditor-ui-toolbar .group .tool-select .options .option[rel=heading-5] { + font-size: 100%; + font-weight: bold; +} +/* Booklet */ +.wikiEditor-ui-toolbar .booklet .index { + float: left; + width: 20%; + height: 125px; + overflow: auto; +} +.wikiEditor-ui-toolbar .booklet .index div { + padding: 4px; + padding-left: 6px; + cursor: pointer; + color: #0645ad; +} +.wikiEditor-ui-toolbar .booklet .index .current { + background-color: #FAFAFA; + color: #333333; + cursor: default; +} +.wikiEditor-ui-toolbar .booklet .pages { + float: right; + width: 80%; + height: 125px; + overflow: auto; + background-color: #FAFAFA; +} +/* Help Pages */ +.wikiEditor-ui-toolbar .page-table table { + padding-left: 5px; + padding-right: 5px; + background: none; +} +.wikiEditor-ui-toolbar .page-table th { + color: #999999; +} +.wikiEditor-ui-toolbar .page-table td { + color: black; + border-top: solid 1px #EEEEEE; +} +.wikiEditor-ui-toolbar .page-table th, +.wikiEditor-ui-toolbar .page-table td { + text-align: left; + padding: 5px; + margin: 0px; +} +.wikiEditor-ui-toolbar .section-help .page-table td.cell-syntax, +.wikiEditor-ui-toolbar .section-help .page-table td.syntax { + font-family: monospace, "Courier New"; +} +.wikiEditor-ui-toolbar .section-help .page-table td.syntax, +.wikiEditor-ui-toolbar .section-help .page-table td.cell-syntax, +.wikiEditor-ui-toolbar .section-help .page-table td.cell-result, +.wikiEditor-ui-toolbar .section-help .page-table td.result { + width: 40%; +} +.wikiEditor-ui-toolbar .section-help .page-table td.description, +.wikiEditor-ui-toolbar .section-help .page-table td.description { + width: 20%; +} +/* Characters Pages */ +.wikiEditor-ui-toolbar .page-characters div span { + border: solid 1px #DDDDDD; + padding: 5px; + padding-left: 8px; + padding-right: 8px; + margin-left: 5px; + margin-top: 5px; + height: 1em; + float: left; + display: block; + color: black; + text-decoration: none; + cursor: pointer; + font-family: monospace, "Courier New"; + font-size: 1.25em; +} +.wikiEditor-ui-toolbar .page-characters div[dir=rtl] span { + direction: rtl; +} +.wikiEditor-ui-toolbar .page-characters div span:hover { + background-color: white; + text-decoration: none; + border-color: #a8d7f9; +} +.ui-widget table td.wikieditor-toolbar-table-preview-wrapper span { + padding: 4px 6px 0px; + display: block; +} +.ui-widget table .wikieditor-toolbar-table-preview-frame { + width: 340px; + background: #fff; + padding: 10px; + overflow: hidden; + display: block; + position: relative; +} +.ui-widget table .wikieditor-toolbar-table-preview-content { + width: 375px; + display: block; +} +.ui-widget table .wikieditor-toolbar-table-preview { + width: 340px; +} +.ui-widget table td.wikieditor-toolbar-table-preview-wrapper { + background: #e5e5e5; + padding: 10px; +} diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.js b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.js new file mode 100644 index 00000000..7a72c923 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.toolbar.js @@ -0,0 +1,775 @@ +/** + * Toolbar module for wikiEditor + */ +( function( $ ) { $.wikiEditor.modules.toolbar = { + +/** + * API accessible functions + */ +api : { + addToToolbar : function( context, data ) { + + var smooth = true, type, i; + + for ( type in data ) { + switch ( type ) { + case 'sections': + var $sections = context.modules.toolbar.$toolbar.find( 'div.sections' ), + $tabs = context.modules.toolbar.$toolbar.find( 'div.tabs' ); + for ( var section in data[type] ) { + if ( section == 'main' ) { + // Section + context.modules.toolbar.$toolbar.prepend( + $.wikiEditor.modules.toolbar.fn.buildSection( + context, section, data[type][section] + ) + ); + continue; + } + // Section + $sections.append( + $.wikiEditor.modules.toolbar.fn.buildSection( context, section, data[type][section] ) + ); + // Tab + $tabs.append( + $.wikiEditor.modules.toolbar.fn.buildTab( context, section, data[type][section] ) + ); + } + break; + case 'groups': + if ( ! ( 'section' in data ) ) { + continue; + } + var $section = context.modules.toolbar.$toolbar.find( 'div[rel="' + data.section + '"].section' ); + for ( var group in data[type] ) { + // Group + $section.append( + $.wikiEditor.modules.toolbar.fn.buildGroup( context, group, data[type][group] ) + ); + } + smooth = false; + break; + case 'tools': + if ( ! ( 'section' in data && 'group' in data ) ) { + continue; + } + var $group = context.modules.toolbar.$toolbar.find( + 'div[rel="' + data.section + '"].section ' + 'div[rel="' + data.group + '"].group' + ); + for ( var tool in data[type] ) { + // Tool + $group.append( $.wikiEditor.modules.toolbar.fn.buildTool( context, tool, data[type][tool] ) ); + } + if ( $group.children().length ) { + $group.show(); + } + smooth = false; + break; + case 'pages': + if ( ! ( 'section' in data ) ) { + continue; + } + var $pages = context.modules.toolbar.$toolbar.find( + 'div[rel="' + data.section + '"].section .pages' + ); + var $index = context.modules.toolbar.$toolbar.find( + 'div[rel="' + data.section + '"].section .index' + ); + for ( var page in data[type] ) { + // Page + $pages.append( $.wikiEditor.modules.toolbar.fn.buildPage( context, page, data[type][page] ) ); + // Index + $index.append( + $.wikiEditor.modules.toolbar.fn.buildBookmark( context, page, data[type][page] ) + ); + } + $.wikiEditor.modules.toolbar.fn.updateBookletSelection( context, page, $pages, $index ); + smooth = false; + break; + case 'rows': + if ( ! ( 'section' in data && 'page' in data ) ) { + continue; + } + var $table = context.modules.toolbar.$toolbar.find( + 'div[rel="' + data.section + '"].section ' + 'div[rel="' + data.page + '"].page table' + ); + for ( i = 0; i < data.rows.length; i++ ) { + // Row + $table.append( $.wikiEditor.modules.toolbar.fn.buildRow( context, data.rows[i] ) ); + } + smooth = false; + break; + case 'characters': + if ( ! ( 'section' in data && 'page' in data ) ) { + continue; + } + var $characters = context.modules.toolbar.$toolbar.find( + 'div[rel="' + data.section + '"].section ' + 'div[rel="' + data.page + '"].page div' + ); + var actions = $characters.data( 'actions' ); + for ( i = 0; i < data.characters.length; i++ ) { + // Character + $characters + .append( + $( $.wikiEditor.modules.toolbar.fn.buildCharacter( data.characters[i], actions ) ) + .mousedown( function( e ) { + context.fn.saveCursorAndScrollTop(); + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + $.wikiEditor.modules.toolbar.fn.doAction( $(this).parent().data( 'context' ), + $(this).parent().data( 'actions' )[$(this).attr( 'rel' )] ); + e.preventDefault(); + return false; + } ) + ); + } + smooth = false; + break; + default: break; + } + } + + // Fix div.section size after adding things; if smooth is true uses a smooth + // animation, otherwise just change height (breaking any ongoing animation) + var $divSections = context.modules.toolbar.$toolbar.find( 'div.sections' ); + var $visibleSection = $divSections.find( '.section:visible' ); + if ( $visibleSection.size() ) { + if ( smooth ) { + $divSections.animate( { 'height': $visibleSection.outerHeight() }, 'fast' ); + } else { + $divSections.height( $visibleSection.outerHeight() ); + } + } + }, + removeFromToolbar : function( context, data ) { + if ( typeof data.section == 'string' ) { + // Section + var tab = 'div.tabs span[rel="' + data.section + '"].tab'; + var target = 'div[rel="' + data.section + '"].section'; + var group = null; + if ( typeof data.group == 'string' ) { + // Toolbar group + target += ' div[rel="' + data.group + '"].group'; + if ( typeof data.tool == 'string' ) { + // Save for later checking if empty + group = target; + // Tool + target += ' span[rel="' + data.tool + '"].tool'; + } + } else if ( typeof data.page == 'string' ) { + // Booklet page + var index = target + ' div.index div[rel="' + data.page + '"]'; + target += ' div.pages div[rel="' + data.page + '"].page'; + if ( typeof data.character == 'string' ) { + // Character + target += ' span[rel="' + data.character + '"]'; + } else if ( typeof data.row == 'number' ) { + // Table row + target += ' table tr:not(:has(th)):eq(' + data.row + ')'; + } else { + // Just a page, remove the index too! + context.modules.toolbar.$toolbar.find( index ).remove(); + $.wikiEditor.modules.toolbar.fn.updateBookletSelection( + context, + null, + context.modules.toolbar.$toolbar.find( target ), + context.modules.toolbar.$toolbar.find( index ) + ); + } + } else { + // Just a section, remove the tab too! + context.modules.toolbar.$toolbar.find( tab ).remove(); + } + context.modules.toolbar.$toolbar.find( target ).remove(); + // Hide empty groups + if ( group ) { + var $group = context.modules.toolbar.$toolbar.find( group ); + if ( $group.children().length === 0 ) { + $group.hide(); + } + } + } + } +}, +/** + * Event handlers + */ +evt: { + resize: function( context, event ) { + context.$ui.find( '.sections' ).height( context.$ui.find( '.sections .section-visible' ).outerHeight() ); + }, + tocCollapse: function( context, event ) { + $.wikiEditor.modules.toolbar.evt.resize( context, event ); + }, + tocExpand: function( context, event ) { + $.wikiEditor.modules.toolbar.evt.resize( context, event ); + } +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates a toolbar 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 ) { + if ( '$toolbar' in context.modules.toolbar ) { + return; + } + context.modules.toolbar.$toolbar = $( '<div/>' ) + .addClass( 'wikiEditor-ui-toolbar' ) + .attr( 'id', 'wikiEditor-ui-toolbar' ); + $.wikiEditor.modules.toolbar.fn.build( context, config ); + context.$ui.find( '.wikiEditor-ui-top' ).append( context.modules.toolbar.$toolbar ); + }, + /** + * Performs an operation based on parameters + * + * @param {Object} context + * @param {Object} action + * @param {Object} source + */ + doAction : function( context, action, source ) { + // Verify that this has been called from a source that's within the toolbar + // 'trackAction' defined in click tracking + if ( $.trackAction !== undefined && source.closest( '.wikiEditor-ui-toolbar' ).size() ) { + // Build a unique id for this action by tracking the parent rel attributes up to the toolbar level + var rels = []; + var step = source; + var i = 0; + while ( !step.hasClass( 'wikiEditor-ui-toolbar' ) ) { + if ( i > 25 ) { + break; + } + i++; + var rel = step.attr( 'rel' ); + if ( rel ) { + rels.push( step.attr( 'rel' ) ); + } + step = step.parent(); + } + rels.reverse(); + var id = rels.join( '.' ); + $.trackAction( id ); + } + switch ( action.type ) { + case 'replace': + case 'encapsulate': + var parts = { + 'pre' : $.wikiEditor.autoMsg( action.options, 'pre' ), + 'peri' : $.wikiEditor.autoMsg( action.options, 'peri' ), + 'post' : $.wikiEditor.autoMsg( action.options, 'post' ) + }; + var replace = action.type == 'replace'; + if ( 'regex' in action.options && 'regexReplace' in action.options ) { + var selection = context.$textarea.textSelection( 'getSelection' ); + if ( selection !== '' && selection.match( action.options.regex ) ) { + parts.peri = selection.replace( action.options.regex, + action.options.regexReplace ); + parts.pre = parts.post = ''; + replace = true; + } + } + context.$textarea.textSelection( + 'encapsulateSelection', + $.extend( {}, action.options, parts, { 'replace': replace } ) + ); + if ( context.$iframe !== undefined ) { + context.$iframe[0].contentWindow.focus(); + } + break; + case 'callback': + if ( typeof action.execute == 'function' ) { + action.execute( context ); + } + break; + case 'dialog': + context.fn.saveSelection(); + context.$textarea.wikiEditor( 'openDialog', action.module ); + break; + default: break; + } + }, + buildGroup : function( context, id, group ) { + var $group = $( '<div/>' ).attr( { 'class' : 'group group-' + id, 'rel' : id } ); + var label = $.wikiEditor.autoMsg( group, 'label' ); + if ( label ) { + $group.append( '<div class="label">' + label + '</div>' ); + } + var empty = true; + if ( 'tools' in group ) { + for ( var tool in group.tools ) { + tool = $.wikiEditor.modules.toolbar.fn.buildTool( context, tool, group.tools[tool] ); + if ( tool ) { + // Consider a group with only hidden tools empty as well + // .is( ':visible' ) always returns false because tool is not attached to the DOM yet + empty = empty && tool.css( 'display' ) == 'none'; + $group.append( tool ); + } + } + } + if ( empty ) { + $group.hide(); + } + return $group; + }, + buildTool : function( context, id, tool ) { + if ( 'filters' in tool ) { + for ( var i = 0; i < tool.filters.length; i++ ) { + if ( $( tool.filters[i] ).size() === 0 ) { + return null; + } + } + } + var label = $.wikiEditor.autoMsg( tool, 'label' ); + switch ( tool.type ) { + case 'button': + var src = $.wikiEditor.autoIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' ); + var $button = null; + if ( 'offset' in tool ) { + var offsetOrIcon = $.wikiEditor.autoIconOrOffset( tool.icon, tool.offset, + $.wikiEditor.imgPath + 'toolbar/' + ); + if ( typeof offsetOrIcon == 'object' ) { + $button = $( '<span/>' ) + .attr( { + 'alt' : label, + 'title' : label, + 'rel' : id, + 'class' : 'tool tool-button wikiEditor-toolbar-spritedButton' + } ) + .text( label ) + .css( 'backgroundPosition', offsetOrIcon[0] + 'px ' + offsetOrIcon[1] + 'px' ); + } + } + if ( !$button ) { + $button = $( '<img/>' ) + .attr( { + 'src' : src, + 'width' : 22, + 'height' : 22, + 'alt' : label, + 'title' : label, + 'rel' : id, + 'class' : 'tool tool-button' + } ); + } + if ( 'action' in tool ) { + $button + .data( 'action', tool.action ) + .data( 'context', context ) + .mousedown( function( e ) { + context.fn.saveCursorAndScrollTop(); + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + $.wikiEditor.modules.toolbar.fn.doAction( + $(this).data( 'context' ), $(this).data( 'action' ), $(this) + ); + e.preventDefault(); + return false; + } ); + } + return $button; + case 'select': + var $select = $( '<div/>' ) + .attr( { 'rel' : id, 'class' : 'tool tool-select' } ); + var $options = $( '<div/>' ).addClass( 'options' ); + if ( 'list' in tool ) { + for ( var option in tool.list ) { + var optionLabel = $.wikiEditor.autoMsg( tool.list[option], 'label' ); + $options.append( + $( '<a/>' ) + .data( 'action', tool.list[option].action ) + .data( 'context', context ) + .mousedown( function( e ) { + context.fn.saveCursorAndScrollTop(); + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + $.wikiEditor.modules.toolbar.fn.doAction( + $(this).data( 'context' ), $(this).data( 'action' ), $(this) + ); + // Hide the dropdown + // Sanity check: if this somehow gets called while the dropdown + // is hidden, don't show it + if ( $(this).parent().is( ':visible' ) ) { + $(this).parent().animate( { 'opacity': 'toggle' }, 'fast' ); + } + e.preventDefault(); + return false; + } ) + .text( optionLabel ) + .addClass( 'option' ) + .attr( { 'rel': option, 'href': '#' } ) + ); + } + } + $select.append( $( '<div/>' ).addClass( 'menu' ).append( $options ) ); + $select.append( $( '<a/>' ) + .addClass( 'label' ) + .text( label ) + .data( 'options', $options ) + .attr( 'href', '#' ) + .mousedown( function( e ) { + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + $(this).data( 'options' ).animate( { 'opacity': 'toggle' }, 'fast' ); + e.preventDefault(); + return false; + } ) + ); + return $select; + default: + return null; + } + }, + buildBookmark : function( context, id, page ) { + var label = $.wikiEditor.autoMsg( page, + 'label' ); + return $( '<div/>' ) + .text( label ) + .attr( 'rel', id ) + .data( 'context', context ) + .mousedown( function( e ) { + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( event ) { + $(this).parent().parent().find( '.page' ).hide(); + $(this).parent().parent().find( '.page-' + $(this).attr( 'rel' ) ).show(); + $(this).siblings().removeClass( 'current' ); + $(this).addClass( 'current' ); + var section = $(this).parent().parent().attr( 'rel' ); + $.cookie( + 'wikiEditor-' + $(this).data( 'context' ).instance + '-booklet-' + section + '-page', + $(this).attr( 'rel' ), + { expires: 30, path: '/' } + ); + // Click tracking + if ( $.trackAction !== undefined){ + $.trackAction(section + '.' + $(this).attr('rel')); + } + // No dragging! + event.preventDefault(); + return false; + } ); + }, + buildPage : function( context, id, page ) { + var html; + var $page = $( '<div/>' ).attr( { + 'class' : 'page page-' + id, + 'rel' : id + } ); + switch ( page.layout ) { + case 'table': + $page.addClass( 'page-table' ); + html = + '<table cellpadding=0 cellspacing=0 ' + 'border=0 width="100%" class="table table-' + id + '">'; + if ( 'headings' in page ) { + html += $.wikiEditor.modules.toolbar.fn.buildHeading( context, page.headings ); + } + if ( 'rows' in page ) { + for ( var i = 0; i < page.rows.length; i++ ) { + html += $.wikiEditor.modules.toolbar.fn.buildRow( context, page.rows[i] ); + } + } + $page.html( html ); + break; + case 'characters': + $page.addClass( 'page-characters' ); + var $characters = $( '<div/>' ).data( 'context', context ).data( 'actions', {} ); + var actions = $characters.data( 'actions' ); + if ( 'language' in page ) { + $characters.attr( 'lang', page.language ); + } + if ( 'direction' in page ) { + $characters.attr( 'dir', page.direction ); + } + if ( 'characters' in page ) { + html = ''; + for ( var i = 0; i < page.characters.length; i++ ) { + html += $.wikiEditor.modules.toolbar.fn.buildCharacter( page.characters[i], actions ); + } + $characters + .html( html ) + .children() + .mousedown( function( e ) { + context.fn.saveCursorAndScrollTop(); + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + $.wikiEditor.modules.toolbar.fn.doAction( + $(this).parent().data( 'context' ), + $(this).parent().data( 'actions' )[$(this).attr( 'rel' )], + $(this) + ); + e.preventDefault(); + return false; + } ); + } + $page.append( $characters ); + break; + } + return $page; + }, + buildHeading : function( context, headings ) { + var html = '<tr>'; + for ( var i = 0; i< headings.length; i++ ) { + html += '<th>' + $.wikiEditor.autoMsg( headings[i], ['html', 'text'] ) + '</th>'; + } + return html; + }, + buildRow : function( context, row ) { + var html = '<tr>'; + for ( var cell in row ) { + html += '<td class="cell cell-' + cell + '" valign="top"><span>' + + $.wikiEditor.autoMsg( row[cell], ['html', 'text'] ) + '</span></td>'; + } + html += '</tr>'; + return html; + }, + buildCharacter : function( character, actions ) { + if ( typeof character == 'string' ) { + character = { + 'label' : character, + 'action' : { + 'type' : 'replace', + 'options' : { + 'peri' : character, + 'selectPeri': false + } + } + }; + } else if ( 0 in character && 1 in character ) { + character = { + 'label' : character[0], + 'action' : { + 'type' : 'replace', + 'options' : { + 'peri' : character[1], + 'selectPeri': false + } + } + }; + } + if ( 'action' in character && 'label' in character ) { + actions[character.label] = character.action; + return '<span rel="' + character.label + '">' + character.label + '</span>'; + } + }, + buildTab : function( context, id, section ) { + var selected = $.cookie( 'wikiEditor-' + context.instance + '-toolbar-section' ); + // Re-save cookie + if ( selected !== null ) { + $.cookie( 'wikiEditor-' + context.instance + '-toolbar-section', selected, { expires: 30, path: '/' } ); + } + var $link = + $( '<a/>' ) + .addClass( selected == id ? 'current' : null ) + .attr( 'href', '#' ) + .text( $.wikiEditor.autoMsg( section, 'label' ) ) + .data( 'context', context ) + .mouseup( function( e ) { + $(this).blur(); + } ) + .mousedown( function( e ) { + // No dragging! + e.preventDefault(); + return false; + } ) + .click( function( e ) { + var $sections = $(this).data( 'context' ).$ui.find( '.sections' ); + var $section = + $(this).data( 'context' ).$ui.find( '.section-' + $(this).parent().attr( 'rel' ) ); + var show = $section.css( 'display' ) == 'none'; + var $previousSections = $section.parent().find( '.section-visible' ); + $previousSections.css( 'position', 'absolute' ); + $previousSections.removeClass( 'section-visible' ); + $previousSections.fadeOut( 'fast', function() { $(this).css( 'position', 'static' ); } ); + $(this).parent().parent().find( 'a' ).removeClass( 'current' ); + $sections.css( 'overflow', 'hidden' ); + var animate = function( $that ) { + $sections + .css( 'display', 'block' ) + .animate( { 'height': $section.outerHeight() }, $section.outerHeight() * 2, function() { + $that.css( 'overflow', 'visible' ).css( 'height', 'auto' ); + context.fn.trigger( 'resize' ); + } ); + }; + if ( show ) { + $section.addClass( 'section-visible' ); + $section.fadeIn( 'fast' ); + if ( $section.hasClass( 'loading' ) ) { + // Loading of this section was deferred, load it now + var $that = $(this); + $that.addClass( 'current loading' ); + setTimeout( function() { + $section.trigger( 'loadSection' ); + animate( $that ); + $that.removeClass( 'loading' ); + }, 1000 ); + } else { + animate( $(this) ); + $(this).addClass( 'current' ); + } + } else { + $sections + .css( 'height', $section.outerHeight() ) + .animate( { 'height': 'hide' }, $section.outerHeight() * 2, function() { + $(this).css( { 'overflow': 'visible', 'height': 0 } ); + context.fn.trigger( 'resize' ); + } ); + } + // Click tracking + if ( $.trackAction !== undefined ) { + $.trackAction( $section.attr('rel') + '.' + ( show ? 'show': 'hide' ) ); + } + // Save the currently visible section + $.cookie( + 'wikiEditor-' + $(this).data( 'context' ).instance + '-toolbar-section', + show ? $section.attr( 'rel' ) : null, + { expires: 30, path: '/' } + ); + e.preventDefault(); + return false; + }); + return $( '<span/>' ) + .attr({ + 'class' : 'tab tab-' + id, + 'rel' : id + }) + .append( $link ); + }, + buildSection: function( context, id, section ) { + var $section = $( '<div/>' ).attr( { 'class': section.type + ' section section-' + id, 'rel': id } ); + var selected = $.cookie( 'wikiEditor-' + context.instance + '-toolbar-section' ); + var show = selected == id; + + if ( section.deferLoad !== undefined && section.deferLoad && id !== 'main' && !show ) { + // This class shows the spinner and serves as a marker for the click handler in buildTab() + $section.addClass( 'loading' ).append( $( '<div/>' ).addClass( 'spinner' ) ); + $section.bind( 'loadSection', function() { + $.wikiEditor.modules.toolbar.fn.reallyBuildSection( context, id, section, $section ); + $section.removeClass( 'loading' ); + } ); + } else { + $.wikiEditor.modules.toolbar.fn.reallyBuildSection( context, id, section, $section ); + } + + // Show or hide section + if ( id !== 'main' ) { + $section.css( 'display', show ? 'block' : 'none' ); + if ( show ) { + $section.addClass( 'section-visible' ); + } + } + return $section; + }, + reallyBuildSection: function( context, id, section, $section ) { + context.$textarea.trigger( 'wikiEditor-toolbar-buildSection-' + $section.attr( 'rel' ), [section] ); + switch ( section.type ) { + case 'toolbar': + if ( 'groups' in section ) { + for ( var group in section.groups ) { + $section.append( + $.wikiEditor.modules.toolbar.fn.buildGroup( context, group, section.groups[group] ) + ); + } + } + break; + case 'booklet': + var $pages = $( '<div/>' ).addClass( 'pages' ); + var $index = $( '<div/>' ).addClass( 'index' ); + if ( 'pages' in section ) { + for ( var page in section.pages ) { + $pages.append( + $.wikiEditor.modules.toolbar.fn.buildPage( context, page, section.pages[page] ) + ); + $index.append( + $.wikiEditor.modules.toolbar.fn.buildBookmark( context, page, section.pages[page] ) + ); + } + } + $section.append( $index ).append( $pages ); + $.wikiEditor.modules.toolbar.fn.updateBookletSelection( context, id, $pages, $index ); + break; + } + }, + updateBookletSelection : function( context, id, $pages, $index ) { + var cookie = 'wikiEditor-' + context.instance + '-booklet-' + id + '-page'; + var selected = $.cookie( cookie ); + // Re-save cookie + if ( selected != null ) { + $.cookie( cookie, selected, { expires: 30, path: '/' } ); + } + var $selectedIndex = $index.find( '*[rel="' + selected + '"]' ); + if ( $selectedIndex.size() === 0 ) { + $selectedIndex = $index.children().eq( 0 ); + selected = $selectedIndex.attr( 'rel' ); + $.cookie( cookie, selected, { expires: 30, path: '/' } ); + } + $pages.children().hide(); + $pages.find( '*[rel="' + selected + '"]' ).show(); + $index.children().removeClass( 'current' ); + $selectedIndex.addClass( 'current' ); + }, + build : function( context, config ) { + var $tabs = $( '<div/>' ).addClass( 'tabs' ).appendTo( context.modules.toolbar.$toolbar ); + var $sections = $( '<div/>' ).addClass( 'sections' ).appendTo( context.modules.toolbar.$toolbar ); + context.modules.toolbar.$toolbar.append( $( '<div/>' ).css( 'clear', 'both' ) ); + var sectionQueue = []; + for ( var section in config ) { + if ( section == 'main' ) { + context.modules.toolbar.$toolbar.prepend( + $.wikiEditor.modules.toolbar.fn.buildSection( context, section, config[section] ) + ); + } else { + sectionQueue.push( { + '$sections' : $sections, + 'context' : context, + 'id' : section, + 'config' : config[section] + } ); + $tabs.append( $.wikiEditor.modules.toolbar.fn.buildTab( context, section, config[section] ) ); + } + } + $.eachAsync( sectionQueue, { + 'bulk' : 0, + 'end' : function() { + // HACK: Opera doesn't seem to want to redraw after these bits + // are added to the DOM, so we can just FORCE it! + var oldValue = $( 'body' ).css( 'position' ); + $( 'body' ).css( 'position', 'static' ); + $( 'body' ).css( 'position', oldValue ); + }, + 'loop' : function( i, s ) { + s.$sections.append( $.wikiEditor.modules.toolbar.fn.buildSection( s.context, s.id, s.config ) ); + var $section = s.$sections.find( '.section:visible' ); + if ( $section.size() ) { + $sections.animate( { 'height': $section.outerHeight() }, $section.outerHeight() * 2, function( ) { + context.fn.trigger( 'resize' ); + } ); + } + } + } ); + } +} + +}; } )( jQuery ); |