themes/default/scripts/quickQuote.js   F
last analyzed

Complexity

Total Complexity 89
Complexity/F 3.87

Size

Lines of Code 729
Function Count 23

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 89
eloc 396
mnd 66
bc 66
fnc 23
dl 0
loc 729
rs 2
bpm 2.8695
cpm 3.8695
noi 12
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like themes/default/scripts/quickQuote.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
 * @package   Quick Quote
3
 * @copyright Frenzie : Frans de Jonge
4
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
5
 *
6
 * @version 2.0 dev
7
 */
8
9
/**
10
 * This particular function was based on My Opera Enhancements and is
11
 * licensed under the BSD license.
12
 *
13
 * Some specific refactoring done for ElkArte core inclusion
14
 */
15
function Elk_QuickQuote (oOptions)
16
{
17
	'use strict';
18
19
	this.opts = Object.assign({}, this.defaults, oOptions);
20
21
	this.pointerDirection = 'right';
22
	this.startPointerX = 0;
23
	this.endPointerX = 0;
24
25
	this.init();
26
}
27
28
/**
29
 * Get things rolling
30
 */
31
Elk_QuickQuote.prototype.init = function() {
32
	this.treeToBBCode.defaults = {
33
		strong: {before: '[b]', after: '[/b]'},
34
		b: {before: '[b]', after: '[/b]'},
35
		i: {before: '[i]', after: '[/i]'},
36
		em: {before: '[i]', after: '[/i]'},
37
		s: {before: '[s]', after: '[/s]'},
38
		sup: {before: '[sup]', after: '[/sup]'},
39
		sub: {before: '[sub]', after: '[/sub]'},
40
		pre: {before: '[code]', after: '[/code]'},
41
		br: {before: '\n', after: ''}
42
	};
43
44
	this.postSelector = document.getElementById('topic_summary') ? '.postarea2' : '.postarea';
45
46
	// Check if passive is supported, should be for most browsers since 2016
47
	let supportsPassive = false;
48
	try
49
	{
50
		let opts = Object.defineProperty({}, 'passive', {get: function() {supportsPassive = true;}});
51
		window.addEventListener('test', null, opts);
52
	}
53
	catch (e)
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
54
	{
55
		// Just fall through, it does not support passive mouse events
56
	}
57
58
	// Pointer event capabilities
59
	let hasPointerEvents = (('PointerEvent' in window) || (window.navigator && 'msPointerEnabled' in window.navigator));
60
	this.mouseDown = hasPointerEvents ? 'pointerdown' : is_touch ? 'touchstart' : 'mousedown';
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable is_touch is declared in the current environment, consider using typeof is_touch === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
61
	this.mouseUp = hasPointerEvents ? 'pointerup' : is_touch ? 'touchend' : 'mouseup';
62
63
	// Initialize Quick Quote, set event listener to all messageContent areas
64
	document.querySelectorAll('.messageContent').forEach((message) => {
65
		message.addEventListener(this.mouseDown, this.getEventStartPosition.bind(this), supportsPassive ? {passive: true} : false);
66
		message.addEventListener(this.mouseUp, this.getEventEndPosition.bind(this), supportsPassive ? {passive: true} : false);
67
		message.addEventListener(this.mouseUp, this.prepareQuickQuoteButton.bind(this), supportsPassive ? {passive: true} : false);
68
69
		// Needed for android touch chrome as the mouseUp event is held by the context menu ribbon
70
		if (is_touch)
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable is_touch is declared in the current environment, consider using typeof is_touch === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
71
		{
72
			message.addEventListener('contextmenu', this.prepareQuickQuoteButton.bind(this), false);
73
		}
74
	});
75
};
76
77
/**
78
 * Get the X coordinate of the pointer down event
79
 *
80
 * @param {TouchEvent|MouseEvent} event
81
 */
