Add WebCms
This commit is contained in:
@@ -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:
|
||||
// <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 '';
|
||||
}
|
||||
|
||||
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);
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user