diff -r fd8fb93e1b6a -r a361355b55c7 src/pyams_skin/resources/js/ext/tinymce/dev/plugins/spellchecker/plugin.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_skin/resources/js/ext/tinymce/dev/plugins/spellchecker/plugin.js Wed Jun 17 10:00:10 2015 +0200 @@ -0,0 +1,996 @@ +/** + * Compiled inline version. (Library mode) + */ + +/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ +/*globals $code */ + +(function(exports, undefined) { + "use strict"; + + var modules = {}; + + function require(ids, callback) { + var module, defs = []; + + for (var i = 0; i < ids.length; ++i) { + module = modules[ids[i]] || resolve(ids[i]); + if (!module) { + throw 'module definition dependecy not found: ' + ids[i]; + } + + defs.push(module); + } + + callback.apply(null, defs); + } + + function define(id, dependencies, definition) { + if (typeof id !== 'string') { + throw 'invalid module definition, module id must be defined and be a string'; + } + + if (dependencies === undefined) { + throw 'invalid module definition, dependencies must be specified'; + } + + if (definition === undefined) { + throw 'invalid module definition, definition function must be specified'; + } + + require(dependencies, function() { + modules[id] = definition.apply(null, arguments); + }); + } + + function defined(id) { + return !!modules[id]; + } + + function resolve(id) { + var target = exports; + var fragments = id.split(/[.\/]/); + + for (var fi = 0; fi < fragments.length; ++fi) { + if (!target[fragments[fi]]) { + return; + } + + target = target[fragments[fi]]; + } + + return target; + } + + function expose(ids) { + for (var i = 0; i < ids.length; i++) { + var target = exports; + var id = ids[i]; + var fragments = id.split(/[.\/]/); + + for (var fi = 0; fi < fragments.length - 1; ++fi) { + if (target[fragments[fi]] === undefined) { + target[fragments[fi]] = {}; + } + + target = target[fragments[fi]]; + } + + target[fragments[fragments.length - 1]] = modules[id]; + } + } + +// Included from: js/tinymce/plugins/spellchecker/classes/DomTextMatcher.js + +/** + * DomTextMatcher.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/*eslint no-labels:0, no-constant-condition: 0 */ + +/** + * This class logic for filtering text and matching words. + * + * @class tinymce.spellcheckerplugin.TextFilter + * @private + */ +define("tinymce/spellcheckerplugin/DomTextMatcher", [], function() { + // Based on work developed by: James Padolsey http://james.padolsey.com + // released under UNLICENSE that is compatible with LGPL + // TODO: Handle contentEditable edgecase: + //

texttexttexttexttext

+ return function(node, editor) { + var m, matches = [], text, dom = editor.dom; + var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap; + + blockElementsMap = editor.schema.getBlockElements(); // H1-H6, P, TD etc + hiddenTextElementsMap = editor.schema.getWhiteSpaceElements(); // TEXTAREA, PRE, STYLE, SCRIPT + shortEndedElementsMap = editor.schema.getShortEndedElements(); // BR, IMG, INPUT + + function createMatch(m, data) { + if (!m[0]) { + throw 'findAndReplaceDOMText cannot handle zero-length matches'; + } + + return { + start: m.index, + end: m.index + m[0].length, + text: m[0], + data: data + }; + } + + function getText(node) { + var txt; + + if (node.nodeType === 3) { + return node.data; + } + + if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) { + return ''; + } + + txt = ''; + + if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) { + txt += '\n'; + } + + if ((node = node.firstChild)) { + do { + txt += getText(node); + } while ((node = node.nextSibling)); + } + + return txt; + } + + function stepThroughMatches(node, matches, replaceFn) { + var startNode, endNode, startNodeIndex, + endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, + matchLocation, matchIndex = 0; + + matches = matches.slice(0); + matches.sort(function(a, b) { + return a.start - b.start; + }); + + matchLocation = matches.shift(); + + out: while (true) { + if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName]) { + atIndex++; + } + + if (curNode.nodeType === 3) { + if (!endNode && curNode.length + atIndex >= matchLocation.end) { + // We've found the ending + endNode = curNode; + endNodeIndex = matchLocation.end - atIndex; + } else if (startNode) { + // Intersecting node + innerNodes.push(curNode); + } + + if (!startNode && curNode.length + atIndex > matchLocation.start) { + // We've found the match start + startNode = curNode; + startNodeIndex = matchLocation.start - atIndex; + } + + atIndex += curNode.length; + } + + if (startNode && endNode) { + curNode = replaceFn({ + startNode: startNode, + startNodeIndex: startNodeIndex, + endNode: endNode, + endNodeIndex: endNodeIndex, + innerNodes: innerNodes, + match: matchLocation.text, + matchIndex: matchIndex + }); + + // replaceFn has to return the node that replaced the endNode + // and then we step back so we can continue from the end of the + // match: + atIndex -= (endNode.length - endNodeIndex); + startNode = null; + endNode = null; + innerNodes = []; + matchLocation = matches.shift(); + matchIndex++; + + if (!matchLocation) { + break; // no more matches + } + } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) { + // Move down + curNode = curNode.firstChild; + continue; + } else if (curNode.nextSibling) { + // Move forward: + curNode = curNode.nextSibling; + continue; + } + + // Move forward or up: + while (true) { + if (curNode.nextSibling) { + curNode = curNode.nextSibling; + break; + } else if (curNode.parentNode !== node) { + curNode = curNode.parentNode; + } else { + break out; + } + } + } + } + + /** + * Generates the actual replaceFn which splits up text nodes + * and inserts the replacement element. + */ + function genReplacer(callback) { + function makeReplacementNode(fill, matchIndex) { + var match = matches[matchIndex]; + + if (!match.stencil) { + match.stencil = callback(match); + } + + var clone = match.stencil.cloneNode(false); + clone.setAttribute('data-mce-index', matchIndex); + + if (fill) { + clone.appendChild(dom.doc.createTextNode(fill)); + } + + return clone; + } + + return function(range) { + var before, after, parentNode, startNode = range.startNode, + endNode = range.endNode, matchIndex = range.matchIndex, + doc = dom.doc; + + if (startNode === endNode) { + var node = startNode; + + parentNode = node.parentNode; + if (range.startNodeIndex > 0) { + // Add "before" text node (before the match) + before = doc.createTextNode(node.data.substring(0, range.startNodeIndex)); + parentNode.insertBefore(before, node); + } + + // Create the replacement node: + var el = makeReplacementNode(range.match, matchIndex); + parentNode.insertBefore(el, node); + if (range.endNodeIndex < node.length) { + // Add "after" text node (after the match) + after = doc.createTextNode(node.data.substring(range.endNodeIndex)); + parentNode.insertBefore(after, node); + } + + node.parentNode.removeChild(node); + + return el; + } else { + // Replace startNode -> [innerNodes...] -> endNode (in that order) + before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex)); + after = doc.createTextNode(endNode.data.substring(range.endNodeIndex)); + var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex); + var innerEls = []; + + for (var i = 0, l = range.innerNodes.length; i < l; ++i) { + var innerNode = range.innerNodes[i]; + var innerEl = makeReplacementNode(innerNode.data, matchIndex); + innerNode.parentNode.replaceChild(innerEl, innerNode); + innerEls.push(innerEl); + } + + var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex); + + parentNode = startNode.parentNode; + parentNode.insertBefore(before, startNode); + parentNode.insertBefore(elA, startNode); + parentNode.removeChild(startNode); + + parentNode = endNode.parentNode; + parentNode.insertBefore(elB, endNode); + parentNode.insertBefore(after, endNode); + parentNode.removeChild(endNode); + + return elB; + } + }; + } + + function unwrapElement(element) { + var parentNode = element.parentNode; + parentNode.insertBefore(element.firstChild, element); + element.parentNode.removeChild(element); + } + + function getWrappersByIndex(index) { + var elements = node.getElementsByTagName('*'), wrappers = []; + + index = typeof index == "number" ? "" + index : null; + + for (var i = 0; i < elements.length; i++) { + var element = elements[i], dataIndex = element.getAttribute('data-mce-index'); + + if (dataIndex !== null && dataIndex.length) { + if (dataIndex === index || index === null) { + wrappers.push(element); + } + } + } + + return wrappers; + } + + /** + * Returns the index of a specific match object or -1 if it isn't found. + * + * @param {Match} match Text match object. + * @return {Number} Index of match or -1 if it isn't found. + */ + function indexOf(match) { + var i = matches.length; + while (i--) { + if (matches[i] === match) { + return i; + } + } + + return -1; + } + + /** + * Filters the matches. If the callback returns true it stays if not it gets removed. + * + * @param {Function} callback Callback to execute for each match. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function filter(callback) { + var filteredMatches = []; + + each(function(match, i) { + if (callback(match, i)) { + filteredMatches.push(match); + } + }); + + matches = filteredMatches; + + /*jshint validthis:true*/ + return this; + } + + /** + * Executes the specified callback for each match. + * + * @param {Function} callback Callback to execute for each match. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function each(callback) { + for (var i = 0, l = matches.length; i < l; i++) { + if (callback(matches[i], i) === false) { + break; + } + } + + /*jshint validthis:true*/ + return this; + } + + /** + * Wraps the current matches with nodes created by the specified callback. + * Multiple clones of these matches might occur on matches that are on multiple nodex. + * + * @param {Function} callback Callback to execute in order to create elements for matches. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function wrap(callback) { + if (matches.length) { + stepThroughMatches(node, matches, genReplacer(callback)); + } + + /*jshint validthis:true*/ + return this; + } + + /** + * Finds the specified regexp and adds them to the matches collection. + * + * @param {RegExp} regex Global regexp to search the current node by. + * @param {Object} [data] Optional custom data element for the match. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function find(regex, data) { + if (text && regex.global) { + while ((m = regex.exec(text))) { + matches.push(createMatch(m, data)); + } + } + + return this; + } + + /** + * Unwraps the specified match object or all matches if unspecified. + * + * @param {Object} [match] Optional match object. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function unwrap(match) { + var i, elements = getWrappersByIndex(match ? indexOf(match) : null); + + i = elements.length; + while (i--) { + unwrapElement(elements[i]); + } + + return this; + } + + /** + * Returns a match object by the specified DOM element. + * + * @param {DOMElement} element Element to return match object for. + * @return {Object} Match object for the specified element. + */ + function matchFromElement(element) { + return matches[element.getAttribute('data-mce-index')]; + } + + /** + * Returns a DOM element from the specified match element. This will be the first element if it's split + * on multiple nodes. + * + * @param {Object} match Match element to get first element of. + * @return {DOMElement} DOM element for the specified match object. + */ + function elementFromMatch(match) { + return getWrappersByIndex(indexOf(match))[0]; + } + + /** + * Adds match the specified range for example a grammar line. + * + * @param {Number} start Start offset. + * @param {Number} length Length of the text. + * @param {Object} data Custom data object for match. + * @return {DomTextMatcher} Current DomTextMatcher instance. + */ + function add(start, length, data) { + matches.push({ + start: start, + end: start + length, + text: text.substr(start, length), + data: data + }); + + return this; + } + + /** + * Returns a DOM range for the specified match. + * + * @param {Object} match Match object to get range for. + * @return {DOMRange} DOM Range for the specified match. + */ + function rangeFromMatch(match) { + var wrappers = getWrappersByIndex(indexOf(match)); + + var rng = editor.dom.createRng(); + rng.setStartBefore(wrappers[0]); + rng.setEndAfter(wrappers[wrappers.length - 1]); + + return rng; + } + + /** + * Replaces the specified match with the specified text. + * + * @param {Object} match Match object to replace. + * @param {String} text Text to replace the match with. + * @return {DOMRange} DOM range produced after the replace. + */ + function replace(match, text) { + var rng = rangeFromMatch(match); + + rng.deleteContents(); + + if (text.length > 0) { + rng.insertNode(editor.dom.doc.createTextNode(text)); + } + + return rng; + } + + /** + * Resets the DomTextMatcher instance. This will remove any wrapped nodes and remove any matches. + * + * @return {[type]} [description] + */ + function reset() { + matches.splice(0, matches.length); + unwrap(); + + return this; + } + + text = getText(node); + + return { + text: text, + matches: matches, + each: each, + filter: filter, + reset: reset, + matchFromElement: matchFromElement, + elementFromMatch: elementFromMatch, + find: find, + add: add, + wrap: wrap, + unwrap: unwrap, + replace: replace, + rangeFromMatch: rangeFromMatch, + indexOf: indexOf + }; + }; +}); + +// Included from: js/tinymce/plugins/spellchecker/classes/Plugin.js + +/** + * Plugin.js + * + * Copyright, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://www.tinymce.com/license + * Contributing: http://www.tinymce.com/contributing + */ + +/*jshint camelcase:false */ + +/** + * This class contains all core logic for the spellchecker plugin. + * + * @class tinymce.spellcheckerplugin.Plugin + * @private + */ +define("tinymce/spellcheckerplugin/Plugin", [ + "tinymce/spellcheckerplugin/DomTextMatcher", + "tinymce/PluginManager", + "tinymce/util/Tools", + "tinymce/ui/Menu", + "tinymce/dom/DOMUtils", + "tinymce/util/XHR", + "tinymce/util/URI", + "tinymce/util/JSON" +], function(DomTextMatcher, PluginManager, Tools, Menu, DOMUtils, XHR, URI, JSON) { + PluginManager.add('spellchecker', function(editor, url) { + var languageMenuItems, self = this, lastSuggestions, started, suggestionsMenu, settings = editor.settings; + var hasDictionarySupport; + + function getTextMatcher() { + if (!self.textMatcher) { + self.textMatcher = new DomTextMatcher(editor.getBody(), editor); + } + + return self.textMatcher; + } + + function buildMenuItems(listName, languageValues) { + var items = []; + + Tools.each(languageValues, function(languageValue) { + items.push({ + selectable: true, + text: languageValue.name, + data: languageValue.value + }); + }); + + return items; + } + + var languagesString = settings.spellchecker_languages || + 'English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr_FR,' + + 'German=de,Italian=it,Polish=pl,Portuguese=pt_BR,' + + 'Spanish=es,Swedish=sv'; + + languageMenuItems = buildMenuItems('Language', + Tools.map(languagesString.split(','), function(langPair) { + langPair = langPair.split('='); + + return { + name: langPair[0], + value: langPair[1] + }; + }) + ); + + function isEmpty(obj) { + /*jshint unused:false*/ + /*eslint no-unused-vars:0 */ + for (var name in obj) { + return false; + } + + return true; + } + + function showSuggestions(word, spans) { + var items = [], suggestions = lastSuggestions[word]; + + Tools.each(suggestions, function(suggestion) { + items.push({ + text: suggestion, + onclick: function() { + editor.insertContent(editor.dom.encode(suggestion)); + editor.dom.remove(spans); + checkIfFinished(); + } + }); + }); + + items.push({text: '-'}); + + if (hasDictionarySupport) { + items.push({text: 'Add to Dictionary', onclick: function() { + addToDictionary(word, spans); + }}); + } + + items.push.apply(items, [ + {text: 'Ignore', onclick: function() { + ignoreWord(word, spans); + }}, + + {text: 'Ignore all', onclick: function() { + ignoreWord(word, spans, true); + }} + ]); + + // Render menu + suggestionsMenu = new Menu({ + items: items, + context: 'contextmenu', + onautohide: function(e) { + if (e.target.className.indexOf('spellchecker') != -1) { + e.preventDefault(); + } + }, + onhide: function() { + suggestionsMenu.remove(); + suggestionsMenu = null; + } + }); + + suggestionsMenu.renderTo(document.body); + + // Position menu + var pos = DOMUtils.DOM.getPos(editor.getContentAreaContainer()); + var targetPos = editor.dom.getPos(spans[0]); + var root = editor.dom.getRoot(); + + // Adjust targetPos for scrolling in the editor + if (root.nodeName == 'BODY') { + targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft; + targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop; + } else { + targetPos.x -= root.scrollLeft; + targetPos.y -= root.scrollTop; + } + + pos.x += targetPos.x; + pos.y += targetPos.y; + + suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight); + } + + function getWordCharPattern() { + // Regexp for finding word specific characters this will split words by + // spaces, quotes, copy right characters etc. It's escaped with unicode characters + // to make it easier to output scripts on servers using different encodings + // so if you add any characters outside the 128 byte range make sure to escape it + return editor.getParam('spellchecker_wordchar_pattern') || new RegExp("[^" + + "\\s!\"#$%&()*+,-./:;<=>?@[\\]^_{|}`" + + "\u00a7\u00a9\u00ab\u00ae\u00b1\u00b6\u00b7\u00b8\u00bb" + + "\u00bc\u00bd\u00be\u00bf\u00d7\u00f7\u00a4\u201d\u201c\u201e\u00a0\u2002\u2003\u2009" + + "]+", "g"); + } + + function defaultSpellcheckCallback(method, text, doneCallback, errorCallback) { + var data = {method: method}, postData = ''; + + if (method == "spellcheck") { + data.text = text; + data.lang = settings.spellchecker_language; + } + + if (method == "addToDictionary") { + data.word = text; + } + + Tools.each(data, function(value, key) { + if (postData) { + postData += '&'; + } + + postData += key + '=' + encodeURIComponent(value); + }); + + XHR.send({ + url: new URI(url).toAbsolute(settings.spellchecker_rpc_url), + type: "post", + content_type: 'application/x-www-form-urlencoded', + data: postData, + success: function(result) { + result = JSON.parse(result); + + if (!result) { + errorCallback("Sever response wasn't proper JSON."); + } else if (result.error) { + errorCallback(result.error); + } else { + doneCallback(result); + } + }, + error: function(type, xhr) { + errorCallback("Spellchecker request error: " + xhr.status); + } + }); + } + + function sendRpcCall(name, data, successCallback, errorCallback) { + var spellCheckCallback = settings.spellchecker_callback || defaultSpellcheckCallback; + spellCheckCallback.call(self, name, data, successCallback, errorCallback); + } + + function spellcheck() { + if (started) { + finish(); + return; + } else { + finish(); + } + + function errorCallback(message) { + editor.windowManager.alert(message); + editor.setProgressState(false); + finish(); + } + + editor.setProgressState(true); + sendRpcCall("spellcheck", getTextMatcher().text, markErrors, errorCallback); + editor.focus(); + } + + function checkIfFinished() { + if (!editor.dom.select('span.mce-spellchecker-word').length) { + finish(); + } + } + + function addToDictionary(word, spans) { + editor.setProgressState(true); + + sendRpcCall("addToDictionary", word, function() { + editor.setProgressState(false); + editor.dom.remove(spans, true); + checkIfFinished(); + }, function(message) { + editor.windowManager.alert(message); + editor.setProgressState(false); + }); + } + + function ignoreWord(word, spans, all) { + editor.selection.collapse(); + + if (all) { + Tools.each(editor.dom.select('span.mce-spellchecker-word'), function(span) { + if (span.getAttribute('data-mce-word') == word) { + editor.dom.remove(span, true); + } + }); + } else { + editor.dom.remove(spans, true); + } + + checkIfFinished(); + } + + function finish() { + getTextMatcher().reset(); + self.textMatcher = null; + + if (started) { + started = false; + editor.fire('SpellcheckEnd'); + } + } + + function getElmIndex(elm) { + var value = elm.getAttribute('data-mce-index'); + + if (typeof value == "number") { + return "" + value; + } + + return value; + } + + function findSpansByIndex(index) { + var nodes, spans = []; + + nodes = Tools.toArray(editor.getBody().getElementsByTagName('span')); + if (nodes.length) { + for (var i = 0; i < nodes.length; i++) { + var nodeIndex = getElmIndex(nodes[i]); + + if (nodeIndex === null || !nodeIndex.length) { + continue; + } + + if (nodeIndex === index.toString()) { + spans.push(nodes[i]); + } + } + } + + return spans; + } + + editor.on('click', function(e) { + var target = e.target; + + if (target.className == "mce-spellchecker-word") { + e.preventDefault(); + + var spans = findSpansByIndex(getElmIndex(target)); + + if (spans.length > 0) { + var rng = editor.dom.createRng(); + rng.setStartBefore(spans[0]); + rng.setEndAfter(spans[spans.length - 1]); + editor.selection.setRng(rng); + showSuggestions(target.getAttribute('data-mce-word'), spans); + } + } + }); + + editor.addMenuItem('spellchecker', { + text: 'Spellcheck', + context: 'tools', + onclick: spellcheck, + selectable: true, + onPostRender: function() { + var self = this; + + self.active(started); + + editor.on('SpellcheckStart SpellcheckEnd', function() { + self.active(started); + }); + } + }); + + function updateSelection(e) { + var selectedLanguage = settings.spellchecker_language; + + e.control.items().each(function(ctrl) { + ctrl.active(ctrl.settings.data === selectedLanguage); + }); + } + + /** + * Find the specified words and marks them. It will also show suggestions for those words. + * + * @example + * editor.plugins.spellchecker.markErrors({ + * dictionary: true, + * words: { + * "word1": ["suggestion 1", "Suggestion 2"] + * } + * }); + * @param {Object} data Data object containing the words with suggestions. + */ + function markErrors(data) { + var suggestions; + + if (data.words) { + hasDictionarySupport = !!data.dictionary; + suggestions = data.words; + } else { + // Fallback to old format + suggestions = data; + } + + editor.setProgressState(false); + + if (isEmpty(suggestions)) { + editor.windowManager.alert('No misspellings found'); + started = false; + return; + } + + lastSuggestions = suggestions; + + getTextMatcher().find(getWordCharPattern()).filter(function(match) { + return !!suggestions[match.text]; + }).wrap(function(match) { + return editor.dom.create('span', { + "class": 'mce-spellchecker-word', + "data-mce-bogus": 1, + "data-mce-word": match.text + }); + }); + + started = true; + editor.fire('SpellcheckStart'); + } + + var buttonArgs = { + tooltip: 'Spellcheck', + onclick: spellcheck, + onPostRender: function() { + var self = this; + + editor.on('SpellcheckStart SpellcheckEnd', function() { + self.active(started); + }); + } + }; + + if (languageMenuItems.length > 1) { + buttonArgs.type = 'splitbutton'; + buttonArgs.menu = languageMenuItems; + buttonArgs.onshow = updateSelection; + buttonArgs.onselect = function(e) { + settings.spellchecker_language = e.control.settings.data; + }; + } + + editor.addButton('spellchecker', buttonArgs); + editor.addCommand('mceSpellCheck', spellcheck); + + editor.on('remove', function() { + if (suggestionsMenu) { + suggestionsMenu.remove(); + suggestionsMenu = null; + } + }); + + editor.on('change', checkIfFinished); + + this.getTextMatcher = getTextMatcher; + this.getWordCharPattern = getWordCharPattern; + this.markErrors = markErrors; + this.getLanguage = function() { + return settings.spellchecker_language; + }; + + // Set default spellchecker language if it's not specified + settings.spellchecker_language = settings.spellchecker_language || settings.language || 'en'; + }); +}); + +expose(["tinymce/spellcheckerplugin/DomTextMatcher"]); +})(this); \ No newline at end of file