82
Elk_QuickQuote.prototype.getEventStartPosition = function(event) {
83
	if (typeof event.changedTouches === 'undefined')
84
	{
85
		this.startPointerX = event.clientX;
86
	}
87
	else
88
	{
89
		this.startPointerX = event.changedTouches[0].pageX;
90
	}
91
};
92
93
/**
94
 * Get the X coordinate of the pointer up event, Set right or left for movement
95
 *
96
 * @param {TouchEvent|MouseEvent} event
97
 */
98
Elk_QuickQuote.prototype.getEventEndPosition = function(event) {
99
	if (typeof event.changedTouches === 'undefined')
100
	{
101
		this.endPointerX = event.clientX;
102
	}
103
	else
104
	{
105
		this.endPointerX = event.changedTouches[0].pageX;
106
	}
107
108
	this.pointerDirection = this.endPointerX > this.startPointerX ? 'right' : 'left';
109
};
110
111
/**
112
 * Determine the window position of the selected text.  If the selection
113
 * can not be determined (multi click or other) then the event location would be used.
114
 *
115
 * @param {PointerEvent} event
116
 * @return {Object} Returns the x and y position
117
 */
118
Elk_QuickQuote.prototype.getEventPosition = function(event) {
119
	// Set an approximate position as a backup
120
	let posRight = window.innerWidth - event.pageX - 10,
121
		posLeft = event.pageX,
122
		posBottom = event.pageY + 15,
123
		posTop = event.pageY - 5;
124
125
	let selectionRange = window.getSelection().getRangeAt(0).cloneRange(),
126
		relativePos = document.body.parentNode.getBoundingClientRect();
127
128
	// Collapse on start or end based on pointer movement direction
129
	selectionRange.collapse(this.pointerDirection === 'left');
130
	let selectionBox = selectionRange.getClientRects();
131
132
	if (selectionBox.length > 0)
133
	{
134
		posRight = -Math.round(selectionBox[0].right - relativePos.right);
135
		posLeft = Math.round(selectionBox[0].left - relativePos.left);
136
137
		posBottom = Math.round(selectionBox[0].bottom - relativePos.top + 5);
138
		posTop = Math.round(selectionBox[0].top - relativePos.top - 5);
139
	}
140
141
	return {
142
		right: posRight,
143
		left: posLeft,
144
		bottom: posBottom,
145
		top: posTop
146
	};
147
};
148
149
/**
150
 * Positions the button close to the mouse/touch up location for best
151
 * user interaction
152
 *
153
 * @param {PointerEvent} event The event
154
 * @param {HTMLElement} button The element to position
155
 */
156
Elk_QuickQuote.prototype.setButtonPosition = function(event, button) {
157
	let clickCoords = this.getEventPosition(event),
158
		buttonBottom = clickCoords.bottom + button.offsetHeight,
159
		windowBottom = window.scrollY + window.innerHeight;
160
161
	// Don't go off the bottom of the viewport
162
	if (buttonBottom > windowBottom)
163
	{
164
		button.style.top = clickCoords.top - button.offsetHeight + 'px';
165
	}
166
	else
167
	{
168
		button.style.top = clickCoords.bottom + 'px';
169
	}
170
171
	// For touch devices we need to account for selection bounding handles.  There is not a consistent
172
	// way to disable the default selection menu, so positioning below the text + handles is the
173
	// only available option
174
	if (is_touch)
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable is_touch is declared in the current environment, consider using typeof is_touch === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
175
	{
176
		button.style.top = clickCoords.bottom + 25 + 'px';
177
	}
178
179
	// Don't go outside our message area
180
	// @todo simplify
181
	let postPos = event.currentTarget.getBoundingClientRect();
182
	if (this.pointerDirection === 'right')
183
	{
184
		if (clickCoords.left - button.offsetWidth < postPos.left)
185
		{
186
			let shift = postPos.left - (clickCoords.left - button.offsetWidth);
187
			button.style.right = Math.round(clickCoords.right - shift - 10) + 'px';
188
		}
189
		else
190
		{
191
			button.style.right = clickCoords.right + 'px';
192
		}
193
	}
194
	else
195
	{
196
		if (clickCoords.left + button.offsetWidth > postPos.right)
197
		{
198
			let shift = (clickCoords.left + button.offsetWidth) - postPos.right;
199
			button.style.right = Math.round(clickCoords.right - shift - 10) + 'px';
200
		}
201
		else
202
		{
203
			button.style.right = clickCoords.right - button.offsetWidth + 'px';
204
		}
205
	}
206
};
207
208
/**
209
 * Transverses tree under node and set a flag telling whether element is hidden or not
210
 *
211
 * @param {object} node
212
 */
