PageSourceSearch

https://rubryka.com/wp-content/plugins/mistape/assets/js/mistape-front.js?ver=1664349892

js rubryka.com collected 2026-10-02 05:58:51 UTC 29,707 bytes, 952 lines download raw bytes

1/**
2 * JavaScript Client Detection
3 * (C) viazenetti GmbH (Christian Ludwig)
4 */
5(function (window) {
6	{
7		var unknown = '-';
8
9		// screen
10		var screenSize = '';
11		if (screen.width) {
12			width = (screen.width) ? screen.width : '';
13			height = (screen.height) ? screen.height : '';
14			screenSize += '' + width + " x " + height;
15		}
16
17		// browser
18		var nVer = navigator.appVersion;
19		var nAgt = navigator.userAgent;
20		var browser = navigator.appName;
21		var version = '' + parseFloat(navigator.appVersion);
22		var majorVersion = parseInt(navigator.appVersion, 10);
23		var nameOffset, verOffset, ix;
24
25		// Opera
26		if ((verOffset = nAgt.indexOf('Opera')) !== -1) {
27			browser = 'Opera';
28			version = nAgt.substring(verOffset + 6);
29			if ((verOffset = nAgt.indexOf('Version')) !== -1) {
30				version = nAgt.substring(verOffset + 8);
31			}
32		}
33		// Opera Next
34		if ((verOffset = nAgt.indexOf('OPR')) !== -1) {
35			browser = 'Opera';
36			version = nAgt.substring(verOffset + 4);
37		}
38		// USbrowser
39		else if ((verOffset = nAgt.indexOf('UCBrowser')) !== -1) {
40			browser = 'UCBrowser';
41			version = nAgt.substring(verOffset + 6);
42			if ((verOffset = nAgt.indexOf('Version')) !== -1) {
43				version = nAgt.substring(verOffset + 8);
44			}
45		}
46		// MSIE
47		else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {
48			browser = 'Microsoft Internet Explorer';
49			version = nAgt.substring(verOffset + 5);
50		}
51		// MSE
52		else if ((verOffset = nAgt.indexOf('Edge')) !== -1) {
53			browser = 'Edge';
54			version = nAgt.substring(verOffset + 7);
55		}
56		// Chrome
57		else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {
58			browser = 'Chrome';
59			version = nAgt.substring(verOffset + 7);
60		}
61		// Safari
62		else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {
63			browser = 'Safari';
64			version = nAgt.substring(verOffset + 7);
65			if ((verOffset = nAgt.indexOf('Version')) !== -1) {
66				version = nAgt.substring(verOffset + 8);
67			}
68		}
69		// Firefox
70		else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {
71			browser = 'Firefox';
72			version = nAgt.substring(verOffset + 8);
73		}
74		// MSIE 11+
75		else if (nAgt.indexOf('Trident/') !== -1) {
76			browser = 'Microsoft Internet Explorer';
77			version = nAgt.substring(nAgt.indexOf('rv:') + 3);
78		}
79		// Other browsers
80		else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
81			browser = nAgt.substring(nameOffset, verOffset);
82			version = nAgt.substring(verOffset + 1);
83			if (browser.toLowerCase() === browser.toUpperCase()) {
84				browser = navigator.appName;
85			}
86		}
87		// trim the version string
88		if ((ix = version.indexOf(';')) !== -1) version = version.substring(0, ix);
89		if ((ix = version.indexOf(' ')) !== -1) version = version.substring(0, ix);
90		if ((ix = version.indexOf(')')) !== -1) version = version.substring(0, ix);
91
92		majorVersion = parseInt('' + version, 10);
93		if (isNaN(majorVersion)) {
94			version = '' + parseFloat(navigator.appVersion);
95			majorVersion = parseInt(navigator.appVersion, 10);
96		}
97
98		// mobile version
99		var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);
100
101		// cookie
102		var cookieEnabled = !!(navigator.cookieEnabled);
103
104		if (typeof navigator.cookieEnabled === 'undefined' && !cookieEnabled) {
105			document.cookie = 'testcookie';
106			cookieEnabled = (document.cookie.indexOf('testcookie') !== -1);
107		}
108
109		// system
110		var os = unknown;
111		var clientStrings = [
112			{s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/},
113			{s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/},
114			{s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/},
115			{s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/},
116			{s: 'Windows Vista', r: /Windows NT 6.0/},
117			{s: 'Windows Server 2003', r: /Windows NT 5.2/},
118			{s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/},
119			{s: 'Windows 2000', r: /(Windows NT 5.0|Windows 2000)/},
120			{s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/},
121			{s: 'Windows 98', r: /(Windows 98|Win98)/},
122			{s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/},
123			{s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
124			{s: 'Windows CE', r: /Windows CE/},
125			{s: 'Windows 3.11', r: /Win16/},
126			{s: 'Android', r: /Android/},
127			{s: 'Open BSD', r: /OpenBSD/},
128			{s: 'Sun OS', r: /SunOS/},
129			{s: 'Linux', r: /(Linux|X11)/},
130			{s: 'iOS', r: /(iPhone|iPad|iPod)/},
131			{s: 'Mac OS X', r: /Mac OS X/},
132			{s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
133			{s: 'QNX', r: /QNX/},
134			{s: 'UNIX', r: /UNIX/},
135			{s: 'BeOS', r: /BeOS/},
136			{s: 'OS/2', r: /OS\/2/},
137			{s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
138		];
139		for (var id in clientStrings) {
140			var cs = clientStrings[id];
141			if (cs.r.test(nAgt)) {
142				os = cs.s;
143				break;
144			}
145		}
146
147
148
149		if (/Windows/.test(os)) {
150			os = 'Windows';
151		}
152
153		var flashVersion = 'no check';
154		if (typeof swfobject !== 'undefined') {
155			var fv = swfobject.getFlashPlayerVersion();
156			if (fv.major > 0) {
157				flashVersion = fv.major + '.' + fv.minor + ' r' + fv.release;
158			} else {
159				flashVersion = unknown;
160			}
161		}
162	}
163
164	window.jscd = {
165		screen             : screenSize,
166		browser            : browser,
167		browserVersion     : version,
168		browserMajorVersion: majorVersion,
169		mobile             : mobile,
170		os                 : os,
171		cookies            : cookieEnabled,
172		flashVersion       : flashVersion
173	};
174}(window));
175
176/**
177 * dialogFx.js v1.0.0
178 * http://www.codrops.com
179 *
180 * Licensed under the MIT license.
181 * http://www.opensource.org/licenses/mit-license.php
182 *
183 * Copyright 2014, Codrops
184 * http://www.codrops.com
185 */
186
187(function (window) {
188
189	'use strict';
190
191	var support = {animations: Modernizr.cssanimations},
192		animEndEventNames = {
193			'WebkitAnimation': 'webkitAnimationEnd',
194			'OAnimation'     : 'oAnimationEnd',
195			'msAnimation'    : 'MSAnimationEnd',
196			'animation'      : 'animationend'
197		},
198		animEndEventName = animEndEventNames[Modernizr.prefixed('animation')],
199		onEndAnimation = function (el, callback) {
200			var onEndCallbackFn = function (ev) {
201				if (support.animations) {
202					if (ev.target !== this) return;
203					this.removeEventListener(animEndEventName, onEndCallbackFn);
204				}
205				if (callback && typeof callback === 'function') {
206					callback.call();
207				}
208			};
209			if (support.animations) {
210				el.addEventListener(animEndEventName, onEndCallbackFn);
211			} else {
212				onEndCallbackFn();
213			}
214		};
215
216	function extend(a, b) {
217		for (var key in b) {
218			if (b.hasOwnProperty(key)) {
219				a[key] = b[key];
220			}
221		}
222		return a;
223	}
224
225	function DialogFx(el, options) {
226		this.el = el;
227		this.options = extend({}, this.options);
228		extend(this.options, options);
229		this.isOpen = false;
230		this._initEvents();
231	}
232
233	DialogFx.prototype.options = {
234		// callbacks
235		onOpenDialog       : function () {
236			return false;
237		},
238		onCloseDialog      : function () {
239			return false;
240		},
241		onOpenAnimationEnd : function () {
242			return false;
243		},
244		onCloseAnimationEnd: function () {
245			return false;
246		}
247	};
248
249	DialogFx.prototype._initEvents = function () {
250		var self = this;
251
252		// esc key closes dialog
253		document.addEventListener('keydown', function (ev) {
254			var keyCode = ev.keyCode || ev.which;
255			if (keyCode === 27 && self.isOpen) {
256				self.toggle();
257			}
258		});
259
260		this.el.querySelector('.dialog__overlay').addEventListener('click', this.toggle.bind(this));
261	};
262
263	DialogFx.prototype.toggle = function () {
264		var self = this;
265		if (this.isOpen) {
266			jQuery(this.el).removeClass('dialog--open');
267			jQuery(self.el).addClass('dialog--close');
268
269			onEndAnimation(this.el.querySelector('.dialog__content'), function () {
270				jQuery(self.el).removeClass('dialog--close');
271				self.options.onCloseAnimationEnd(self);
272			});
273
274			// callback on close
275			this.options.onCloseDialog(this);
276		} else {
277			jQuery(this.el).addClass('dialog--open');
278
279			// callback on open
280			this.options.onOpenDialog(this);
281
282			onEndAnimation(this.el.querySelector('.dialog__content'), function () {
283				jQuery(self.el).removeClass('dialog--close');
284				self.options.onOpenAnimationEnd(self);
285			});
286		}
287		this.isOpen = !this.isOpen;
288	};
289
290	// add to global namespace
291	window.DialogFx = DialogFx;
292
293})(window);
294
295
296/**
297 * Mistape
298 */
299(function ($) {
300
301	// return if no args passed from backend
302	if (!window.decoMistape) {
303		return;
304	}
305
306	var getPointerEvent = function (event) {
307		return event.originalEvent.targetTouches ? event.originalEvent.targetTouches[0] : event;
308	};
309
310	// var $touchArea = $('body'),
311	// 	touchStarted = false, // detect if a touch event is sarted
312	// 	touchPopupShow = false, // detect if a touch event is sarted
313	// 	currX = 0,
314	// 	currY = 0,
315	// 	cachedX = 0,
316	// 	cachedY = 0;
317	// var timeoutTouch = '';
318	//
319	// $touchArea.on('touchstart', function (e) {
320	// 	var pointer = getPointerEvent(e);
321	// 	cachedX = currX = pointer.pageX;
322	// 	cachedY = currY = pointer.pageY;
323	//
324	// 	if (touchPopupShow === true) {
325	// 		touchPopupShow = false;
326	// 		var report = decoMistape.getSelectionData();
327	// 		if (report) {
328	// 			decoMistape.showDialog(report);
329	// 		}
330	// 	}
331	//
332	// 	// }, 1000);
333	// });
334	// $touchArea.on('touchend touchcancel', function (e) {
335	// 	touchPopupShow = true;
336	// 	currX = pointer.pageX;
337	// 	currY = pointer.pageY;
338	// });
339	//
340	// $touchArea.on('touchmove', function (e) {
341	// 	// e.preventDefault();
342	// 	var pointer = getPointerEvent(e);
343	// 	currX = pointer.pageX;
344	// 	currY = pointer.pageY;
345	// 	if (touchStarted) {
346	// 		// here you are swiping
347	// 		// alert('Swiping');
348	// 	}
349	//
350	// });
351
352	// }
353
354	window.decoMistape = $.extend(window.decoMistape, {
355
356		onReady: function () {
357			decoMistape.initDialogFx();
358
359			var $dialog = $(decoMistape.dlg.el);
360
361			$(document).on('click', '.mistape_action', function () {
362				if ($(this).is('[data-action=send]')) {
363					var data;
364					if (!$dialog.data('dry-run') && (data = $dialog.data('report'))) {
365						if ($dialog.data('mode') === 'comment') {
366							data.comment = $dialog.find('#mistape_comment').val();
367							$('#mistape_comment').val('');
368						}
369						data.post_id = $(this).data('id');
370						decoMistape.reportSpellError(data);
371					}
372					decoMistape.animateLetter();
373				} else if ($(this).is('[data-dialog-close]')) {
374					decoMistape.dlg.toggle();
375				}
376			});
377
378			$(document).keyup(function (ev) {
379				if (ev.keyCode === 13 && ev.ctrlKey && ev.target.nodeName.toLowerCase() !== 'textarea' && $('#mistape_dialog.dialog--open').length === 0) {
380					var report = decoMistape.getSelectionData();
381					if (report) {
382						decoMistape.showDialog(report);
383					}
384				}
385			});
386		},
387
388		initDialogFx: function () {
389			decoMistape.dlg = new DialogFx(document.getElementById('mistape_dialog'), {
390				onOpenDialog       : function (dialog) {
391					$(dialog.el).css('display', 'flex');
392				},
393				onCloseAnimationEnd: function (dialog) {
394					$(dialog.el).css('display', 'none');
395					decoMistape.resetDialog();
396				}
397			});
398		},
399
400		animateLetter: function () {
401			var dialog = $(decoMistape.dlg.el),
402				content = dialog.find('.dialog__content'),
403				letterTop = dialog.find('.mistape-letter-top'),
404				letterFront = dialog.find('.mistape-letter-front'),
405				letterBack = dialog.find('.mistape-letter-back'),
406				dialogWrap = dialog.find('.dialog-wrap');
407
408			content.addClass('show-letter');
409
410			setTimeout(function () {
411				var y = (letterTop.offset().top - letterFront.offset().top) + letterTop.outerHeight();
412				letterTop.css({
413					'bottom' : Math.floor(y),
414					'opacity': 1
415				});
416				jQuery('.mistape-letter-back-top').hide();
417				if (content.hasClass('with-comment')) {
418					dialogWrap.css('transform', 'scaleY(0.5) scaleX(0.28)');
419				} else {
420					dialogWrap.css('transform', 'scaleY(0.5) scaleX(0.4)');
421				}
422				setTimeout(function () {
423					if (content.hasClass('with-comment')) {
424						dialogWrap.css('transform', 'translateY(12%) scaleY(0.5) scaleX(0.4)');
425					} else {
426						dialogWrap.css('transform', 'translateY(28%) scaleY(0.5) scaleX(0.45)');
427					}
428					setTimeout(function () {
429						letterTop.css('z-index', '9');
430						letterTop.addClass('close');
431						setTimeout(function () {
432							dialogWrap.css({
433								'visibility': 'hidden',
434								'opacity'   : '0'
435							});
436							letterFront.css('animation', 'send-letter1 0.7s');
437							letterBack.css('animation', 'send-letter1 0.7s');
438							letterTop.css('animation', 'send-letter2 0.7s');
439							setTimeout(function () {
440								decoMistape.dlg.toggle();
441							}, 400)
442						}, 400)
443					}, 400)
444				}, 300)
445			}, 400);
446		},
447
448		showDialog: function (report) {
449			if (report.hasOwnProperty('selection') && report.hasOwnProperty('context')) {
450				var $dialog = $(decoMistape.dlg.el);
451
452				if ($dialog.data('mode') === 'notify') {
453					decoMistape.reportSpellError(report);
454					decoMistape.dlg.toggle();
455				} else {
456					$dialog.data('report', report);
457					$dialog.find('#mistape_reported_text').html(report.preview_text);
458					decoMistape.dlg.toggle();
459				}
460			}
461		},
462
463		resetDialog: function () {
464			var $dialog = $(decoMistape.dlg.el);
465
466			if ($dialog.data('mode') != 'notify') {
467				$dialog.find('#mistape_confirm_dialog').css('display', '');
468				$dialog.find('#mistape_success_dialog').remove();
469			}
470
471			// letter
472			$dialog.find('.dialog__content').removeClass('show-letter');
473			$dialog.find('.mistape-letter-top, .mistape-letter-front, .mistape-letter-back, .dialog-wrap, .mistape-letter-back-top').removeAttr('style');
474			$dialog.find('.mistape-letter-top').removeClass('close');
475		},
476
477		reportSpellError: function (data) {
478			data.action = 'mistape_report_error';
479			$.ajax({
480				type    : 'post',
481				dataType: 'json',
482				url     : decoMistape.ajaxurl,
483				data    : data
484			})
485		},
486
487		getSelectionData: function () {
488			// Check for existence of window.getSelection()
489			if (!window.getSelection) {
490				return false;
491			}
492
493			var parentEl, sel, selChars, selWord, textToHighlight, maxContextLength = 140;
494
495			var stringifyContent = function (string) {
496				return typeof string === 'string' ? string.replace(/\s*(?:(?:\r\n)+|\r+|\n+)\t*/gm, '\r\n').replace(/\s{2,}/gm, ' ') : '';
497			};
498
499			var isSubstrUnique = function (substr, context) {
500				if (typeof context === 'undefined') {
501					context = decoMistape.contextBuffer;
502				}
503				if (typeof substr === 'undefined') {
504					substr = decoMistape.selBuffer;
505				}
506				var split = context.split(substr);
507				var count = split.length - 1;
508				return count === 1;
509
510			};
511
512			var getExactSelPos = function (selection, context) {
513				// if there is only one match, that's it
514				if (isSubstrUnique(selWithContext, context)) {
515					return context.indexOf(selWithContext);
516				}
517				// check if we can get the occurrence match from selection offsets
518				if (!backwards) {
519					// check anchor element
520					if (context.substring(sel.anchorOffset, sel.anchorOffset + selection.length) == selection) {
521						return sel.anchorOffset;
522					}
523					// check anchor parent element
524					var parentElOffset = sel.anchorOffset;
525					var prevEl = sel.anchorNode.previousSibling;
526					while (prevEl !== null) {
527						parentElOffset += prevEl.textContent.length;
528						prevEl = prevEl.previousSibling;
529					}
530					if (context.substring(parentElOffset, parentElOffset + selection.length) == selection) {
531						return parentElOffset;
532					}
533				}
534				if (backwards && context.substring(sel.focusOffset, sel.focusOffset + selection.length) == selection) {
535					return sel.anchorOffset;
536				}
537				return -1;
538			};
539
540			var getExtendedSelection = function (limit, nodeExtensions) {
541
542				limit = parseInt(limit) || 40;
543				nodeExtensions = nodeExtensions || {left: '', right: ''};
544				var i = 0, selContent, selEndNode = sel.focusNode, selEndOffset = sel.focusOffset;
545
546				while (i <= limit) {
547
548					if ((selContent = stringifyContent(sel.toString().trim())).length >= maxContextLength || isSubstrUnique(selContent, context)) {
549						return selContent;
550					}
551
552					// only even iteration
553					if (i % 2 == 0 && sel.anchorOffset > 0 || nodeExtensions.left.length && i < limit / 2) {
554						// reset
555						if (backwards) {
556							sel.collapseToEnd();
557						} else {
558							sel.collapseToStart();
559						}
560						sel.modify("move", direction[1], "character");
561						sel.extend(selEndNode, selEndOffset);
562					} else if (sel.focusOffset < sel.focusNode.length || nodeExtensions.right.length && i < limit / 2) {
563						sel.modify('extend', direction[0], 'character');
564						if (sel.focusOffset === 1) {
565							selEndNode = sel.focusNode;
566							selEndOffset = sel.focusOffset;
567						}
568					} else if (i % 2 === 0) {
569						break;
570					}
571
572					i++;
573				}
574
575				return stringifyContent(sel.toString().trim());
576			};
577
578			var getExtendedContext = function (context, element, method) {
579				var contentPrepend = '', contentAppend = '', e = element, i;
580				method = method || 'textContent';
581
582				for (i = 0; i < 20; i++) {
583					if (contentPrepend || (e = e.previousSibling) === null) {
584						break;
585					}
586
587					if ((contentPrepend = stringifyContent(e[method].trim())).length) {
588						context = contentPrepend + context;
589
590					}
591				}
592
593				// reset element
594				e = element;
595
596				for (i = 0; i < 20; i++) {
597					if (contentAppend || (e = e.nextSibling) === null) {
598						break;
599					}
600					if ((contentAppend = stringifyContent(e[method]).trim()).length) {
601						context += contentAppend;
602					} else if (context.slice(-1) !== ' ') {
603						context += ' ';
604					}
605				}
606
607				return {
608					contents  : context,
609					extensions: {
610						left : contentPrepend,
611						right: contentAppend
612					}
613				};
614			};
615
616			// check that getSelection() has a modify() method. IE has both selection APIs but no modify() method.
617			// this works on modern browsers following standards
618			if ((sel = window.getSelection()).modify) {
619				// check if there is any text selected
620				if (!sel.isCollapsed) {
621
622					/**
623					 * So the first step is to get selection extended to the boundaries of words
624					 *
625					 * e.g. if the sentence is "What a wonderful life!" and selection is "rful li",
626					 * we get "wonderful life" stored in selWord variable
627					 */
628
629					selChars = sel.toString();
630
631					// return early if no selection to work with or if its length exceeds the limit
632					if (!selChars || selChars.length > maxContextLength) {
633						return;
634					}
635
636					// here we get the nearest parent node which is common for the whole selection
637					if (sel.rangeCount) {
638						parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode;
639						while (parentEl.textContent == sel.toString()) {
640							parentEl = parentEl.parentNode;
641						}
642					}
643
644					// Detect if selection was made backwards
645					// further logic depends on it
646					var range = document.createRange();
647					range.setStart(sel.anchorNode, sel.anchorOffset);
648					range.setEnd(sel.focusNode, sel.focusOffset);
649					var backwards = range.collapsed;
650					range = null;
651
652					// save initial selection to restore in the end
653					var initialSel = {
654						focusNode   : sel.focusNode,
655						focusOffset : sel.focusOffset,
656						anchorNode  : sel.anchorNode,
657						anchorOffset: sel.anchorOffset
658					};
659
660					// modify() works on the focus of the selection (not virtually) so we manipulate it
661					var endNode = sel.focusNode, endOffset = sel.focusOffset;
662
663					// determine second char of selection and the one before last
664					// they will be our starting point for word boundaries detection
665					var direction, secondChar, oneBeforeLastChar;
666					if (backwards) {
667						direction = ['backward', 'forward'];
668						secondChar = selChars.charAt(selChars.length - 1);
669						oneBeforeLastChar = selChars.charAt(0);
670					} else {
671						direction = ['forward', 'backward'];
672						secondChar = selChars.charAt(0);
673						oneBeforeLastChar = selChars.charAt(selChars.length - 1);
674					}
675
676					// collapse the cursor to the first char
677					sel.collapse(sel.anchorNode, sel.anchorOffset);
678					// move it one char forward
679					sel.modify("move", direction[0], "character");
680
681					// if the second character was a letter or digit, move cursor another step further
682					// this way we are certain that we are in the middle of the word
683					if (null === secondChar.match(/'[\w\d]'/)) {
684						sel.modify("move", direction[0], "character");
685					}
686
687					// and now we can determine the beginning position of the word
688					sel.modify("move", direction[1], "word");
689
690					// then extend the selection up to the initial point
691					// thus assure that selection starts with the beginning of the word
692					sel.extend(endNode, endOffset);
693
694					// do the same trick with the ending--extending it precisely up to the end of the word
695					sel.modify("extend", direction[1], "character");
696					if (null === oneBeforeLastChar.match(/'[\w\d]'/)) {
697						sel.modify("extend", direction[1], "character");
698					}
699					sel.modify("extend", direction[0], "word");
700					if (!backwards && sel.focusOffset === 1) {
701						sel.modify("extend", 'backward', "character");
702					}
703
704					// since different browser extend by "word" differently and some of them extend beyond the word
705					// covering spaces and punctuation, we need to collapse the selection back so it ends with the word
706					var i = 0, lengthBefore, lengthAfter;
707					while (i < 5 && (sel.toString().slice(-1).match(/[\s\n\t]/) || '').length) {
708						lengthBefore = sel.toString().length;
709						if (backwards) {
710							endNode = sel.anchorOffset == 0 ? sel.anchorNode.previousSibling : sel.anchorNode;
711							endOffset = sel.anchorOffset == 0 ? sel.anchorNode.previousSibling.length : sel.anchorOffset;
712							sel.modify('move', 'backward', 'character');
713							sel.extend(endNode, endOffset);
714							backwards = false;
715							direction = ['forward', 'backward'];
716						} else {
717							sel.modify('extend', 'backward', 'character');
718						}
719						lengthAfter = sel.toString().length;
720
721						// workaround for WebKit quirk: undo last iteration
722						if (lengthBefore - lengthAfter > 1) {
723							sel.modify('extend', 'forward', 'character');
724							break;
725						}
726					}
727
728					// finally, we've got a modified selection which is bound to words
729					// save it to highlight it later
730					selWord = stringifyContent(sel.toString().trim());
731				}
732			}
733			// this one is for IE11
734			else if (sel = window.getSelection()) {
735				var startOffset, startNode, endNode;
736				selChars = sel.toString();
737				range = document.createRange();
738				if (range.collapsed) {
739					startNode = sel.focusNode;
740					endNode = sel.anchorNode;
741					startOffset = sel.focusOffset;
742					endOffset = sel.anchorOffset;
743				} else {
744					startNode = sel.anchorNode;
745					endNode = sel.focusNode;
746					startOffset = sel.anchorOffset;
747					endOffset = sel.focusOffset;
748				}
749
750				while (startOffset && !startNode.textContent.slice(startOffset - 1, startOffset).match(/[\s\n\t]/)) {
751					startOffset--;
752				}
753				while (endOffset < endNode.length && !endNode.textContent.slice(endOffset, endOffset + 1).match(/[\s\n\t]/)) {
754					endOffset++;
755				}
756
757				// here we get the nearest parent node which is common for the whole selection
758				if (sel.rangeCount) {
759					parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode;
760					while (parentEl.textContent == sel.toString()) {
761						parentEl = parentEl.parentNode;
762					}
763				}
764
765				selWord = stringifyContent(sel.toString().trim());
766
767				// this logic is for IE<10
768				// } else if ((sel = document.selection) && sel.type != "Control") {
769				//     var textRange = sel.createRange();
770				//
771				//      if (!textRange || textRange.text.length > maxContextLength) {
772				//      return;
773				//      }
774				//
775				//      if (textRange.text) {
776				//      selChars = textRange.text;
777				//      textRange.expand("word");
778				//      // Move the end back to not include the word's trailing space(s), if necessary
779				//      while (/\s$/.test(textRange.text)) {
780				//      textRange.moveEnd("character", -1);
781				//      }
782				//      selWord = textRange.text;
783				//      parentEl = textRange.parentNode;
784				//      }
785
786			}
787
788
789			if (typeof parentEl == 'undefined') {
790				return;
791			}
792
793			var selToFindInContext,
794				contextsToCheck = { // different browsers implement different methods, we try them by turn
795					textContent: parentEl.textContent,
796					innerText  : parentEl.innerText
797				};
798
799			textToHighlight = selWord;
800
801			for (var method in contextsToCheck) {
802				if (contextsToCheck.hasOwnProperty(method) && typeof contextsToCheck[method] != 'undefined') {
803
804					// start with counting selected word occurrences in context
805					var scope = {selection: 'word', context: 'initial'};
806					var context = stringifyContent(contextsToCheck[method].trim());
807					var selWithContext = stringifyContent(sel.toString().trim());
808					decoMistape.contextBuffer = context;
809					decoMistape.selBuffer = selWithContext;
810					var selPos; // this is what we are going to find
811					var selExactMatch = false;
812
813
814					if ((selPos = getExactSelPos(selWithContext, context)) != -1) {
815						selExactMatch = true;
816						selToFindInContext = selWithContext;
817						break;
818					}
819
820					// if there is more than one occurrence, extend the selection
821					selWithContext = getExtendedSelection(40);
822					scope.selection = 'word extended';
823
824					if ((selPos = getExactSelPos(selWithContext, context)) != -1) {
825						selExactMatch = true;
826						selToFindInContext = selWithContext;
827						break;
828					}
829
830					// if still have duplicates, extend the context and selection, and try again
831					var initialContext = context;
832					var extContext = getExtendedContext(context, parentEl, method);
833					context = extContext.contents;
834					selWithContext = getExtendedSelection(40, extContext.extensions);
835					scope.context = 'extended';
836
837					if ((selPos = getExactSelPos(selWithContext, context)) != -1) {
838						selExactMatch = true;
839						selToFindInContext = selWithContext;
840						break;
841					}
842
843					// skip to next context getting method and start over, or exit
844					if (!selWithContext) {
845						continue;
846					}
847
848					if (isSubstrUnique(selWord, selWithContext) || selWord == selChars.trim()) {
849						context = selWithContext;
850						selWithContext = selWord;
851						textToHighlight = selWord;
852						scope.selection = 'word';
853						scope.context = 'extended';
854					} else {
855						context = selWord;
856						selWithContext = selChars.trim();
857						textToHighlight = selChars.trim();
858						scope.selection = 'initial';
859						scope.context = 'word';
860					}
861
862					selPos = context.indexOf(selWithContext);
863
864					if (selPos !== -1) {
865						selToFindInContext = selWithContext;
866					} else if ((selPos = context.indexOf(selWord)) !== -1) {
867						selToFindInContext = selWord;
868					} else if ((selPos = context.indexOf(selChars)) !== -1) {
869						selToFindInContext = selChars;
870					} else {
871						continue;
872					}
873					break;
874				}
875			}
876
877			if (selToFindInContext) {
878				sel.removeAllRanges();
879			} else {
880				decoMistape.restoreInitSelection(sel, initialSel);
881				return;
882			}
883
884			if (scope.context === 'extended') {
885				context = extContext.extensions.left + initialContext + ' ' + extContext.extensions.right;
886			}
887
888			var contExcerptStartPos, contExcerptEndPos, selPosInContext, highlightedChars, previewText;
889			maxContextLength = Math.min(context.length, maxContextLength);
890
891			var truncatedContext = context;
892
893			if (context.length > maxContextLength) {
894
895				if (selPos + selToFindInContext.length / 2 < maxContextLength / 2) {
896					selPosInContext = 'beginning';
897					contExcerptStartPos = 0;
898					contExcerptEndPos = Math.max(selPos + selToFindInContext.length, context.indexOf(' ', maxContextLength - 10));
899				} else if (selPos + selToFindInContext.length / 2 > context.length - maxContextLength / 2) {
900					selPosInContext = 'end';
901					contExcerptStartPos = Math.min(selPos, context.indexOf(' ', context.length - maxContextLength + 10));
902					contExcerptEndPos = context.length;
903				} else {
904					selPosInContext = 'middle';
905					var centerPos = selPos + Math.round(selToFindInContext.length / 2);
906					contExcerptStartPos = Math.min(selPos, context.indexOf(' ', centerPos - maxContextLength / 2 - 10));
907					contExcerptEndPos = Math.max(selPos + selToFindInContext.length, context.indexOf(' ', centerPos + maxContextLength / 2 - 10));
908				}
909
910				truncatedContext = context.substring(contExcerptStartPos, contExcerptEndPos).trim();
911
912				if (selPosInContext !== 'beginning' && context.charAt(contExcerptStartPos - 1) !== '.') {
913					truncatedContext = '... ' + truncatedContext;
914				}
915				if (selPosInContext !== 'end' && context.charAt(contExcerptStartPos + contExcerptEndPos - 1) !== '.') {
916					truncatedContext = truncatedContext + ' ...';
917				}
918			}
919
920			if (isSubstrUnique(selChars, textToHighlight)) {
921				highlightedChars = textToHighlight.replace(selChars, '<span class="mistape_mistake_inner">' + selChars + '</span>')
922			} else {
923				highlightedChars = '<strong class="mistape_mistake_inner">' + textToHighlight + '</strong>';
924			}
925
926			var selWithContextHighlighted = selToFindInContext.replace(textToHighlight, '<span class="mistape_mistake_outer">
926' + highlightedChars + '</span>');
927
928			if (selExactMatch && truncatedContext === context) {
929				previewText = truncatedContext.substring(0, selPos) + selWithContextHighlighted + truncatedContext.substring(selPos + selWithContext.length) || selWithContextHighlighted;
930			} else {
931				previewText = truncatedContext.replace(selWithContext, selWithContextHighlighted) || selWithContextHighlighted;
932			}
933
934			return {
935				selection      : selChars,
936				word           : selWord,
937				replace_context: selToFindInContext,
938				context        : truncatedContext,
939				preview_text   : previewText,
940				// post_id: decoMistape.getPostId()
941			};
942		},
943
944		restoreInitSelection: function (sel, initialSel) {
945			sel.collapse(initialSel.anchorNode, initialSel.anchorOffset);
946			sel.extend(initialSel.focusNode, initialSel.focusOffset);
947		}
948	});
949
950	$(document).ready(decoMistape.onReady);
951
952})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.