Total Complexity | 141 |
Complexity/F | 1.5 |
Lines of Code | 594 |
Function Count | 94 |
Duplicated Lines | 115 |
Ratio | 19.36 % |
Changes | 0 |
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:
Complex classes like public/js/tinymce/plugins/textpattern/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 textpattern = (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 constant = function (value) { |
||
26 | return function () { |
||
27 | return value; |
||
28 | }; |
||
29 | }; |
||
30 | var never = constant(false); |
||
31 | var always = constant(true); |
||
32 | |||
33 | var never$1 = never; |
||
34 | var always$1 = always; |
||
35 | var none = function () { |
||
36 | return NONE; |
||
37 | }; |
||
38 | View Code Duplication | var NONE = function () { |
|
39 | var eq = function (o) { |
||
40 | return o.isNone(); |
||
41 | }; |
||
42 | var call$$1 = function (thunk) { |
||
43 | return thunk(); |
||
44 | }; |
||
45 | var id = function (n) { |
||
46 | return n; |
||
47 | }; |
||
48 | var noop$$1 = function () { |
||
49 | }; |
||
50 | var nul = function () { |
||
51 | return null; |
||
52 | }; |
||
53 | var undef = function () { |
||
54 | return undefined; |
||
55 | }; |
||
56 | var me = { |
||
57 | fold: function (n, s) { |
||
58 | return n(); |
||
59 | }, |
||
60 | is: never$1, |
||
61 | isSome: never$1, |
||
62 | isNone: always$1, |
||
63 | getOr: id, |
||
64 | getOrThunk: call$$1, |
||
65 | getOrDie: function (msg) { |
||
66 | throw new Error(msg || 'error: getOrDie called on none.'); |
||
67 | }, |
||
68 | getOrNull: nul, |
||
69 | getOrUndefined: undef, |
||
70 | or: id, |
||
71 | orThunk: call$$1, |
||
72 | map: none, |
||
73 | ap: none, |
||
74 | each: noop$$1, |
||
75 | bind: none, |
||
76 | flatten: none, |
||
77 | exists: never$1, |
||
78 | forall: always$1, |
||
79 | filter: none, |
||
80 | equals: eq, |
||
81 | equals_: eq, |
||
82 | toArray: function () { |
||
83 | return []; |
||
84 | }, |
||
85 | toString: constant('none()') |
||
86 | }; |
||
87 | if (Object.freeze) |
||
88 | Object.freeze(me); |
||
89 | return me; |
||
90 | }(); |
||
91 | View Code Duplication | var some = function (a) { |
|
92 | var constant_a = function () { |
||
93 | return a; |
||
94 | }; |
||
95 | var self = function () { |
||
96 | return me; |
||
97 | }; |
||
98 | var map = function (f) { |
||
99 | return some(f(a)); |
||
100 | }; |
||
101 | var bind = function (f) { |
||
102 | return f(a); |
||
103 | }; |
||
104 | var me = { |
||
105 | fold: function (n, s) { |
||
106 | return s(a); |
||
107 | }, |
||
108 | is: function (v) { |
||
109 | return a === v; |
||
110 | }, |
||
111 | isSome: always$1, |
||
112 | isNone: never$1, |
||
113 | getOr: constant_a, |
||
114 | getOrThunk: constant_a, |
||
115 | getOrDie: constant_a, |
||
116 | getOrNull: constant_a, |
||
117 | getOrUndefined: constant_a, |
||
118 | or: self, |
||
119 | orThunk: self, |
||
120 | map: map, |
||
121 | ap: function (optfab) { |
||
122 | return optfab.fold(none, function (fab) { |
||
123 | return some(fab(a)); |
||
124 | }); |
||
125 | }, |
||
126 | each: function (f) { |
||
127 | f(a); |
||
128 | }, |
||
129 | bind: bind, |
||
130 | flatten: constant_a, |
||
131 | exists: bind, |
||
132 | forall: bind, |
||
133 | filter: function (f) { |
||
134 | return f(a) ? me : NONE; |
||
135 | }, |
||
136 | equals: function (o) { |
||
137 | return o.is(a); |
||
138 | }, |
||
139 | equals_: function (o, elementEq) { |
||
140 | return o.fold(never$1, function (b) { |
||
141 | return elementEq(a, b); |
||
142 | }); |
||
143 | }, |
||
144 | toArray: function () { |
||
145 | return [a]; |
||
146 | }, |
||
147 | toString: function () { |
||
148 | return 'some(' + a + ')'; |
||
149 | } |
||
150 | }; |
||
151 | return me; |
||
152 | }; |
||
153 | var from = function (value) { |
||
154 | return value === null || value === undefined ? NONE : some(value); |
||
155 | }; |
||
156 | var Option = { |
||
157 | some: some, |
||
158 | none: none, |
||
159 | from: from |
||
160 | }; |
||
161 | |||
162 | var typeOf = function (x) { |
||
163 | if (x === null) |
||
164 | return 'null'; |
||
165 | var t = typeof x; |
||
166 | if (t === 'object' && Array.prototype.isPrototypeOf(x)) |
||
167 | return 'array'; |
||
168 | if (t === 'object' && String.prototype.isPrototypeOf(x)) |
||
169 | return 'string'; |
||
170 | return t; |
||
171 | }; |
||
172 | var isType = function (type) { |
||
173 | return function (value) { |
||
174 | return typeOf(value) === type; |
||
175 | }; |
||
176 | }; |
||
177 | var isFunction = isType('function'); |
||
178 | |||
179 | var filter = function (xs, pred) { |
||
180 | var r = []; |
||
181 | for (var i = 0, len = xs.length; i < len; i++) { |
||
182 | var x = xs[i]; |
||
183 | if (pred(x, i, xs)) { |
||
184 | r.push(x); |
||
185 | } |
||
186 | } |
||
187 | return r; |
||
188 | }; |
||
189 | var slice = Array.prototype.slice; |
||
190 | var sort = function (xs, comparator) { |
||
191 | var copy = slice.call(xs, 0); |
||
192 | copy.sort(comparator); |
||
193 | return copy; |
||
194 | }; |
||
195 | var from$1 = isFunction(Array.from) ? Array.from : function (x) { |
||
196 | return slice.call(x); |
||
197 | }; |
||
198 | |||
199 | var hasOwnProperty = Object.hasOwnProperty; |
||
200 | var get = function (obj, key) { |
||
201 | return has(obj, key) ? Option.some(obj[key]) : Option.none(); |
||
202 | }; |
||
203 | var has = function (obj, key) { |
||
204 | return hasOwnProperty.call(obj, key); |
||
205 | }; |
||
206 | |||
207 | var isInlinePattern = function (pattern) { |
||
208 | return has(pattern, 'start') && has(pattern, 'end'); |
||
209 | }; |
||
210 | var isBlockPattern = function (pattern) { |
||
211 | return !has(pattern, 'end') && !has(pattern, 'replacement'); |
||
212 | }; |
||
213 | var isReplacementPattern = function (pattern) { |
||
214 | return has(pattern, 'replacement'); |
||
215 | }; |
||
216 | var sortPatterns = function (patterns) { |
||
217 | return sort(patterns, function (a, b) { |
||
218 | if (a.start.length === b.start.length) { |
||
219 | return 0; |
||
220 | } |
||
221 | return a.start.length > b.start.length ? -1 : 1; |
||
222 | }); |
||
223 | }; |
||
224 | var createPatternSet = function (patterns) { |
||
225 | return { |
||
226 | inlinePatterns: sortPatterns(filter(patterns, isInlinePattern)), |
||
227 | blockPatterns: sortPatterns(filter(patterns, isBlockPattern)), |
||
228 | replacementPatterns: filter(patterns, isReplacementPattern) |
||
229 | }; |
||
230 | }; |
||
231 | |||
232 | var get$1 = function (patternsState) { |
||
233 | var setPatterns = function (newPatterns) { |
||
234 | patternsState.set(createPatternSet(newPatterns)); |
||
235 | }; |
||
236 | var getPatterns = function () { |
||
237 | return patternsState.get().inlinePatterns.concat(patternsState.get().blockPatterns, patternsState.get().replacementPatterns); |
||
238 | }; |
||
239 | return { |
||
240 | setPatterns: setPatterns, |
||
241 | getPatterns: getPatterns |
||
242 | }; |
||
243 | }; |
||
244 | var Api = { get: get$1 }; |
||
245 | |||
246 | var defaultPatterns = [ |
||
247 | { |
||
248 | start: '*', |
||
249 | end: '*', |
||
250 | format: 'italic' |
||
251 | }, |
||
252 | { |
||
253 | start: '**', |
||
254 | end: '**', |
||
255 | format: 'bold' |
||
256 | }, |
||
257 | { |
||
258 | start: '***', |
||
259 | end: '***', |
||
260 | format: [ |
||
261 | 'bold', |
||
262 | 'italic' |
||
263 | ] |
||
264 | }, |
||
265 | { |
||
266 | start: '#', |
||
267 | format: 'h1' |
||
268 | }, |
||
269 | { |
||
270 | start: '##', |
||
271 | format: 'h2' |
||
272 | }, |
||
273 | { |
||
274 | start: '###', |
||
275 | format: 'h3' |
||
276 | }, |
||
277 | { |
||
278 | start: '####', |
||
279 | format: 'h4' |
||
280 | }, |
||
281 | { |
||
282 | start: '#####', |
||
283 | format: 'h5' |
||
284 | }, |
||
285 | { |
||
286 | start: '######', |
||
287 | format: 'h6' |
||
288 | }, |
||
289 | { |
||
290 | start: '1. ', |
||
291 | cmd: 'InsertOrderedList' |
||
292 | }, |
||
293 | { |
||
294 | start: '* ', |
||
295 | cmd: 'InsertUnorderedList' |
||
296 | }, |
||
297 | { |
||
298 | start: '- ', |
||
299 | cmd: 'InsertUnorderedList' |
||
300 | } |
||
301 | ]; |
||
302 | var getPatternSet = function (editorSettings) { |
||
303 | var patterns = get(editorSettings, 'textpattern_patterns').getOr(defaultPatterns); |
||
304 | return createPatternSet(patterns); |
||
305 | }; |
||
306 | |||
307 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Delay'); |
||
308 | |||
309 | var global$2 = tinymce.util.Tools.resolve('tinymce.util.VK'); |
||
310 | |||
311 | var global$3 = tinymce.util.Tools.resolve('tinymce.dom.TreeWalker'); |
||
312 | |||
313 | var global$4 = tinymce.util.Tools.resolve('tinymce.util.Tools'); |
||
314 | |||
315 | var findPattern = function (patterns, text) { |
||
316 | for (var i = 0; i < patterns.length; i++) { |
||
317 | var pattern = patterns[i]; |
||
318 | if (text.indexOf(pattern.start) !== 0) { |
||
319 | continue; |
||
320 | } |
||
321 | if (pattern.end && text.lastIndexOf(pattern.end) !== text.length - pattern.end.length) { |
||
322 | continue; |
||
323 | } |
||
324 | return pattern; |
||
325 | } |
||
326 | }; |
||
327 | var isMatchingPattern = function (pattern, text, offset, delta) { |
||
328 | var textEnd = text.substr(offset - pattern.end.length - delta, pattern.end.length); |
||
329 | return textEnd === pattern.end; |
||
330 | }; |
||
331 | var hasContent = function (offset, delta, pattern) { |
||
332 | return offset - delta - pattern.end.length - pattern.start.length > 0; |
||
333 | }; |
||
334 | var findEndPattern = function (patterns, text, offset, delta) { |
||
335 | var pattern, i; |
||
336 | for (i = 0; i < patterns.length; i++) { |
||
337 | pattern = patterns[i]; |
||
338 | if (pattern.end !== undefined && isMatchingPattern(pattern, text, offset, delta) && hasContent(offset, delta, pattern)) { |
||
339 | return pattern; |
||
340 | } |
||
341 | } |
||
342 | }; |
||
343 | var findInlinePattern = function (patterns, rng, space) { |
||
344 | if (rng.collapsed === false) { |
||
345 | return; |
||
346 | } |
||
347 | var container = rng.startContainer; |
||
348 | var text = container.data; |
||
349 | var delta = space === true ? 1 : 0; |
||
350 | if (container.nodeType !== 3) { |
||
351 | return; |
||
352 | } |
||
353 | var endPattern = findEndPattern(patterns, text, rng.startOffset, delta); |
||
354 | if (endPattern === undefined) { |
||
355 | return; |
||
356 | } |
||
357 | var endOffset = text.lastIndexOf(endPattern.end, rng.startOffset - delta); |
||
358 | var startOffset = text.lastIndexOf(endPattern.start, endOffset - endPattern.end.length); |
||
359 | endOffset = text.indexOf(endPattern.end, startOffset + endPattern.start.length); |
||
360 | if (startOffset === -1) { |
||
361 | return; |
||
362 | } |
||
363 | var patternRng = document.createRange(); |
||
364 | patternRng.setStart(container, startOffset); |
||
365 | patternRng.setEnd(container, endOffset + endPattern.end.length); |
||
366 | var startPattern = findPattern(patterns, patternRng.toString()); |
||
367 | if (endPattern === undefined || startPattern !== endPattern || container.data.length <= endPattern.start.length + endPattern.end.length) { |
||
368 | return; |
||
369 | } |
||
370 | return { |
||
371 | pattern: endPattern, |
||
372 | startOffset: startOffset, |
||
373 | endOffset: endOffset |
||
374 | }; |
||
375 | }; |
||
376 | var findReplacementPattern = function (patterns, startSearch, text) { |
||
377 | for (var i = 0; i < patterns.length; i++) { |
||
378 | var index = text.lastIndexOf(patterns[i].start, startSearch); |
||
379 | if (index !== -1) { |
||
380 | return Option.some({ |
||
381 | pattern: patterns[i], |
||
382 | startOffset: index |
||
383 | }); |
||
384 | } |
||
385 | } |
||
386 | return Option.none(); |
||
387 | }; |
||
388 | |||
389 | var setSelection = function (editor, textNode, offset) { |
||
390 | var newRng = editor.dom.createRng(); |
||
391 | newRng.setStart(textNode, offset); |
||
392 | newRng.setEnd(textNode, offset); |
||
393 | editor.selection.setRng(newRng); |
||
394 | }; |
||
395 | var splitContainer = function (container, pattern, endOffset, startOffset) { |
||
396 | container = startOffset > 0 ? container.splitText(startOffset) : container; |
||
397 | container.splitText(endOffset - startOffset + pattern.end.length); |
||
398 | container.deleteData(0, pattern.start.length); |
||
399 | container.deleteData(container.data.length - pattern.end.length, pattern.end.length); |
||
400 | return container; |
||
401 | }; |
||
402 | var splitAndApply = function (editor, container, found) { |
||
403 | var formatArray = global$4.isArray(found.pattern.format) ? found.pattern.format : [found.pattern.format]; |
||
404 | var validFormats = global$4.grep(formatArray, function (formatName) { |
||
405 | var format = editor.formatter.get(formatName); |
||
406 | return format && format[0].inline; |
||
407 | }); |
||
408 | if (validFormats.length !== 0) { |
||
409 | editor.undoManager.transact(function () { |
||
410 | container = splitContainer(container, found.pattern, found.endOffset, found.startOffset); |
||
411 | formatArray.forEach(function (format) { |
||
412 | editor.formatter.apply(format, {}, container); |
||
413 | }); |
||
414 | }); |
||
415 | return container; |
||
416 | } |
||
417 | }; |
||
418 | var applyInlinePattern = function (editor, patterns, space) { |
||
419 | var rng = editor.selection.getRng(); |
||
420 | return Option.from(findInlinePattern(patterns, rng, space)).map(function (foundPattern) { |
||
421 | return splitAndApply(editor, rng.startContainer, foundPattern); |
||
422 | }); |
||
423 | }; |
||
424 | var applyInlinePatternSpace = function (editor, patterns) { |
||
425 | applyInlinePattern(editor, patterns, true).each(function (wrappedTextNode) { |
||
426 | var lastChar = wrappedTextNode.data.slice(-1); |
||
427 | if (/[\u00a0 ]/.test(lastChar)) { |
||
428 | wrappedTextNode.deleteData(wrappedTextNode.data.length - 1, 1); |
||
429 | var lastCharNode = editor.dom.doc.createTextNode(lastChar); |
||
430 | editor.dom.insertAfter(lastCharNode, wrappedTextNode.parentNode); |
||
431 | setSelection(editor, lastCharNode, 1); |
||
432 | } |
||
433 | }); |
||
434 | }; |
||
435 | var applyInlinePatternEnter = function (editor, patterns) { |
||
436 | applyInlinePattern(editor, patterns, false).each(function (wrappedTextNode) { |
||
437 | setSelection(editor, wrappedTextNode, wrappedTextNode.data.length); |
||
438 | }); |
||
439 | }; |
||
440 | var applyBlockPattern = function (editor, patterns) { |
||
441 | var selection, dom, container, firstTextNode, node, format, textBlockElm, pattern, walker, rng, offset; |
||
442 | selection = editor.selection; |
||
443 | dom = editor.dom; |
||
444 | if (!selection.isCollapsed()) { |
||
445 | return; |
||
446 | } |
||
447 | textBlockElm = dom.getParent(selection.getStart(), 'p'); |
||
448 | if (textBlockElm) { |
||
449 | walker = new global$3(textBlockElm, textBlockElm); |
||
450 | while (node = walker.next()) { |
||
451 | if (node.nodeType === 3) { |
||
452 | firstTextNode = node; |
||
453 | break; |
||
454 | } |
||
455 | } |
||
456 | if (firstTextNode) { |
||
457 | pattern = findPattern(patterns, firstTextNode.data); |
||
458 | if (!pattern) { |
||
459 | return; |
||
460 | } |
||
461 | rng = selection.getRng(true); |
||
462 | container = rng.startContainer; |
||
463 | offset = rng.startOffset; |
||
464 | if (firstTextNode === container) { |
||
465 | offset = Math.max(0, offset - pattern.start.length); |
||
466 | } |
||
467 | if (global$4.trim(firstTextNode.data).length === pattern.start.length) { |
||
468 | return; |
||
469 | } |
||
470 | if (pattern.format) { |
||
471 | format = editor.formatter.get(pattern.format); |
||
472 | if (format && format[0].block) { |
||
473 | firstTextNode.deleteData(0, pattern.start.length); |
||
474 | editor.formatter.apply(pattern.format, {}, firstTextNode); |
||
475 | rng.setStart(container, offset); |
||
476 | rng.collapse(true); |
||
477 | selection.setRng(rng); |
||
478 | } |
||
479 | } |
||
480 | if (pattern.cmd) { |
||
481 | editor.undoManager.transact(function () { |
||
482 | firstTextNode.deleteData(0, pattern.start.length); |
||
483 | editor.execCommand(pattern.cmd); |
||
484 | }); |
||
485 | } |
||
486 | } |
||
487 | } |
||
488 | }; |
||
489 | var replaceData = function (target, match) { |
||
490 | target.deleteData(match.startOffset, match.pattern.start.length); |
||
491 | target.insertData(match.startOffset, match.pattern.replacement); |
||
492 | }; |
||
493 | var replaceMiddle = function (editor, target, match) { |
||
494 | var startOffset = editor.selection.getRng().startOffset; |
||
495 | replaceData(target, match); |
||
496 | var newOffset = startOffset - match.pattern.start.length + match.pattern.replacement.length; |
||
497 | setSelection(editor, target, newOffset); |
||
498 | }; |
||
499 | var replaceEnd = function (editor, target, match) { |
||
500 | replaceData(target, match); |
||
501 | setSelection(editor, target, target.data.length); |
||
502 | }; |
||
503 | var replace = function (editor, target, match) { |
||
504 | if (match.startOffset < target.data.length) { |
||
505 | replaceMiddle(editor, target, match); |
||
506 | } else { |
||
507 | replaceEnd(editor, target, match); |
||
508 | } |
||
509 | }; |
||
510 | var applyReplacementPattern = function (editor, patterns) { |
||
511 | var rng = editor.selection.getRng(); |
||
512 | if (rng.collapsed && rng.startContainer.nodeType === 3) { |
||
513 | var container_1 = rng.startContainer; |
||
514 | findReplacementPattern(patterns, rng.startOffset, container_1.data).each(function (match) { |
||
515 | replace(editor, container_1, match); |
||
516 | }); |
||
517 | } |
||
518 | }; |
||
519 | |||
520 | var handleEnter = function (editor, patternSet) { |
||
521 | applyReplacementPattern(editor, patternSet.replacementPatterns); |
||
522 | applyInlinePatternEnter(editor, patternSet.inlinePatterns); |
||
523 | applyBlockPattern(editor, patternSet.blockPatterns); |
||
524 | }; |
||
525 | var handleInlineKey = function (editor, patternSet) { |
||
526 | applyReplacementPattern(editor, patternSet.replacementPatterns); |
||
527 | applyInlinePatternSpace(editor, patternSet.inlinePatterns); |
||
528 | }; |
||
529 | var checkKeyEvent = function (codes, event, predicate) { |
||
530 | for (var i = 0; i < codes.length; i++) { |
||
531 | if (predicate(codes[i], event)) { |
||
532 | return true; |
||
533 | } |
||
534 | } |
||
535 | }; |
||
536 | var checkKeyCode = function (codes, event) { |
||
537 | return checkKeyEvent(codes, event, function (code, event) { |
||
538 | return code === event.keyCode && global$2.modifierPressed(event) === false; |
||
539 | }); |
||
540 | }; |
||
541 | var checkCharCode = function (chars, event) { |
||
542 | return checkKeyEvent(chars, event, function (chr, event) { |
||
543 | return chr.charCodeAt(0) === event.charCode; |
||
544 | }); |
||
545 | }; |
||
546 | var KeyHandler = { |
||
547 | handleEnter: handleEnter, |
||
548 | handleInlineKey: handleInlineKey, |
||
549 | checkCharCode: checkCharCode, |
||
550 | checkKeyCode: checkKeyCode |
||
551 | }; |
||
552 | |||
553 | var setup = function (editor, patternsState) { |
||
554 | var charCodes = [ |
||
555 | ',', |
||
556 | '.', |
||
557 | ';', |
||
558 | ':', |
||
559 | '!', |
||
560 | '?' |
||
561 | ]; |
||
562 | var keyCodes = [32]; |
||
563 | editor.on('keydown', function (e) { |
||
564 | if (e.keyCode === 13 && !global$2.modifierPressed(e)) { |
||
565 | KeyHandler.handleEnter(editor, patternsState.get()); |
||
566 | } |
||
567 | }, true); |
||
568 | editor.on('keyup', function (e) { |
||
569 | if (KeyHandler.checkKeyCode(keyCodes, e)) { |
||
570 | KeyHandler.handleInlineKey(editor, patternsState.get()); |
||
571 | } |
||
572 | }); |
||
573 | editor.on('keypress', function (e) { |
||
574 | if (KeyHandler.checkCharCode(charCodes, e)) { |
||
575 | global$1.setEditorTimeout(editor, function () { |
||
576 | KeyHandler.handleInlineKey(editor, patternsState.get()); |
||
577 | }); |
||
578 | } |
||
579 | }); |
||
580 | }; |
||
581 | var Keyboard = { setup: setup }; |
||
582 | |||
583 | global.add('textpattern', function (editor) { |
||
584 | var patternsState = Cell(getPatternSet(editor.settings)); |
||
585 | Keyboard.setup(editor, patternsState); |
||
586 | return Api.get(patternsState); |
||
587 | }); |
||
588 | function Plugin () { |
||
589 | } |
||
590 | |||
591 | return Plugin; |
||
592 | |||
593 | }()); |
||
594 | })(); |
||
595 |