213
Elk_QuickQuote.prototype.setHiddenFlag = function(node) {
214
	if (!node)
215
	{
216
		return;
217
	}
218
219
	if (typeof node.item === 'function')
220
	{
221
		node.forEach((asNode) => {
222
			this.setHiddenFlag(asNode);
223
		});
224
	}
225
	else if (this.isHidden(node) !== '')
226
	{
227
		node.setAttribute('userjsishidden', 'true');
228
	}
229
	else
230
	{
231
		if (node.removeAttribute)
232
		{
233
			node.removeAttribute('userjsishidden');
234
		}
235
236
		if (node.childNodes)
237
		{
238
			this.setHiddenFlag(node.childNodes);
239
		}
240
	}
241
};
242
243
/**
244
 * Tells if element should be considered as not visible
245
 *
246
 * @param {Node} node
247
 * @returns {string}
248
 */
249
Elk_QuickQuote.prototype.isHidden = function(node) {
250
	if (node && node.nodeType === Node.ELEMENT_NODE)
0 ignored issues
show
Bug introduced by
The variable Node seems to be never declared. If this is a global, consider adding a /** global: Node */ 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...
251
	{
252
		let compStyles = getComputedStyle(node, '');
253
254
		if (node.nodeName.toLowerCase() === 'br')
255
		{
256
			return '';
257
		}
258
		if (compStyles.display === 'none')
259
		{
260
			return 'display:none';
261
		}
262
		if (compStyles.visibility === 'hidden')
263
		{
264
			return 'visibility:hidden';
265
		}
266
		if (parseFloat(compStyles.opacity) < 0.1)
267
		{
268
			return 'opacity';
269
		}
270
		if (node.offsetHeight < 4)
271
		{
272
			return 'offsetHeight';
273
		}
274
		if (node.offsetWidth < 4)
275
		{
276
			return 'offsetWidth';
277
		}
278
279
		return '';
280
	}
281
282
	return '';
283
};
284
285
/**
286
 * Compares CSS properties against a predefined array
287
 *
288
 * @param {object} node
289
 * @param {array} props
290
 * @returns {{start: string, end: string}}
291
 */
292
Elk_QuickQuote.prototype.checkCSSProps = function(node, props) {
293
	let start = '',
294
		end = '',
295
		value;
296
297
	props.forEach((prop) => {
298
		// Check for class name
299
		if (typeof prop.isClass === 'undefined')
300
		{
301
			value = this.trim(node.style[prop.name] || '', ' "');
302
		}
303
		else
304
		{
305
			value = node.classList.contains(prop.name) ? prop.name : '';
306
		}
307
308
		if ((prop.forceValue && value === prop.forceValue) || (!prop.forceValue && value))
309
		{
310
			start += prop.before.replace('@value', (prop.values ? prop.values[value] : null) || value);
311
			end += prop.after;
312
		}
313
	});
314
315
	return {start: start, end: end};
316
};
317
318
/**
319
 * Parses the tree into bbcode
320
 *
321
 * @param {object} node
322
 * @returns {string}
323
 */
