Total Complexity | 108 |
Complexity/F | 1.37 |
Lines of Code | 453 |
Function Count | 79 |
Duplicated Lines | 115 |
Ratio | 25.39 % |
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/visualchars/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 visualchars = (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 get = function (toggleState) { |
||
26 | var isEnabled = function () { |
||
27 | return toggleState.get(); |
||
28 | }; |
||
29 | return { isEnabled: isEnabled }; |
||
30 | }; |
||
31 | var Api = { get: get }; |
||
32 | |||
33 | var fireVisualChars = function (editor, state) { |
||
34 | return editor.fire('VisualChars', { state: state }); |
||
35 | }; |
||
36 | var Events = { fireVisualChars: fireVisualChars }; |
||
37 | |||
38 | var charMap = { |
||
39 | '\xA0': 'nbsp', |
||
40 | '\xAD': 'shy' |
||
41 | }; |
||
42 | var charMapToRegExp = function (charMap, global) { |
||
43 | var key, regExp = ''; |
||
44 | for (key in charMap) { |
||
45 | regExp += key; |
||
46 | } |
||
47 | return new RegExp('[' + regExp + ']', global ? 'g' : ''); |
||
48 | }; |
||
49 | var charMapToSelector = function (charMap) { |
||
50 | var key, selector = ''; |
||
51 | for (key in charMap) { |
||
52 | if (selector) { |
||
53 | selector += ','; |
||
54 | } |
||
55 | selector += 'span.mce-' + charMap[key]; |
||
56 | } |
||
57 | return selector; |
||
58 | }; |
||
59 | var Data = { |
||
60 | charMap: charMap, |
||
61 | regExp: charMapToRegExp(charMap), |
||
62 | regExpGlobal: charMapToRegExp(charMap, true), |
||
63 | selector: charMapToSelector(charMap), |
||
64 | charMapToRegExp: charMapToRegExp, |
||
65 | charMapToSelector: charMapToSelector |
||
66 | }; |
||
67 | |||
68 | var constant = function (value) { |
||
69 | return function () { |
||
70 | return value; |
||
71 | }; |
||
72 | }; |
||
73 | var never = constant(false); |
||
74 | var always = constant(true); |
||
75 | |||
76 | var never$1 = never; |
||
77 | var always$1 = always; |
||
78 | var none = function () { |
||
79 | return NONE; |
||
80 | }; |
||
81 | View Code Duplication | var NONE = function () { |
|
82 | var eq = function (o) { |
||
83 | return o.isNone(); |
||
84 | }; |
||
85 | var call$$1 = function (thunk) { |
||
86 | return thunk(); |
||
87 | }; |
||
88 | var id = function (n) { |
||
89 | return n; |
||
90 | }; |
||
91 | var noop$$1 = function () { |
||
92 | }; |
||
93 | var nul = function () { |
||
94 | return null; |
||
95 | }; |
||
96 | var undef = function () { |
||
97 | return undefined; |
||
98 | }; |
||
99 | var me = { |
||
100 | fold: function (n, s) { |
||
101 | return n(); |
||
102 | }, |
||
103 | is: never$1, |
||
104 | isSome: never$1, |
||
105 | isNone: always$1, |
||
106 | getOr: id, |
||
107 | getOrThunk: call$$1, |
||
108 | getOrDie: function (msg) { |
||
109 | throw new Error(msg || 'error: getOrDie called on none.'); |
||
110 | }, |
||
111 | getOrNull: nul, |
||
112 | getOrUndefined: undef, |
||
113 | or: id, |
||
114 | orThunk: call$$1, |
||
115 | map: none, |
||
116 | ap: none, |
||
117 | each: noop$$1, |
||
118 | bind: none, |
||
119 | flatten: none, |
||
120 | exists: never$1, |
||
121 | forall: always$1, |
||
122 | filter: none, |
||
123 | equals: eq, |
||
124 | equals_: eq, |
||
125 | toArray: function () { |
||
126 | return []; |
||
127 | }, |
||
128 | toString: constant('none()') |
||
129 | }; |
||
130 | if (Object.freeze) |
||
131 | Object.freeze(me); |
||
132 | return me; |
||
133 | }(); |
||
134 | View Code Duplication | var some = function (a) { |
|
135 | var constant_a = function () { |
||
136 | return a; |
||
137 | }; |
||
138 | var self = function () { |
||
139 | return me; |
||
140 | }; |
||
141 | var map = function (f) { |
||
142 | return some(f(a)); |
||
143 | }; |
||
144 | var bind = function (f) { |
||
145 | return f(a); |
||
146 | }; |
||
147 | var me = { |
||
148 | fold: function (n, s) { |
||
149 | return s(a); |
||
150 | }, |
||
151 | is: function (v) { |
||
152 | return a === v; |
||
153 | }, |
||
154 | isSome: always$1, |
||
155 | isNone: never$1, |
||
156 | getOr: constant_a, |
||
157 | getOrThunk: constant_a, |
||
158 | getOrDie: constant_a, |
||
159 | getOrNull: constant_a, |
||
160 | getOrUndefined: constant_a, |
||
161 | or: self, |
||
162 | orThunk: self, |
||
163 | map: map, |
||
164 | ap: function (optfab) { |
||
165 | return optfab.fold(none, function (fab) { |
||
166 | return some(fab(a)); |
||
167 | }); |
||
168 | }, |
||
169 | each: function (f) { |
||
170 | f(a); |
||
171 | }, |
||
172 | bind: bind, |
||
173 | flatten: constant_a, |
||
174 | exists: bind, |
||
175 | forall: bind, |
||
176 | filter: function (f) { |
||
177 | return f(a) ? me : NONE; |
||
178 | }, |
||
179 | equals: function (o) { |
||
180 | return o.is(a); |
||
181 | }, |
||
182 | equals_: function (o, elementEq) { |
||
183 | return o.fold(never$1, function (b) { |
||
184 | return elementEq(a, b); |
||
185 | }); |
||
186 | }, |
||
187 | toArray: function () { |
||
188 | return [a]; |
||
189 | }, |
||
190 | toString: function () { |
||
191 | return 'some(' + a + ')'; |
||
192 | } |
||
193 | }; |
||
194 | return me; |
||
195 | }; |
||
196 | var from = function (value) { |
||
197 | return value === null || value === undefined ? NONE : some(value); |
||
198 | }; |
||
199 | var Option = { |
||
200 | some: some, |
||
201 | none: none, |
||
202 | from: from |
||
203 | }; |
||
204 | |||
205 | var typeOf = function (x) { |
||
206 | if (x === null) |
||
207 | return 'null'; |
||
208 | var t = typeof x; |
||
209 | if (t === 'object' && Array.prototype.isPrototypeOf(x)) |
||
210 | return 'array'; |
||
211 | if (t === 'object' && String.prototype.isPrototypeOf(x)) |
||
212 | return 'string'; |
||
213 | return t; |
||
214 | }; |
||
215 | var isType = function (type) { |
||
216 | return function (value) { |
||
217 | return typeOf(value) === type; |
||
218 | }; |
||
219 | }; |
||
220 | var isFunction = isType('function'); |
||
221 | |||
222 | var map = function (xs, f) { |
||
223 | var len = xs.length; |
||
224 | var r = new Array(len); |
||
225 | for (var i = 0; i < len; i++) { |
||
226 | var x = xs[i]; |
||
227 | r[i] = f(x, i, xs); |
||
228 | } |
||
229 | return r; |
||
230 | }; |
||
231 | var each = function (xs, f) { |
||
232 | for (var i = 0, len = xs.length; i < len; i++) { |
||
233 | var x = xs[i]; |
||
234 | f(x, i, xs); |
||
235 | } |
||
236 | }; |
||
237 | var slice = Array.prototype.slice; |
||
238 | var from$1 = isFunction(Array.from) ? Array.from : function (x) { |
||
239 | return slice.call(x); |
||
240 | }; |
||
241 | |||
242 | var fromHtml = function (html, scope) { |
||
243 | var doc = scope || document; |
||
244 | var div = doc.createElement('div'); |
||
245 | div.innerHTML = html; |
||
246 | if (!div.hasChildNodes() || div.childNodes.length > 1) { |
||
247 | console.error('HTML does not have a single root node', html); |
||
248 | throw 'HTML must have a single root node'; |
||
249 | } |
||
250 | return fromDom(div.childNodes[0]); |
||
251 | }; |
||
252 | var fromTag = function (tag, scope) { |
||
253 | var doc = scope || document; |
||
254 | var node = doc.createElement(tag); |
||
255 | return fromDom(node); |
||
256 | }; |
||
257 | var fromText = function (text, scope) { |
||
258 | var doc = scope || document; |
||
259 | var node = doc.createTextNode(text); |
||
260 | return fromDom(node); |
||
261 | }; |
||
262 | var fromDom = function (node) { |
||
263 | if (node === null || node === undefined) |
||
264 | throw new Error('Node cannot be null or undefined'); |
||
265 | return { dom: constant(node) }; |
||
266 | }; |
||
267 | var fromPoint = function (docElm, x, y) { |
||
268 | var doc = docElm.dom(); |
||
269 | return Option.from(doc.elementFromPoint(x, y)).map(fromDom); |
||
270 | }; |
||
271 | var Element$$1 = { |
||
272 | fromHtml: fromHtml, |
||
273 | fromTag: fromTag, |
||
274 | fromText: fromText, |
||
275 | fromDom: fromDom, |
||
276 | fromPoint: fromPoint |
||
277 | }; |
||
278 | |||
279 | var ATTRIBUTE = Node.ATTRIBUTE_NODE; |
||
280 | var CDATA_SECTION = Node.CDATA_SECTION_NODE; |
||
281 | var COMMENT = Node.COMMENT_NODE; |
||
282 | var DOCUMENT = Node.DOCUMENT_NODE; |
||
283 | var DOCUMENT_TYPE = Node.DOCUMENT_TYPE_NODE; |
||
284 | var DOCUMENT_FRAGMENT = Node.DOCUMENT_FRAGMENT_NODE; |
||
285 | var ELEMENT = Node.ELEMENT_NODE; |
||
286 | var TEXT = Node.TEXT_NODE; |
||
287 | var PROCESSING_INSTRUCTION = Node.PROCESSING_INSTRUCTION_NODE; |
||
288 | var ENTITY_REFERENCE = Node.ENTITY_REFERENCE_NODE; |
||
289 | var ENTITY = Node.ENTITY_NODE; |
||
290 | var NOTATION = Node.NOTATION_NODE; |
||
291 | |||
292 | var type = function (element) { |
||
293 | return element.dom().nodeType; |
||
294 | }; |
||
295 | var value = function (element) { |
||
296 | return element.dom().nodeValue; |
||
297 | }; |
||
298 | var isType$1 = function (t) { |
||
299 | return function (element) { |
||
300 | return type(element) === t; |
||
301 | }; |
||
302 | }; |
||
303 | var isText = isType$1(TEXT); |
||
304 | |||
305 | var wrapCharWithSpan = function (value) { |
||
306 | return '<span data-mce-bogus="1" class="mce-' + Data.charMap[value] + '">' + value + '</span>'; |
||
307 | }; |
||
308 | var Html = { wrapCharWithSpan: wrapCharWithSpan }; |
||
309 | |||
310 | var isMatch = function (n) { |
||
311 | return isText(n) && value(n) !== undefined && Data.regExp.test(value(n)); |
||
312 | }; |
||
313 | var filterDescendants = function (scope, predicate) { |
||
314 | var result = []; |
||
315 | var dom = scope.dom(); |
||
316 | var children = map(dom.childNodes, Element$$1.fromDom); |
||
317 | each(children, function (x) { |
||
318 | if (predicate(x)) { |
||
319 | result = result.concat([x]); |
||
320 | } |
||
321 | result = result.concat(filterDescendants(x, predicate)); |
||
322 | }); |
||
323 | return result; |
||
324 | }; |
||
325 | var findParentElm = function (elm, rootElm) { |
||
326 | while (elm.parentNode) { |
||
327 | if (elm.parentNode === rootElm) { |
||
328 | return elm; |
||
329 | } |
||
330 | elm = elm.parentNode; |
||
331 | } |
||
332 | }; |
||
333 | var replaceWithSpans = function (html) { |
||
334 | return html.replace(Data.regExpGlobal, Html.wrapCharWithSpan); |
||
335 | }; |
||
336 | var Nodes = { |
||
337 | isMatch: isMatch, |
||
338 | filterDescendants: filterDescendants, |
||
339 | findParentElm: findParentElm, |
||
340 | replaceWithSpans: replaceWithSpans |
||
341 | }; |
||
342 | |||
343 | var show = function (editor, rootElm) { |
||
344 | var node, div; |
||
345 | var nodeList = Nodes.filterDescendants(Element$$1.fromDom(rootElm), Nodes.isMatch); |
||
346 | each(nodeList, function (n) { |
||
347 | var withSpans = Nodes.replaceWithSpans(value(n)); |
||
348 | div = editor.dom.create('div', null, withSpans); |
||
349 | while (node = div.lastChild) { |
||
350 | editor.dom.insertAfter(node, n.dom()); |
||
351 | } |
||
352 | editor.dom.remove(n.dom()); |
||
353 | }); |
||
354 | }; |
||
355 | var hide = function (editor, body) { |
||
356 | var nodeList = editor.dom.select(Data.selector, body); |
||
357 | each(nodeList, function (node) { |
||
358 | editor.dom.remove(node, 1); |
||
359 | }); |
||
360 | }; |
||
361 | var toggle = function (editor) { |
||
362 | var body = editor.getBody(); |
||
363 | var bookmark = editor.selection.getBookmark(); |
||
364 | var parentNode = Nodes.findParentElm(editor.selection.getNode(), body); |
||
365 | parentNode = parentNode !== undefined ? parentNode : body; |
||
366 | hide(editor, parentNode); |
||
367 | show(editor, parentNode); |
||
368 | editor.selection.moveToBookmark(bookmark); |
||
369 | }; |
||
370 | var VisualChars = { |
||
371 | show: show, |
||
372 | hide: hide, |
||
373 | toggle: toggle |
||
374 | }; |
||
375 | |||
376 | var toggleVisualChars = function (editor, toggleState) { |
||
377 | var body = editor.getBody(); |
||
378 | var selection = editor.selection; |
||
379 | var bookmark; |
||
380 | toggleState.set(!toggleState.get()); |
||
381 | Events.fireVisualChars(editor, toggleState.get()); |
||
382 | bookmark = selection.getBookmark(); |
||
383 | if (toggleState.get() === true) { |
||
384 | VisualChars.show(editor, body); |
||
385 | } else { |
||
386 | VisualChars.hide(editor, body); |
||
387 | } |
||
388 | selection.moveToBookmark(bookmark); |
||
389 | }; |
||
390 | var Actions = { toggleVisualChars: toggleVisualChars }; |
||
391 | |||
392 | var register = function (editor, toggleState) { |
||
393 | editor.addCommand('mceVisualChars', function () { |
||
394 | Actions.toggleVisualChars(editor, toggleState); |
||
395 | }); |
||
396 | }; |
||
397 | var Commands = { register: register }; |
||
398 | |||
399 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Delay'); |
||
400 | |||
401 | var setup = function (editor, toggleState) { |
||
402 | var debouncedToggle = global$1.debounce(function () { |
||
403 | VisualChars.toggle(editor); |
||
404 | }, 300); |
||
405 | if (editor.settings.forced_root_block !== false) { |
||
406 | editor.on('keydown', function (e) { |
||
407 | if (toggleState.get() === true) { |
||
408 | e.keyCode === 13 ? VisualChars.toggle(editor) : debouncedToggle(); |
||
409 | } |
||
410 | }); |
||
411 | } |
||
412 | }; |
||
413 | var Keyboard = { setup: setup }; |
||
414 | |||
415 | var toggleActiveState = function (editor) { |
||
416 | return function (e) { |
||
417 | var ctrl = e.control; |
||
418 | editor.on('VisualChars', function (e) { |
||
419 | ctrl.active(e.state); |
||
420 | }); |
||
421 | }; |
||
422 | }; |
||
423 | var register$1 = function (editor) { |
||
424 | editor.addButton('visualchars', { |
||
425 | active: false, |
||
426 | title: 'Show invisible characters', |
||
427 | cmd: 'mceVisualChars', |
||
428 | onPostRender: toggleActiveState(editor) |
||
429 | }); |
||
430 | editor.addMenuItem('visualchars', { |
||
431 | text: 'Show invisible characters', |
||
432 | cmd: 'mceVisualChars', |
||
433 | onPostRender: toggleActiveState(editor), |
||
434 | selectable: true, |
||
435 | context: 'view', |
||
436 | prependToContext: true |
||
437 | }); |
||
438 | }; |
||
439 | |||
440 | global.add('visualchars', function (editor) { |
||
441 | var toggleState = Cell(false); |
||
442 | Commands.register(editor, toggleState); |
||
443 | register$1(editor); |
||
444 | Keyboard.setup(editor, toggleState); |
||
445 | return Api.get(toggleState); |
||
446 | }); |
||
447 | function Plugin () { |
||
448 | } |
||
449 | |||
450 | return Plugin; |
||
451 | |||
452 | }()); |
||
453 | })(); |
||
454 |