Completed
Branch master (2f299a)
by Ron
09:20
created

plugin.js ➔ ... ➔ stepThroughMatches   F

Complexity

Conditions 22
Paths 169

Size

Total Lines 65
Code Lines 52

Duplication

Lines 56
Ratio 86.15 %

Importance

Changes 0
Metric Value
eloc 52
nc 169
nop 3
dl 56
loc 65
c 0
b 0
f 0
cc 22
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like plugin.js ➔ ... ➔ stepThroughMatches often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
(function () {
2
var spellchecker = (function () {
3
    'use strict';
4
5
    var Cell = function (initial) {
6
      var value = initial;
7
      var get = function () {
8
        return value;
9
      };
10
      var set = function (v) {
11
        value = v;
12
      };
13
      var clone = function () {
14
        return Cell(get());
15
      };
16
      return {
17
        get: get,
18
        set: set,
19
        clone: clone
20
      };
21
    };
22
23
    var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
0 ignored issues
show
Bug introduced by
The variable tinymce seems to be never declared. If this is a global, consider adding a /** global: tinymce */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
24
25
    var hasProPlugin = function (editor) {
26
      if (/(^|[ ,])tinymcespellchecker([, ]|$)/.test(editor.settings.plugins) && global.get('tinymcespellchecker')) {
27
        if (typeof window.console !== 'undefined' && window.console.log) {
28
          window.console.log('Spell Checker Pro is incompatible with Spell Checker plugin! ' + 'Remove \'spellchecker\' from the \'plugins\' option.');
29
        }
30
        return true;
31
      } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
32
        return false;
33
      }
34
    };
35
    var DetectProPlugin = { hasProPlugin: hasProPlugin };
36
37
    var getLanguages = function (editor) {
38
      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';
39
      return editor.getParam('spellchecker_languages', defaultLanguages);
40
    };
41
    var getLanguage = function (editor) {
42
      var defaultLanguage = editor.getParam('language', 'en');
43
      return editor.getParam('spellchecker_language', defaultLanguage);
44
    };
45
    var getRpcUrl = function (editor) {
46
      return editor.getParam('spellchecker_rpc_url');
47
    };
48
    var getSpellcheckerCallback = function (editor) {
49
      return editor.getParam('spellchecker_callback');
50
    };
51
    var getSpellcheckerWordcharPattern = function (editor) {
52
      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');
53
      return editor.getParam('spellchecker_wordchar_pattern', defaultPattern);
54
    };
55
    var Settings = {
56
      getLanguages: getLanguages,
57
      getLanguage: getLanguage,
58
      getRpcUrl: getRpcUrl,
59
      getSpellcheckerCallback: getSpellcheckerCallback,
60
      getSpellcheckerWordcharPattern: getSpellcheckerWordcharPattern
61
    };
62
63
    var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
64
65
    var global$2 = tinymce.util.Tools.resolve('tinymce.util.URI');
66
67
    var global$3 = tinymce.util.Tools.resolve('tinymce.util.XHR');
68
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
80
    function isContentEditableFalse(node) {
81
      return node && node.nodeType === 1 && node.contentEditable === 'false';
82
    }
83
    var DomTextMatcher = function (node, editor) {
84
      var m, matches = [], text;
85
      var dom = editor.dom;
86
      var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
87
      blockElementsMap = editor.schema.getBlockElements();
88
      hiddenTextElementsMap = editor.schema.getWhiteSpaceElements();
89
      shortEndedElementsMap = editor.schema.getShortEndedElements();
90
      function createMatch(m, data) {
91
        if (!m[0]) {
92
          throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
93
        }
94
        return {
95
          start: m.index,
96
          end: m.index + m[0].length,
97
          text: m[0],
98
          data: data
99
        };
100
      }
101 View Code Duplication
      function getText(node) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
102
        var txt;
103
        if (node.nodeType === 3) {
104
          return node.data;
105
        }
106
        if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
107
          return '';
108
        }
109
        if (isContentEditableFalse(node)) {
110
          return '\n';
111
        }
112
        txt = '';
113
        if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
114
          txt += '\n';
115
        }
116
        if (node = node.firstChild) {
117
          do {
118
            txt += getText(node);
119
          } while (node = node.nextSibling);
120
        }
121
        return txt;
122
      }
123
      function stepThroughMatches(node, matches, replaceFn) {
124
        var startNode, endNode, startNodeIndex, endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, matchLocation, matchIndex = 0;
125
        matches = matches.slice(0);
126
        matches.sort(function (a, b) {
127
          return a.start - b.start;
128
        });
129
        matchLocation = matches.shift();
130
        out:
131 View Code Duplication
          while (true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
132
            if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
133
              atIndex++;
134
            }
135
            if (curNode.nodeType === 3) {
136
              if (!endNode && curNode.length + atIndex >= matchLocation.end) {
137
                endNode = curNode;
138
                endNodeIndex = matchLocation.end - atIndex;
139
              } else if (startNode) {
140
                innerNodes.push(curNode);
141
              }
142
              if (!startNode && curNode.length + atIndex > matchLocation.start) {
143
                startNode = curNode;
144
                startNodeIndex = matchLocation.start - atIndex;
145
              }
146
              atIndex += curNode.length;
147
            }
148
            if (startNode && endNode) {
149
              curNode = replaceFn({
150
                startNode: startNode,
151
                startNodeIndex: startNodeIndex,
0 ignored issues
show
Bug introduced by
The variable startNodeIndex seems to not be initialized for all possible execution paths.
Loading history...
152
                endNode: endNode,
153
                endNodeIndex: endNodeIndex,
0 ignored issues
show
Bug introduced by
The variable endNodeIndex seems to not be initialized for all possible execution paths.
Loading history...
154
                innerNodes: innerNodes,
155
                match: matchLocation.text,
156
                matchIndex: matchIndex
157
              });
158
              atIndex -= endNode.length - endNodeIndex;
159
              startNode = null;
160
              endNode = null;
161
              innerNodes = [];
162
              matchLocation = matches.shift();
163
              matchIndex++;
164
              if (!matchLocation) {
165
                break;
166
              }
167
            } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
168
              if (!isContentEditableFalse(curNode)) {
169
                curNode = curNode.firstChild;
170
                continue;
171
              }
172
            } else if (curNode.nextSibling) {
173
              curNode = curNode.nextSibling;
174
              continue;
175
            }
176
            while (true) {
177
              if (curNode.nextSibling) {
178
                curNode = curNode.nextSibling;
179
                break;
180
              } else if (curNode.parentNode !== node) {
181
                curNode = curNode.parentNode;
182
              } else {
183
                break out;
184
              }
185
            }
186
          }
