Completed
Push — master ( 8967bc...e88c19 )
by Rain
02:47
created

dev/Common/HtmlEditor.js   F

Complexity

Total Complexity 126
Complexity/F 2.38

Size

Lines of Code 414
Function Count 53

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
c 1
b 0
f 0
nc 1
dl 0
loc 414
rs 3.12
wmc 126
mnd 5
bc 94
fnc 53
bpm 1.7735
cpm 2.3773
noi 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A HtmlEditor.js ➔ ??? 0 17 1

How to fix   Complexity   

Complexity

Complex classes like dev/Common/HtmlEditor.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
2
import window from 'window';
3
import _ from '_';
4
import $ from '$';
5
import {htmlEditorDefaultConfig, htmlEditorLangsMap} from 'Common/Globals';
6
import {EventKeyCode} from 'Common/Enums';
7
import * as Settings from 'Storage/Settings';
8
9
class HtmlEditor
10
{
11
	/**
12
	 * @constructor
13
	 * @param {Object} element
14
	 * @param {Function=} onBlur
15
	 * @param {Function=} onReady
16
	 * @param {Function=} onModeChange
17
	 */
18
	constructor(element, onBlur = null, onReady = null, onModeChange = null)
19
	{
20
		this.editor = null;
21
		this.blurTimer = 0;
22
23
		this.onBlur = onBlur;
24
		this.onReady = onReady;
25
		this.onModeChange = onModeChange;
26
27
		this.$element = $(element);
28
29
		this.resize = _.throttle(_.bind(this.resize, this), 100);
30
31
		this.__inited = false;
32
33
		this.init();
34
	}
35
36
	blurTrigger() {
37
		if (this.onBlur)
38
		{
39
			window.clearTimeout(this.blurTimer);
40
			this.blurTimer = window.setTimeout(() => {
41
				this.onBlur();
42
			}, 200);
43
		}
44
	}
45
46
	focusTrigger() {
47
		if (this.onBlur)
48
		{
49
			window.clearTimeout(this.blurTimer);
50
		}
51
	}
52
53
	/**
54
	 * @returns {boolean}
55
	 */
56
	isHtml() {
57
		return this.editor ? 'wysiwyg' === this.editor.mode : false;
58
	}
59
60
	/**
61
	 * @param {string} signature
62
	 * @param {bool} html
63
	 * @param {bool} insertBefore
64
	 */
65
	setSignature(signature, html, insertBefore) {
66
		if (this.editor)
67
		{
68
			this.editor.execCommand('insertSignature', {
69
				isHtml: html,
70
				insertBefore: insertBefore,
71
				signature: signature
72
			});
73
		}
74
	}
75
76
	/**
77
	 * @returns {boolean}
78
	 */
79
	checkDirty() {
80
		return this.editor ? this.editor.checkDirty() : false;
81
	}
82
83
	resetDirty() {
84
		if (this.editor)
85
		{
86
			this.editor.resetDirty();
87
		}
88
	}
89
90
	/**
91
	 * @param {string} text
92
	 * @returns {string}
93
	 */
94
	clearSignatureSigns(text) {
95
		return text.replace(/(\u200C|\u0002)/g, '');
96
	}
97
98
	/**
99
	 * @param {boolean=} wrapIsHtml = false
100
	 * @param {boolean=} clearSignatureSigns = false
101
	 * @returns {string}
102
	 */
103
	getData(wrapIsHtml = false, clearSignatureSigns = false) {
104
105
		let result = '';
106
		if (this.editor)
107
		{
108
			try
109
			{
110
				if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain)
111
				{
112
					result = this.editor.__plain.getRawData();
113
				}
114
				else
115
				{
116
					result = wrapIsHtml ?
117
						'<div data-html-editor-font-wrapper="true" style="font-family: arial, sans-serif; font-size: 13px;">' +
118
							this.editor.getData() + '</div>' : this.editor.getData();
119
				}
120
			}
121
			catch (e) {} // eslint-disable-line no-empty
122
123
			if (clearSignatureSigns)
124
			{
125
				result = this.clearSignatureSigns(result);
126
			}
127
		}
128
129
		return result;
130
	}
