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.