187
      }
188
      function genReplacer(callback) {
189
        function makeReplacementNode(fill, matchIndex) {
190
          var match = matches[matchIndex];
191
          if (!match.stencil) {
192
            match.stencil = callback(match);
193
          }
194
          var clone = match.stencil.cloneNode(false);
195
          clone.setAttribute('data-mce-index', matchIndex);
196
          if (fill) {
197
            clone.appendChild(dom.doc.createTextNode(fill));
198
          }
199
          return clone;
200
        }
201 View Code Duplication
        return function (range) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
202
          var before;
203
          var after;
204
          var parentNode;
205
          var startNode = range.startNode;
206
          var endNode = range.endNode;
207
          var matchIndex = range.matchIndex;
208
          var doc = dom.doc;
209
          if (startNode === endNode) {
210
            var node_1 = startNode;
211
            parentNode = node_1.parentNode;
212
            if (range.startNodeIndex > 0) {
213
              before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
214
              parentNode.insertBefore(before, node_1);
215
            }
216
            var el = makeReplacementNode(range.match, matchIndex);
217
            parentNode.insertBefore(el, node_1);
218
            if (range.endNodeIndex < node_1.length) {
219
              after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
220
              parentNode.insertBefore(after, node_1);
221
            }
222
            node_1.parentNode.removeChild(node_1);
223
            return el;
224
          }
225
          before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
226
          after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
227
          var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
228
          for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
229
            var innerNode = range.innerNodes[i];
230
            var innerEl = makeReplacementNode(innerNode.data, matchIndex);
231
            innerNode.parentNode.replaceChild(innerEl, innerNode);
232
          }
