You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
26 KiB
JavaScript

/**
* Copyright (c) Tiny Technologies, Inc. All rights reserved.
* Licensed under the LGPL or a commercial license.
* For LGPL see License.txt in the project root for license information.
* For commercial licenses see https://www.tiny.cloud/
*
* Version: 5.9.2 (2021-09-08)
*/
(function () {
'use strict';
var Cell = function (initial) {
var value = initial;
var get = function () {
return value;
};
var set = function (v) {
value = v;
};
return {
get: get,
set: set
};
};
var global$3 = tinymce.util.Tools.resolve('tinymce.PluginManager');
var hasProPlugin = function (editor) {
if (editor.hasPlugin('tinymcespellchecker', true)) {
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;
}
};
var hasOwnProperty = Object.hasOwnProperty;
var isEmpty = function (r) {
for (var x in r) {
if (hasOwnProperty.call(r, x)) {
return false;
}
}
return true;
};
var global$2 = tinymce.util.Tools.resolve('tinymce.util.Tools');
var global$1 = tinymce.util.Tools.resolve('tinymce.util.URI');
var global = tinymce.util.Tools.resolve('tinymce.util.XHR');
var fireSpellcheckStart = function (editor) {
return editor.fire('SpellcheckStart');
};
var fireSpellcheckEnd = function (editor) {
return editor.fire('SpellcheckEnd');
};
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!"#$%&()*+,-./:;<=>?@[\\]^_{|}`' + '\xA7\xA9\xAB\xAE\xB1\xB6\xB7\xB8\xBB' + '\xBC\xBD\xBE\xBF\xD7\xF7\xA4\u201D\u201C\u201E\xA0\u2002\u2003\u2009' + ']+', 'g');
return editor.getParam('spellchecker_wordchar_pattern', defaultPattern);
};
var isContentEditableFalse = function (node) {
return node && node.nodeType === 1 && node.contentEditable === 'false';
};
var DomTextMatcher = function (node, editor) {
var m, matches = [];
var dom = editor.dom;
var blockElementsMap = editor.schema.getBlockElements();
var hiddenTextElementsMap = editor.schema.getWhiteSpaceElements();
var shortEndedElementsMap = editor.schema.getShortEndedElements();
var createMatch = function (m, data) {
if (!m[0]) {
throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
}
return {
start: m.index,
end: m.index + m[0].length,
text: m[0],
data: data
};
};
var getText = function (node) {
if (node.nodeType === 3) {
return node.data;
}
if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
return '';
}
if (isContentEditableFalse(node)) {
return '\n';
}
var txt = '';
if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
txt += '\n';
}
if (node = node.firstChild) {
do {
txt += getText(node);
} while (node = node.nextSibling);
}
return txt;
};
var stepThroughMatches = function (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] || isContentEditableFalse(curNode)) {
atIndex++;
}
if (curNode.nodeType === 3) {
if (!endNode && curNode.length + atIndex >= matchLocation.end) {
endNode = curNode;
endNodeIndex = matchLocation.end - atIndex;
} else if (startNode) {
innerNodes.push(curNode);
}
if (!startNode && curNode.length + atIndex > matchLocation.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
});
atIndex -= endNode.length - endNodeIndex;
startNode = null;
endNode = null;
innerNodes = [];
matchLocation = matches.shift();
matchIndex++;
if (!matchLocation) {
break;
}
} else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
if (!isContentEditableFalse(curNode)) {
curNode = curNode.firstChild;
continue;
}
} else if (curNode.nextSibling) {
curNode = curNode.nextSibling;
continue;
}
while (true) {
if (curNode.nextSibling) {
curNode = curNode.nextSibling;
break;
} else if (curNode.parentNode !== node) {
curNode = curNode.parentNode;
} else {
break out;
}
}
}
};
var genReplacer = function (callback) {
var makeReplacementNode = function (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;
var after;
var parentNode;
var startNode = range.startNode;
var endNode = range.endNode;
var matchIndex = range.matchIndex;
var doc = dom.doc;
if (startNode === endNode) {
var node_1 = startNode;
parentNode = node_1.parentNode;
if (range.startNodeIndex > 0) {
before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
parentNode.insertBefore(before, node_1);
}
var el = makeReplacementNode(range.match, matchIndex);
parentNode.insertBefore(el, node_1);
if (range.endNodeIndex < node_1.length) {
after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
parentNode.insertBefore(after, node_1);
}
node_1.parentNode.removeChild(node_1);
return el;
}
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);
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);
}
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;
};
};
var unwrapElement = function (element) {
var parentNode = element.parentNode;
while (element.childNodes.length > 0) {
parentNode.insertBefore(element.childNodes[0], element);
}
parentNode.removeChild(element);
};
var hasClass = function (elm) {
return elm.className.indexOf('mce-spellchecker-word') !== -1;
};
var getWrappersByIndex = function (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;
};
var indexOf = function (match) {
var i = matches.length;
while (i--) {
if (matches[i] === match) {
return i;
}
}
return -1;
};
function filter(callback) {
var filteredMatches = [];
each(function (match, i) {
if (callback(match, i)) {
filteredMatches.push(match);
}
});
matches = filteredMatches;
return this;
}
function each(callback) {
for (var i = 0, l = matches.length; i < l; i++) {
if (callback(matches[i], i) === false) {
break;
}
}
return this;
}
function wrap(callback) {
if (matches.length) {
stepThroughMatches(node, matches, genReplacer(callback));
}
return this;
}
function find(regex, data) {
if (text && regex.global) {
while (m = regex.exec(text)) {
matches.push(createMatch(m, data));
}
}
return this;
}
function unwrap(match) {
var i;
var elements = getWrappersByIndex(match ? indexOf(match) : null);
i = elements.length;
while (i--) {
unwrapElement(elements[i]);
}
return this;
}
var matchFromElement = function (element) {
return matches[element.getAttribute('data-mce-index')];
};
var elementFromMatch = function (match) {
return getWrappersByIndex(indexOf(match))[0];
};
function add(start, length, data) {
matches.push({
start: start,
end: start + length,
text: text.substr(start, length),
data: data
});
return this;
}
var rangeFromMatch = function (match) {
var wrappers = getWrappersByIndex(indexOf(match));
var rng = editor.dom.createRng();
rng.setStartBefore(wrappers[0]);
rng.setEndAfter(wrappers[wrappers.length - 1]);
return rng;
};
var replace = function (match, text) {
var rng = rangeFromMatch(match);
rng.deleteContents();
if (text.length > 0) {
rng.insertNode(editor.dom.doc.createTextNode(text));
}
return rng;
};
function reset() {
matches.splice(0, matches.length);
unwrap();
return this;
}
var 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
};
};
var getTextMatcher = function (editor, textMatcherState) {
if (!textMatcherState.get()) {
var textMatcher = DomTextMatcher(editor.getBody(), editor);
textMatcherState.set(textMatcher);
}
return textMatcherState.get();
};
var defaultSpellcheckCallback = function (editor, pluginUrl, currentLanguageState) {
return function (method, text, doneCallback, errorCallback) {
var data = {
method: method,
lang: currentLanguageState.get()
};
var postData = '';
data[method === 'addToDictionary' ? 'word' : 'text'] = text;
global$2.each(data, function (value, key) {
if (postData) {
postData += '&';
}
postData += key + '=' + encodeURIComponent(value);
});
global.send({
url: new global$1(pluginUrl).toAbsolute(getRpcUrl(editor)),
type: 'post',
content_type: 'application/x-www-form-urlencoded',
data: postData,
success: function (result) {
var parseResult = JSON.parse(result);
if (!parseResult) {
var message = editor.translate('Server response wasn\'t proper JSON.');
errorCallback(message);
} else if (parseResult.error) {
errorCallback(parseResult.error);
} else {
doneCallback(parseResult);
}
},
error: function () {
var message = editor.translate('The spelling service was not found: (') + getRpcUrl(editor) + editor.translate(')');
errorCallback(message);
}
});
};
};
var sendRpcCall = function (editor, pluginUrl, currentLanguageState, name, data, successCallback, errorCallback) {
var userSpellcheckCallback = 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, currentLanguageState, word, spans) {
editor.setProgressState(true);
sendRpcCall(editor, pluginUrl, currentLanguageState, '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) {
global$2.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) {
var bookmark = editor.selection.getBookmark();
getTextMatcher(editor, textMatcherState).reset();
editor.selection.moveToBookmark(bookmark);
textMatcherState.set(null);
if (startedState.get()) {
startedState.set(false);
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 spans = [];
var nodes = global$2.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 hasDictionarySupport = !!data.dictionary;
var suggestions = data.words;
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
});
var bookmark = editor.selection.getBookmark();
getTextMatcher(editor, textMatcherState).find(getSpellcheckerWordcharPattern(editor)).filter(function (match) {
return !!suggestions[match.text];
}).wrap(function (match) {
return editor.dom.create('span', {
'class': 'mce-spellchecker-word',
'aria-invalid': 'spelling',
'data-mce-bogus': 1,
'data-mce-word': match.text
});
});
editor.selection.moveToBookmark(bookmark);
startedState.set(true);
fireSpellcheckStart(editor);
};
var get = function (editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState) {
var getWordCharPattern = function () {
return getSpellcheckerWordcharPattern(editor);
};
var markErrors$1 = function (data) {
markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
};
return {
getTextMatcher: textMatcherState.get,
getWordCharPattern: getWordCharPattern,
markErrors: markErrors$1,
getLanguage: currentLanguageState.get
};
};
var register$1 = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
editor.addCommand('mceSpellCheck', function () {
spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
});
};
var __assign = function () {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s)
if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var spellcheckerEvents = 'SpellcheckStart SpellcheckEnd';
var buildMenuItems = function (listName, languageValues) {
var items = [];
global$2.each(languageValues, function (languageValue) {
items.push({
selectable: true,
text: languageValue.name,
data: languageValue.value
});
});
return items;
};
var getItems = function (editor) {
return global$2.map(getLanguages(editor).split(','), function (langPair) {
var langPairs = langPair.split('=');
return {
name: langPairs[0],
value: langPairs[1]
};
});
};
var register = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState) {
var languageMenuItems = buildMenuItems('Language', getItems(editor));
var startSpellchecking = function () {
spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
};
var buttonArgs = {
tooltip: 'Spellcheck',
onAction: startSpellchecking,
icon: 'spell-check',
onSetup: function (buttonApi) {
var setButtonState = function () {
buttonApi.setActive(startedState.get());
};
editor.on(spellcheckerEvents, setButtonState);
return function () {
editor.off(spellcheckerEvents, setButtonState);
};
}
};
var splitButtonArgs = __assign(__assign({}, buttonArgs), {
type: 'splitbutton',
select: function (value) {
return value === currentLanguageState.get();
},
fetch: function (callback) {
var items = global$2.map(languageMenuItems, function (languageItem) {
return {
type: 'choiceitem',
value: languageItem.data,
text: languageItem.text
};
});
callback(items);
},
onItemAction: function (splitButtonApi, value) {
currentLanguageState.set(value);
}
});
if (languageMenuItems.length > 1) {
editor.ui.registry.addSplitButton('spellchecker', splitButtonArgs);
} else {
editor.ui.registry.addToggleButton('spellchecker', buttonArgs);
}
editor.ui.registry.addToggleMenuItem('spellchecker', {
text: 'Spellcheck',
icon: 'spell-check',
onSetup: function (menuApi) {
menuApi.setActive(startedState.get());
var setMenuItemCheck = function () {
menuApi.setActive(startedState.get());
};
editor.on(spellcheckerEvents, setMenuItemCheck);
return function () {
editor.off(spellcheckerEvents, setMenuItemCheck);
};
},
onAction: startSpellchecking
});
};
var ignoreAll = true;
var getSuggestions = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, word, spans) {
var items = [];
var suggestions = lastSuggestionsState.get().suggestions[word];
global$2.each(suggestions, function (suggestion) {
items.push({
text: suggestion,
onAction: function () {
editor.insertContent(editor.dom.encode(suggestion));
editor.dom.remove(spans);
checkIfFinished(editor, startedState, textMatcherState);
}
});
});
var hasDictionarySupport = lastSuggestionsState.get().hasDictionarySupport;
if (hasDictionarySupport) {
items.push({ type: 'separator' });
items.push({
text: 'Add to dictionary',
onAction: function () {
addToDictionary(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans);
}
});
}
items.push.apply(items, [
{ type: 'separator' },
{
text: 'Ignore',
onAction: function () {
ignoreWord(editor, startedState, textMatcherState, word, spans);
}
},
{
text: 'Ignore all',
onAction: function () {
ignoreWord(editor, startedState, textMatcherState, word, spans, ignoreAll);
}
}
]);
return items;
};
var setup = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState) {
var update = function (element) {
var target = element;
if (target.className === 'mce-spellchecker-word') {
var spans = findSpansByIndex(editor, 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);
return getSuggestions(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, target.getAttribute('data-mce-word'), spans);
}
} else {
return [];
}
};
editor.ui.registry.addContextMenu('spellchecker', { update: update });
};
function Plugin () {
global$3.add('spellchecker', function (editor, pluginUrl) {
if (hasProPlugin(editor) === false) {
console.warn('The spellchecker plugin has been deprecated and marked for removal in TinyMCE 6.0');
var startedState = Cell(false);
var currentLanguageState = Cell(getLanguage(editor));
var textMatcherState = Cell(null);
var lastSuggestionsState = Cell(null);
register(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState);
setup(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState);
register$1(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
return get(editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState);
}
});
}
Plugin();
}());