mirror of
https://github.com/documize/community.git
synced 2025-07-21 14:19:43 +02:00
1420 lines
40 KiB
JavaScript
1420 lines
40 KiB
JavaScript
|
(function () {
|
||
|
|
||
|
var defs = {}; // id -> {dependencies, definition, instance (possibly undefined)}
|
||
|
|
||
|
// Used when there is no 'main' module.
|
||
|
// The name is probably (hopefully) unique so minification removes for releases.
|
||
|
var register_3795 = function (id) {
|
||
|
var module = dem(id);
|
||
|
var fragments = id.split('.');
|
||
|
var target = Function('return this;')();
|
||
|
for (var i = 0; i < fragments.length - 1; ++i) {
|
||
|
if (target[fragments[i]] === undefined)
|
||
|
target[fragments[i]] = {};
|
||
|
target = target[fragments[i]];
|
||
|
}
|
||
|
target[fragments[fragments.length - 1]] = module;
|
||
|
};
|
||
|
|
||
|
var instantiate = function (id) {
|
||
|
var actual = defs[id];
|
||
|
var dependencies = actual.deps;
|
||
|
var definition = actual.defn;
|
||
|
var len = dependencies.length;
|
||
|
var instances = new Array(len);
|
||
|
for (var i = 0; i < len; ++i)
|
||
|
instances[i] = dem(dependencies[i]);
|
||
|
var defResult = definition.apply(null, instances);
|
||
|
if (defResult === undefined)
|
||
|
throw 'module [' + id + '] returned undefined';
|
||
|
actual.instance = defResult;
|
||
|
};
|
||
|
|
||
|
var def = function (id, dependencies, definition) {
|
||
|
if (typeof id !== 'string')
|
||
|
throw 'module id must be a string';
|
||
|
else if (dependencies === undefined)
|
||
|
throw 'no dependencies for ' + id;
|
||
|
else if (definition === undefined)
|
||
|
throw 'no definition function for ' + id;
|
||
|
defs[id] = {
|
||
|
deps: dependencies,
|
||
|
defn: definition,
|
||
|
instance: undefined
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var dem = function (id) {
|
||
|
var actual = defs[id];
|
||
|
if (actual === undefined)
|
||
|
throw 'module [' + id + '] was undefined';
|
||
|
else if (actual.instance === undefined)
|
||
|
instantiate(id);
|
||
|
return actual.instance;
|
||
|
};
|
||
|
|
||
|
var req = function (ids, callback) {
|
||
|
var len = ids.length;
|
||
|
var instances = new Array(len);
|
||
|
for (var i = 0; i < len; ++i)
|
||
|
instances[i] = dem(ids[i]);
|
||
|
callback.apply(null, instances);
|
||
|
};
|
||
|
|
||
|
var ephox = {};
|
||
|
|
||
|
ephox.bolt = {
|
||
|
module: {
|
||
|
api: {
|
||
|
define: def,
|
||
|
require: req,
|
||
|
demand: dem
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var define = def;
|
||
|
var require = req;
|
||
|
var demand = dem;
|
||
|
// this helps with minification when using a lot of global references
|
||
|
var defineGlobal = function (id, ref) {
|
||
|
define(id, [], function () { return ref; });
|
||
|
};
|
||
|
/*jsc
|
||
|
["tinymce.plugins.spellchecker.Plugin","ephox.katamari.api.Cell","tinymce.core.PluginManager","tinymce.plugins.spellchecker.alien.DetectProPlugin","tinymce.plugins.spellchecker.api.Api","tinymce.plugins.spellchecker.api.Commands","tinymce.plugins.spellchecker.api.Settings","tinymce.plugins.spellchecker.ui.Buttons","tinymce.plugins.spellchecker.ui.SuggestionsMenu","global!tinymce.util.Tools.resolve","global!window","tinymce.plugins.spellchecker.core.Actions","tinymce.core.util.Tools","global!document","tinymce.core.dom.DOMUtils","tinymce.core.ui.Factory","tinymce.core.util.URI","tinymce.core.util.XHR","tinymce.plugins.spellchecker.api.Events","tinymce.plugins.spellchecker.core.DomTextMatcher"]
|
||
|
jsc*/
|
||
|
define(
|
||
|
'ephox.katamari.api.Cell',
|
||
|
|
||
|
[
|
||
|
],
|
||
|
|
||
|
function () {
|
||
|
var Cell = function (initial) {
|
||
|
var value = initial;
|
||
|
|
||
|
var get = function () {
|
||
|
return value;
|
||
|
};
|
||
|
|
||
|
var set = function (v) {
|
||
|
value = v;
|
||
|
};
|
||
|
|
||
|
var clone = function () {
|
||
|
return Cell(get());
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
get: get,
|
||
|
set: set,
|
||
|
clone: clone
|
||
|
};
|
||
|
};
|
||
|
|
||
|
return Cell;
|
||
|
}
|
||
|
);
|
||
|
|
||
|
defineGlobal("global!tinymce.util.Tools.resolve", tinymce.util.Tools.resolve);
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.PluginManager',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.PluginManager');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
defineGlobal("global!window", window);
|
||
|
/**
|
||
|
* DetectProPlugin.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.alien.DetectProPlugin',
|
||
|
[
|
||
|
'global!window',
|
||
|
'tinymce.core.PluginManager'
|
||
|
],
|
||
|
function (window, PluginManager) {
|
||
|
var hasProPlugin = function (editor) {
|
||
|
// draw back if power version is requested and registered
|
||
|
if (/(^|[ ,])tinymcespellchecker([, ]|$)/.test(editor.settings.plugins) && PluginManager.get('tinymcespellchecker')) {
|
||
|
/*eslint no-console:0 */
|
||
|
if (typeof window.console !== "undefined" && window.console.log) {
|
||
|
window.console.log(
|
||
|
"Spell Checker Pro is incompatible with Spell Checker plugin! " +
|
||
|
"Remove 'spellchecker' from the 'plugins' option."
|
||
|
);
|
||
|
}
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
hasProPlugin: hasProPlugin
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Settings.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
[
|
||
|
],
|
||
|
function () {
|
||
|
var getLanguages = function (editor) {
|
||
|
var defaultLanguages = 'English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr_FR,German=de,Italian=it,Polish=pl,Portuguese=pt_BR,Spanish=es,Swedish=sv';
|
||
|
return editor.getParam('spellchecker_languages', defaultLanguages);
|
||
|
};
|
||
|
|
||
|
var getLanguage = function (editor) {
|
||
|
var defaultLanguage = editor.getParam('language', 'en');
|
||
|
return editor.getParam('spellchecker_language', defaultLanguage);
|
||
|
};
|
||
|
|
||
|
var getRpcUrl = function (editor) {
|
||
|
return editor.getParam('spellchecker_rpc_url');
|
||
|
};
|
||
|
|
||
|
var getSpellcheckerCallback = function (editor) {
|
||
|
return editor.getParam('spellchecker_callback');
|
||
|
};
|
||
|
|
||
|
var getSpellcheckerWordcharPattern = function (editor) {
|
||
|
var defaultPattern = 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");
|
||
|
return editor.getParam('spellchecker_wordchar_pattern', defaultPattern);
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
getLanguages: getLanguages,
|
||
|
getLanguage: getLanguage,
|
||
|
getRpcUrl: getRpcUrl,
|
||
|
getSpellcheckerCallback: getSpellcheckerCallback,
|
||
|
getSpellcheckerWordcharPattern: getSpellcheckerWordcharPattern
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.util.Tools',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.util.Tools');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.util.URI',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.util.URI');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.util.XHR',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.util.XHR');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Events.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.api.Events',
|
||
|
[
|
||
|
],
|
||
|
function () {
|
||
|
var fireSpellcheckStart = function (editor) {
|
||
|
return editor.fire('SpellcheckStart');
|
||
|
};
|
||
|
|
||
|
var fireSpellcheckEnd = function (editor) {
|
||
|
return editor.fire('SpellcheckEnd');
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
fireSpellcheckStart: fireSpellcheckStart,
|
||
|
fireSpellcheckEnd: fireSpellcheckEnd
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* DomTextMatcher.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.core.DomTextMatcher',
|
||
|
[
|
||
|
],
|
||
|
function () {
|
||
|
function isContentEditableFalse(node) {
|
||
|
return node && node.nodeType === 1 && node.contentEditable === "false";
|
||
|
}
|
||
|
|
||
|
// Based on work developed by: James Padolsey http://james.padolsey.com
|
||
|
// released under UNLICENSE that is compatible with LGPL
|
||
|
// TODO: Handle contentEditable edgecase:
|
||
|
// <p>text<span contentEditable="false">text<span contentEditable="true">text</span>text</span>text</p>
|
||
|
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 '';
|
||
|
}
|
||
|
|
||
|
if (isContentEditableFalse(node)) {
|
||
|
return '\n';
|
||
|
}
|
||
|
|
||
|
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) { //eslint-disable-line no-constant-condition
|
||
|
if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
|
||
|
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) {
|
||
|
if (!isContentEditableFalse(curNode)) {
|
||
|
// Move down
|
||
|
curNode = curNode.firstChild;
|
||
|
continue;
|
||
|
}
|
||
|
} else if (curNode.nextSibling) {
|
||
|
// Move forward:
|
||
|
curNode = curNode.nextSibling;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Move forward or up:
|
||
|
while (true) { //eslint-disable-line no-constant-condition
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// 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 hasClass(elm) {
|
||
|
return elm.className.indexOf('mce-spellchecker-word') !== -1;
|
||
|
}
|
||
|
|
||
|
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 && hasClass(element)) {
|
||
|
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
|
||
|
};
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* Actions.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.core.Actions',
|
||
|
[
|
||
|
'tinymce.core.util.Tools',
|
||
|
'tinymce.core.util.URI',
|
||
|
'tinymce.core.util.XHR',
|
||
|
'tinymce.plugins.spellchecker.api.Events',
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
'tinymce.plugins.spellchecker.core.DomTextMatcher'
|
||
|
],
|
||
|
function (Tools, URI, XHR, Events, Settings, DomTextMatcher) {
|
||
|
var getTextMatcher = function (editor, textMatcherState) {
|
||
|
if (!textMatcherState.get()) {
|
||
|
var textMatcher = new DomTextMatcher(editor.getBody(), editor);
|
||
|
textMatcherState.set(textMatcher);
|
||
|
}
|
||
|
|
||
|
return textMatcherState.get();
|
||
|
};
|
||
|
|
||
|
var isEmpty = function (obj) {
|
||
|
for (var name in obj) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
var defaultSpellcheckCallback = function (editor, pluginUrl, currentLanguageState) {
|
||
|
return function (method, text, doneCallback, errorCallback) {
|
||
|
var data = { method: method, lang: currentLanguageState.get() }, postData = '';
|
||
|
|
||
|
data[method === "addToDictionary" ? "word" : "text"] = text;
|
||
|
|
||
|
Tools.each(data, function (value, key) {
|
||
|
if (postData) {
|
||
|
postData += '&';
|
||
|
}
|
||
|
|
||
|
postData += key + '=' + encodeURIComponent(value);
|
||
|
});
|
||
|
|
||
|
XHR.send({
|
||
|
url: new URI(pluginUrl).toAbsolute(Settings.getRpcUrl(editor)),
|
||
|
type: "post",
|
||
|
content_type: 'application/x-www-form-urlencoded',
|
||
|
data: postData,
|
||
|
success: function (result) {
|
||
|
result = JSON.parse(result);
|
||
|
|
||
|
if (!result) {
|
||
|
var message = editor.translate("Server response wasn't proper JSON.");
|
||
|
errorCallback(message);
|
||
|
} else if (result.error) {
|
||
|
errorCallback(result.error);
|
||
|
} else {
|
||
|
doneCallback(result);
|
||
|
}
|
||
|
},
|
||
|
error: function () {
|
||
|
var message = editor.translate("The spelling service was not found: (") +
|
||
|
Settings.getRpcUrl(editor) +
|
||
|
editor.translate(")");
|
||
|
errorCallback(message);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var sendRpcCall = function (editor, pluginUrl, currentLanguageState, name, data, successCallback, errorCallback) {
|
||
|
var userSpellcheckCallback = Settings.getSpellcheckerCallback(editor);
|
||
|
var spellCheckCallback = userSpellcheckCallback ? userSpellcheckCallback : defaultSpellcheckCallback(editor, pluginUrl, currentLanguageState);
|
||
|
spellCheckCallback.call(editor.plugins.spellchecker, name, data, successCallback, errorCallback);
|
||
|
};
|
||
|
|
||
|
var spellcheck = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
|
||
|
if (finish(editor, startedState, textMatcherState)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var errorCallback = function (message) {
|
||
|
editor.notificationManager.open({ text: message, type: 'error' });
|
||
|
editor.setProgressState(false);
|
||
|
finish(editor, startedState, textMatcherState);
|
||
|
};
|
||
|
|
||
|
var successCallback = function (data) {
|
||
|
markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
|
||
|
};
|
||
|
|
||
|
editor.setProgressState(true);
|
||
|
sendRpcCall(editor, pluginUrl, currentLanguageState, "spellcheck", getTextMatcher(editor, textMatcherState).text, successCallback, errorCallback);
|
||
|
editor.focus();
|
||
|
};
|
||
|
|
||
|
var checkIfFinished = function (editor, startedState, textMatcherState) {
|
||
|
if (!editor.dom.select('span.mce-spellchecker-word').length) {
|
||
|
finish(editor, startedState, textMatcherState);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var addToDictionary = function (editor, pluginUrl, startedState, textMatcherState, word, spans) {
|
||
|
editor.setProgressState(true);
|
||
|
|
||
|
sendRpcCall(editor, pluginUrl, 'addToDictionary', word, function () {
|
||
|
editor.setProgressState(false);
|
||
|
editor.dom.remove(spans, true);
|
||
|
checkIfFinished(editor, startedState, textMatcherState);
|
||
|
}, function (message) {
|
||
|
editor.notificationManager.open({ text: message, type: 'error' });
|
||
|
editor.setProgressState(false);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var ignoreWord = function (editor, startedState, textMatcherState, 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(editor, startedState, textMatcherState);
|
||
|
};
|
||
|
|
||
|
var finish = function (editor, startedState, textMatcherState) {
|
||
|
getTextMatcher(editor, textMatcherState).reset();
|
||
|
textMatcherState.set(null);
|
||
|
|
||
|
if (startedState.get()) {
|
||
|
startedState.set(false);
|
||
|
Events.fireSpellcheckEnd(editor);
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var getElmIndex = function (elm) {
|
||
|
var value = elm.getAttribute('data-mce-index');
|
||
|
|
||
|
if (typeof value === "number") {
|
||
|
return "" + value;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
};
|
||
|
|
||
|
var findSpansByIndex = function (editor, 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;
|
||
|
};
|
||
|
|
||
|
var markErrors = function (editor, startedState, textMatcherState, lastSuggestionsState, data) {
|
||
|
var suggestions, hasDictionarySupport;
|
||
|
|
||
|
if (data.words) {
|
||
|
hasDictionarySupport = !!data.dictionary;
|
||
|
suggestions = data.words;
|
||
|
} else {
|
||
|
// Fallback to old format
|
||
|
suggestions = data;
|
||
|
}
|
||
|
|
||
|
editor.setProgressState(false);
|
||
|
|
||
|
if (isEmpty(suggestions)) {
|
||
|
var message = editor.translate('No misspellings found.');
|
||
|
editor.notificationManager.open({ text: message, type: 'info' });
|
||
|
startedState.set(false);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
lastSuggestionsState.set({
|
||
|
suggestions: suggestions,
|
||
|
hasDictionarySupport: hasDictionarySupport
|
||
|
});
|
||
|
|
||
|
getTextMatcher(editor, textMatcherState).find(Settings.getSpellcheckerWordcharPattern(editor)).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
|
||
|
});
|
||
|
});
|
||
|
|
||
|
startedState.set(true);
|
||
|
Events.fireSpellcheckStart(editor);
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
spellcheck: spellcheck,
|
||
|
checkIfFinished: checkIfFinished,
|
||
|
addToDictionary: addToDictionary,
|
||
|
ignoreWord: ignoreWord,
|
||
|
findSpansByIndex: findSpansByIndex,
|
||
|
getElmIndex: getElmIndex,
|
||
|
markErrors: markErrors
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* Api.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.api.Api',
|
||
|
[
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
'tinymce.plugins.spellchecker.core.Actions'
|
||
|
],
|
||
|
function (Settings, Actions) {
|
||
|
var get = function (editor, startedState, lastSuggestionsState, textMatcherState, url) {
|
||
|
var getLanguage = function () {
|
||
|
return Settings.getLanguage(editor);
|
||
|
};
|
||
|
|
||
|
var getWordCharPattern = function () {
|
||
|
return Settings.getSpellcheckerWordcharPattern(editor);
|
||
|
};
|
||
|
|
||
|
var markErrors = function (data) {
|
||
|
Actions.markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
|
||
|
};
|
||
|
|
||
|
var getTextMatcher = function () {
|
||
|
return textMatcherState.get();
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
getTextMatcher: getTextMatcher,
|
||
|
getWordCharPattern: getWordCharPattern,
|
||
|
markErrors: markErrors,
|
||
|
getLanguage: getLanguage
|
||
|
};
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
get: get
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* Commands.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.api.Commands',
|
||
|
[
|
||
|
'tinymce.plugins.spellchecker.core.Actions'
|
||
|
],
|
||
|
function (Actions) {
|
||
|
var register = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
|
||
|
editor.addCommand('mceSpellCheck', function () {
|
||
|
Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
register: register
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
/**
|
||
|
* Buttons.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.ui.Buttons',
|
||
|
[
|
||
|
'tinymce.core.util.Tools',
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
'tinymce.plugins.spellchecker.core.Actions'
|
||
|
],
|
||
|
function (Tools, Settings, Actions) {
|
||
|
var buildMenuItems = function (listName, languageValues) {
|
||
|
var items = [];
|
||
|
|
||
|
Tools.each(languageValues, function (languageValue) {
|
||
|
items.push({
|
||
|
selectable: true,
|
||
|
text: languageValue.name,
|
||
|
data: languageValue.value
|
||
|
});
|
||
|
});
|
||
|
|
||
|
return items;
|
||
|
};
|
||
|
|
||
|
var updateSelection = function (editor) {
|
||
|
return function (e) {
|
||
|
var selectedLanguage = Settings.getLanguage(editor);
|
||
|
|
||
|
e.control.items().each(function (ctrl) {
|
||
|
ctrl.active(ctrl.settings.data === selectedLanguage);
|
||
|
});
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var getItems = function (editor) {
|
||
|
return Tools.map(Settings.getLanguages(editor).split(','), function (langPair) {
|
||
|
langPair = langPair.split('=');
|
||
|
|
||
|
return {
|
||
|
name: langPair[0],
|
||
|
value: langPair[1]
|
||
|
};
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var register = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState) {
|
||
|
var languageMenuItems = buildMenuItems('Language', getItems(editor));
|
||
|
var startSpellchecking = function () {
|
||
|
Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
|
||
|
};
|
||
|
|
||
|
var buttonArgs = {
|
||
|
tooltip: 'Spellcheck',
|
||
|
onclick: startSpellchecking,
|
||
|
onPostRender: function (e) {
|
||
|
var ctrl = e.control;
|
||
|
|
||
|
editor.on('SpellcheckStart SpellcheckEnd', function () {
|
||
|
ctrl.active(startedState.get());
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (languageMenuItems.length > 1) {
|
||
|
buttonArgs.type = 'splitbutton';
|
||
|
buttonArgs.menu = languageMenuItems;
|
||
|
buttonArgs.onshow = updateSelection(editor);
|
||
|
buttonArgs.onselect = function (e) {
|
||
|
currentLanguageState.set(e.control.settings.data);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
editor.addButton('spellchecker', buttonArgs);
|
||
|
|
||
|
editor.addMenuItem('spellchecker', {
|
||
|
text: 'Spellcheck',
|
||
|
context: 'tools',
|
||
|
onclick: startSpellchecking,
|
||
|
selectable: true,
|
||
|
onPostRender: function () {
|
||
|
var self = this;
|
||
|
|
||
|
self.active(startedState.get());
|
||
|
|
||
|
editor.on('SpellcheckStart SpellcheckEnd', function () {
|
||
|
self.active(startedState.get());
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
register: register
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
defineGlobal("global!document", document);
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.dom.DOMUtils',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.dom.DOMUtils');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* ResolveGlobal.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.core.ui.Factory',
|
||
|
[
|
||
|
'global!tinymce.util.Tools.resolve'
|
||
|
],
|
||
|
function (resolve) {
|
||
|
return resolve('tinymce.ui.Factory');
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* SuggestionsMenu.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.ui.SuggestionsMenu',
|
||
|
[
|
||
|
'global!document',
|
||
|
'tinymce.core.dom.DOMUtils',
|
||
|
'tinymce.core.ui.Factory',
|
||
|
'tinymce.core.util.Tools',
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
'tinymce.plugins.spellchecker.core.Actions'
|
||
|
],
|
||
|
function (document, DOMUtils, Factory, Tools, Settings, Actions) {
|
||
|
var suggestionsMenu;
|
||
|
|
||
|
var showSuggestions = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, word, spans) {
|
||
|
var items = [], suggestions = lastSuggestionsState.get().suggestions[word];
|
||
|
|
||
|
Tools.each(suggestions, function (suggestion) {
|
||
|
items.push({
|
||
|
text: suggestion,
|
||
|
onclick: function () {
|
||
|
editor.insertContent(editor.dom.encode(suggestion));
|
||
|
editor.dom.remove(spans);
|
||
|
Actions.checkIfFinished(editor, startedState, textMatcherState);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
items.push({ text: '-' });
|
||
|
|
||
|
var hasDictionarySupport = lastSuggestionsState.get().hasDictionarySupport;
|
||
|
if (hasDictionarySupport) {
|
||
|
items.push({
|
||
|
text: 'Add to Dictionary', onclick: function () {
|
||
|
Actions.addToDictionary(editor, pluginUrl, startedState, textMatcherState, word, spans);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
items.push.apply(items, [
|
||
|
{
|
||
|
text: 'Ignore', onclick: function () {
|
||
|
Actions.ignoreWord(editor, startedState, textMatcherState, word, spans);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
{
|
||
|
text: 'Ignore all', onclick: function () {
|
||
|
Actions.ignoreWord(editor, startedState, textMatcherState, word, spans, true);
|
||
|
}
|
||
|
}
|
||
|
]);
|
||
|
|
||
|
// Render menu
|
||
|
suggestionsMenu = Factory.create('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);
|
||
|
};
|
||
|
|
||
|
var setup = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState) {
|
||
|
editor.on('click', function (e) {
|
||
|
var target = e.target;
|
||
|
|
||
|
if (target.className === "mce-spellchecker-word") {
|
||
|
e.preventDefault();
|
||
|
|
||
|
var spans = Actions.findSpansByIndex(editor, Actions.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(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, target.getAttribute('data-mce-word'), spans);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
setup: setup
|
||
|
};
|
||
|
}
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Plugin.js
|
||
|
*
|
||
|
* Released under LGPL License.
|
||
|
* Copyright (c) 1999-2017 Ephox Corp. All rights reserved
|
||
|
*
|
||
|
* License: http://www.tinymce.com/license
|
||
|
* Contributing: http://www.tinymce.com/contributing
|
||
|
*/
|
||
|
|
||
|
define(
|
||
|
'tinymce.plugins.spellchecker.Plugin',
|
||
|
[
|
||
|
'ephox.katamari.api.Cell',
|
||
|
'tinymce.core.PluginManager',
|
||
|
'tinymce.plugins.spellchecker.alien.DetectProPlugin',
|
||
|
'tinymce.plugins.spellchecker.api.Api',
|
||
|
'tinymce.plugins.spellchecker.api.Commands',
|
||
|
'tinymce.plugins.spellchecker.api.Settings',
|
||
|
'tinymce.plugins.spellchecker.ui.Buttons',
|
||
|
'tinymce.plugins.spellchecker.ui.SuggestionsMenu'
|
||
|
],
|
||
|
function (Cell, PluginManager, DetectProPlugin, Api, Commands, Settings, Buttons, SuggestionsMenu) {
|
||
|
PluginManager.add('spellchecker', function (editor, pluginUrl) {
|
||
|
if (DetectProPlugin.hasProPlugin(editor) === false) {
|
||
|
var startedState = Cell(false);
|
||
|
var currentLanguageState = Cell(Settings.getLanguage(editor));
|
||
|
var textMatcherState = Cell(null);
|
||
|
var lastSuggestionsState = Cell({});
|
||
|
|
||
|
Buttons.register(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState);
|
||
|
SuggestionsMenu.setup(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState);
|
||
|
Commands.register(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
|
||
|
|
||
|
return Api.get(editor, startedState, lastSuggestionsState, textMatcherState, pluginUrl);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return function () { };
|
||
|
}
|
||
|
);
|
||
|
dem('tinymce.plugins.spellchecker.Plugin')();
|
||
|
})();
|