Passed
Push — master ( 7e07d9...30706c )
by Ron
02:08 queued 12s
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
      getTextMatcher(editor, textMatcherState).reset();
478
      textMatcherState.set(null);
479
      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...
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 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...
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 suggestions, hasDictionarySupport;
511
      if (typeof data !== 'string' && data.words) {
512
        hasDictionarySupport = !!data.dictionary;
513
        suggestions = data.words;
514
      } else {
515
        suggestions = data;
516
      }
517
      editor.setProgressState(false);
518
      if (isEmpty(suggestions)) {
519
        var message = editor.translate('No misspellings found.');
520
        editor.notificationManager.open({
521
          text: message,
522
          type: 'info'
523
        });
524
        startedState.set(false);
525
        return;
526
      }
527
      lastSuggestionsState.set({
528
        suggestions: suggestions,
529
        hasDictionarySupport: hasDictionarySupport
0 ignored issues
show
Bug introduced by
The variable hasDictionarySupport seems to not be initialized for all possible execution paths.
Loading history...
530
      });
531
      getTextMatcher(editor, textMatcherState).find(Settings.getSpellcheckerWordcharPattern(editor)).filter(function (match) {
532
        return !!suggestions[match.text];
533
      }).wrap(function (match) {
534
        return editor.dom.create('span', {
535
          'class': 'mce-spellchecker-word',
536
          'data-mce-bogus': 1,
537
          'data-mce-word': match.text
538
        });
539
      });
540
      startedState.set(true);
541
      Events.fireSpellcheckStart(editor);
542
    };
543
    var Actions = {
544
      spellcheck: spellcheck,
545
      checkIfFinished: checkIfFinished,
546
      addToDictionary: addToDictionary,
547
      ignoreWord: ignoreWord,
548
      findSpansByIndex: findSpansByIndex,
549
      getElmIndex: getElmIndex,
550
      markErrors: markErrors
551
    };
552
553
    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...
554
      var getLanguage = function () {
555
        return currentLanguageState.get();
556
      };
557
      var getWordCharPattern = function () {
558
        return Settings.getSpellcheckerWordcharPattern(editor);
559
      };
560
      var markErrors = function (data) {
561
        Actions.markErrors(editor, startedState, textMatcherState, lastSuggestionsState, data);
562
      };
563
      var getTextMatcher = function () {
564
        return textMatcherState.get();
565
      };
566
      return {
567
        getTextMatcher: getTextMatcher,
568
        getWordCharPattern: getWordCharPattern,
569
        markErrors: markErrors,
570
        getLanguage: getLanguage
571
      };
572
    };
573
    var Api = { get: get };
574
575
    var register = function (editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState) {
576
      editor.addCommand('mceSpellCheck', function () {
577
        Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
578
      });
579
    };
580
    var Commands = { register: register };
581
582
    var buildMenuItems = function (listName, languageValues) {
583
      var items = [];
584
      global$1.each(languageValues, function (languageValue) {
585
        items.push({
586
          selectable: true,
587
          text: languageValue.name,
588
          data: languageValue.value
589
        });
590
      });
591
      return items;
592
    };
593
    var updateSelection = function (editor, currentLanguageState) {
594
      return function (e) {
595
        var selectedLanguage = currentLanguageState.get();
596
        e.control.items().each(function (ctrl) {
597
          ctrl.active(ctrl.settings.data === selectedLanguage);
598
        });
599
      };
600
    };
601
    var getItems = function (editor) {
602
      return global$1.map(Settings.getLanguages(editor).split(','), function (langPair) {
603
        langPair = langPair.split('=');
604
        return {
605
          name: langPair[0],
606
          value: langPair[1]
607
        };
608
      });
609
    };