233
          var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
234
          parentNode = startNode.parentNode;
235
          parentNode.insertBefore(before, startNode);
236
          parentNode.insertBefore(elA, startNode);
237
          parentNode.removeChild(startNode);
238
          parentNode = endNode.parentNode;
239
          parentNode.insertBefore(elB, endNode);
240
          parentNode.insertBefore(after, endNode);
241
          parentNode.removeChild(endNode);
242
          return elB;
243
        };
244
      }
245
      function unwrapElement(element) {
246
        var parentNode = element.parentNode;
247
        parentNode.insertBefore(element.firstChild, element);
248
        element.parentNode.removeChild(element);
249
      }
250
      function hasClass(elm) {
251
        return elm.className.indexOf('mce-spellchecker-word') !== -1;
252
      }
253
      function getWrappersByIndex(index) {
254
        var elements = node.getElementsByTagName('*'), wrappers = [];
255
        index = typeof index === 'number' ? '' + index : null;
256
        for (var i = 0; i < elements.length; i++) {
257
          var element = elements[i], dataIndex = element.getAttribute('data-mce-index');
258
          if (dataIndex !== null && dataIndex.length && hasClass(element)) {
259
            if (dataIndex === index || index === null) {
260
              wrappers.push(element);
261
            }
262
          }
263
        }
264
        return wrappers;
265
      }
266
      function indexOf(match) {
267
        var i = matches.length;
268
        while (i--) {
269
          if (matches[i] === match) {
270
            return i;
271
          }
272
        }
273
        return -1;
274
      }
275
      function filter(callback) {
276
        var filteredMatches = [];
277
        each(function (match, i) {
278
          if (callback(match, i)) {
279
            filteredMatches.push(match);
280
          }
281
        });
282
        matches = filteredMatches;
283
        return this;
284
      }
285
      function each(callback) {
286
        for (var i = 0, l = matches.length; i < l; i++) {
287
          if (callback(matches[i], i) === false) {
288
            break;
289
          }
290
        }
291
        return this;
292
      }
293
      function wrap(callback) {
294
        if (matches.length) {
295
          stepThroughMatches(node, matches, genReplacer(callback));
296
        }
297
        return this;
298
      }
299
      function find(regex, data) {
300
        if (text && regex.global) {
301
          while (m = regex.exec(text)) {
302
            matches.push(createMatch(m, data));
0 ignored issues
show
Bug introduced by
The variable m is changed as part of the while loop for example by regex.exec(text) on line 301. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
303
          }
304
        }
305
        return this;
306
      }
307
      function unwrap(match) {
308
        var i;
309
        var elements = getWrappersByIndex(match ? indexOf(match) : null);
310
        i = elements.length;
311
        while (i--) {
312
          unwrapElement(elements[i]);
313
        }
314
        return this;
315
      }
316
      function matchFromElement(element) {
317
        return matches[element.getAttribute('data-mce-index')];
318
      }
319
      function elementFromMatch(match) {
320
        return getWrappersByIndex(indexOf(match))[0];
321
      }
322
      function add(start, length, data) {
323
        matches.push({
324
          start: start,
325
          end: start + length,
326
          text: text.substr(start, length),
327
          data: data
328
        });
329
        return this;
330
      }
331
      function rangeFromMatch(match) {
332
        var wrappers = getWrappersByIndex(indexOf(match));
333
        var rng = editor.dom.createRng();
334
        rng.setStartBefore(wrappers[0]);
335
        rng.setEndAfter(wrappers[wrappers.length - 1]);
336
        return rng;
337
      }
