plugin.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. /**
  2. * Copyright (c) Tiny Technologies, Inc. All rights reserved.
  3. * Licensed under the LGPL or a commercial license.
  4. * For LGPL see License.txt in the project root for license information.
  5. * For commercial licenses see https://www.tiny.cloud/
  6. *
  7. * Version: 5.0.1 (2019-02-21)
  8. */
  9. (function () {
  10. var spellchecker = (function (domGlobals) {
  11. 'use strict';
  12. var Cell = function (initial) {
  13. var value = initial;
  14. var get = function () {
  15. return value;
  16. };
  17. var set = function (v) {
  18. value = v;
  19. };
  20. var clone = function () {
  21. return Cell(get());
  22. };
  23. return {
  24. get: get,
  25. set: set,
  26. clone: clone
  27. };
  28. };
  29. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  30. var hasProPlugin = function (editor) {
  31. if (/(^|[ ,])tinymcespellchecker([, ]|$)/.test(editor.settings.plugins) && global.get('tinymcespellchecker')) {
  32. if (typeof domGlobals.window.console !== 'undefined' && domGlobals.window.console.log) {
  33. domGlobals.window.console.log('Spell Checker Pro is incompatible with Spell Checker plugin! ' + 'Remove \'spellchecker\' from the \'plugins\' option.');
  34. }
  35. return true;
  36. } else {
  37. return false;
  38. }
  39. };
  40. var DetectProPlugin = { hasProPlugin: hasProPlugin };
  41. var getLanguages = function (editor) {
  42. 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';
  43. return editor.getParam('spellchecker_languages', defaultLanguages);
  44. };
  45. var getLanguage = function (editor) {
  46. var defaultLanguage = editor.getParam('language', 'en');
  47. return editor.getParam('spellchecker_language', defaultLanguage);
  48. };
  49. var getRpcUrl = function (editor) {
  50. return editor.getParam('spellchecker_rpc_url');
  51. };
  52. var getSpellcheckerCallback = function (editor) {
  53. return editor.getParam('spellchecker_callback');
  54. };
  55. var getSpellcheckerWordcharPattern = function (editor) {
  56. 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');
  57. return editor.getParam('spellchecker_wordchar_pattern', defaultPattern);
  58. };
  59. var Settings = {
  60. getLanguages: getLanguages,
  61. getLanguage: getLanguage,
  62. getRpcUrl: getRpcUrl,
  63. getSpellcheckerCallback: getSpellcheckerCallback,
  64. getSpellcheckerWordcharPattern: getSpellcheckerWordcharPattern
  65. };
  66. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  67. var global$2 = tinymce.util.Tools.resolve('tinymce.util.URI');
  68. var global$3 = tinymce.util.Tools.resolve('tinymce.util.XHR');
  69. var fireSpellcheckStart = function (editor) {
  70. return editor.fire('SpellcheckStart');
  71. };
  72. var fireSpellcheckEnd = function (editor) {
  73. return editor.fire('SpellcheckEnd');
  74. };
  75. var Events = {
  76. fireSpellcheckStart: fireSpellcheckStart,
  77. fireSpellcheckEnd: fireSpellcheckEnd
  78. };
  79. function isContentEditableFalse(node) {
  80. return node && node.nodeType === 1 && node.contentEditable === 'false';
  81. }
  82. var DomTextMatcher = function (node, editor) {
  83. var m, matches = [], text;
  84. var dom = editor.dom;
  85. var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
  86. blockElementsMap = editor.schema.getBlockElements();
  87. hiddenTextElementsMap = editor.schema.getWhiteSpaceElements();
  88. shortEndedElementsMap = editor.schema.getShortEndedElements();
  89. function createMatch(m, data) {
  90. if (!m[0]) {
  91. throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
  92. }
  93. return {
  94. start: m.index,
  95. end: m.index + m[0].length,
  96. text: m[0],
  97. data: data
  98. };
  99. }
  100. function getText(node) {
  101. var txt;
  102. if (node.nodeType === 3) {
  103. return node.data;
  104. }
  105. if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
  106. return '';
  107. }
  108. if (isContentEditableFalse(node)) {
  109. return '\n';
  110. }
  111. txt = '';
  112. if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
  113. txt += '\n';
  114. }
  115. if (node = node.firstChild) {
  116. do {
  117. txt += getText(node);
  118. } while (node = node.nextSibling);
  119. }
  120. return txt;
  121. }
  122. function stepThroughMatches(node, matches, replaceFn) {
  123. var startNode, endNode, startNodeIndex, endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, matchLocation, matchIndex = 0;
  124. matches = matches.slice(0);
  125. matches.sort(function (a, b) {
  126. return a.start - b.start;
  127. });
  128. matchLocation = matches.shift();
  129. out:
  130. while (true) {
  131. if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
  132. atIndex++;
  133. }
  134. if (curNode.nodeType === 3) {
  135. if (!endNode && curNode.length + atIndex >= matchLocation.end) {
  136. endNode = curNode;
  137. endNodeIndex = matchLocation.end - atIndex;
  138. } else if (startNode) {
  139. innerNodes.push(curNode);
  140. }
  141. if (!startNode && curNode.length + atIndex > matchLocation.start) {
  142. startNode = curNode;
  143. startNodeIndex = matchLocation.start - atIndex;
  144. }
  145. atIndex += curNode.length;
  146. }
  147. if (startNode && endNode) {
  148. curNode = replaceFn({
  149. startNode: startNode,
  150. startNodeIndex: startNodeIndex,
  151. endNode: endNode,
  152. endNodeIndex: endNodeIndex,
  153. innerNodes: innerNodes,
  154. match: matchLocation.text,
  155. matchIndex: matchIndex
  156. });
  157. atIndex -= endNode.length - endNodeIndex;
  158. startNode = null;
  159. endNode = null;
  160. innerNodes = [];
  161. matchLocation = matches.shift();
  162. matchIndex++;
  163. if (!matchLocation) {
  164. break;
  165. }
  166. } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
  167. if (!isContentEditableFalse(curNode)) {
  168. curNode = curNode.firstChild;
  169. continue;
  170. }
  171. } else if (curNode.nextSibling) {
  172. curNode = curNode.nextSibling;
  173. continue;
  174. }
  175. while (true) {
  176. if (curNode.nextSibling) {
  177. curNode = curNode.nextSibling;
  178. break;
  179. } else if (curNode.parentNode !== node) {
  180. curNode = curNode.parentNode;
  181. } else {
  182. break out;
  183. }
  184. }
  185. }
  186. }
  187. function genReplacer(callback) {
  188. function makeReplacementNode(fill, matchIndex) {
  189. var match = matches[matchIndex];
  190. if (!match.stencil) {
  191. match.stencil = callback(match);
  192. }
  193. var clone = match.stencil.cloneNode(false);
  194. clone.setAttribute('data-mce-index', matchIndex);
  195. if (fill) {
  196. clone.appendChild(dom.doc.createTextNode(fill));
  197. }
  198. return clone;
  199. }
  200. return function (range) {
  201. var before;
  202. var after;
  203. var parentNode;
  204. var startNode = range.startNode;
  205. var endNode = range.endNode;
  206. var matchIndex = range.matchIndex;
  207. var doc = dom.doc;
  208. if (startNode === endNode) {
  209. var node_1 = startNode;
  210. parentNode = node_1.parentNode;
  211. if (range.startNodeIndex > 0) {
  212. before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
  213. parentNode.insertBefore(before, node_1);
  214. }
  215. var el = makeReplacementNode(range.match, matchIndex);
  216. parentNode.insertBefore(el, node_1);
  217. if (range.endNodeIndex < node_1.length) {
  218. after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
  219. parentNode.insertBefore(after, node_1);
  220. }
  221. node_1.parentNode.removeChild(node_1);
  222. return el;
  223. }
  224. before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
  225. after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
  226. var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
  227. for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
  228. var innerNode = range.innerNodes[i];
  229. var innerEl = makeReplacementNode(innerNode.data, matchIndex);
  230. innerNode.parentNode.replaceChild(innerEl, innerNode);
  231. }
  232. var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
  233. parentNode = startNode.parentNode;
  234. parentNode.insertBefore(before, startNode);
  235. parentNode.insertBefore(elA, startNode);
  236. parentNode.removeChild(startNode);
  237. parentNode = endNode.parentNode;
  238. parentNode.insertBefore(elB, endNode);
  239. parentNode.insertBefore(after, endNode);
  240. parentNode.removeChild(endNode);
  241. return elB;
  242. };
  243. }
  244. function unwrapElement(element) {
  245. var parentNode = element.parentNode;
  246. parentNode.insertBefore(element.firstChild, element);
  247. element.parentNode.removeChild(element);
  248. }
  249. function hasClass(elm) {
  250. return elm.className.indexOf('mce-spellchecker-word') !== -1;
  251. }
  252. function getWrappersByIndex(index) {
  253. var elements = node.getElementsByTagName('*'), wrappers = [];
  254. index = typeof index === 'number' ? '' + index : null;
  255. for (var i = 0; i < elements.length; i++) {
  256. var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
  257. if (dataIndex !== null && dataIndex.length && hasClass(element)) {
  258. if (dataIndex === index || index === null) {
  259. wrappers.push(element);
  260. }
  261. }
  262. }
  263. return wrappers;
  264. }
  265. function indexOf(match) {
  266. var i = matches.length;
  267. while (i--) {
  268. if (matches[i] === match) {
  269. return i;
  270. }
  271. }
  272. return -1;
  273. }
  274. function filter(callback) {
  275. var filteredMatches = [];
  276. each(function (match, i) {
  277. if (callback(match, i)) {
  278. filteredMatches.push(match);
  279. }
  280. });
  281. matches = filteredMatches;
  282. return this;
  283. }
  284. function each(callback) {
  285. for (var i = 0, l = matches.length; i < l; i++) {
  286. if (callback(matches[i], i) === false) {
  287. break;
  288. }
  289. }
  290. return this;
  291. }
  292. function wrap(callback) {
  293. if (matches.length) {
  294. stepThroughMatches(node, matches, genReplacer(callback));
  295. }
  296. return this;
  297. }
  298. function find(regex, data) {
  299. if (text && regex.global) {
  300. while (m = regex.exec(text)) {
  301. matches.push(createMatch(m, data));
  302. }
  303. }
  304. return this;
  305. }
  306. function unwrap(match) {
  307. var i;
  308. var elements = getWrappersByIndex(match ? indexOf(match) : null);
  309. i = elements.length;
  310. while (i--) {
  311. unwrapElement(elements[i]);
  312. }
  313. return this;
  314. }
  315. function matchFromElement(element) {
  316. return matches[element.getAttribute('data-mce-index')];
  317. }
  318. function elementFromMatch(match) {
  319. return getWrappersByIndex(indexOf(match))[0];
  320. }
  321. function add(start, length, data) {
  322. matches.push({
  323. start: start,
  324. end: start + length,
  325. text: text.substr(start, length),
  326. data: data
  327. });
  328. return this;
  329. }
  330. function rangeFromMatch(match) {
  331. var wrappers = getWrappersByIndex(indexOf(match));
  332. var rng = editor.dom.createRng();
  333. rng.setStartBefore(wrappers[0]);
  334. rng.setEndAfter(wrappers[wrappers.length - 1]);
  335. return rng;
  336. }
  337. function replace(match, text) {
  338. var rng = rangeFromMatch(match);
  339. rng.deleteContents();
  340. if (text.length > 0) {
  341. rng.insertNode(editor.dom.doc.createTextNode(text));
  342. }
  343. return rng;
  344. }
  345. function reset() {
  346. matches.splice(0, matches.length);
  347. unwrap();
  348. return this;
  349. }
  350. text = getText(node);
  351. return {
  352. text: text,
  353. matches: matches,
  354. each: each,
  355. filter: filter,
  356. reset: reset,
  357. matchFromElement: matchFromElement,
  358. elementFromMatch: elementFromMatch,
  359. find: find,
  360. add: add,
  361. wrap: wrap,
  362. unwrap: unwrap,
  363. replace: replace,
  364. rangeFromMatch: rangeFromMatch,
  365. indexOf: indexOf
  366. };
  367. };
  368. var getTextMatcher = function (editor, textMatcherState) {
  369. if (!textMatcherState.get()) {
  370. var textMatcher = DomTextMatcher(editor.getBody(), editor);
  371. textMatcherState.set(textMatcher);
  372. }
  373. return textMatcherState.get();
  374. };
  375. var isEmpty = function (obj) {
  376. for (var _ in obj) {
  377. return false;
  378. }
  379. return true;
  380. };
  381. var defaultSpellcheckCallback = function (editor, pluginUrl, currentLanguageState) {
  382. return function (method, text, doneCallback, errorCallback) {
  383. var data = {
  384. method: method,
  385. lang: currentLanguageState.get()
  386. };
  387. var postData = '';
  388. data[method === 'addToDictionary' ? 'word' : 'text'] = text;
  389. global$1.each(data, function (value, key) {
  390. if (postData) {
  391. postData += '&';
  392. }
  393. postData += key + '=' + encodeURIComponent(value);
  394. });
  395. global$3.send({
  396. url: new global$2(pluginUrl).toAbsolute(Settings.getRpcUrl(editor)),
  397. type: 'post',
  398. content_type: 'application/x-www-form-urlencoded',
  399. data: postData,
  400. success: function (result) {
  401. result = JSON.parse(result);
  402. if (!result) {
  403. var message = editor.translate('Server response wasn\'t proper JSON.');
  404. errorCallback(message);
  405. } else if (result.error) {
  406. errorCallback(result.error);
  407. } else {
  408. doneCallback(result);
  409. }
  410. },
  411. error: function () {
  412. var message = editor.translate('The spelling service was not found: (') + Settings.getRpcUrl(editor) + editor.translate(')');
  413. errorCallback(message);
  414. }
  415. });
  416. };
  417. };
  418. var sendRpcCall = function (editor, pluginUrl, currentLanguageState, name, data, successCallback, errorCallback) {
  419. var userSpellcheckCallback = Settings.getSpellcheckerCallback(editor);
  420. var spellCheckCallback = userSpellcheckCallback ? userSpellcheckCallback : defaultSpellcheckCallback(editor, pluginUrl, currentLanguageState);
  421. spellCheckCallback.call(editor.plugins.spellchecker, name, data, successCallback, errorCallback);
  422. };
  423. var spellcheck = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
  424. if (finish(editor, startedState, textMatcherState)) {
  425. return;
  426. }
  427. var errorCallback = function (message) {
  428. editor.notificationManager.open({
  429. text: message,
  430. type: 'error'
  431. });
  432. editor.setProgressState(false);
  433. finish(editor, startedState, textMatcherState);
  434. };
  435. var successCallback = function (data) {
  436. markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
  437. };
  438. editor.setProgressState(true);
  439. sendRpcCall(editor, pluginUrl, currentLanguageState, 'spellcheck', getTextMatcher(editor, textMatcherState).text, successCallback, errorCallback);
  440. editor.focus();
  441. };
  442. var checkIfFinished = function (editor, startedState, textMatcherState) {
  443. if (!editor.dom.select('span.mce-spellchecker-word').length) {
  444. finish(editor, startedState, textMatcherState);
  445. }
  446. };
  447. var addToDictionary = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans) {
  448. editor.setProgressState(true);
  449. sendRpcCall(editor, pluginUrl, currentLanguageState, 'addToDictionary', word, function () {
  450. editor.setProgressState(false);
  451. editor.dom.remove(spans, true);
  452. checkIfFinished(editor, startedState, textMatcherState);
  453. }, function (message) {
  454. editor.notificationManager.open({
  455. text: message,
  456. type: 'error'
  457. });
  458. editor.setProgressState(false);
  459. });
  460. };
  461. var ignoreWord = function (editor, startedState, textMatcherState, word, spans, all) {
  462. editor.selection.collapse();
  463. if (all) {
  464. global$1.each(editor.dom.select('span.mce-spellchecker-word'), function (span) {
  465. if (span.getAttribute('data-mce-word') === word) {
  466. editor.dom.remove(span, true);
  467. }
  468. });
  469. } else {
  470. editor.dom.remove(spans, true);
  471. }
  472. checkIfFinished(editor, startedState, textMatcherState);
  473. };
  474. var finish = function (editor, startedState, textMatcherState) {
  475. var bookmark = editor.selection.getBookmark();
  476. getTextMatcher(editor, textMatcherState).reset();
  477. editor.selection.moveToBookmark(bookmark);
  478. textMatcherState.set(null);
  479. if (startedState.get()) {
  480. startedState.set(false);
  481. Events.fireSpellcheckEnd(editor);
  482. return true;
  483. }
  484. };
  485. var getElmIndex = function (elm) {
  486. var value = elm.getAttribute('data-mce-index');
  487. if (typeof value === 'number') {
  488. return '' + value;
  489. }
  490. return value;
  491. };
  492. var findSpansByIndex = function (editor, index) {
  493. var nodes;
  494. var spans = [];
  495. nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
  496. if (nodes.length) {
  497. for (var i = 0; i < nodes.length; i++) {
  498. var nodeIndex = getElmIndex(nodes[i]);
  499. if (nodeIndex === null || !nodeIndex.length) {
  500. continue;
  501. }
  502. if (nodeIndex === index.toString()) {
  503. spans.push(nodes[i]);
  504. }
  505. }
  506. }
  507. return spans;
  508. };
  509. var markErrors = function (editor, startedState, textMatcherState, lastSuggestionsState, data) {
  510. var hasDictionarySupport = !!data.dictionary;
  511. var suggestions = data.words;
  512. editor.setProgressState(false);
  513. if (isEmpty(suggestions)) {
  514. var message = editor.translate('No misspellings found.');
  515. editor.notificationManager.open({
  516. text: message,
  517. type: 'info'
  518. });
  519. startedState.set(false);
  520. return;
  521. }
  522. lastSuggestionsState.set({
  523. suggestions: suggestions,
  524. hasDictionarySupport: hasDictionarySupport
  525. });
  526. var bookmark = editor.selection.getBookmark();
  527. getTextMatcher(editor, textMatcherState).find(Settings.getSpellcheckerWordcharPattern(editor)).filter(function (match) {
  528. return !!suggestions[match.text];
  529. }).wrap(function (match) {
  530. return editor.dom.create('span', {
  531. 'class': 'mce-spellchecker-word',
  532. 'aria-invalid': 'spelling',
  533. 'data-mce-bogus': 1,
  534. 'data-mce-word': match.text
  535. });
  536. });
  537. editor.selection.moveToBookmark(bookmark);
  538. startedState.set(true);
  539. Events.fireSpellcheckStart(editor);
  540. };
  541. var Actions = {
  542. spellcheck: spellcheck,
  543. checkIfFinished: checkIfFinished,
  544. addToDictionary: addToDictionary,
  545. ignoreWord: ignoreWord,
  546. findSpansByIndex: findSpansByIndex,
  547. getElmIndex: getElmIndex,
  548. markErrors: markErrors
  549. };
  550. var get = function (editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState, url) {
  551. var getLanguage = function () {
  552. return currentLanguageState.get();
  553. };
  554. var getWordCharPattern = function () {
  555. return Settings.getSpellcheckerWordcharPattern(editor);
  556. };
  557. var markErrors = function (data) {
  558. Actions.markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
  559. };
  560. var getTextMatcher = function () {
  561. return textMatcherState.get();
  562. };
  563. return {
  564. getTextMatcher: getTextMatcher,
  565. getWordCharPattern: getWordCharPattern,
  566. markErrors: markErrors,
  567. getLanguage: getLanguage
  568. };
  569. };
  570. var Api = { get: get };
  571. var register = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
  572. editor.addCommand('mceSpellCheck', function () {
  573. Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
  574. });
  575. };
  576. var Commands = { register: register };
  577. var hasOwnProperty = Object.prototype.hasOwnProperty;
  578. var shallow = function (old, nu) {
  579. return nu;
  580. };
  581. var baseMerge = function (merger) {
  582. return function () {
  583. var objects = new Array(arguments.length);
  584. for (var i = 0; i < objects.length; i++)
  585. objects[i] = arguments[i];
  586. if (objects.length === 0)
  587. throw new Error('Can\'t merge zero objects');
  588. var ret = {};
  589. for (var j = 0; j < objects.length; j++) {
  590. var curObject = objects[j];
  591. for (var key in curObject)
  592. if (hasOwnProperty.call(curObject, key)) {
  593. ret[key] = merger(ret[key], curObject[key]);
  594. }
  595. }
  596. return ret;
  597. };
  598. };
  599. var merge = baseMerge(shallow);
  600. var spellcheckerEvents = 'SpellcheckStart SpellcheckEnd';
  601. var buildMenuItems = function (listName, languageValues) {
  602. var items = [];
  603. global$1.each(languageValues, function (languageValue) {
  604. items.push({
  605. selectable: true,
  606. text: languageValue.name,
  607. data: languageValue.value
  608. });
  609. });
  610. return items;
  611. };
  612. var getItems = function (editor) {
  613. return global$1.map(Settings.getLanguages(editor).split(','), function (langPair) {
  614. langPair = langPair.split('=');
  615. return {
  616. name: langPair[0],
  617. value: langPair[1]
  618. };
  619. });
  620. };
  621. var register$1 = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState) {
  622. var languageMenuItems = buildMenuItems('Language', getItems(editor));
  623. var startSpellchecking = function () {
  624. Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
  625. };
  626. var buttonArgs = {
  627. tooltip: 'Spellcheck',
  628. onAction: startSpellchecking,
  629. icon: 'spell-check',
  630. onSetup: function (buttonApi) {
  631. var setButtonState = function () {
  632. buttonApi.setActive(startedState.get());
  633. };
  634. editor.on(spellcheckerEvents, setButtonState);
  635. return function () {
  636. editor.off(spellcheckerEvents, setButtonState);
  637. };
  638. }
  639. };
  640. var getSplitButtonArgs = function () {
  641. return {
  642. type: 'splitbutton',
  643. menu: languageMenuItems,
  644. select: function (value) {
  645. return value === currentLanguageState.get();
  646. },
  647. fetch: function (callback) {
  648. var items = global$1.map(languageMenuItems, function (languageItem) {
  649. return {
  650. type: 'choiceitem',
  651. value: languageItem.data,
  652. text: languageItem.text
  653. };
  654. });
  655. callback(items);
  656. },
  657. onItemAction: function (splitButtonApi, value) {
  658. currentLanguageState.set(value);
  659. }
  660. };
  661. };
  662. editor.ui.registry.addButton('spellchecker', merge(buttonArgs, languageMenuItems.length > 1 ? getSplitButtonArgs() : { type: 'togglebutton' }));
  663. editor.ui.registry.addToggleMenuItem('spellchecker', {
  664. text: 'Spellcheck',
  665. onSetup: function (menuApi) {
  666. menuApi.setActive(startedState.get());
  667. var setMenuItemCheck = function () {
  668. menuApi.setActive(startedState.get());
  669. };
  670. editor.on(spellcheckerEvents, setMenuItemCheck);
  671. return function () {
  672. editor.off(spellcheckerEvents, setMenuItemCheck);
  673. };
  674. },
  675. onAction: startSpellchecking
  676. });
  677. };
  678. var Buttons = { register: register$1 };
  679. var ignoreAll = true;
  680. var getSuggestions = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, word, spans) {
  681. var items = [], suggestions = lastSuggestionsState.get().suggestions[word];
  682. global$1.each(suggestions, function (suggestion) {
  683. items.push({
  684. text: suggestion,
  685. onAction: function () {
  686. editor.insertContent(editor.dom.encode(suggestion));
  687. editor.dom.remove(spans);
  688. Actions.checkIfFinished(editor, startedState, textMatcherState);
  689. }
  690. });
  691. });
  692. var hasDictionarySupport = lastSuggestionsState.get().hasDictionarySupport;
  693. if (hasDictionarySupport) {
  694. items.push({ type: 'separator' });
  695. items.push({
  696. text: 'Add to dictionary',
  697. onAction: function () {
  698. Actions.addToDictionary(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans);
  699. }
  700. });
  701. }
  702. items.push.apply(items, [
  703. { type: 'separator' },
  704. {
  705. text: 'Ignore',
  706. onAction: function () {
  707. Actions.ignoreWord(editor, startedState, textMatcherState, word, spans);
  708. }
  709. },
  710. {
  711. text: 'Ignore all',
  712. onAction: function () {
  713. Actions.ignoreWord(editor, startedState, textMatcherState, word, spans, ignoreAll);
  714. }
  715. }
  716. ]);
  717. return items;
  718. };
  719. var setup = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState) {
  720. var update = function (element) {
  721. var target = element;
  722. if (target.className === 'mce-spellchecker-word') {
  723. var spans = Actions.findSpansByIndex(editor, Actions.getElmIndex(target));
  724. if (spans.length > 0) {
  725. var rng = editor.dom.createRng();
  726. rng.setStartBefore(spans[0]);
  727. rng.setEndAfter(spans[spans.length - 1]);
  728. editor.selection.setRng(rng);
  729. return getSuggestions(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, target.getAttribute('data-mce-word'), spans);
  730. }
  731. } else {
  732. return [];
  733. }
  734. };
  735. editor.ui.registry.addContextMenu('spellchecker', { update: update });
  736. };
  737. var SuggestionsMenu = { setup: setup };
  738. global.add('spellchecker', function (editor, pluginUrl) {
  739. if (DetectProPlugin.hasProPlugin(editor) === false) {
  740. var startedState = Cell(false);
  741. var currentLanguageState = Cell(Settings.getLanguage(editor));
  742. var textMatcherState = Cell(null);
  743. var lastSuggestionsState = Cell(null);
  744. Buttons.register(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState);
  745. SuggestionsMenu.setup(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState);
  746. Commands.register(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
  747. return Api.get(editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState, pluginUrl);
  748. }
  749. });
  750. function Plugin () {
  751. }
  752. return Plugin;
  753. }(window));
  754. })();