610
    var register$1 = function (editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState) {
611
      var languageMenuItems = buildMenuItems('Language', getItems(editor));
612
      var startSpellchecking = function () {
613
        Actions.spellcheck(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
614
      };
615
      var buttonArgs = {
616
        tooltip: 'Spellcheck',
617
        onclick: startSpellchecking,
618
        onPostRender: function (e) {
619
          var ctrl = e.control;
620
          editor.on('SpellcheckStart SpellcheckEnd', function () {
621
            ctrl.active(startedState.get());
622
          });
623
        }
624
      };
625
      if (languageMenuItems.length > 1) {
626
        buttonArgs.type = 'splitbutton';
627
        buttonArgs.menu = languageMenuItems;
628
        buttonArgs.onshow = updateSelection(editor, currentLanguageState);
629
        buttonArgs.onselect = function (e) {
630
          currentLanguageState.set(e.control.settings.data);
631
        };
632
      }
633
      editor.addButton('spellchecker', buttonArgs);
634
      editor.addMenuItem('spellchecker', {
635
        text: 'Spellcheck',
636
        context: 'tools',
637
        onclick: startSpellchecking,
638
        selectable: true,
639
        onPostRender: function () {
640
          var self = this;
641
          self.active(startedState.get());
642
          editor.on('SpellcheckStart SpellcheckEnd', function () {
643
            self.active(startedState.get());
644
          });
645
        }
646
      });
647
    };
648
    var Buttons = { register: register$1 };
649
650
    var global$4 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils');
651
652
    var global$5 = tinymce.util.Tools.resolve('tinymce.ui.Factory');
653
654
    var suggestionsMenu;
655
    var showSuggestions = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, word, spans) {
656
      var items = [], suggestions = lastSuggestionsState.get().suggestions[word];
657
      global$1.each(suggestions, function (suggestion) {
658
        items.push({
659
          text: suggestion,
660
          onclick: function () {
661
            editor.insertContent(editor.dom.encode(suggestion));
662
            editor.dom.remove(spans);
663
            Actions.checkIfFinished(editor, startedState, textMatcherState);
664
          }
665
        });
666
      });
667
      items.push({ text: '-' });
668
      var hasDictionarySupport = lastSuggestionsState.get().hasDictionarySupport;
669
      if (hasDictionarySupport) {
670
        items.push({
671
          text: 'Add to Dictionary',
672
          onclick: function () {
673
            Actions.addToDictionary(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, word, spans);
674
          }
675
        });
676
      }
677
      items.push.apply(items, [
678
        {
679
          text: 'Ignore',
680
          onclick: function () {
681
            Actions.ignoreWord(editor, startedState, textMatcherState, word, spans);
682
          }
683
        },
684
        {
685
          text: 'Ignore all',
686
          onclick: function () {
687
            Actions.ignoreWord(editor, startedState, textMatcherState, word, spans, true);
688
          }
689
        }
690
      ]);
691
      suggestionsMenu = global$5.create('menu', {
692
        items: items,
693
        context: 'contextmenu',
694
        onautohide: function (e) {
695
          if (e.target.className.indexOf('spellchecker') !== -1) {
696
            e.preventDefault();
697
          }
698
        },
699
        onhide: function () {
700
          suggestionsMenu.remove();
701
          suggestionsMenu = null;
702
        }
703
      });
704
      suggestionsMenu.renderTo(document.body);
705
      var pos = global$4.DOM.getPos(editor.getContentAreaContainer());
706
      var targetPos = editor.dom.getPos(spans[0]);
707
      var root = editor.dom.getRoot();
708
      if (root.nodeName === 'BODY') {
709
        targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
710
        targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
711
      } else {
712
        targetPos.x -= root.scrollLeft;
713
        targetPos.y -= root.scrollTop;
714
      }
715
      pos.x += targetPos.x;
716
      pos.y += targetPos.y;
717
      suggestionsMenu.moveTo(pos.x, pos.y + spans[0].offsetHeight);
718
    };
719
    var setup = function (editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState) {
720
      editor.on('click', function (e) {
721
        var target = e.target;
722
        if (target.className === 'mce-spellchecker-word') {
723
          e.preventDefault();
724
          var spans = Actions.findSpansByIndex(editor, Actions.getElmIndex(target));
725
          if (spans.length > 0) {
726
            var rng = editor.dom.createRng();
727
            rng.setStartBefore(spans[0]);
728
            rng.setEndAfter(spans[spans.length - 1]);
729
            editor.selection.setRng(rng);
730
            showSuggestions(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState, target.getAttribute('data-mce-word'), spans);
731
          }
732
        }
733
      });
734
    };
735
    var SuggestionsMenu = { setup: setup };
736
737
    global.add('spellchecker', function (editor, pluginUrl) {
738
      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...
739
        var startedState = Cell(false);
740
        var currentLanguageState = Cell(Settings.getLanguage(editor));
741
        var textMatcherState = Cell(null);
742
        var lastSuggestionsState = Cell(null);
743
        Buttons.register(editor, pluginUrl, startedState, textMatcherState, currentLanguageState, lastSuggestionsState);
744
        SuggestionsMenu.setup(editor, pluginUrl, lastSuggestionsState, startedState, textMatcherState, currentLanguageState);
745
        Commands.register(editor, pluginUrl, startedState, textMatcherState, lastSuggestionsState, currentLanguageState);
746
        return Api.get(editor, startedState, lastSuggestionsState, textMatcherState, currentLanguageState, pluginUrl);
747
      }
748
    });
749
    function Plugin () {
750
    }
751
752
    return Plugin;
753
754
}());
755
})();
756