338
      function replace(match, text) {
339
        var rng = rangeFromMatch(match);
340
        rng.deleteContents();
341
        if (text.length > 0) {
342
          rng.insertNode(editor.dom.doc.createTextNode(text));
343
        }
344
        return rng;
345
      }
346
      function reset() {
347
        matches.splice(0, matches.length);
348
        unwrap();
349
        return this;
350
      }
351
      text = getText(node);
352
      return {
353
        text: text,
354
        matches: matches,
355
        each: each,
356
        filter: filter,
357
        reset: reset,
358
        matchFromElement: matchFromElement,
359
        elementFromMatch: elementFromMatch,
360
        find: find,
361
        add: add,
362
        wrap: wrap,
363
        unwrap: unwrap,
364
        replace: replace,
365
        rangeFromMatch: rangeFromMatch,
366
        indexOf: indexOf
367
      };
368
    };
369
370
    var getTextMatcher = function (editor, textMatcherState) {
371
      if (!textMatcherState.get()) {
372
        var textMatcher = DomTextMatcher(editor.getBody(), editor);
373
        textMatcherState.set(textMatcher);
374
      }
375
      return textMatcherState.get();
376
    };
377
    var isEmpty = function (obj) {
378
      for (var _ in obj) {
379
        return false;
380
      }
381
      return true;
382
    };
383
    var defaultSpellcheckCallback = function (editor, pluginUrl, currentLanguageState) {
384
      return function (method, text, doneCallback, errorCallback) {
385
        var data = {
386
          method: method,
387
          lang: currentLanguageState.get()
388
        };
389
        var postData = '';
390
        data[method === 'addToDictionary' ? 'word' : 'text'] = text;
391
        global$1.each(data, function (value, key) {
392
          if (postData) {
393
            postData += '&';
394
          }
395
          postData += key + '=' + encodeURIComponent(value);
396
        });
397
        global$3.send({
398
          url: new global$2(pluginUrl).toAbsolute(Settings.getRpcUrl(editor)),
0 ignored issues
show
Coding Style Best Practice introduced by
By convention, constructors like global$2 should be capitalized.
Loading history...
399
          type: 'post',
400
          content_type: 'application/x-www-form-urlencoded',
401
          data: postData,
402
          success: function (result) {
403
            result = JSON.parse(result);
404
            if (!result) {
405
              var message = editor.translate('Server response wasn\'t proper JSON.');
406
              errorCallback(message);
407
            } else if (result.error) {
408
              errorCallback(result.error);
409
            } else {
410
              doneCallback(result);
411
            }
412
          },
413
          error: function () {
414
            var message = editor.translate('The spelling service was not found: (') + Settings.getRpcUrl(editor) + editor.translate(')');
415
            errorCallback(message);
416
          }
417
        });
418
      };
419
    };
420
    var sendRpcCall = function (editor, pluginUrl, currentLanguageState, name, data, successCallback, errorCallback) {
421
      var userSpellcheckCallback = Settings.getSpellcheckerCallback(editor);
422
      var spellCheckCallback = userSpellcheckCallback ? userSpellcheckCallback : defaultSpellcheckCallback(editor, pluginUrl, currentLanguageState);
423
      spellCheckCallback.call(editor.plugins.spellchecker, name, data, successCallback, errorCallback);
424
    };
425
    var spellcheck = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
426
      if (finish(editor, startedState, textMatcherState)) {
427
        return;
428
      }
429
      var errorCallback = function (message) {
430
        editor.notificationManager.open({
431
          text: message,
432
          type: 'error'
433
        });
434
        editor.setProgressState(false);
435
        finish(editor, startedState, textMatcherState);
436
      };
437
      var successCallback = function (data) {
438
        markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
439
      };
440
      editor.setProgressState(true);
