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