131
132
	/**
133
	 * @param {boolean=} wrapIsHtml = false
134
	 * @param {boolean=} clearSignatureSigns = false
135
	 * @returns {string}
136
	 */
137
	getDataWithHtmlMark(wrapIsHtml = false, clearSignatureSigns = false) {
138
		return (this.isHtml() ? ':HTML:' : '') + this.getData(wrapIsHtml, clearSignatureSigns);
139
	}
140
141
	modeToggle(plain, resize) {
142
		if (this.editor)
143
		{
144
			try {
145
				if (plain)
146
				{
147
					if ('plain' === this.editor.mode)
148
					{
149
						this.editor.setMode('wysiwyg');
150
					}
151
				}
152
				else
153
				{
154
					if ('wysiwyg' === this.editor.mode)
155
					{
156
						this.editor.setMode('plain');
157
					}
158
				}
159
			}
160
			catch (e) {} // eslint-disable-line no-empty
161
162
			if (resize)
163
			{
164
				this.resize();
165
			}
166
		}
167
	}
168
169
	setHtmlOrPlain(text, focus) {
170
		if (':HTML:' === text.substr(0, 6))
171
		{
172
			this.setHtml(text.substr(6), focus);
173
		}
174
		else
175
		{
176
			this.setPlain(text, focus);
177
		}
178
	}
179
180
	setHtml(html, focus) {
181
		if (this.editor && this.__inited)
182
		{
183
			this.modeToggle(true);
184
185
			html = html.replace(/<p[^>]*><\/p>/ig, '');
186
187
			try {
188
				this.editor.setData(html);
189
			}
190
			catch (e) {} // eslint-disable-line no-empty
191
192
			if (focus)
193
			{
194
				this.focus();
195
			}
196
		}
197
	}
198
199
	replaceHtml(find, replaceHtml) {
200
		if (this.editor && this.__inited && 'wysiwyg' === this.editor.mode)
201
		{
202
			try {
203
				this.editor.setData(
204
					this.editor.getData().replace(find, replaceHtml));
205
			}
206
			catch (e) {} // eslint-disable-line no-empty
207
		}
208
	}
209
210
	setPlain(plain, focus) {
211
		if (this.editor && this.__inited)
212
		{
213
			this.modeToggle(false);
214
			if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain)
215
			{
216
				this.editor.__plain.setRawData(plain);
217
			}
218
			else
219
			{
220
				try {
221
					this.editor.setData(plain);
222
				}
223
				catch (e) {} // eslint-disable-line no-empty
224
			}
225
226
			if (focus)
227
			{
228
				this.focus();
229
			}
230
		}
231
	}