441
      sendRpcCall(editor, pluginUrl, currentLanguageState, 'spellcheck', getTextMatcher(editor, textMatcherState).text, successCallback, errorCallback);
442
      editor.focus();
443
    };
444
    var checkIfFinished = function (editor, startedState, textMatcherState) {
445
      if (!editor.dom.select('span.mce-spellchecker-word').length) {
446
        finish(editor, startedState, textMatcherState);
447
      }
448
    };
449
    var addToDictionary = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans) {
450
      editor.setProgressState(true);
451
      sendRpcCall(editor, pluginUrl, currentLanguageState, 'addToDictionary', word, function () {
452
        editor.setProgressState(false);
453
        editor.dom.remove(spans, true);
454
        checkIfFinished(editor, startedState, textMatcherState);
455
      }, function (message) {
456
        editor.notificationManager.open({
457
          text: message,
458
          type: 'error'
459
        });
460
        editor.setProgressState(false);
461
      });
462
    };
463
    var ignoreWord = function (editor, startedState, textMatcherState, word, spans, all) {
464
      editor.selection.collapse();
465
      if (all) {
466
        global$1.each(editor.dom.select('span.mce-spellchecker-word'), function (span) {
467
          if (span.getAttribute('data-mce-word') === word) {
468
            editor.dom.remove(span, true);
469
          }
470
        });
471
      } else {
472
        editor.dom.remove(spans, true);
473
      }
474
      checkIfFinished(editor, startedState, textMatcherState);
475
    };
476
    var finish = function (editor, startedState, textMatcherState) {
477
      var bookmark = editor.selection.getBookmark();
478
      getTextMatcher(editor, textMatcherState).reset();
479
      editor.selection.moveToBookmark(bookmark);
480
      textMatcherState.set(null);
481
      if (startedState.get()) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if startedState.get() is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
482
        startedState.set(false);
483
        Events.fireSpellcheckEnd(editor);
484
        return true;
485
      }
486
    };
487
    var getElmIndex = function (elm) {
488
      var value = elm.getAttribute('data-mce-index');
489
      if (typeof value === 'number') {
490
        return '' + value;
491
      }
492
      return value;
493
    };
494 View Code Duplication
    var findSpansByIndex = function (editor, index) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
495
      var nodes;
496
      var spans = [];
497
      nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
498
      if (nodes.length) {
499
        for (var i = 0; i < nodes.length; i++) {
500
          var nodeIndex = getElmIndex(nodes[i]);
501
          if (nodeIndex === null || !nodeIndex.length) {
502
            continue;
503
          }
504
          if (nodeIndex === index.toString()) {
505
            spans.push(nodes[i]);
506
          }
507
        }
508
      }
509
      return spans;
510
    };
511
    var markErrors = function (editor, startedState, textMatcherState, lastSuggestionsState, data) {
512
      var suggestions, hasDictionarySupport;
513
      if (typeof data !== 'string' && data.words) {
514
        hasDictionarySupport = !!data.dictionary;
515
        suggestions = data.words;
516
      } else {
517
        suggestions = data;
518
      }
519
      editor.setProgressState(false);
520
      if (isEmpty(suggestions)) {
521
        var message = editor.translate('No misspellings found.');
522
        editor.notificationManager.open({
523
          text: message,
524
          type: 'info'
525
        });
526
        startedState.set(false);
527
        return;
528
      }
529
      lastSuggestionsState.set({
530
        suggestions: suggestions,
531
        hasDictionarySupport: hasDictionarySupport
0 ignored issues
show
Bug introduced by
The variable hasDictionarySupport seems to not be initialized for all possible execution paths.
Loading history...
532
      });
533
      var bookmark = editor.selection.getBookmark();
534
      getTextMatcher(editor, textMatcherState).find(Settings.getSpellcheckerWordcharPattern(editor)).filter(function (match) {
535
        return !!suggestions[match.text];
536
      }).wrap(function (match) {
537
        return editor.dom.create('span', {
538
          'class': 'mce-spellchecker-word',
539
          'data-mce-bogus': 1,
540
          'data-mce-word': match.text
541
        });
542
      });
543
      editor.selection.moveToBookmark(bookmark);
544
      startedState.set(true);
545
      Events.fireSpellcheckStart(editor);
546
    };
