Passed
Push — master ( fdcd11...4b42b4 )
by Ron
07:37
created

public/js/tinymce/plugins/searchreplace/plugin.js   F

Complexity

Total Complexity 119
Complexity/F 2.29

Size

Lines of Code 601
Function Count 52

Duplication

Duplicated Lines 137
Ratio 22.8 %

Importance

Changes 0
Metric Value
eloc 458
c 0
b 0
f 0
dl 137
loc 601
rs 2
wmc 119
mnd 67
bc 67
fnc 52
bpm 1.2884
cpm 2.2884
noi 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like public/js/tinymce/plugins/searchreplace/plugin.js 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 searchreplace = (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');
24
25
    var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
26
27
    function isContentEditableFalse(node) {
28
      return node && node.nodeType === 1 && node.contentEditable === 'false';
29
    }
30
    function findAndReplaceDOMText(regex, node, replacementNode, captureGroup, schema) {
31
      var m;
32
      var matches = [];
33
      var text, count = 0, doc;
34
      var blockElementsMap, hiddenTextElementsMap, shortEndedElementsMap;
35
      doc = node.ownerDocument;
36
      blockElementsMap = schema.getBlockElements();
37
      hiddenTextElementsMap = schema.getWhiteSpaceElements();
38
      shortEndedElementsMap = schema.getShortEndedElements();
39
      function getMatchIndexes(m, captureGroup) {
40
        captureGroup = captureGroup || 0;
41
        if (!m[0]) {
42
          throw new Error('findAndReplaceDOMText cannot handle zero-length matches');
43
        }
44
        var index = m.index;
45
        if (captureGroup > 0) {
46
          var cg = m[captureGroup];
47
          if (!cg) {
48
            throw new Error('Invalid capture group');
49
          }
50
          index += m[0].indexOf(cg);
51
          m[0] = cg;
52
        }
53
        return [
54
          index,
55
          index + m[0].length,
56
          [m[0]]
57
        ];
58
      }
59
      function getText(node) {
60
        var txt;
61
        if (node.nodeType === 3) {
62
          return node.data;
63
        }
64
        if (hiddenTextElementsMap[node.nodeName] && !blockElementsMap[node.nodeName]) {
65
          return '';
66
        }
67
        txt = '';
68
        if (isContentEditableFalse(node)) {
69
          return '\n';
70
        }
71
        if (blockElementsMap[node.nodeName] || shortEndedElementsMap[node.nodeName]) {
72
          txt += '\n';
73
        }
74
        if (node = node.firstChild) {
75
          do {
76
            txt += getText(node);
77
          } while (node = node.nextSibling);
78
        }
79
        return txt;
80
      }
81
      function stepThroughMatches(node, matches, replaceFn) {
82
        var startNode, endNode, startNodeIndex, endNodeIndex, innerNodes = [], atIndex = 0, curNode = node, matchLocation = matches.shift(), matchIndex = 0;
83
        out:
84
          while (true) {
85
            if (blockElementsMap[curNode.nodeName] || shortEndedElementsMap[curNode.nodeName] || isContentEditableFalse(curNode)) {
86
              atIndex++;
87
            }
88
            if (curNode.nodeType === 3) {
89
              if (!endNode && curNode.length + atIndex >= matchLocation[1]) {
90
                endNode = curNode;
91
                endNodeIndex = matchLocation[1] - atIndex;
92
              } else if (startNode) {
93
                innerNodes.push(curNode);
94
              }
95
              if (!startNode && curNode.length + atIndex > matchLocation[0]) {
96
                startNode = curNode;
97
                startNodeIndex = matchLocation[0] - atIndex;
98
              }
99
              atIndex += curNode.length;
100
            }
101
            if (startNode && endNode) {
102
              curNode = replaceFn({
103
                startNode: startNode,
104
                startNodeIndex: startNodeIndex,
105
                endNode: endNode,
106
                endNodeIndex: endNodeIndex,
107
                innerNodes: innerNodes,
108
                match: matchLocation[2],
109
                matchIndex: matchIndex
110
              });
111
              atIndex -= endNode.length - endNodeIndex;
112
              startNode = null;
113
              endNode = null;
114
              innerNodes = [];
115
              matchLocation = matches.shift();
116
              matchIndex++;
117
              if (!matchLocation) {
118
                break;
119
              }
120
            } else if ((!hiddenTextElementsMap[curNode.nodeName] || blockElementsMap[curNode.nodeName]) && curNode.firstChild) {
121
              if (!isContentEditableFalse(curNode)) {
122
                curNode = curNode.firstChild;
123
                continue;
124
              }
125
            } else if (curNode.nextSibling) {
126
              curNode = curNode.nextSibling;
127
              continue;
128
            }
129
            while (true) {
130
              if (curNode.nextSibling) {
131
                curNode = curNode.nextSibling;
132
                break;
133
              } else if (curNode.parentNode !== node) {
134
                curNode = curNode.parentNode;
135
              } else {
136
                break out;
137
              }
138
            }
139
          }
140
      }
141
      function genReplacer(nodeName) {
142
        var makeReplacementNode;
143
        if (typeof nodeName !== 'function') {
144
          var stencilNode_1 = nodeName.nodeType ? nodeName : doc.createElement(nodeName);
145
          makeReplacementNode = function (fill, matchIndex) {
146
            var clone = stencilNode_1.cloneNode(false);
147
            clone.setAttribute('data-mce-index', matchIndex);
148
            if (fill) {
149
              clone.appendChild(doc.createTextNode(fill));
150
            }
151
            return clone;
152
          };
153
        } else {
154
          makeReplacementNode = nodeName;
155
        }
156
        return function (range) {
157
          var before;
158
          var after;
159
          var parentNode;
160
          var startNode = range.startNode;
161
          var endNode = range.endNode;
162
          var matchIndex = range.matchIndex;
163
          if (startNode === endNode) {
164
            var node_1 = startNode;
165
            parentNode = node_1.parentNode;
166
            if (range.startNodeIndex > 0) {
167
              before = doc.createTextNode(node_1.data.substring(0, range.startNodeIndex));
168
              parentNode.insertBefore(before, node_1);
169
            }
170
            var el = makeReplacementNode(range.match[0], matchIndex);
171
            parentNode.insertBefore(el, node_1);
172
            if (range.endNodeIndex < node_1.length) {
173
              after = doc.createTextNode(node_1.data.substring(range.endNodeIndex));
174
              parentNode.insertBefore(after, node_1);
175
            }
176
            node_1.parentNode.removeChild(node_1);
177
            return el;
178
          }
179
          before = doc.createTextNode(startNode.data.substring(0, range.startNodeIndex));
180
          after = doc.createTextNode(endNode.data.substring(range.endNodeIndex));
181
          var elA = makeReplacementNode(startNode.data.substring(range.startNodeIndex), matchIndex);
182
          for (var i = 0, l = range.innerNodes.length; i < l; ++i) {
183
            var innerNode = range.innerNodes[i];
184
            var innerEl = makeReplacementNode(innerNode.data, matchIndex);
185
            innerNode.parentNode.replaceChild(innerEl, innerNode);
186
          }
187
          var elB = makeReplacementNode(endNode.data.substring(0, range.endNodeIndex), matchIndex);
188
          parentNode = startNode.parentNode;
189
          parentNode.insertBefore(before, startNode);
190
          parentNode.insertBefore(elA, startNode);
191
          parentNode.removeChild(startNode);
192
          parentNode = endNode.parentNode;
193
          parentNode.insertBefore(elB, endNode);
194
          parentNode.insertBefore(after, endNode);
195
          parentNode.removeChild(endNode);
196
          return elB;
197
        };
198
      }
199
      text = getText(node);
200
      if (!text) {
201
        return;
202
      }
203
      if (regex.global) {
204
        while (m = regex.exec(text)) {
205
          matches.push(getMatchIndexes(m, captureGroup));
206
        }
207
      } else {
208
        m = text.match(regex);
209
        matches.push(getMatchIndexes(m, captureGroup));
210
      }
211
      if (matches.length) {
212
        count = matches.length;
213
        stepThroughMatches(node, matches, genReplacer(replacementNode));
214
      }
215
      return count;
216
    }
217
    var FindReplaceText = { findAndReplaceDOMText: findAndReplaceDOMText };
218
219
    var getElmIndex = function (elm) {
220
      var value = elm.getAttribute('data-mce-index');
221
      if (typeof value === 'number') {
222
        return '' + value;
223
      }
224
      return value;
225
    };
226
    var markAllMatches = function (editor, currentIndexState, regex) {
227
      var node, marker;
228
      marker = editor.dom.create('span', { 'data-mce-bogus': 1 });
229
      marker.className = 'mce-match-marker';
230
      node = editor.getBody();
231
      done(editor, currentIndexState, false);
232
      return FindReplaceText.findAndReplaceDOMText(regex, node, marker, false, editor.schema);
233
    };
234
    var unwrap = function (node) {
235
      var parentNode = node.parentNode;
236
      if (node.firstChild) {
237
        parentNode.insertBefore(node.firstChild, node);
238
      }
239
      node.parentNode.removeChild(node);
240
    };
241
    var findSpansByIndex = function (editor, index) {
242
      var nodes;
243
      var spans = [];
244
      nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
245
      if (nodes.length) {
246
        for (var i = 0; i < nodes.length; i++) {
247
          var nodeIndex = getElmIndex(nodes[i]);
248
          if (nodeIndex === null || !nodeIndex.length) {
249
            continue;
250
          }
251
          if (nodeIndex === index.toString()) {
252
            spans.push(nodes[i]);
253
          }
254
        }
255
      }
256
      return spans;
257
    };
258
    var moveSelection = function (editor, currentIndexState, forward) {
259
      var testIndex = currentIndexState.get();
260
      var dom = editor.dom;
261
      forward = forward !== false;
262
      if (forward) {
263
        testIndex++;
264
      } else {
265
        testIndex--;
266
      }
267
      dom.removeClass(findSpansByIndex(editor, currentIndexState.get()), 'mce-match-marker-selected');
268
      var spans = findSpansByIndex(editor, testIndex);
269
      if (spans.length) {
270
        dom.addClass(findSpansByIndex(editor, testIndex), 'mce-match-marker-selected');
271
        editor.selection.scrollIntoView(spans[0]);
272
        return testIndex;
273
      }
274
      return -1;
275
    };
276
    var removeNode = function (dom, node) {
277
      var parent = node.parentNode;
278
      dom.remove(node);
279
      if (dom.isEmpty(parent)) {
280
        dom.remove(parent);
281
      }
282
    };
283
    var find = function (editor, currentIndexState, text, matchCase, wholeWord) {
284
      text = text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
285
      text = text.replace(/\s/g, '[^\\S\\r\\n]');
286
      text = wholeWord ? '\\b' + text + '\\b' : text;
287
      var count = markAllMatches(editor, currentIndexState, new RegExp(text, matchCase ? 'g' : 'gi'));
288
      if (count) {
289
        currentIndexState.set(-1);
290
        currentIndexState.set(moveSelection(editor, currentIndexState, true));
291
      }
292
      return count;
293
    };
294
    var next = function (editor, currentIndexState) {
295
      var index = moveSelection(editor, currentIndexState, true);
296
      if (index !== -1) {
297
        currentIndexState.set(index);
298
      }
299
    };
300
    var prev = function (editor, currentIndexState) {
301
      var index = moveSelection(editor, currentIndexState, false);
302
      if (index !== -1) {
303
        currentIndexState.set(index);
304
      }
305
    };
306
    var isMatchSpan = function (node) {
307
      var matchIndex = getElmIndex(node);
308
      return matchIndex !== null && matchIndex.length > 0;
309
    };
310
    var replace = function (editor, currentIndexState, text, forward, all) {
311
      var i, nodes, node, matchIndex, currentMatchIndex, nextIndex = currentIndexState.get(), hasMore;
312
      forward = forward !== false;
313
      node = editor.getBody();
314
      nodes = global$1.grep(global$1.toArray(node.getElementsByTagName('span')), isMatchSpan);
315
      for (i = 0; i < nodes.length; i++) {
316
        var nodeIndex = getElmIndex(nodes[i]);
317
        matchIndex = currentMatchIndex = parseInt(nodeIndex, 10);
318
        if (all || matchIndex === currentIndexState.get()) {
319
          if (text.length) {
320
            nodes[i].firstChild.nodeValue = text;
321
            unwrap(nodes[i]);
322
          } else {
323
            removeNode(editor.dom, nodes[i]);
324
          }
325
          while (nodes[++i]) {
326
            matchIndex = parseInt(getElmIndex(nodes[i]), 10);
327
            if (matchIndex === currentMatchIndex) {
328
              removeNode(editor.dom, nodes[i]);
329
            } else {
330
              i--;
331
              break;
332
            }
333
          }
334
          if (forward) {
335
            nextIndex--;
336
          }
337
        } else if (currentMatchIndex > currentIndexState.get()) {
338
          nodes[i].setAttribute('data-mce-index', currentMatchIndex - 1);
339
        }
340
      }
341
      currentIndexState.set(nextIndex);
342
      if (forward) {
343
        hasMore = hasNext(editor, currentIndexState);
344
        next(editor, currentIndexState);
345
      } else {
346
        hasMore = hasPrev(editor, currentIndexState);
347
        prev(editor, currentIndexState);
348
      }
349
      return !all && hasMore;
350
    };
351
    var done = function (editor, currentIndexState, keepEditorSelection) {
352
      var i, nodes, startContainer, endContainer;
353
      nodes = global$1.toArray(editor.getBody().getElementsByTagName('span'));
354
      for (i = 0; i < nodes.length; i++) {
355
        var nodeIndex = getElmIndex(nodes[i]);
356
        if (nodeIndex !== null && nodeIndex.length) {
357
          if (nodeIndex === currentIndexState.get().toString()) {
358
            if (!startContainer) {
359
              startContainer = nodes[i].firstChild;
360
            }
361
            endContainer = nodes[i].firstChild;
362
          }
363
          unwrap(nodes[i]);
364
        }
365
      }
366
      if (startContainer && endContainer) {
367
        var rng = editor.dom.createRng();
368
        rng.setStart(startContainer, 0);
369
        rng.setEnd(endContainer, endContainer.data.length);
370
        if (keepEditorSelection !== false) {
371
          editor.selection.setRng(rng);
372
        }
373
        return rng;
374
      }
375
    };
376
    var hasNext = function (editor, currentIndexState) {
377
      return findSpansByIndex(editor, currentIndexState.get() + 1).length > 0;
378
    };
379
    var hasPrev = function (editor, currentIndexState) {
380
      return findSpansByIndex(editor, currentIndexState.get() - 1).length > 0;
381
    };
382
    var Actions = {
383
      done: done,
384
      find: find,
385
      next: next,
386
      prev: prev,
387
      replace: replace,
388
      hasNext: hasNext,
389
      hasPrev: hasPrev
390
    };
391
392
    var get = function (editor, currentIndexState) {
393
      var done = function (keepEditorSelection) {
394
        return Actions.done(editor, currentIndexState, keepEditorSelection);
395
      };
396
      var find = function (text, matchCase, wholeWord) {
397
        return Actions.find(editor, currentIndexState, text, matchCase, wholeWord);
398
      };
399
      var next = function () {
400
        return Actions.next(editor, currentIndexState);
401
      };
402
      var prev = function () {
403
        return Actions.prev(editor, currentIndexState);
404
      };
405
      var replace = function (text, forward, all) {
406
        return Actions.replace(editor, currentIndexState, text, forward, all);
407
      };
408
      return {
409
        done: done,
410
        find: find,
411
        next: next,
412
        prev: prev,
413
        replace: replace
414
      };
415
    };
416
    var Api = { get: get };
417
418
    var open = function (editor, currentIndexState) {
419
      var last = {}, selectedText;
420
      editor.undoManager.add();
421
      selectedText = global$1.trim(editor.selection.getContent({ format: 'text' }));
422
      function updateButtonStates() {
423
        win.statusbar.find('#next').disabled(Actions.hasNext(editor, currentIndexState) === false);
424
        win.statusbar.find('#prev').disabled(Actions.hasPrev(editor, currentIndexState) === false);
425
      }
426
      function notFoundAlert() {
427
        editor.windowManager.alert('Could not find the specified string.', function () {
428
          win.find('#find')[0].focus();
429
        });
430
      }
431
      var win = editor.windowManager.open({
432
        layout: 'flex',
433
        pack: 'center',
434
        align: 'center',
435
        onClose: function () {
436
          editor.focus();
437
          Actions.done(editor, currentIndexState);
438
          editor.undoManager.add();
439
        },
440
        onSubmit: function (e) {
441
          var count, caseState, text, wholeWord;
442
          e.preventDefault();
443
          caseState = win.find('#case').checked();
444
          wholeWord = win.find('#words').checked();
445
          text = win.find('#find').value();
446
          if (!text.length) {
447
            Actions.done(editor, currentIndexState, false);
448
            win.statusbar.items().slice(1).disabled(true);
449
            return;
450
          }
451
          if (last.text === text && last.caseState === caseState && last.wholeWord === wholeWord) {
452
            if (!Actions.hasNext(editor, currentIndexState)) {
453
              notFoundAlert();
454
              return;
455
            }
456
            Actions.next(editor, currentIndexState);
457
            updateButtonStates();
458
            return;
459
          }
460
          count = Actions.find(editor, currentIndexState, text, caseState, wholeWord);
461
          if (!count) {
462
            notFoundAlert();
463
          }
464
          win.statusbar.items().slice(1).disabled(count === 0);
465
          updateButtonStates();
466
          last = {
467
            text: text,
468
            caseState: caseState,
469
            wholeWord: wholeWord
470
          };
471
        },
472
        buttons: [
473
          {
474
            text: 'Find',
475
            subtype: 'primary',
476
            onclick: function () {
477
              win.submit();
478
            }
479
          },
480
          {
481
            text: 'Replace',
482
            disabled: true,
483
            onclick: function () {
484
              if (!Actions.replace(editor, currentIndexState, win.find('#replace').value())) {
485
                win.statusbar.items().slice(1).disabled(true);
486
                currentIndexState.set(-1);
487
                last = {};
488
              }
489
            }
490
          },
491
          {
492
            text: 'Replace all',
493
            disabled: true,
494
            onclick: function () {
495
              Actions.replace(editor, currentIndexState, win.find('#replace').value(), true, true);
496
              win.statusbar.items().slice(1).disabled(true);
497
              last = {};
498
            }
499
          },
500
          {
501
            type: 'spacer',
502
            flex: 1
503
          },
504
          {
505
            text: 'Prev',
506
            name: 'prev',
507
            disabled: true,
508
            onclick: function () {
509
              Actions.prev(editor, currentIndexState);
510
              updateButtonStates();
511
            }
512
          },
513
          {
514
            text: 'Next',
515
            name: 'next',
516
            disabled: true,
517
            onclick: function () {
518
              Actions.next(editor, currentIndexState);
519
              updateButtonStates();
520
            }
521
          }
522
        ],
523
        title: 'Find and replace',
524
        items: {
525
          type: 'form',
526
          padding: 20,
527
          labelGap: 30,
528
          spacing: 10,
529
          items: [
530
            {
531
              type: 'textbox',
532
              name: 'find',
533
              size: 40,
534
              label: 'Find',
535
              value: selectedText
536
            },
537
            {
538
              type: 'textbox',
539
              name: 'replace',
540
              size: 40,
541
              label: 'Replace with'
542
            },
543
            {
544
              type: 'checkbox',
545
              name: 'case',
546
              text: 'Match case',
547
              label: ' '
548
            },
549
            {
550
              type: 'checkbox',
551
              name: 'words',
552
              text: 'Whole words',
553
              label: ' '
554
            }
555
          ]
556
        }
557
      });
558
    };
559
    var Dialog = { open: open };
560
561
    var register = function (editor, currentIndexState) {
562
      editor.addCommand('SearchReplace', function () {
563
        Dialog.open(editor, currentIndexState);
564
      });
565
    };
566
    var Commands = { register: register };
567
568
    var showDialog = function (editor, currentIndexState) {
569
      return function () {
570
        Dialog.open(editor, currentIndexState);
571
      };
572
    };
573
    var register$1 = function (editor, currentIndexState) {
574
      editor.addMenuItem('searchreplace', {
575
        text: 'Find and replace',
576
        shortcut: 'Meta+F',
577
        onclick: showDialog(editor, currentIndexState),
578
        separator: 'before',
579
        context: 'edit'
580
      });
581
      editor.addButton('searchreplace', {
582
        tooltip: 'Find and replace',
583
        onclick: showDialog(editor, currentIndexState)
584
      });
585
      editor.shortcuts.add('Meta+F', '', showDialog(editor, currentIndexState));
586
    };
587
    var Buttons = { register: register$1 };
588
589
    global.add('searchreplace', function (editor) {
590
      var currentIndexState = Cell(-1);
591
      Commands.register(editor, currentIndexState);
592
      Buttons.register(editor, currentIndexState);
593
      return Api.get(editor, currentIndexState);
594
    });
595
    function Plugin () {
596
    }
597
598
    return Plugin;
599
600
}());
601
})();
602