232
233
	init() {
234
		if (this.$element && this.$element[0] && !this.editor)
235
		{
236
			const
237
				initFunc = () => {
238
239
					const
240
						config = htmlEditorDefaultConfig,
241
						language = Settings.settingsGet('Language'),
242
						allowSource = !!Settings.appSettingsGet('allowHtmlEditorSourceButton'),
243
						biti = !!Settings.appSettingsGet('allowHtmlEditorBitiButtons');
244
245
					if ((allowSource || !biti) && !config.toolbarGroups.__cfgInited)
246
					{
247
						config.toolbarGroups.__cfgInited = true;
248
249
						if (allowSource)
250
						{
251
							config.removeButtons = config.removeButtons.replace(',Source', '');
252
						}
253
254
						if (!biti)
255
						{
256
							config.removePlugins += (config.removePlugins ? ',' : '') + 'bidi';
257
						}
258
					}
259
260
					config.enterMode = window.CKEDITOR.ENTER_BR;
261
					config.shiftEnterMode = window.CKEDITOR.ENTER_P;
262
263
					config.language = htmlEditorLangsMap[(language || 'en').toLowerCase()] || 'en';
264
					if (window.CKEDITOR.env)
265
					{
266
						window.CKEDITOR.env.isCompatible = true;
267
					}
268
269
					this.editor = window.CKEDITOR.appendTo(this.$element[0], config);
270
271
					this.editor.on('key', (event) => {
272
						if (event && event.data && EventKeyCode.Tab === event.data.keyCode)
273
						{
274
							return false;
275
						}
276
277
						return true;
278
					});
279
280
					this.editor.on('blur', () => {
281
						this.blurTrigger();
282
					});
283
284
					this.editor.on('mode', () => {
285
						this.blurTrigger();
286
						if (this.onModeChange)
287
						{
288
							this.onModeChange('plain' !== this.editor.mode);
289
						}
290
					});
291
292
					this.editor.on('focus', () => {
293
						this.focusTrigger();
294
					});
295
296
					if (window.FileReader)
297
					{
298
						this.editor.on('drop', (event) => {
299
							if (0 < event.data.dataTransfer.getFilesCount())
300
							{
301
								const file = event.data.dataTransfer.getFile(0);
302
								if (file && window.FileReader && event.data.dataTransfer.id &&
303
									file.type && file.type.match(/^image/i))
304
								{
305
									var
306
										id = event.data.dataTransfer.id,
307
										imageId = `[img=${id}]`,
308
										reader = new window.FileReader();
309
310
									reader.onloadend = () => {
311
										if (reader.result)
312
										{
313
											this.replaceHtml(imageId, `<img src="${reader.result}" />`);
314
										}
315
									};
316
317
									reader.readAsDataURL(file);
318
319
									event.data.dataTransfer.setData('text/html', imageId);
320
								}
321
							}
322
						});
323
					}
324
325
					this.editor.on('instanceReady', () => {
326
327
						if (this.editor.removeMenuItem)
328
						{
329
							this.editor.removeMenuItem('cut');
330
							this.editor.removeMenuItem('copy');
331
							this.editor.removeMenuItem('paste');
332
						}
333
334
						this.__resizable = true;
335
						this.__inited = true;
336
337
						this.resize();
338
339
						if (this.onReady)
340
						{
341
							this.onReady();
342
						}
343
344
					});
345
				};
346
347
			if (window.CKEDITOR)
348
			{
349
				initFunc();
350
			}
351
			else
352
			{
353
				window.__initEditor = initFunc;
354
			}
355
		}
356
	}
357
358
	focus() {
359
		if (this.editor)
360
		{
361
			try {
362
				this.editor.focus();
363
			}
364
			catch (e) {} // eslint-disable-line no-empty
365
		}
366
	}
367
368
	hasFocus() {
369
		if (this.editor)
370
		{
371
			try {
372
				return !!this.editor.focusManager.hasFocus;
373
			}
374
			catch (e) {} // eslint-disable-line no-empty
375
		}
376
377
		return false;
378
	}
379
380
	blur() {
381
		if (this.editor)
382
		{
383
			try {
384
				this.editor.focusManager.blur(true);
385
			}
386
			catch (e) {} // eslint-disable-line no-empty
387
		}
388
	}
389
390
	resize() {
391
		if (this.editor && this.__resizable)
392
		{
393
			try {
394
				this.editor.resize(this.$element.width(), this.$element.innerHeight());
395
			}
396
			catch (e) {} // eslint-disable-line no-empty
397
		}
398
	}
399
400
	setReadOnly(value) {
401
		if (this.editor)
402
		{
403
			try {
404
				this.editor.setReadOnly(!!value);
405
			}
406
			catch (e) {} // eslint-disable-line no-empty
407
		}
408
	}
409
410
	clear(focus) {
411
		this.setHtml('', focus);
412
	}
413
}
414
415
export {HtmlEditor, HtmlEditor as default};
416