547
    var Actions = {
548
      spellcheck: spellcheck,
549
      checkIfFinished: checkIfFinished,
550
      addToDictionary: addToDictionary,
551
      ignoreWord: ignoreWord,
552
      findSpansByIndex: findSpansByIndex,
553
      getElmIndex: getElmIndex,
554
      markErrors: markErrors
555
    };
556
557
    var get = function (editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState, url) {
0 ignored issues
show
Unused Code introduced by
The parameter url is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
558
      var getLanguage = function () {
559
        return currentLanguageState.get();
560
      };
561
      var getWordCharPattern = function () {
562
        return Settings.getSpellcheckerWordcharPattern(editor);
563
      };
564
      var markErrors = function (data) {
565
        Actions.markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
566
      };
567
      var getTextMatcher = function () {
568
        return textMatcherState.get();
569
      };
570
      return {
571
        getTextMatcher: getTextMatcher,
572
        getWordCharPattern: getWordCharPattern,
573
        markErrors: markErrors,
574
        getLanguage: getLanguage
575
      };
576
    };
577
    var Api = { get: get };
578
579
    var register = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
580
      editor.addCommand('mceSpellCheck', function () {
581
        Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
582
      });
583
    };
584
    var Commands = { register: register };
585
586
    var buildMenuItems = function (listName, languageValues) {
587
      var items = [];
588
      global$1.each(languageValues, function (languageValue) {
589
        items.push({
590
          selectable: true,
591
          text: languageValue.name,
592
          data: languageValue.value
593
        });
594
      });
595
      return items;
596
    };
597
    var updateSelection = function (editor, currentLanguageState) {
598
      return function (e) {
599
        var selectedLanguage = currentLanguageState.get();
600
        e.control.items().each(function (ctrl) {
601
          ctrl.active(ctrl.settings.data === selectedLanguage);
602
        });
603
      };
604
    };
605
    var getItems = function (editor) {
606
      return global$1.map(Settings.getLanguages(editor).split(','), function (langPair) {
607
        langPair = langPair.split('=');
608
        return {
609
          name: langPair[0],
610
          value: langPair[1]
611
        };
612
      });
613
    };
614
    var register$1 = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState) {
615
      var languageMenuItems = buildMenuItems('Language', getItems(editor));
616
      var startSpellchecking = function () {
617
        Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
618
      };
619
      var buttonArgs = {
620
        tooltip: 'Spellcheck',
621
        onclick: startSpellchecking,
622
        onPostRender: function (e) {
623
          var ctrl = e.control;
624
          editor.on('SpellcheckStart SpellcheckEnd', function () {
625
            ctrl.active(startedState.get());
626
          });
627
        }
628
      };
629
      if (languageMenuItems.length > 1) {
630
        buttonArgs.type = 'splitbutton';
631
        buttonArgs.menu = languageMenuItems;
632
        buttonArgs.onshow = updateSelection(editor, currentLanguageState);
633
        buttonArgs.onselect = function (e) {
634
          currentLanguageState.set(e.control.settings.data);
635
        };
636
      }
637
      editor.addButton('spellchecker', buttonArgs);
638
      editor.addMenuItem('spellchecker', {
639
        text: 'Spellcheck',
640
        context: 'tools',
641
        onclick: startSpellchecking,
642
        selectable: true,
643
        onPostRender: function () {
644
          var self = this;
645
          self.active(startedState.get());
646
          editor.on('SpellcheckStart SpellcheckEnd', function () {
647
            self.active(startedState.get());
648
          });
649
        }
650
      });
651
    };