324
Elk_QuickQuote.prototype.treeToBBCode = function(node) {
325
	let checked,
326
		start,
327
		end,
328
		bb = [],
329
		props = [];
0 ignored issues
show
Unused Code introduced by
The assignment to variable props seems to be never used. Consider removing it.
Loading history...
330
331
	if (typeof node.item === 'function')
332
	{
333
		node.forEach((asNode) => {
334
			bb.push(this.treeToBBCode(asNode));
335
		});
336
337
		return bb.join('');
338
	}
339
340
	if (node.getAttribute && node.getAttribute('userjsishidden') === 'true')
341
	{
342
		return '';
343
	}
344
345
	switch (node.nodeType)
346
	{
347
		// nodeType 1, like div, p, ul
348
		case Node.ELEMENT_NODE:
0 ignored issues
show
Bug introduced by
The variable Node seems to be never declared. If this is a global, consider adding a /** global: Node */ 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...
349
			let nodeName = node.nodeName.toLowerCase(),
350
				def = this.treeToBBCode.defaults[nodeName];
351
352
			// Generic wrap behavior for basic BBC tags like [b], [i], [u]
353
			if (def)
354
			{
355
				bb.push(def.before || '');
356
				bb.push(this.treeToBBCode(node.childNodes));
357
				bb.push(def.after || '');
358
			}
359
			// Special Processing cases
360
			else
361
			{
362
				switch (nodeName)
363
				{
364
					case 'a':
365
						if (node.href.indexOf('mailto:') === 0)
366
						{
367
							bb.push('[email=' + node.href.substring(7) + ']');
368
							bb.push(this.treeToBBCode(node.childNodes));
369
							bb.push('[/email]');
370
						}
371
						else if (node.className.indexOf('attach') >= 0)
372
						{
373
							bb.push('[attach=' + node.href + ']');
374
							bb.push(this.treeToBBCode(node.childNodes));
375
							bb.push('[/attach]');
376
						}
377
						else
378
						{
379
							bb.push('[url=' + node.href + ']');
380
							bb.push(this.treeToBBCode(node.childNodes));
381
							bb.push('[/url]');
382
						}
383
						break;
384
					case 'div':
385
						props = [
386
							{name: 'textAlign', forceValue: 'left', before: '[left]', after: '[/left]'},
387
							{name: 'textAlign', forceValue: 'right', before: '[right]', after: '[/right]'},
388
							{name: 'centertext', before: '[center]', after: '[/center]', isClass: true},
389
						];
390
						checked = this.checkCSSProps(node, props);
391
392
						bb.push(checked.start);
393
						bb.push(this.treeToBBCode(node.childNodes));
394
						bb.push(checked.end);
395
						break;
396
					case 'img':
397
						let smileyCode = this.getSmileyCode(node);
398
399
						bb.push(smileyCode ? ' ' + smileyCode + ' ' : '[img]' + node.src + '[/img]');
400
						break;
401
					case 'ul':
402
						props = [
403
							{
404
								name: 'listStyleType',
405
								forceValue: 'decimal',
406
								before: '[list type=decimal]',
407
								after: '[/list]'
408
							},
409
						];
410
						checked = this.checkCSSProps(node, props);
411
412
						bb.push((checked.start === '') ? '[list]' : checked.start);
413
414
						let lis = node.querySelectorAll('li');
415
416
						lis.forEach((li) => {
417
							bb.push('\n  [*] ' + this.trim(this.treeToBBCode(li)));
418
						});
419
420
						bb.push('[/list]');
421
						break;
422
					case 'span':
423
						// Check for css properties
424
						props = [
425
							{name: 'textDecoration', forceValue: 'underline', before: '[u]', after: '[/u]'},
426
							{name: 'color', before: '[color=@value]', after: '[/color]'},
427
							{name: 'fontFamily', before: '[font=@value]', after: '[/font]'},
428
							{name: 'bbc_tt', before: '[tt]', after: '[/tt]', isClass: true},
429
							{
430
								name: 'fontSize', before: '[size=@value]', after: '[/size]', values: {
431
									'xx-small': 1,
432
									'x-small': 2,
433
									'small': 3,
434
									'medium': 4,
435
									'large': 5,
436
									'x-large': 6,
437
									'xx-large': 7
438
								}
439
							}
440
						];
441
442
						checked = this.checkCSSProps(node, props);
443
						start = checked.start;
444
						end = checked.end;
445
446
						bb.push(start);
447
						bb.push(this.treeToBBCode(node.childNodes));
448
						bb.push(end);
449
						break;
450
					case 'p':
451
						bb.push(this.treeToBBCode(node.childNodes));
452
						break;
453
					case 'blockquote':
454
						if (node.classList.contains('bbc_quote'))
455
						{
456
							let author = node.getAttribute('data-quoted'),
457
								datetime = node.getAttribute('data-datetime'),
458
								link = node.getAttribute('data-link');
459
460
							bb.push('[quote' +
461
								(author ? ' author=' + author : '') +
462
								((link && datetime) ? ' link=' + link + ' date=' + datetime : '') +
463
								']\n');
464
							bb.push(this.treeToBBCode(node.childNodes));
465
							bb.push('\n[/quote]\n');
466
						}
467
						else
468
						{
469
							bb.push(this.treeToBBCode(node.childNodes));
470
						}
471
						break;
472
					default:
473
						bb.push(this.treeToBBCode(node.childNodes));
474
						break;
475
				}
476
			}
477
			break;
478
		case Node.DOCUMENT_NODE:// 9
479
		case Node.DOCUMENT_FRAGMENT_NODE:// 11
480
			bb.push(this.treeToBBCode(node.childNodes));
481
			break;
482
		case Node.TEXT_NODE:// 3
483
		case Node.CDATA_SECTION_NODE:// 4
484
			let text = node.nodeValue,
485
				codecheck = document.evaluate('ancestor::pre', node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
0 ignored issues
show
Bug introduced by
The variable XPathResult seems to be never declared. If this is a global, consider adding a /** global: XPathResult */ 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...
486
487
			if (!codecheck)
488
			{
489
				text = text.replace(/\n[ \t]+/g, '\n');
490
			}
491
			bb.push(text);
492
			break;
493
	}
494
495
	return bb.join('').replace(/quote\]\n\n\[\//g, 'quote]\n[\/');
496
};
497
498
/**
499
 * Trim string by whitespace or specific characters
500
 *
501
 * @param {string} str
502
 * @param {string|null} charToReplace
503
 * @returns {string}
504
 */
505
Elk_QuickQuote.prototype.trim = function(str, charToReplace) {
506
	if (charToReplace)
507
	{
508
		return String(str).replace(new RegExp('^[' + charToReplace + ']+|[' + charToReplace + ']+$', 'g'), '');
509
	}
510
511
	return str.trim();
512
};
513
514
/**
515
 * Returns smiley code
516
 *
517
 * @param {Object} img
518
 * @returns {string}
519
 */
520
Elk_QuickQuote.prototype.getSmileyCode = function(img) {
521
	if (img.alt && img.className && img.classList.contains('smiley'))
522
	{
523
		// Alternative text corresponds to smiley and emoji code
524
		return img.alt;
525
	}
526
527
	return '';
528
};
529
530
/**
531
 * Called when the quick quote button is pressed, passed a PointerEvent
532
 *
533
 * @param {PointerEvent} event
534
 */
535
Elk_QuickQuote.prototype.executeQuickQuote = function(event) {
536
	event.preventDefault();
537
	event.stopImmediatePropagation();
538
539
	let startTag = event.target.startTag,
540
		endTag = event.target.endTag;
541
542
	// isCollapsed is true for an empty selection
543
	let selection = window.getSelection().isCollapsed ? null : window.getSelection().getRangeAt(0);
544
545
	// Always clear out the button
546
	this.removeQuickQuote(event, true);
547
548
	if (selection)
549
	{
550
		let selectionAncestor = selection.commonAncestorContainer,
551
			selectionContents,
552
			postAncestor = document.evaluate('ancestor-or-self::section[contains(@class,"messageContent")]', selectionAncestor, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
0 ignored issues
show
Bug introduced by
The variable XPathResult seems to be never declared. If this is a global, consider adding a /** global: XPathResult */ 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...
553
554
		this.setHiddenFlag(selectionAncestor);
555
556
		if (selectionAncestor.nodeType !== 3 && selectionAncestor.nodeType !== 4)
557
		{
558
			// Most likely an element node
559
			selectionContents = selectionAncestor.cloneNode(false);
560
			selectionContents.appendChild(selection.cloneContents());
561
		}
562
		else
563
		{
564
			// Plain text
565
			selectionContents = selection.cloneContents();
566
		}
567
568
		if (postAncestor)
569
		{
570
			// Clone tree upwards. Some BBCode requires more context
571
			// than just the current node, like lists.
572
			let newSelectionContents;
573
			while (selectionAncestor !== postAncestor)
574
			{
575
				selectionAncestor = selectionAncestor.parentNode;
576
577
				// If in a blockquote, grab the cite details
578
				if (selectionAncestor.nodeName.toLowerCase() === 'blockquote' &&
579
					selectionAncestor.classList.contains('bbc_quote'))
580
				{
581
					this.handleQuote(selectionAncestor);
582
				}
583
584
				newSelectionContents = selectionAncestor.cloneNode(false);
585
586
				newSelectionContents.appendChild(selectionContents);
587
588
				selectionContents = newSelectionContents;
589
			}
590
		}
591
592
		let selectedText = this.trim(this.treeToBBCode(selectionContents));
593
594
		// Full Editor
595
		let $editor = $editor_data[post_box_name],
0 ignored issues
show
Bug introduced by
The variable $editor_data seems to be never declared. If this is a global, consider adding a /** global: $editor_data */ 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...
Bug introduced by
The variable post_box_name seems to be never declared. If this is a global, consider adding a /** global: post_box_name */ 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...
596
			text = startTag + selectedText + endTag;
597
598
		// Add the text to the editor
599
		$editor.insert(this.trim(text));
600
601
		// In wizzy mode, we need to move the cursor out of the quote block
602
		let
603
			rangeHelper = $editor.getRangeHelper(),
604
			parent = rangeHelper.parentNode();
605
606
		if (parent && parent.nodeName === 'BLOCKQUOTE')
607
		{
608
			let range = rangeHelper.selectedRange();
609
610
			range.setStartAfter(parent);
611
			rangeHelper.selectRange(range);
612
		}
613
		else
614
		{
615
			$editor.insert('\n');
616
		}
617
618
		// Move to the editor
619
		document.getElementById('editor_toolbar_container').scrollIntoView();
620
	}
621
};
622
623
/**
624
 * Extracts the cite data and places them in the blockquote data- attributes
625
 *
626
 * @param {Element} selectionAncestor
627
 */
628
Elk_QuickQuote.prototype.handleQuote = function(selectionAncestor) {
629
	let data_quoted = '',
630
		data_link = '',
631
		data_datetime = '';
632
633
	let cite = selectionAncestor.firstChild;
634
635
	// Extract the cite details
636
	if (cite.textContent.includes(':'))
637
	{
638
		data_quoted = cite.textContent.split(':')[1].trim();
639
	}
640
641
	// Name and date may be separated by &ndash; as author - date
642
	let separator = ' ' + String.fromCharCode(8211) + ' ';
643
	if (data_quoted.includes(separator))
644
	{
645
		data_quoted = data_quoted.split(separator)[0].trim();
646
	}
647
648
	if (cite.firstElementChild && cite.firstElementChild.hasAttribute('href'))
649
	{
650
		data_link = new URL(cite.firstElementChild.getAttribute('href')).search.substring(1);
0 ignored issues
show
Bug introduced by
The variable URL seems to be never declared. If this is a global, consider adding a /** global: URL */ 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...
651
	}
652
653
	if (cite.querySelector('time') !== null)
654
	{
655
		data_datetime = cite.querySelector('time').getAttribute('data-timestamp');
656
	}
657
658
	// Set what we found as data attributes of the blockquote
659
	selectionAncestor.setAttribute('data-quoted', data_quoted);
660
	selectionAncestor.setAttribute('data-link', data_link);
661
	selectionAncestor.setAttribute('data-datetime', data_datetime);
662
};
663
664
/**
665
 * Called when the user selects some text.  It prepares the Quick Quote Button
666
 * action
667
 *
668
 * @param {PointerEvent} event
669
 */
670
Elk_QuickQuote.prototype.prepareQuickQuoteButton = function(event) {
671
	// The message that this event is attached to
672
	let postArea = event.currentTarget;
673
674
	// The link button to show, poster and time of post being quoted
675
	let msgid = parseInt(postArea.getAttribute('data-msgid')),
676
		link = document.getElementById('button_float_qq_' + msgid),
677
		username,
678
		time_unix;
679
680
	// If there is some text selected
681
	if (!window.getSelection().isCollapsed && link.classList.contains('hide'))
682
	{
683
		// Show and then position the button
684
		link.classList.remove('hide');
685
		this.setButtonPosition(event, link);
686
687
		// Topic Display, Grab the name / time from the aside area
688
		if (this.postSelector === '.postarea')
689
		{
690
			username = (postArea.parentNode.previousElementSibling.querySelector('.name').textContent).trim();
691
			let keyInfo = document.getElementById('info_' + msgid);
692
			time_unix = keyInfo.querySelector('time').getAttribute('data-forumtime');
693
		}
694
		// Topic Summary on post page
695
		else
696
		{
697
			username = (postArea.parentNode.querySelector('.name').textContent).trim();
698
			time_unix = postArea.parentNode.querySelector('time').getAttribute('data-forumtime');
699
		}
700
701
		// Build the quick quote wrapper and set the button click event
702
		link.startTag = '[quote' +
703
			(username ? ' author=' + username : '') +
704
			((msgid && time_unix) ? ' link=msg=' + msgid + ' date=' + time_unix : '') + ']\n';
705
		link.endTag = '\n[/quote]';
706
707
		// Save the function pointers (due to bind) so we can remove the EventListeners
708
		this.execute = this.executeQuickQuote.bind(this);
709
		this.remove = this.removeQuickQuote.bind(this);
710
711
		// Button click
712
		link.addEventListener(this.mouseDown, this.execute, true);
713
714
		// Provide a way to escape should they click anywhere in the window
715
		document.addEventListener('click', this.remove, false);
716
	}
717
};
718
719
/**
720
 * Removes all QQ button click listeners and hides them all.
721
 *
722
 * @param {PointerEvent} event
723
 * @param {boolean} always
724
 */
725
Elk_QuickQuote.prototype.removeQuickQuote = function(event, always = false) {
726
	event.stopImmediatePropagation();
727
728
	// Nothing selected, reset the UI and listeners
729
	if (window.getSelection().isCollapsed || always)
730
	{
731
		document.removeEventListener('click', this.remove, false);
732
733
		let topicContents = document.querySelectorAll('.messageContent'),
734
			link;
735
736
		// Reset the UI on de-selection
737
		topicContents.forEach((message) => {
738
			link = message.parentElement.querySelector('.quick_quote_button');
739
			link.classList.add('hide');
740
			link.removeEventListener(this.mouseDown, this.execute, true);
741
		});
742
	}
743
};
744