652
    var Buttons = { register: register$1 };
653
654
    var global$4 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils');
655
656
    var global$5 = tinymce.util.Tools.resolve('tinymce.ui.Factory');
657
658
    var suggestionsMenu;
659
    var showSuggestions = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, word, spans) {
660
      var items = [], suggestions = lastSuggestionsState.get().suggestions[word];
661
      global$1.each(suggestions, function (suggestion) {
662
        items.push({
663
          text: suggestion,
664
          onclick: function () {
665
            editor.insertContent(editor.dom.encode(suggestion));
666
            editor.dom.remove(spans);
667
            Actions.checkIfFinished(editor, startedState, textMatcherState);
668
          }
669
        });
670
      });
671
      items.push({ text: '-' });
672
      var hasDictionarySupport = lastSuggestionsState.get().hasDictionarySupport;
673
      if (hasDictionarySupport) {
674
        items.push({
675
          text: 'Add to Dictionary',
676
          onclick: function () {
677
            Actions.addToDictionary(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans);
678
          }
679
        });
680
      }
681
      items.push.apply(items, [
682
        {
683
          text: 'Ignore',
684
          onclick: function () {
685
            Actions.ignoreWord(editor, startedState, textMatcherState, word, spans);
686
          }
687
        },
688
        {
689
          text: 'Ignore all',
690
          onclick: function () {
691
            Actions.ignoreWord(editor, startedState, textMatcherState, word, spans, true);
692
          }
693
        }
694
      ]);
695
      suggestionsMenu = global$5.create('menu', {
696
        items: items,
697
        context: 'contextmenu',
698
        onautohide: function (e) {
699
          if (e.target.className.indexOf('spellchecker') !== -1) {
700
            e.preventDefault();
701
          }
702
        },
703
        onhide: function () {
704
          suggestionsMenu.remove();
705
          suggestionsMenu = null;
706
        }
707
      });
708
      suggestionsMenu.renderTo(document.body);
709
      var pos = global$4.DOM.getPos(editor.getContentAreaContainer());
710
      var targetPos = editor.dom.getPos(spans[0]);
711
      var root = editor.dom.getRoot();
712
      if (root.nodeName === 'BODY') {
713
        targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
714
        targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
715
      } else {
716
        targetPos.x -= root.scrollLeft;
717
        targetPos.y -= root.scrollTop;
718
      }
719
      pos.x += targetPos.x;
720
      pos.y += targetPos.y;
721
      suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight);
722
    };
723
    var setup = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState) {
724
      editor.on('click', function (e) {
725
        var target = e.target;
726
        if (target.className === 'mce-spellchecker-word') {
727
          e.preventDefault();
728
          var spans = Actions.findSpansByIndex(editor, Actions.getElmIndex(target));
729
          if (spans.length > 0) {
730
            var rng = editor.dom.createRng();
731
            rng.setStartBefore(spans[0]);
732
            rng.setEndAfter(spans[spans.length - 1]);
733
            editor.selection.setRng(rng);
734
            showSuggestions(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, target.getAttribute('data-mce-word'), spans);
735
          }
736
        }
737
      });
738
    };
739
    var SuggestionsMenu = { setup: setup };
740
741
    global.add('spellchecker', function (editor, pluginUrl) {
742
      if (DetectProPlugin.hasProPlugin(editor) === false) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if DetectProPlugin.hasProPlugin(editor) === false is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
743
        var startedState = Cell(false);
744
        var currentLanguageState = Cell(Settings.getLanguage(editor));
745
        var textMatcherState = Cell(null);
746
        var lastSuggestionsState = Cell(null);
747
        Buttons.register(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState);
748
        SuggestionsMenu.setup(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState);
749
        Commands.register(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
750
        return Api.get(editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState, pluginUrl);
751
      }
752
    });
753
    function Plugin () {
754
    }
755
756
    return Plugin;
757
758
}());
759
})();
760