Changeset 3109820
- Timestamp:
- 06/29/2024 10:07:30 PM (21 months ago)
- Location:
- mixtape
- Files:
-
- 6 added
- 12 edited
- 1 copied
-
tags/1.2 (copied) (copied from mixtape/trunk)
-
tags/1.2/assets/js/mixtape-front.js (modified) (5 diffs)
-
tags/1.2/e2e (added)
-
tags/1.2/e2e/tests.spec.js (added)
-
tags/1.2/package-lock.json (modified) (6 diffs)
-
tags/1.2/package.json (modified) (1 diff)
-
tags/1.2/playwright.config.js (added)
-
tags/1.2/readme.md (modified) (2 diffs)
-
tags/1.2/readme.txt (modified) (2 diffs)
-
tags/1.2/src/class-mixtape-abstract.php (modified) (3 diffs)
-
trunk/assets/js/mixtape-front.js (modified) (5 diffs)
-
trunk/e2e (added)
-
trunk/e2e/tests.spec.js (added)
-
trunk/package-lock.json (modified) (6 diffs)
-
trunk/package.json (modified) (1 diff)
-
trunk/playwright.config.js (added)
-
trunk/readme.md (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/class-mixtape-abstract.php (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mixtape/tags/1.2/assets/js/mixtape-front.js
r2998174 r3109820 4 4 */ 5 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 ( 81 (nameOffset = nAgt.lastIndexOf(" ") + 1) < 82 (verOffset = nAgt.lastIndexOf("/")) 83 ) { 84 browser = nAgt.substring(nameOffset, verOffset); 85 version = nAgt.substring(verOffset + 1); 86 if (browser.toLowerCase() === browser.toUpperCase()) { 87 browser = navigator.appName; 88 } 89 } 90 // trim the version string 91 if ((ix = version.indexOf(";")) !== -1) version = version.substring(0, ix); 92 if ((ix = version.indexOf(" ")) !== -1) version = version.substring(0, ix); 93 if ((ix = version.indexOf(")")) !== -1) version = version.substring(0, ix); 94 95 majorVersion = parseInt("" + version, 10); 96 if (isNaN(majorVersion)) { 97 version = "" + parseFloat(navigator.appVersion); 98 majorVersion = parseInt(navigator.appVersion, 10); 99 } 100 101 // mobile version 102 var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer); 103 104 // cookie 105 var cookieEnabled = !!navigator.cookieEnabled; 106 107 if (typeof navigator.cookieEnabled === "undefined" && !cookieEnabled) { 108 document.cookie = "testcookie"; 109 cookieEnabled = document.cookie.indexOf("testcookie") !== -1; 110 } 111 112 // system 113 var os = unknown; 114 var clientStrings = [ 115 { s: "Windows 10", r: /(Windows 10.0|Windows NT 10.0)/ }, 116 { s: "Windows 8.1", r: /(Windows 8.1|Windows NT 6.3)/ }, 117 { s: "Windows 8", r: /(Windows 8|Windows NT 6.2)/ }, 118 { s: "Windows 7", r: /(Windows 7|Windows NT 6.1)/ }, 119 { s: "Windows Vista", r: /Windows NT 6.0/ }, 120 { s: "Windows Server 2003", r: /Windows NT 5.2/ }, 121 { s: "Windows XP", r: /(Windows NT 5.1|Windows XP)/ }, 122 { s: "Windows 2000", r: /(Windows NT 5.0|Windows 2000)/ }, 123 { s: "Windows ME", r: /(Win 9x 4.90|Windows ME)/ }, 124 { s: "Windows 98", r: /(Windows 98|Win98)/ }, 125 { s: "Windows 95", r: /(Windows 95|Win95|Windows_95)/ }, 126 { s: "Windows NT 4.0", r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ }, 127 { s: "Windows CE", r: /Windows CE/ }, 128 { s: "Windows 3.11", r: /Win16/ }, 129 { s: "Android", r: /Android/ }, 130 { s: "Open BSD", r: /OpenBSD/ }, 131 { s: "Sun OS", r: /SunOS/ }, 132 { s: "Linux", r: /(Linux|X11)/ }, 133 { s: "iOS", r: /(iPhone|iPad|iPod)/ }, 134 { s: "Mac OS X", r: /Mac OS X/ }, 135 { s: "Mac OS", r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, 136 { s: "QNX", r: /QNX/ }, 137 { s: "UNIX", r: /UNIX/ }, 138 { s: "BeOS", r: /BeOS/ }, 139 { s: "OS/2", r: /OS\/2/ }, 140 { 141 s: "Search Bot", 142 r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/, 143 }, 144 ]; 145 for (var id in clientStrings) { 146 var cs = clientStrings[id]; 147 if (cs.r.test(nAgt)) { 148 os = cs.s; 149 break; 150 } 151 } 152 153 if (/Windows/.test(os)) { 154 os = "Windows"; 155 } 156 157 var flashVersion = "no check"; 158 if (typeof swfobject !== "undefined") { 159 var fv = swfobject.getFlashPlayerVersion(); 160 if (fv.major > 0) { 161 flashVersion = fv.major + "." + fv.minor + " r" + fv.release; 162 } else { 163 flashVersion = unknown; 164 } 6 var unknown = "-"; 7 8 var screenSize = screen.width ? `${screen.width} x ${screen.height}` : ""; 9 10 var nVer = navigator.appVersion; 11 var nAgt = navigator.userAgent; 12 var browser = navigator.appName; 13 var version = `${parseFloat(navigator.appVersion)}`; 14 var majorVersion = parseInt(navigator.appVersion, 10); 15 16 var browsers = [ 17 { name: "Opera", key: "Opera", versionKey: "Version" }, 18 { name: "Opera", key: "OPR", versionKey: "Version" }, 19 { name: "UCBrowser", key: "UCBrowser", versionKey: "Version" }, 20 { name: "Microsoft Internet Explorer", key: "MSIE" }, 21 { name: "Edge", key: "Edge" }, 22 { name: "Chrome", key: "Chrome" }, 23 { name: "Safari", key: "Safari", versionKey: "Version" }, 24 { name: "Firefox", key: "Firefox" }, 25 { name: "Microsoft Internet Explorer", key: "Trident/", versionKey: "rv:" }, 26 ]; 27 28 for (let b of browsers) { 29 let verOffset = nAgt.indexOf(b.key); 30 if (verOffset !== -1) { 31 browser = b.name; 32 version = nAgt.substring(verOffset + b.key.length + 1); 33 if (b.versionKey) { 34 let versionOffset = nAgt.indexOf(b.versionKey); 35 if (versionOffset !== -1) { 36 version = nAgt.substring(versionOffset + b.versionKey.length + 1); 37 } 38 } 39 break; 165 40 } 166 41 } 42 43 let verEndings = [";", " ", ")"]; 44 for (let end of verEndings) { 45 let ix = version.indexOf(end); 46 if (ix !== -1) version = version.substring(0, ix); 47 } 48 49 majorVersion = parseInt(version, 10); 50 if (isNaN(majorVersion)) { 51 version = `${parseFloat(navigator.appVersion)}`; 52 majorVersion = parseInt(navigator.appVersion, 10); 53 } 54 55 var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer); 56 var cookieEnabled = 57 navigator.cookieEnabled ?? 58 ((document.cookie = "testcookie"), 59 document.cookie.indexOf("testcookie") !== -1); 60 61 var os = unknown; 62 var clientStrings = [ 63 { s: "Windows 10", r: /(Windows 10.0|Windows NT 10.0)/ }, 64 { s: "Windows 8.1", r: /(Windows 8.1|Windows NT 6.3)/ }, 65 { s: "Windows 8", r: /(Windows 8|Windows NT 6.2)/ }, 66 { s: "Windows 7", r: /(Windows 7|Windows NT 6.1)/ }, 67 { s: "Windows Vista", r: /Windows NT 6.0/ }, 68 { s: "Windows Server 2003", r: /Windows NT 5.2/ }, 69 { s: "Windows XP", r: /(Windows NT 5.1|Windows XP)/ }, 70 { s: "Windows 2000", r: /(Windows NT 5.0|Windows 2000)/ }, 71 { s: "Windows ME", r: /(Win 9x 4.90|Windows ME)/ }, 72 { s: "Windows 98", r: /(Windows 98|Win98)/ }, 73 { s: "Windows 95", r: /(Windows 95|Win95|Windows_95)/ }, 74 { s: "Windows NT 4.0", r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ }, 75 { s: "Windows CE", r: /Windows CE/ }, 76 { s: "Windows 3.11", r: /Win16/ }, 77 { s: "Android", r: /Android/ }, 78 { s: "Open BSD", r: /OpenBSD/ }, 79 { s: "Sun OS", r: /SunOS/ }, 80 { s: "Linux", r: /(Linux|X11)/ }, 81 { s: "iOS", r: /(iPhone|iPad|iPod)/ }, 82 { s: "Mac OS X", r: /Mac OS X/ }, 83 { s: "Mac OS", r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, 84 { s: "QNX", r: /QNX/ }, 85 { s: "UNIX", r: /UNIX/ }, 86 { s: "BeOS", r: /BeOS/ }, 87 { s: "OS/2", r: /OS\/2/ }, 88 { 89 s: "Search Bot", 90 r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/, 91 }, 92 ]; 93 94 for (let cs of clientStrings) { 95 if (cs.r.test(nAgt)) { 96 os = cs.s; 97 break; 98 } 99 } 100 101 if (/Windows/.test(os)) os = "Windows"; 167 102 168 103 window.jscd = { … … 174 109 os: os, 175 110 cookies: cookieEnabled, 176 flashVersion: flashVersion,177 111 }; 178 112 })(window); 179 113 180 /**181 * dialogFx.js v1.0.0182 * http://www.codrops.com183 *184 * Licensed under the MIT license.185 * http://www.opensource.org/licenses/mit-license.php186 *187 * Copyright 2014, Codrops188 * http://www.codrops.com189 */190 191 114 (function (window) { 192 115 "use strict"; 193 116 194 var support = { animations: Modernizr.cssanimations }, 195 animEndEventNames = { 196 WebkitAnimation: "webkitAnimationEnd", 197 OAnimation: "oAnimationEnd", 198 msAnimation: "MSAnimationEnd", 199 animation: "animationend", 200 }, 201 animEndEventName = animEndEventNames[Modernizr.prefixed("animation")], 202 onEndAnimation = function (el, callback) { 203 var onEndCallbackFn = function (ev) { 204 if (support.animations) { 205 if (ev.target !== this) return; 206 this.removeEventListener(animEndEventName, onEndCallbackFn); 117 function checkAnimationSupport() { 118 var animation = false, 119 animationstring = "animation", 120 keyframeprefix = "", 121 domPrefixes = "Webkit Moz O ms Khtml".split(" "), 122 pfx = "", 123 elm = document.createElement("div"); 124 125 if (elm.style.animationName !== undefined) { 126 animation = true; 127 } 128 129 if (animation === false) { 130 for (var i = 0; i < domPrefixes.length; i++) { 131 if (elm.style[domPrefixes[i] + "AnimationName"] !== undefined) { 132 pfx = domPrefixes[i]; 133 animationstring = pfx + "Animation"; 134 keyframeprefix = "-" + pfx.toLowerCase() + "-"; 135 animation = true; 136 break; 207 137 } 208 if (callback && typeof callback === "function") { 209 callback.call(); 210 } 211 }; 212 if (support.animations) { 213 el.addEventListener(animEndEventName, onEndCallbackFn); 214 } else { 215 onEndCallbackFn(); 216 } 138 } 139 } 140 141 return { 142 supported: animation, 143 pfx: pfx, 144 animationstring: animationstring, 145 keyframeprefix: keyframeprefix, 146 eventName: animation 147 ? pfx 148 ? pfx + "AnimationEnd" 149 : "animationend" 150 : null, 217 151 }; 152 } 153 154 var animationSupport = checkAnimationSupport(); 155 156 // Визначення події завершення анімації 157 var animEndEventNames = { 158 WebkitAnimation: "webkitAnimationEnd", 159 OAnimation: "oAnimationEnd", 160 msAnimation: "MSAnimationEnd", 161 animation: "animationend", 162 }; 163 var animEndEventName = animationSupport.eventName; 164 165 var onEndAnimation = function (el, callback) { 166 var onEndCallbackFn = function (ev) { 167 if (animationSupport.supported) { 168 if (ev.target !== this) return; 169 this.removeEventListener(animEndEventName, onEndCallbackFn); 170 } 171 if (callback && typeof callback === "function") { 172 callback.call(); 173 } 174 }; 175 if (animationSupport.supported) { 176 el.addEventListener(animEndEventName, onEndCallbackFn); 177 } else { 178 onEndCallbackFn(); 179 } 180 }; 218 181 219 182 function extend(a, b) { … … 300 263 * mixtape 301 264 */ 302 (function ($) { 303 // return if no args passed from backend 304 if (!window.mixtape) { 305 return; 306 } 307 308 var reportButton; 309 310 window.mixtape = $.extend(window.mixtape, { 311 onReady: function () { 312 mixtape.initDialogFx(); 313 314 var $dialog = $(mixtape.dlg.el); 265 266 jQuery(function ($) { 267 const Mixtape = { 268 reportButton: null, 269 270 onReady() { 271 this.initDialogFx(); 272 273 var $dialog = $(this.dlg.el); 315 274 316 275 $(document).on("click", ".mixtape_action", function () { 317 276 if ($(this).is("[data-action=send]")) { 318 277 var data; 319 320 278 if (!$dialog.data("dry-run") && (data = $dialog.data("report"))) { 321 279 if ($dialog.data("mode") === "comment") { … … 325 283 data.post_id = $(this).data("id"); 326 284 data.nonce = $dialog.data("nonce"); 327 mixtape.reportSpellError(data);285 Mixtape.reportSpellError(data); 328 286 } 329 mixtape.animateLetter();330 mixtape.hideReportButton();287 Mixtape.animateLetter(); 288 Mixtape.hideReportButton(); 331 289 } 332 290 }); 333 291 334 292 $(document).on("click", "#mixtape-close-btn", function () { 335 mixtape.dlg.toggle();293 Mixtape.dlg.toggle(); 336 294 }); 337 295 338 296 $(document).keyup(function (ev) { 339 297 if ( 298 ev.ctrlKey && 340 299 ev.keyCode === 13 && 341 ev.ctrlKey &&342 300 ev.target.nodeName.toLowerCase() !== "textarea" && 343 301 $("#mixtape_dialog.dialog--open").length === 0 344 302 ) { 345 var report = mixtape.getSelectionData(); 346 if (report) { 347 mixtape.showDialog(report); 303 var report = Mixtape.getSelectionData(); 304 if (report) Mixtape.showDialog(report); 305 } 306 }); 307 308 document.addEventListener("selectionchange", function () { 309 if ($("#mixtape_dialog.dialog--open").length == 0) { 310 var selection = window.getSelection().toString().trim(); 311 if (selection !== "") { 312 Mixtape.showReportButton(); 313 } else { 314 Mixtape.hideReportButton(); 348 315 } 349 316 } 350 317 }); 351 352 document.addEventListener("selectionchange", function () { 353 if ($("#mixtape_dialog.dialog--open").length === 0) { 354 var selection = window.getSelection().toString().trim(); 355 if (selection !== "" && window.innerWidth < 1024) { 356 mixtape.showReportButton(); 357 } else { 358 mixtape.hideReportButton(); 359 } 360 } 361 }); 362 }, 363 364 initDialogFx: function () { 365 mixtape.dlg = new DialogFx(document.getElementById("mixtape_dialog"), { 366 onOpenDialog: function (dialog) { 367 $(dialog.el).css("display", "flex"); 368 }, 369 onCloseAnimationEnd: function (dialog) { 370 $(dialog.el).css("display", "none"); 371 mixtape.resetDialog(); 372 }, 373 }); 374 }, 375 376 animateLetter: function () { 377 var dialog = $(mixtape.dlg.el), 378 content = dialog.find(".dialog__content"), 379 letterTop = dialog.find(".mixtape-letter-top"), 380 letterFront = dialog.find(".mixtape-letter-front"), 381 letterBack = dialog.find(".mixtape-letter-back"), 382 dialogWrap = dialog.find(".dialog-wrap"); 383 384 content.addClass("show-letter"); 385 386 setTimeout(function () { 387 var y = 388 letterTop.offset().top - 389 letterFront.offset().top + 390 letterTop.outerHeight(); 391 letterTop.css({ 392 bottom: Math.floor(y), 393 opacity: 1, 394 }); 395 jQuery(".mixtape-letter-back-top").hide(); 396 if (content.hasClass("with-comment")) { 397 dialogWrap.css("transform", "scaleY(0.5) scaleX(0.28)"); 398 } else { 399 dialogWrap.css("transform", "scaleY(0.5) scaleX(0.4)"); 400 } 401 setTimeout(function () { 402 if (content.hasClass("with-comment")) { 403 dialogWrap.css( 404 "transform", 405 "translateY(12%) scaleY(0.5) scaleX(0.4)" 406 ); 407 } else { 408 dialogWrap.css( 409 "transform", 410 "translateY(28%) scaleY(0.5) scaleX(0.45)" 411 ); 412 } 413 setTimeout(function () { 414 letterTop.css("z-index", "9"); 415 letterTop.addClass("close"); 416 setTimeout(function () { 417 dialogWrap.css({ 418 visibility: "hidden", 419 opacity: "0", 420 }); 421 letterFront.css("animation", "send-letter1 0.7s"); 422 letterBack.css("animation", "send-letter1 0.7s"); 423 letterTop.css("animation", "send-letter2 0.7s"); 424 setTimeout(function () { 425 mixtape.dlg.toggle(); 426 }, 400); 427 }, 400); 428 }, 400); 429 }, 300); 430 }, 400); 431 }, 432 433 showDialog: function (report) { 434 if ( 435 report.hasOwnProperty("selection") && 436 report.hasOwnProperty("context") 437 ) { 438 var $dialog = $(mixtape.dlg.el); 439 440 if ($dialog.data("mode") === "notify") { 441 mixtape.reportSpellError(report); 442 mixtape.dlg.toggle(); 443 } else { 444 $dialog.data("report", report); 445 $dialog.find("#mixtape_reported_text").html(report.preview_text); 446 mixtape.dlg.toggle(); 447 } 448 } 449 }, 450 451 resetDialog: function () { 452 var $dialog = $(mixtape.dlg.el); 453 454 if ($dialog.data("mode") != "notify") { 318 }, 319 320 getSelectionData() { 321 if (!window.getSelection) return false; 322 323 const sel = window.getSelection(); 324 if (sel.isCollapsed) return; 325 326 const selChars = sel.toString(); 327 const maxContextLength = 140; 328 329 //if (selChars.length > maxContextLength) return; 330 331 const parentEl = this._getParentElement(sel); 332 if (!parentEl) return; 333 334 const { context, selWithContext, initialSel, backwards, direction } = 335 this._prepareSelection(sel, parentEl); 336 if (!selWithContext) return; 337 338 const { selToFindInContext, truncatedContext, previewText } = 339 this._getContextAndPreviewText( 340 selWithContext, 341 context, 342 maxContextLength, 343 selChars 344 ); 345 346 return { 347 selection: selChars, 348 word: selWithContext, 349 replace_context: selToFindInContext, 350 context: truncatedContext, 351 preview_text: previewText, 352 nonce: $('input[name="mixtape_nonce"]').val(), 353 }; 354 }, 355 356 reportSpellError(data) { 357 data.action = "mixtape_report_error"; 358 $.ajax({ 359 type: "post", 360 dataType: "json", 361 url: MixtapeLocalize.ajaxurl, 362 data: data, 363 }); 364 }, 365 366 resetDialog() { 367 var $dialog = $(this.dlg.el); 368 369 if ($dialog.data("mode") !== "notify") { 455 370 $dialog.find("#mixtape_confirm_dialog").css("display", ""); 456 371 $dialog.find("#mixtape_success_dialog").remove(); 457 372 } 458 373 459 // letter460 374 $dialog.find(".dialog__content").removeClass("show-letter"); 461 375 $dialog … … 467 381 }, 468 382 469 reportSpellError: function (data) { 470 data.action = "mixtape_report_error"; 471 $.ajax({ 472 type: "post", 473 dataType: "json", 474 url: mixtape.ajaxurl, 475 data: data, 476 }); 477 }, 478 479 getSelectionData: function () { 480 // Check for existence of window.getSelection() 481 if (!window.getSelection) { 482 return false; 483 } 484 485 var parentEl, 486 sel, 383 showDialog(report) { 384 if (report.selection && report.context) { 385 var $dialog = $(this.dlg.el); 386 387 if ($dialog.data("mode") === "notify") { 388 this.reportSpellError(report); 389 this.dlg.toggle(); 390 } else { 391 $dialog.data("report", report); 392 $dialog.find("#mixtape_reported_text").html(report.preview_text); 393 this.dlg.toggle(); 394 } 395 } 396 }, 397 398 animateLetter() { 399 var dialog = $(this.dlg.el), 400 content = dialog.find(".dialog__content"), 401 letterTop = dialog.find(".mixtape-letter-top"), 402 letterFront = dialog.find(".mixtape-letter-front"), 403 letterBack = dialog.find(".mixtape-letter-back"), 404 dialogWrap = dialog.find(".dialog-wrap"); 405 406 content.addClass("show-letter"); 407 408 setTimeout(function () { 409 var y = 410 letterTop.offset().top - 411 letterFront.offset().top + 412 letterTop.outerHeight(); 413 letterTop.css({ bottom: Math.floor(y), opacity: 1 }); 414 jQuery(".mixtape-letter-back-top").hide(); 415 var scaleX = content.hasClass("with-comment") ? 0.28 : 0.4; 416 dialogWrap.css("transform", `scaleY(0.5) scaleX(${scaleX})`); 417 setTimeout(function () { 418 var translateY = content.hasClass("with-comment") ? "12%" : "28%"; 419 dialogWrap.css( 420 "transform", 421 `translateY(${translateY}) scaleY(0.5) scaleX(${scaleX + 0.05})` 422 ); 423 setTimeout(function () { 424 letterTop.css("z-index", "9").addClass("close"); 425 setTimeout(function () { 426 dialogWrap.css({ visibility: "hidden", opacity: "0" }); 427 letterFront.add(letterBack).css("animation", "send-letter1 0.7s"); 428 letterTop.css("animation", "send-letter2 0.7s"); 429 setTimeout(function () { 430 Mixtape.dlg.toggle(); 431 }, 400); 432 }, 400); 433 }, 400); 434 }, 300); 435 }, 400); 436 }, 437 438 initDialogFx() { 439 this.dlg = new DialogFx(document.getElementById("mixtape_dialog"), { 440 onOpenDialog: function (dialog) { 441 $(dialog.el).css("display", "flex"); 442 }, 443 onCloseAnimationEnd: function (dialog) { 444 $(dialog.el).css("display", "none"); 445 Mixtape.resetDialog(); 446 }, 447 }); 448 }, 449 450 _getParentElement(sel) { 451 if (sel.rangeCount) { 452 let parentEl = sel.getRangeAt(0).commonAncestorContainer; 453 while (parentEl && parentEl.nodeType !== 1) { 454 parentEl = parentEl.parentNode; 455 } 456 return parentEl; 457 } 458 return null; 459 }, 460 461 _prepareSelection(sel, parentEl) { 462 const selChars = sel.toString().trim(); 463 console.log('Initial Selection Chars:', selChars); // Логування початкового виділення 464 465 if (!selChars) { 466 return { 467 context: '', 468 selWithContext: '', 469 initialSel: {}, 470 backwards: false, 471 direction: {} 472 }; 473 } 474 475 const direction = this._determineDirection(sel); 476 console.log('Direction:', direction); // Логування напрямку 477 478 const initialSel = this._saveInitialSelection(sel); 479 console.log('Initial Selection:', initialSel); // Логування початкового стану виділення 480 481 const context = this._getContext(parentEl); 482 console.log('Context:', context); // Логування контексту 483 484 this._extendSelection(sel, direction, context, selChars); 485 486 const selWithContext = sel.toString().trim(); 487 console.log('Selection with Context:', selWithContext); // Логування виділення з контекстом 488 489 return { 490 context, 491 selWithContext, 492 initialSel, 493 backwards: direction.backwards, 494 direction, 495 }; 496 }, 497 498 _determineDirection(sel) { 499 const range = document.createRange(); 500 range.setStart(sel.anchorNode, sel.anchorOffset); 501 range.setEnd(sel.focusNode, sel.focusOffset); 502 const backwards = range.collapsed; 503 range.detach(); 504 return { 505 forward: backwards ? "backward" : "forward", 506 backward: backwards ? "forward" : "backward", 507 backwards, 508 }; 509 }, 510 511 _saveInitialSelection(sel) { 512 return { 513 focusNode: sel.focusNode, 514 focusOffset: sel.focusOffset, 515 anchorNode: sel.anchorNode, 516 anchorOffset: sel.anchorOffset, 517 }; 518 }, 519 520 _getContext(element) { 521 const context = element ? element.textContent.trim() : ''; 522 console.log('Element Context:', context); // Логування контексту елемента 523 return this._stringifyContent(context); 524 }, 525 526 _extendSelection(sel, direction, context, selChars) { 527 console.log('Before Extend - Selection:', sel.toString(), 'Direction:', direction); // Логування перед розширенням 528 529 // Зберігаємо початкове положення виділення 530 const initialRange = sel.getRangeAt(0).cloneRange(); 531 532 // Розширюємо виділення вперед 533 sel.modify("extend", direction.forward, "character"); 534 535 if (!/\w/.test(selChars.charAt(0))) { 536 sel.modify("extend", direction.forward, "character"); 537 } 538 539 sel.modify("extend", direction.backward, "word"); 540 541 if (!/\w/.test(selChars.charAt(selChars.length - 1))) { 542 sel.modify("extend", direction.backward, "character"); 543 } 544 545 const extendedSelection = sel.toString(); 546 console.log('After Extend - Selection:', extendedSelection); // Логування після розширення 547 548 // Якщо розширення виділення не дало результату, відновлюємо початкове виділення 549 if (extendedSelection.trim() === '') { 550 sel.removeAllRanges(); 551 sel.addRange(initialRange); 552 } 553 }, 554 555 _getContextAndPreviewText( 556 selWithContext, 557 context, 558 maxContextLength, 559 selChars 560 ) { 561 const selPos = this._getExactSelPos(selWithContext, context); 562 let truncatedContext = context; 563 564 if (context.length > maxContextLength) { 565 truncatedContext = this._truncateContext( 566 context, 567 selPos, 568 selWithContext.length, 569 maxContextLength 570 ); 571 } 572 573 const selWithContextHighlighted = selWithContext.replace( 487 574 selChars, 488 selWord, 489 textToHighlight, 490 maxContextLength = 140; 491 492 var stringifyContent = function (string) { 493 return typeof string === "string" 494 ? string 495 .replace(/\s*(?:(?:\r\n)+|\r+|\n+)\t*/gm, "\r\n") 496 .replace(/\s{2,}/gm, " ") 497 : ""; 575 `<span class="mixtape_mistake_inner">${selChars}</span>` 576 ); 577 578 const previewText = truncatedContext.replace( 579 selWithContext, 580 selWithContextHighlighted 581 ); 582 return { 583 selToFindInContext: selWithContext, 584 truncatedContext, 585 previewText, 498 586 }; 499 500 var isSubstrUnique = function (substr, context) { 501 if (typeof context === "undefined") { 502 context = mixtape.contextBuffer; 503 } 504 if (typeof substr === "undefined") { 505 substr = mixtape.selBuffer; 506 } 507 var split = context.split(substr); 508 var count = split.length - 1; 509 return count === 1; 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 ( 521 context.substring( 522 sel.anchorOffset, 523 sel.anchorOffset + selection.length 524 ) == selection 525 ) { 526 return sel.anchorOffset; 527 } 528 // check anchor parent element 529 var parentElOffset = sel.anchorOffset; 530 var prevEl = sel.anchorNode.previousSibling; 531 while (prevEl !== null) { 532 parentElOffset += prevEl.textContent.length; 533 prevEl = prevEl.previousSibling; 534 } 535 if ( 536 context.substring( 537 parentElOffset, 538 parentElOffset + selection.length 539 ) == selection 540 ) { 541 return parentElOffset; 542 } 543 } 544 if ( 545 backwards && 546 context.substring( 547 sel.focusOffset, 548 sel.focusOffset + selection.length 549 ) == selection 550 ) { 551 return sel.anchorOffset; 552 } 553 return -1; 554 }; 555 556 var getExtendedSelection = function (limit, nodeExtensions) { 557 limit = parseInt(limit) || 40; 558 nodeExtensions = nodeExtensions || { left: "", right: "" }; 559 var i = 0, 560 selContent, 561 selEndNode = sel.focusNode, 562 selEndOffset = sel.focusOffset; 563 564 while (i <= limit) { 565 if ( 566 (selContent = stringifyContent(sel.toString().trim())).length >= 567 maxContextLength || 568 isSubstrUnique(selContent, context) 569 ) { 570 return selContent; 571 } 572 573 // only even iteration 574 if ( 575 (i % 2 == 0 && sel.anchorOffset > 0) || 576 (nodeExtensions.left.length && i < limit / 2) 577 ) { 578 // reset 579 if (backwards) { 580 sel.collapseToEnd(); 581 } else { 582 sel.collapseToStart(); 583 } 584 sel.modify("move", direction[1], "character"); 585 sel.extend(selEndNode, selEndOffset); 586 } else if ( 587 sel.focusOffset < sel.focusNode.length || 588 (nodeExtensions.right.length && i < limit / 2) 589 ) { 590 sel.modify("extend", direction[0], "character"); 591 if (sel.focusOffset === 1) { 592 selEndNode = sel.focusNode; 593 selEndOffset = sel.focusOffset; 594 } 595 } else if (i % 2 === 0) { 596 break; 597 } 598 599 i++; 600 } 601 602 return stringifyContent(sel.toString().trim()); 603 }; 604 605 var getExtendedContext = function (context, element, method) { 606 var contentPrepend = "", 607 contentAppend = "", 608 e = element, 609 i; 610 method = method || "textContent"; 611 612 for (i = 0; i < 20; i++) { 613 if (contentPrepend || (e = e.previousSibling) === null) { 614 break; 615 } 616 617 if ((contentPrepend = stringifyContent(e[method].trim())).length) { 618 context = contentPrepend + context; 619 } 620 } 621 622 // reset element 623 e = element; 624 625 for (i = 0; i < 20; i++) { 626 if (contentAppend || (e = e.nextSibling) === null) { 627 break; 628 } 629 if ((contentAppend = stringifyContent(e[method]).trim()).length) { 630 context += contentAppend; 631 } else if (context.slice(-1) !== " ") { 632 context += " "; 633 } 634 } 635 636 return { 637 contents: context, 638 extensions: { 639 left: contentPrepend, 640 right: contentAppend, 641 }, 642 }; 643 }; 644 645 // check that getSelection() has a modify() method. IE has both selection APIs but no modify() method. 646 // this works on modern browsers following standards 647 if ((sel = window.getSelection()).modify) { 648 // check if there is any text selected 649 if (!sel.isCollapsed) { 650 /** 651 * So the first step is to get selection extended to the boundaries of words 652 * 653 * e.g. if the sentence is "What a wonderful life!" and selection is "rful li", 654 * we get "wonderful life" stored in selWord variable 655 */ 656 657 selChars = sel.toString(); 658 659 // return early if no selection to work with or if its length exceeds the limit 660 if (!selChars || selChars.length > maxContextLength) { 661 return; 662 } 663 664 // here we get the nearest parent node which is common for the whole selection 665 if (sel.rangeCount) { 666 parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode; 667 while (parentEl.textContent == sel.toString()) { 668 parentEl = parentEl.parentNode; 669 } 670 } 671 672 // Detect if selection was made backwards 673 // further logic depends on it 674 var range = document.createRange(); 675 range.setStart(sel.anchorNode, sel.anchorOffset); 676 range.setEnd(sel.focusNode, sel.focusOffset); 677 var backwards = range.collapsed; 678 range = null; 679 680 // save initial selection to restore in the end 681 var initialSel = { 682 focusNode: sel.focusNode, 683 focusOffset: sel.focusOffset, 684 anchorNode: sel.anchorNode, 685 anchorOffset: sel.anchorOffset, 686 }; 687 688 // modify() works on the focus of the selection (not virtually) so we manipulate it 689 var endNode = sel.focusNode, 690 endOffset = sel.focusOffset; 691 692 // determine second char of selection and the one before last 693 // they will be our starting point for word boundaries detection 694 var direction, secondChar, oneBeforeLastChar; 695 if (backwards) { 696 direction = ["backward", "forward"]; 697 secondChar = selChars.charAt(selChars.length - 1); 698 oneBeforeLastChar = selChars.charAt(0); 699 } else { 700 direction = ["forward", "backward"]; 701 secondChar = selChars.charAt(0); 702 oneBeforeLastChar = selChars.charAt(selChars.length - 1); 703 } 704 705 // collapse the cursor to the first char 706 sel.collapse(sel.anchorNode, sel.anchorOffset); 707 // move it one char forward 708 sel.modify("move", direction[0], "character"); 709 710 // if the second character was a letter or digit, move cursor another step further 711 // this way we are certain that we are in the middle of the word 712 if (null === secondChar.match(/'[\w\d]'/)) { 713 sel.modify("move", direction[0], "character"); 714 } 715 716 // and now we can determine the beginning position of the word 717 sel.modify("move", direction[1], "word"); 718 719 // then extend the selection up to the initial point 720 // thus assure that selection starts with the beginning of the word 721 sel.extend(endNode, endOffset); 722 723 // do the same trick with the ending--extending it precisely up to the end of the word 724 sel.modify("extend", direction[1], "character"); 725 if (null === oneBeforeLastChar.match(/'[\w\d]'/)) { 726 sel.modify("extend", direction[1], "character"); 727 } 728 sel.modify("extend", direction[0], "word"); 729 if (!backwards && sel.focusOffset === 1) { 730 sel.modify("extend", "backward", "character"); 731 } 732 733 // since different browser extend by "word" differently and some of them extend beyond the word 734 // covering spaces and punctuation, we need to collapse the selection back so it ends with the word 735 var i = 0, 736 lengthBefore, 737 lengthAfter; 738 while ( 739 i < 5 && 740 ( 741 sel 742 .toString() 743 .slice(-1) 744 .match(/[\s\n\t]/) || "" 745 ).length 746 ) { 747 lengthBefore = sel.toString().length; 748 if (backwards) { 749 endNode = 750 sel.anchorOffset == 0 751 ? sel.anchorNode.previousSibling 752 : sel.anchorNode; 753 endOffset = 754 sel.anchorOffset == 0 755 ? sel.anchorNode.previousSibling.length 756 : sel.anchorOffset; 757 sel.modify("move", "backward", "character"); 758 sel.extend(endNode, endOffset); 759 backwards = false; 760 direction = ["forward", "backward"]; 761 } else { 762 sel.modify("extend", "backward", "character"); 763 } 764 lengthAfter = sel.toString().length; 765 766 // workaround for WebKit quirk: undo last iteration 767 if (lengthBefore - lengthAfter > 1) { 768 sel.modify("extend", "forward", "character"); 769 break; 770 } 771 } 772 773 // finally, we've got a modified selection which is bound to words 774 // save it to highlight it later 775 selWord = stringifyContent(sel.toString().trim()); 776 } 777 } 778 // this one is for IE11 779 else if ((sel = window.getSelection())) { 780 var startOffset, startNode, endNode; 781 selChars = sel.toString(); 782 range = document.createRange(); 783 if (range.collapsed) { 784 startNode = sel.focusNode; 785 endNode = sel.anchorNode; 786 startOffset = sel.focusOffset; 787 endOffset = sel.anchorOffset; 788 } else { 789 startNode = sel.anchorNode; 790 endNode = sel.focusNode; 791 startOffset = sel.anchorOffset; 792 endOffset = sel.focusOffset; 793 } 794 795 while ( 796 startOffset && 797 !startNode.textContent 798 .slice(startOffset - 1, startOffset) 799 .match(/[\s\n\t]/) 800 ) { 801 startOffset--; 802 } 803 while ( 804 endOffset < endNode.length && 805 !endNode.textContent.slice(endOffset, endOffset + 1).match(/[\s\n\t]/) 806 ) { 807 endOffset++; 808 } 809 810 // here we get the nearest parent node which is common for the whole selection 811 if (sel.rangeCount) { 812 parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode; 813 while (parentEl.textContent == sel.toString()) { 814 parentEl = parentEl.parentNode; 815 } 816 } 817 818 selWord = stringifyContent(sel.toString().trim()); 819 820 // this logic is for IE<10 821 // } else if ((sel = document.selection) && sel.type != "Control") { 822 // var textRange = sel.createRange(); 823 // 824 // if (!textRange || textRange.text.length > maxContextLength) { 825 // return; 826 // } 827 // 828 // if (textRange.text) { 829 // selChars = textRange.text; 830 // textRange.expand("word"); 831 // // Move the end back to not include the word's trailing space(s), if necessary 832 // while (/\s$/.test(textRange.text)) { 833 // textRange.moveEnd("character", -1); 834 // } 835 // selWord = textRange.text; 836 // parentEl = textRange.parentNode; 837 // } 838 } 839 840 if (typeof parentEl == "undefined") { 841 return; 842 } 843 844 var selToFindInContext, 845 contextsToCheck = { 846 // different browsers implement different methods, we try them by turn 847 textContent: parentEl.textContent, 848 innerText: parentEl.innerText, 849 }; 850 851 textToHighlight = selWord; 852 853 for (var method in contextsToCheck) { 854 if ( 855 contextsToCheck.hasOwnProperty(method) && 856 typeof contextsToCheck[method] != "undefined" 857 ) { 858 // start with counting selected word occurrences in context 859 var scope = { selection: "word", context: "initial" }; 860 var context = stringifyContent(contextsToCheck[method].trim()); 861 var selWithContext = stringifyContent(sel.toString().trim()); 862 mixtape.contextBuffer = context; 863 mixtape.selBuffer = selWithContext; 864 var selPos; // this is what we are going to find 865 var selExactMatch = false; 866 867 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 868 selExactMatch = true; 869 selToFindInContext = selWithContext; 870 break; 871 } 872 873 // if there is more than one occurrence, extend the selection 874 selWithContext = getExtendedSelection(40); 875 scope.selection = "word extended"; 876 877 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 878 selExactMatch = true; 879 selToFindInContext = selWithContext; 880 break; 881 } 882 883 // if still have duplicates, extend the context and selection, and try again 884 var initialContext = context; 885 var extContext = getExtendedContext(context, parentEl, method); 886 context = extContext.contents; 887 selWithContext = getExtendedSelection(40, extContext.extensions); 888 scope.context = "extended"; 889 890 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 891 selExactMatch = true; 892 selToFindInContext = selWithContext; 893 break; 894 } 895 896 // skip to next context getting method and start over, or exit 897 if (!selWithContext) { 898 continue; 899 } 900 901 if ( 902 isSubstrUnique(selWord, selWithContext) || 903 selWord == selChars.trim() 904 ) { 905 context = selWithContext; 906 selWithContext = selWord; 907 textToHighlight = selWord; 908 scope.selection = "word"; 909 scope.context = "extended"; 910 } else { 911 context = selWord; 912 selWithContext = selChars.trim(); 913 textToHighlight = selChars.trim(); 914 scope.selection = "initial"; 915 scope.context = "word"; 916 } 917 918 selPos = context.indexOf(selWithContext); 919 920 if (selPos !== -1) { 921 selToFindInContext = selWithContext; 922 } else if ((selPos = context.indexOf(selWord)) !== -1) { 923 selToFindInContext = selWord; 924 } else if ((selPos = context.indexOf(selChars)) !== -1) { 925 selToFindInContext = selChars; 926 } else { 927 continue; 928 } 929 break; 930 } 931 } 932 933 if (selToFindInContext) { 934 sel.removeAllRanges(); 935 } else { 936 mixtape.restoreInitSelection(sel, initialSel); 937 return; 938 } 939 940 if (scope.context === "extended") { 941 context = 942 extContext.extensions.left + 943 initialContext + 944 " " + 945 extContext.extensions.right; 946 } 947 948 var contExcerptStartPos, 949 contExcerptEndPos, 950 selPosInContext, 951 highlightedChars, 952 previewText; 953 maxContextLength = Math.min(context.length, maxContextLength); 954 955 var truncatedContext = context; 956 957 if (context.length > maxContextLength) { 958 if (selPos + selToFindInContext.length / 2 < maxContextLength / 2) { 959 selPosInContext = "beginning"; 960 contExcerptStartPos = 0; 961 contExcerptEndPos = Math.max( 962 selPos + selToFindInContext.length, 963 context.indexOf(" ", maxContextLength - 10) 964 ); 965 } else if ( 966 selPos + selToFindInContext.length / 2 > 967 context.length - maxContextLength / 2 968 ) { 969 selPosInContext = "end"; 970 contExcerptStartPos = Math.min( 971 selPos, 972 context.indexOf(" ", context.length - maxContextLength + 10) 973 ); 974 contExcerptEndPos = context.length; 975 } else { 976 selPosInContext = "middle"; 977 var centerPos = selPos + Math.round(selToFindInContext.length / 2); 978 contExcerptStartPos = Math.min( 979 selPos, 980 context.indexOf(" ", centerPos - maxContextLength / 2 - 10) 981 ); 982 contExcerptEndPos = Math.max( 983 selPos + selToFindInContext.length, 984 context.indexOf(" ", centerPos + maxContextLength / 2 - 10) 985 ); 986 } 987 988 truncatedContext = context 989 .substring(contExcerptStartPos, contExcerptEndPos) 990 .trim(); 991 992 if ( 993 selPosInContext !== "beginning" && 994 context.charAt(contExcerptStartPos - 1) !== "." 995 ) { 996 truncatedContext = "... " + truncatedContext; 997 } 998 if ( 999 selPosInContext !== "end" && 1000 context.charAt(contExcerptStartPos + contExcerptEndPos - 1) !== "." 1001 ) { 1002 truncatedContext = truncatedContext + " ..."; 1003 } 1004 } 1005 1006 if (isSubstrUnique(selChars, textToHighlight)) { 1007 highlightedChars = textToHighlight.replace( 1008 selChars, 1009 '<span class="mixtape_mistake_inner">' + selChars + "</span>" 1010 ); 1011 } else { 1012 highlightedChars = 1013 '<strong class="mixtape_mistake_inner">' + 1014 textToHighlight + 1015 "</strong>"; 1016 } 1017 1018 var selWithContextHighlighted = selToFindInContext.replace( 1019 textToHighlight, 1020 '<span class="mixtape_mistake_outer">' + highlightedChars + "</span>" 587 }, 588 589 _getExactSelPos(selection, context) { 590 return context.indexOf(selection); 591 }, 592 593 _truncateContext(context, selPos, selLength, maxContextLength) { 594 let start = Math.max(0, selPos - Math.floor(maxContextLength / 2)); 595 let end = Math.min(context.length, start + maxContextLength); 596 597 if (start > 0) { 598 start = context.lastIndexOf(" ", start) + 1; 599 } 600 if (end < context.length) { 601 end = context.indexOf(" ", end); 602 } 603 604 return ( 605 (start > 0 ? "..." : "") + 606 context.slice(start, end) + 607 (end < context.length ? "..." : "") 1021 608 ); 1022 1023 if (selExactMatch && truncatedContext === context) { 1024 previewText = 1025 truncatedContext.substring(0, selPos) + 1026 selWithContextHighlighted + 1027 truncatedContext.substring(selPos + selWithContext.length) || 1028 selWithContextHighlighted; 1029 } else { 1030 previewText = 1031 truncatedContext.replace(selWithContext, selWithContextHighlighted) || 1032 selWithContextHighlighted; 1033 } 1034 1035 return { 1036 selection: selChars, 1037 word: selWord, 1038 replace_context: selToFindInContext, 1039 context: truncatedContext, 1040 preview_text: previewText, 1041 nonce: $('input[name="mixtape_nonce"]').val(), 1042 // post_id: mixtape.getPostId() 1043 }; 1044 }, 1045 1046 restoreInitSelection: function (sel, initialSel) { 609 }, 610 611 _stringifyContent(string) { 612 return typeof string === "string" 613 ? string.replace(/\s+/g, " ").trim() 614 : ""; 615 }, 616 617 restoreInitSelection(sel, initialSel) { 1047 618 sel.collapse(initialSel.anchorNode, initialSel.anchorOffset); 1048 619 sel.extend(initialSel.focusNode, initialSel.focusOffset); 1049 620 }, 1050 621 1051 showReportButton : function() {1052 if (! reportButton) {1053 reportButton = $("<button>")1054 .text( mixtape.reportError)622 showReportButton() { 623 if (!this.reportButton) { 624 this.reportButton = $("<button>") 625 .text(MixtapeLocalize.reportError) 1055 626 .addClass("mixtape-report-button") 1056 .on("click", function (){1057 const report = mixtape.getSelectionData();627 .on("click", () => { 628 const report = Mixtape.getSelectionData(); 1058 629 if (report) { 1059 mixtape.showDialog(report); 630 this.showDialog(report); 631 } else { 632 console.error("No report generated"); 1060 633 } 1061 634 }); 1062 635 1063 $("body").append( reportButton);636 $("body").append(this.reportButton); 1064 637 } 1065 638 1066 639 const selection = window.getSelection(); 640 if (selection.rangeCount === 0) { 641 console.error("No selection range found"); 642 return; 643 } 644 1067 645 const range = selection.getRangeAt(0); 1068 646 const rect = range.getBoundingClientRect(); 1069 1070 const buttonHeight = reportButton.outerHeight(); 1071 647 const buttonHeight = this.reportButton.outerHeight(); 1072 648 const topPosition = rect.top + window.scrollY - 20 - buttonHeight; 1073 649 1074 reportButton.css({1075 top: topPosition + "px",1076 left: rect.left + window.scrollX + "px",650 this.reportButton.css({ 651 top: `${topPosition}px`, 652 left: `${rect.left + window.scrollX}px`, 1077 653 position: "absolute", 1078 654 }); 1079 655 }, 1080 656 1081 hideReportButton: function () { 1082 if (reportButton) { 1083 reportButton.remove(); 1084 reportButton = null; 1085 } 1086 }, 657 hideReportButton() { 658 if (this.reportButton) { 659 this.reportButton.remove(); 660 this.reportButton = null; 661 } 662 }, 663 664 init() { 665 this.onReady(); 666 }, 667 }; 668 669 $(document).ready(function () { 670 Mixtape.init(); 1087 671 }); 1088 1089 $(document).ready(mixtape.onReady); 1090 })(jQuery); 672 }); -
mixtape/tags/1.2/package-lock.json
r2994803 r3109820 9 9 "version": "1.0.0", 10 10 "devDependencies": { 11 "@playwright/test": "^1.45.0", 12 "@types/node": "^20.14.9", 11 13 "gulp": "^4.0.2", 12 14 "gulp-phpcs": "^3.1.0", 13 15 "gulp-wp-pot": "^2.5.0" 16 } 17 }, 18 "node_modules/@playwright/test": { 19 "version": "1.45.0", 20 "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", 21 "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", 22 "dev": true, 23 "dependencies": { 24 "playwright": "1.45.0" 25 }, 26 "bin": { 27 "playwright": "cli.js" 28 }, 29 "engines": { 30 "node": ">=18" 31 } 32 }, 33 "node_modules/@types/node": { 34 "version": "20.14.9", 35 "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", 36 "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", 37 "dev": true, 38 "dependencies": { 39 "undici-types": "~5.26.4" 14 40 } 15 41 }, … … 2859 2885 } 2860 2886 }, 2887 "node_modules/playwright": { 2888 "version": "1.45.0", 2889 "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", 2890 "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", 2891 "dev": true, 2892 "dependencies": { 2893 "playwright-core": "1.45.0" 2894 }, 2895 "bin": { 2896 "playwright": "cli.js" 2897 }, 2898 "engines": { 2899 "node": ">=18" 2900 }, 2901 "optionalDependencies": { 2902 "fsevents": "2.3.2" 2903 } 2904 }, 2905 "node_modules/playwright-core": { 2906 "version": "1.45.0", 2907 "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", 2908 "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", 2909 "dev": true, 2910 "bin": { 2911 "playwright-core": "cli.js" 2912 }, 2913 "engines": { 2914 "node": ">=18" 2915 } 2916 }, 2917 "node_modules/playwright/node_modules/fsevents": { 2918 "version": "2.3.2", 2919 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 2920 "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 2921 "dev": true, 2922 "hasInstallScript": true, 2923 "optional": true, 2924 "os": [ 2925 "darwin" 2926 ], 2927 "engines": { 2928 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2929 } 2930 }, 2861 2931 "node_modules/plugin-error": { 2862 2932 "version": "1.0.1", … … 3820 3890 "node": ">= 0.10" 3821 3891 } 3892 }, 3893 "node_modules/undici-types": { 3894 "version": "5.26.5", 3895 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 3896 "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 3897 "dev": true 3822 3898 }, 3823 3899 "node_modules/union-value": { … … 4141 4217 }, 4142 4218 "dependencies": { 4219 "@playwright/test": { 4220 "version": "1.45.0", 4221 "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", 4222 "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", 4223 "dev": true, 4224 "requires": { 4225 "playwright": "1.45.0" 4226 } 4227 }, 4228 "@types/node": { 4229 "version": "20.14.9", 4230 "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", 4231 "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", 4232 "dev": true, 4233 "requires": { 4234 "undici-types": "~5.26.4" 4235 } 4236 }, 4143 4237 "acorn": { 4144 4238 "version": "8.10.0", … … 6402 6496 } 6403 6497 }, 6498 "playwright": { 6499 "version": "1.45.0", 6500 "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", 6501 "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", 6502 "dev": true, 6503 "requires": { 6504 "fsevents": "2.3.2", 6505 "playwright-core": "1.45.0" 6506 }, 6507 "dependencies": { 6508 "fsevents": { 6509 "version": "2.3.2", 6510 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 6511 "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 6512 "dev": true, 6513 "optional": true 6514 } 6515 } 6516 }, 6517 "playwright-core": { 6518 "version": "1.45.0", 6519 "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", 6520 "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", 6521 "dev": true 6522 }, 6404 6523 "plugin-error": { 6405 6524 "version": "1.0.1", … … 7186 7305 "dev": true 7187 7306 }, 7307 "undici-types": { 7308 "version": "5.26.5", 7309 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 7310 "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 7311 "dev": true 7312 }, 7188 7313 "union-value": { 7189 7314 "version": "1.0.1", -
mixtape/tags/1.2/package.json
r2994803 r3109820 4 4 "scripts": { 5 5 "make-pot": "gulp make-pot", 6 "phpcs": "gulp phpcs" 6 "phpcs": "gulp phpcs", 7 "test": "npx playwright test --ui" 7 8 }, 8 9 "devDependencies": { 10 "@playwright/test": "^1.45.0", 11 "@types/node": "^20.14.9", 9 12 "gulp": "^4.0.2", 10 13 "gulp-phpcs": "^3.1.0", -
mixtape/tags/1.2/readme.md
r2998174 r3109820 7 7 **License:** GPLv2 or later 8 8 **Contributors:** natata7 9 **Stable tag:** 1. 19 **Stable tag:** 1.2 10 10 **License URI:** <http://www.gnu.org/licenses/gpl-2.0.html> 11 11 … … 33 33 1. Go to Settings > Mixtape and set options. 34 34 35 ### 1.2 ### 36 37 * Fix: sending reports on front on some cases. 38 35 39 ### 1.1 ### 36 40 -
mixtape/tags/1.2/readme.txt
r2998174 r3109820 5 5 Tested up to: 6.4.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 17 Stable tag: 1.2 8 8 Contributors: natata7 9 9 License: GPLv2 or later … … 28 28 2. Activate and follow the settings link in the notice you will see at the top. Tick desired checkboxes, save, and that's it! 29 29 30 == 1.2 == 31 32 * Fix: sending reports on front on some cases. 33 30 34 == 1.1 == 31 35 -
mixtape/tags/1.2/src/class-mixtape-abstract.php
r2998174 r3109820 301 301 302 302 // modernizer 303 wp_enqueue_script( 'modernizr', plugins_url( 'assets/js/modernizr.custom.js', self::$plugin_path ), array( 'jquery' ), self::$version, true );303 //wp_enqueue_script( 'modernizr', plugins_url( 'assets/js/modernizr.custom.js', self::$plugin_path ), array( 'jquery' ), self::$version, true ); 304 304 305 305 // frontend script (combined) … … 309 309 array( 310 310 'jquery', 311 'modernizr',311 //'modernizr', 312 312 ), 313 313 filemtime( plugin_dir_path( self::$plugin_path ) . '/assets/js/mixtape-front.js' ), … … 318 318 'reportError' => __( 'Report an error', 'mixtape' ), 319 319 ); 320 wp_localize_script( 'mixtape-front', ' mixtape', $localized_data );320 wp_localize_script( 'mixtape-front', 'MixtapeLocalize', $localized_data ); 321 321 } 322 322 -
mixtape/trunk/assets/js/mixtape-front.js
r2998174 r3109820 4 4 */ 5 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 ( 81 (nameOffset = nAgt.lastIndexOf(" ") + 1) < 82 (verOffset = nAgt.lastIndexOf("/")) 83 ) { 84 browser = nAgt.substring(nameOffset, verOffset); 85 version = nAgt.substring(verOffset + 1); 86 if (browser.toLowerCase() === browser.toUpperCase()) { 87 browser = navigator.appName; 88 } 89 } 90 // trim the version string 91 if ((ix = version.indexOf(";")) !== -1) version = version.substring(0, ix); 92 if ((ix = version.indexOf(" ")) !== -1) version = version.substring(0, ix); 93 if ((ix = version.indexOf(")")) !== -1) version = version.substring(0, ix); 94 95 majorVersion = parseInt("" + version, 10); 96 if (isNaN(majorVersion)) { 97 version = "" + parseFloat(navigator.appVersion); 98 majorVersion = parseInt(navigator.appVersion, 10); 99 } 100 101 // mobile version 102 var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer); 103 104 // cookie 105 var cookieEnabled = !!navigator.cookieEnabled; 106 107 if (typeof navigator.cookieEnabled === "undefined" && !cookieEnabled) { 108 document.cookie = "testcookie"; 109 cookieEnabled = document.cookie.indexOf("testcookie") !== -1; 110 } 111 112 // system 113 var os = unknown; 114 var clientStrings = [ 115 { s: "Windows 10", r: /(Windows 10.0|Windows NT 10.0)/ }, 116 { s: "Windows 8.1", r: /(Windows 8.1|Windows NT 6.3)/ }, 117 { s: "Windows 8", r: /(Windows 8|Windows NT 6.2)/ }, 118 { s: "Windows 7", r: /(Windows 7|Windows NT 6.1)/ }, 119 { s: "Windows Vista", r: /Windows NT 6.0/ }, 120 { s: "Windows Server 2003", r: /Windows NT 5.2/ }, 121 { s: "Windows XP", r: /(Windows NT 5.1|Windows XP)/ }, 122 { s: "Windows 2000", r: /(Windows NT 5.0|Windows 2000)/ }, 123 { s: "Windows ME", r: /(Win 9x 4.90|Windows ME)/ }, 124 { s: "Windows 98", r: /(Windows 98|Win98)/ }, 125 { s: "Windows 95", r: /(Windows 95|Win95|Windows_95)/ }, 126 { s: "Windows NT 4.0", r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ }, 127 { s: "Windows CE", r: /Windows CE/ }, 128 { s: "Windows 3.11", r: /Win16/ }, 129 { s: "Android", r: /Android/ }, 130 { s: "Open BSD", r: /OpenBSD/ }, 131 { s: "Sun OS", r: /SunOS/ }, 132 { s: "Linux", r: /(Linux|X11)/ }, 133 { s: "iOS", r: /(iPhone|iPad|iPod)/ }, 134 { s: "Mac OS X", r: /Mac OS X/ }, 135 { s: "Mac OS", r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, 136 { s: "QNX", r: /QNX/ }, 137 { s: "UNIX", r: /UNIX/ }, 138 { s: "BeOS", r: /BeOS/ }, 139 { s: "OS/2", r: /OS\/2/ }, 140 { 141 s: "Search Bot", 142 r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/, 143 }, 144 ]; 145 for (var id in clientStrings) { 146 var cs = clientStrings[id]; 147 if (cs.r.test(nAgt)) { 148 os = cs.s; 149 break; 150 } 151 } 152 153 if (/Windows/.test(os)) { 154 os = "Windows"; 155 } 156 157 var flashVersion = "no check"; 158 if (typeof swfobject !== "undefined") { 159 var fv = swfobject.getFlashPlayerVersion(); 160 if (fv.major > 0) { 161 flashVersion = fv.major + "." + fv.minor + " r" + fv.release; 162 } else { 163 flashVersion = unknown; 164 } 6 var unknown = "-"; 7 8 var screenSize = screen.width ? `${screen.width} x ${screen.height}` : ""; 9 10 var nVer = navigator.appVersion; 11 var nAgt = navigator.userAgent; 12 var browser = navigator.appName; 13 var version = `${parseFloat(navigator.appVersion)}`; 14 var majorVersion = parseInt(navigator.appVersion, 10); 15 16 var browsers = [ 17 { name: "Opera", key: "Opera", versionKey: "Version" }, 18 { name: "Opera", key: "OPR", versionKey: "Version" }, 19 { name: "UCBrowser", key: "UCBrowser", versionKey: "Version" }, 20 { name: "Microsoft Internet Explorer", key: "MSIE" }, 21 { name: "Edge", key: "Edge" }, 22 { name: "Chrome", key: "Chrome" }, 23 { name: "Safari", key: "Safari", versionKey: "Version" }, 24 { name: "Firefox", key: "Firefox" }, 25 { name: "Microsoft Internet Explorer", key: "Trident/", versionKey: "rv:" }, 26 ]; 27 28 for (let b of browsers) { 29 let verOffset = nAgt.indexOf(b.key); 30 if (verOffset !== -1) { 31 browser = b.name; 32 version = nAgt.substring(verOffset + b.key.length + 1); 33 if (b.versionKey) { 34 let versionOffset = nAgt.indexOf(b.versionKey); 35 if (versionOffset !== -1) { 36 version = nAgt.substring(versionOffset + b.versionKey.length + 1); 37 } 38 } 39 break; 165 40 } 166 41 } 42 43 let verEndings = [";", " ", ")"]; 44 for (let end of verEndings) { 45 let ix = version.indexOf(end); 46 if (ix !== -1) version = version.substring(0, ix); 47 } 48 49 majorVersion = parseInt(version, 10); 50 if (isNaN(majorVersion)) { 51 version = `${parseFloat(navigator.appVersion)}`; 52 majorVersion = parseInt(navigator.appVersion, 10); 53 } 54 55 var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer); 56 var cookieEnabled = 57 navigator.cookieEnabled ?? 58 ((document.cookie = "testcookie"), 59 document.cookie.indexOf("testcookie") !== -1); 60 61 var os = unknown; 62 var clientStrings = [ 63 { s: "Windows 10", r: /(Windows 10.0|Windows NT 10.0)/ }, 64 { s: "Windows 8.1", r: /(Windows 8.1|Windows NT 6.3)/ }, 65 { s: "Windows 8", r: /(Windows 8|Windows NT 6.2)/ }, 66 { s: "Windows 7", r: /(Windows 7|Windows NT 6.1)/ }, 67 { s: "Windows Vista", r: /Windows NT 6.0/ }, 68 { s: "Windows Server 2003", r: /Windows NT 5.2/ }, 69 { s: "Windows XP", r: /(Windows NT 5.1|Windows XP)/ }, 70 { s: "Windows 2000", r: /(Windows NT 5.0|Windows 2000)/ }, 71 { s: "Windows ME", r: /(Win 9x 4.90|Windows ME)/ }, 72 { s: "Windows 98", r: /(Windows 98|Win98)/ }, 73 { s: "Windows 95", r: /(Windows 95|Win95|Windows_95)/ }, 74 { s: "Windows NT 4.0", r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ }, 75 { s: "Windows CE", r: /Windows CE/ }, 76 { s: "Windows 3.11", r: /Win16/ }, 77 { s: "Android", r: /Android/ }, 78 { s: "Open BSD", r: /OpenBSD/ }, 79 { s: "Sun OS", r: /SunOS/ }, 80 { s: "Linux", r: /(Linux|X11)/ }, 81 { s: "iOS", r: /(iPhone|iPad|iPod)/ }, 82 { s: "Mac OS X", r: /Mac OS X/ }, 83 { s: "Mac OS", r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, 84 { s: "QNX", r: /QNX/ }, 85 { s: "UNIX", r: /UNIX/ }, 86 { s: "BeOS", r: /BeOS/ }, 87 { s: "OS/2", r: /OS\/2/ }, 88 { 89 s: "Search Bot", 90 r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/, 91 }, 92 ]; 93 94 for (let cs of clientStrings) { 95 if (cs.r.test(nAgt)) { 96 os = cs.s; 97 break; 98 } 99 } 100 101 if (/Windows/.test(os)) os = "Windows"; 167 102 168 103 window.jscd = { … … 174 109 os: os, 175 110 cookies: cookieEnabled, 176 flashVersion: flashVersion,177 111 }; 178 112 })(window); 179 113 180 /**181 * dialogFx.js v1.0.0182 * http://www.codrops.com183 *184 * Licensed under the MIT license.185 * http://www.opensource.org/licenses/mit-license.php186 *187 * Copyright 2014, Codrops188 * http://www.codrops.com189 */190 191 114 (function (window) { 192 115 "use strict"; 193 116 194 var support = { animations: Modernizr.cssanimations }, 195 animEndEventNames = { 196 WebkitAnimation: "webkitAnimationEnd", 197 OAnimation: "oAnimationEnd", 198 msAnimation: "MSAnimationEnd", 199 animation: "animationend", 200 }, 201 animEndEventName = animEndEventNames[Modernizr.prefixed("animation")], 202 onEndAnimation = function (el, callback) { 203 var onEndCallbackFn = function (ev) { 204 if (support.animations) { 205 if (ev.target !== this) return; 206 this.removeEventListener(animEndEventName, onEndCallbackFn); 117 function checkAnimationSupport() { 118 var animation = false, 119 animationstring = "animation", 120 keyframeprefix = "", 121 domPrefixes = "Webkit Moz O ms Khtml".split(" "), 122 pfx = "", 123 elm = document.createElement("div"); 124 125 if (elm.style.animationName !== undefined) { 126 animation = true; 127 } 128 129 if (animation === false) { 130 for (var i = 0; i < domPrefixes.length; i++) { 131 if (elm.style[domPrefixes[i] + "AnimationName"] !== undefined) { 132 pfx = domPrefixes[i]; 133 animationstring = pfx + "Animation"; 134 keyframeprefix = "-" + pfx.toLowerCase() + "-"; 135 animation = true; 136 break; 207 137 } 208 if (callback && typeof callback === "function") { 209 callback.call(); 210 } 211 }; 212 if (support.animations) { 213 el.addEventListener(animEndEventName, onEndCallbackFn); 214 } else { 215 onEndCallbackFn(); 216 } 138 } 139 } 140 141 return { 142 supported: animation, 143 pfx: pfx, 144 animationstring: animationstring, 145 keyframeprefix: keyframeprefix, 146 eventName: animation 147 ? pfx 148 ? pfx + "AnimationEnd" 149 : "animationend" 150 : null, 217 151 }; 152 } 153 154 var animationSupport = checkAnimationSupport(); 155 156 // Визначення події завершення анімації 157 var animEndEventNames = { 158 WebkitAnimation: "webkitAnimationEnd", 159 OAnimation: "oAnimationEnd", 160 msAnimation: "MSAnimationEnd", 161 animation: "animationend", 162 }; 163 var animEndEventName = animationSupport.eventName; 164 165 var onEndAnimation = function (el, callback) { 166 var onEndCallbackFn = function (ev) { 167 if (animationSupport.supported) { 168 if (ev.target !== this) return; 169 this.removeEventListener(animEndEventName, onEndCallbackFn); 170 } 171 if (callback && typeof callback === "function") { 172 callback.call(); 173 } 174 }; 175 if (animationSupport.supported) { 176 el.addEventListener(animEndEventName, onEndCallbackFn); 177 } else { 178 onEndCallbackFn(); 179 } 180 }; 218 181 219 182 function extend(a, b) { … … 300 263 * mixtape 301 264 */ 302 (function ($) { 303 // return if no args passed from backend 304 if (!window.mixtape) { 305 return; 306 } 307 308 var reportButton; 309 310 window.mixtape = $.extend(window.mixtape, { 311 onReady: function () { 312 mixtape.initDialogFx(); 313 314 var $dialog = $(mixtape.dlg.el); 265 266 jQuery(function ($) { 267 const Mixtape = { 268 reportButton: null, 269 270 onReady() { 271 this.initDialogFx(); 272 273 var $dialog = $(this.dlg.el); 315 274 316 275 $(document).on("click", ".mixtape_action", function () { 317 276 if ($(this).is("[data-action=send]")) { 318 277 var data; 319 320 278 if (!$dialog.data("dry-run") && (data = $dialog.data("report"))) { 321 279 if ($dialog.data("mode") === "comment") { … … 325 283 data.post_id = $(this).data("id"); 326 284 data.nonce = $dialog.data("nonce"); 327 mixtape.reportSpellError(data);285 Mixtape.reportSpellError(data); 328 286 } 329 mixtape.animateLetter();330 mixtape.hideReportButton();287 Mixtape.animateLetter(); 288 Mixtape.hideReportButton(); 331 289 } 332 290 }); 333 291 334 292 $(document).on("click", "#mixtape-close-btn", function () { 335 mixtape.dlg.toggle();293 Mixtape.dlg.toggle(); 336 294 }); 337 295 338 296 $(document).keyup(function (ev) { 339 297 if ( 298 ev.ctrlKey && 340 299 ev.keyCode === 13 && 341 ev.ctrlKey &&342 300 ev.target.nodeName.toLowerCase() !== "textarea" && 343 301 $("#mixtape_dialog.dialog--open").length === 0 344 302 ) { 345 var report = mixtape.getSelectionData(); 346 if (report) { 347 mixtape.showDialog(report); 303 var report = Mixtape.getSelectionData(); 304 if (report) Mixtape.showDialog(report); 305 } 306 }); 307 308 document.addEventListener("selectionchange", function () { 309 if ($("#mixtape_dialog.dialog--open").length == 0) { 310 var selection = window.getSelection().toString().trim(); 311 if (selection !== "") { 312 Mixtape.showReportButton(); 313 } else { 314 Mixtape.hideReportButton(); 348 315 } 349 316 } 350 317 }); 351 352 document.addEventListener("selectionchange", function () { 353 if ($("#mixtape_dialog.dialog--open").length === 0) { 354 var selection = window.getSelection().toString().trim(); 355 if (selection !== "" && window.innerWidth < 1024) { 356 mixtape.showReportButton(); 357 } else { 358 mixtape.hideReportButton(); 359 } 360 } 361 }); 362 }, 363 364 initDialogFx: function () { 365 mixtape.dlg = new DialogFx(document.getElementById("mixtape_dialog"), { 366 onOpenDialog: function (dialog) { 367 $(dialog.el).css("display", "flex"); 368 }, 369 onCloseAnimationEnd: function (dialog) { 370 $(dialog.el).css("display", "none"); 371 mixtape.resetDialog(); 372 }, 373 }); 374 }, 375 376 animateLetter: function () { 377 var dialog = $(mixtape.dlg.el), 378 content = dialog.find(".dialog__content"), 379 letterTop = dialog.find(".mixtape-letter-top"), 380 letterFront = dialog.find(".mixtape-letter-front"), 381 letterBack = dialog.find(".mixtape-letter-back"), 382 dialogWrap = dialog.find(".dialog-wrap"); 383 384 content.addClass("show-letter"); 385 386 setTimeout(function () { 387 var y = 388 letterTop.offset().top - 389 letterFront.offset().top + 390 letterTop.outerHeight(); 391 letterTop.css({ 392 bottom: Math.floor(y), 393 opacity: 1, 394 }); 395 jQuery(".mixtape-letter-back-top").hide(); 396 if (content.hasClass("with-comment")) { 397 dialogWrap.css("transform", "scaleY(0.5) scaleX(0.28)"); 398 } else { 399 dialogWrap.css("transform", "scaleY(0.5) scaleX(0.4)"); 400 } 401 setTimeout(function () { 402 if (content.hasClass("with-comment")) { 403 dialogWrap.css( 404 "transform", 405 "translateY(12%) scaleY(0.5) scaleX(0.4)" 406 ); 407 } else { 408 dialogWrap.css( 409 "transform", 410 "translateY(28%) scaleY(0.5) scaleX(0.45)" 411 ); 412 } 413 setTimeout(function () { 414 letterTop.css("z-index", "9"); 415 letterTop.addClass("close"); 416 setTimeout(function () { 417 dialogWrap.css({ 418 visibility: "hidden", 419 opacity: "0", 420 }); 421 letterFront.css("animation", "send-letter1 0.7s"); 422 letterBack.css("animation", "send-letter1 0.7s"); 423 letterTop.css("animation", "send-letter2 0.7s"); 424 setTimeout(function () { 425 mixtape.dlg.toggle(); 426 }, 400); 427 }, 400); 428 }, 400); 429 }, 300); 430 }, 400); 431 }, 432 433 showDialog: function (report) { 434 if ( 435 report.hasOwnProperty("selection") && 436 report.hasOwnProperty("context") 437 ) { 438 var $dialog = $(mixtape.dlg.el); 439 440 if ($dialog.data("mode") === "notify") { 441 mixtape.reportSpellError(report); 442 mixtape.dlg.toggle(); 443 } else { 444 $dialog.data("report", report); 445 $dialog.find("#mixtape_reported_text").html(report.preview_text); 446 mixtape.dlg.toggle(); 447 } 448 } 449 }, 450 451 resetDialog: function () { 452 var $dialog = $(mixtape.dlg.el); 453 454 if ($dialog.data("mode") != "notify") { 318 }, 319 320 getSelectionData() { 321 if (!window.getSelection) return false; 322 323 const sel = window.getSelection(); 324 if (sel.isCollapsed) return; 325 326 const selChars = sel.toString(); 327 const maxContextLength = 140; 328 329 //if (selChars.length > maxContextLength) return; 330 331 const parentEl = this._getParentElement(sel); 332 if (!parentEl) return; 333 334 const { context, selWithContext, initialSel, backwards, direction } = 335 this._prepareSelection(sel, parentEl); 336 if (!selWithContext) return; 337 338 const { selToFindInContext, truncatedContext, previewText } = 339 this._getContextAndPreviewText( 340 selWithContext, 341 context, 342 maxContextLength, 343 selChars 344 ); 345 346 return { 347 selection: selChars, 348 word: selWithContext, 349 replace_context: selToFindInContext, 350 context: truncatedContext, 351 preview_text: previewText, 352 nonce: $('input[name="mixtape_nonce"]').val(), 353 }; 354 }, 355 356 reportSpellError(data) { 357 data.action = "mixtape_report_error"; 358 $.ajax({ 359 type: "post", 360 dataType: "json", 361 url: MixtapeLocalize.ajaxurl, 362 data: data, 363 }); 364 }, 365 366 resetDialog() { 367 var $dialog = $(this.dlg.el); 368 369 if ($dialog.data("mode") !== "notify") { 455 370 $dialog.find("#mixtape_confirm_dialog").css("display", ""); 456 371 $dialog.find("#mixtape_success_dialog").remove(); 457 372 } 458 373 459 // letter460 374 $dialog.find(".dialog__content").removeClass("show-letter"); 461 375 $dialog … … 467 381 }, 468 382 469 reportSpellError: function (data) { 470 data.action = "mixtape_report_error"; 471 $.ajax({ 472 type: "post", 473 dataType: "json", 474 url: mixtape.ajaxurl, 475 data: data, 476 }); 477 }, 478 479 getSelectionData: function () { 480 // Check for existence of window.getSelection() 481 if (!window.getSelection) { 482 return false; 483 } 484 485 var parentEl, 486 sel, 383 showDialog(report) { 384 if (report.selection && report.context) { 385 var $dialog = $(this.dlg.el); 386 387 if ($dialog.data("mode") === "notify") { 388 this.reportSpellError(report); 389 this.dlg.toggle(); 390 } else { 391 $dialog.data("report", report); 392 $dialog.find("#mixtape_reported_text").html(report.preview_text); 393 this.dlg.toggle(); 394 } 395 } 396 }, 397 398 animateLetter() { 399 var dialog = $(this.dlg.el), 400 content = dialog.find(".dialog__content"), 401 letterTop = dialog.find(".mixtape-letter-top"), 402 letterFront = dialog.find(".mixtape-letter-front"), 403 letterBack = dialog.find(".mixtape-letter-back"), 404 dialogWrap = dialog.find(".dialog-wrap"); 405 406 content.addClass("show-letter"); 407 408 setTimeout(function () { 409 var y = 410 letterTop.offset().top - 411 letterFront.offset().top + 412 letterTop.outerHeight(); 413 letterTop.css({ bottom: Math.floor(y), opacity: 1 }); 414 jQuery(".mixtape-letter-back-top").hide(); 415 var scaleX = content.hasClass("with-comment") ? 0.28 : 0.4; 416 dialogWrap.css("transform", `scaleY(0.5) scaleX(${scaleX})`); 417 setTimeout(function () { 418 var translateY = content.hasClass("with-comment") ? "12%" : "28%"; 419 dialogWrap.css( 420 "transform", 421 `translateY(${translateY}) scaleY(0.5) scaleX(${scaleX + 0.05})` 422 ); 423 setTimeout(function () { 424 letterTop.css("z-index", "9").addClass("close"); 425 setTimeout(function () { 426 dialogWrap.css({ visibility: "hidden", opacity: "0" }); 427 letterFront.add(letterBack).css("animation", "send-letter1 0.7s"); 428 letterTop.css("animation", "send-letter2 0.7s"); 429 setTimeout(function () { 430 Mixtape.dlg.toggle(); 431 }, 400); 432 }, 400); 433 }, 400); 434 }, 300); 435 }, 400); 436 }, 437 438 initDialogFx() { 439 this.dlg = new DialogFx(document.getElementById("mixtape_dialog"), { 440 onOpenDialog: function (dialog) { 441 $(dialog.el).css("display", "flex"); 442 }, 443 onCloseAnimationEnd: function (dialog) { 444 $(dialog.el).css("display", "none"); 445 Mixtape.resetDialog(); 446 }, 447 }); 448 }, 449 450 _getParentElement(sel) { 451 if (sel.rangeCount) { 452 let parentEl = sel.getRangeAt(0).commonAncestorContainer; 453 while (parentEl && parentEl.nodeType !== 1) { 454 parentEl = parentEl.parentNode; 455 } 456 return parentEl; 457 } 458 return null; 459 }, 460 461 _prepareSelection(sel, parentEl) { 462 const selChars = sel.toString().trim(); 463 console.log('Initial Selection Chars:', selChars); // Логування початкового виділення 464 465 if (!selChars) { 466 return { 467 context: '', 468 selWithContext: '', 469 initialSel: {}, 470 backwards: false, 471 direction: {} 472 }; 473 } 474 475 const direction = this._determineDirection(sel); 476 console.log('Direction:', direction); // Логування напрямку 477 478 const initialSel = this._saveInitialSelection(sel); 479 console.log('Initial Selection:', initialSel); // Логування початкового стану виділення 480 481 const context = this._getContext(parentEl); 482 console.log('Context:', context); // Логування контексту 483 484 this._extendSelection(sel, direction, context, selChars); 485 486 const selWithContext = sel.toString().trim(); 487 console.log('Selection with Context:', selWithContext); // Логування виділення з контекстом 488 489 return { 490 context, 491 selWithContext, 492 initialSel, 493 backwards: direction.backwards, 494 direction, 495 }; 496 }, 497 498 _determineDirection(sel) { 499 const range = document.createRange(); 500 range.setStart(sel.anchorNode, sel.anchorOffset); 501 range.setEnd(sel.focusNode, sel.focusOffset); 502 const backwards = range.collapsed; 503 range.detach(); 504 return { 505 forward: backwards ? "backward" : "forward", 506 backward: backwards ? "forward" : "backward", 507 backwards, 508 }; 509 }, 510 511 _saveInitialSelection(sel) { 512 return { 513 focusNode: sel.focusNode, 514 focusOffset: sel.focusOffset, 515 anchorNode: sel.anchorNode, 516 anchorOffset: sel.anchorOffset, 517 }; 518 }, 519 520 _getContext(element) { 521 const context = element ? element.textContent.trim() : ''; 522 console.log('Element Context:', context); // Логування контексту елемента 523 return this._stringifyContent(context); 524 }, 525 526 _extendSelection(sel, direction, context, selChars) { 527 console.log('Before Extend - Selection:', sel.toString(), 'Direction:', direction); // Логування перед розширенням 528 529 // Зберігаємо початкове положення виділення 530 const initialRange = sel.getRangeAt(0).cloneRange(); 531 532 // Розширюємо виділення вперед 533 sel.modify("extend", direction.forward, "character"); 534 535 if (!/\w/.test(selChars.charAt(0))) { 536 sel.modify("extend", direction.forward, "character"); 537 } 538 539 sel.modify("extend", direction.backward, "word"); 540 541 if (!/\w/.test(selChars.charAt(selChars.length - 1))) { 542 sel.modify("extend", direction.backward, "character"); 543 } 544 545 const extendedSelection = sel.toString(); 546 console.log('After Extend - Selection:', extendedSelection); // Логування після розширення 547 548 // Якщо розширення виділення не дало результату, відновлюємо початкове виділення 549 if (extendedSelection.trim() === '') { 550 sel.removeAllRanges(); 551 sel.addRange(initialRange); 552 } 553 }, 554 555 _getContextAndPreviewText( 556 selWithContext, 557 context, 558 maxContextLength, 559 selChars 560 ) { 561 const selPos = this._getExactSelPos(selWithContext, context); 562 let truncatedContext = context; 563 564 if (context.length > maxContextLength) { 565 truncatedContext = this._truncateContext( 566 context, 567 selPos, 568 selWithContext.length, 569 maxContextLength 570 ); 571 } 572 573 const selWithContextHighlighted = selWithContext.replace( 487 574 selChars, 488 selWord, 489 textToHighlight, 490 maxContextLength = 140; 491 492 var stringifyContent = function (string) { 493 return typeof string === "string" 494 ? string 495 .replace(/\s*(?:(?:\r\n)+|\r+|\n+)\t*/gm, "\r\n") 496 .replace(/\s{2,}/gm, " ") 497 : ""; 575 `<span class="mixtape_mistake_inner">${selChars}</span>` 576 ); 577 578 const previewText = truncatedContext.replace( 579 selWithContext, 580 selWithContextHighlighted 581 ); 582 return { 583 selToFindInContext: selWithContext, 584 truncatedContext, 585 previewText, 498 586 }; 499 500 var isSubstrUnique = function (substr, context) { 501 if (typeof context === "undefined") { 502 context = mixtape.contextBuffer; 503 } 504 if (typeof substr === "undefined") { 505 substr = mixtape.selBuffer; 506 } 507 var split = context.split(substr); 508 var count = split.length - 1; 509 return count === 1; 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 ( 521 context.substring( 522 sel.anchorOffset, 523 sel.anchorOffset + selection.length 524 ) == selection 525 ) { 526 return sel.anchorOffset; 527 } 528 // check anchor parent element 529 var parentElOffset = sel.anchorOffset; 530 var prevEl = sel.anchorNode.previousSibling; 531 while (prevEl !== null) { 532 parentElOffset += prevEl.textContent.length; 533 prevEl = prevEl.previousSibling; 534 } 535 if ( 536 context.substring( 537 parentElOffset, 538 parentElOffset + selection.length 539 ) == selection 540 ) { 541 return parentElOffset; 542 } 543 } 544 if ( 545 backwards && 546 context.substring( 547 sel.focusOffset, 548 sel.focusOffset + selection.length 549 ) == selection 550 ) { 551 return sel.anchorOffset; 552 } 553 return -1; 554 }; 555 556 var getExtendedSelection = function (limit, nodeExtensions) { 557 limit = parseInt(limit) || 40; 558 nodeExtensions = nodeExtensions || { left: "", right: "" }; 559 var i = 0, 560 selContent, 561 selEndNode = sel.focusNode, 562 selEndOffset = sel.focusOffset; 563 564 while (i <= limit) { 565 if ( 566 (selContent = stringifyContent(sel.toString().trim())).length >= 567 maxContextLength || 568 isSubstrUnique(selContent, context) 569 ) { 570 return selContent; 571 } 572 573 // only even iteration 574 if ( 575 (i % 2 == 0 && sel.anchorOffset > 0) || 576 (nodeExtensions.left.length && i < limit / 2) 577 ) { 578 // reset 579 if (backwards) { 580 sel.collapseToEnd(); 581 } else { 582 sel.collapseToStart(); 583 } 584 sel.modify("move", direction[1], "character"); 585 sel.extend(selEndNode, selEndOffset); 586 } else if ( 587 sel.focusOffset < sel.focusNode.length || 588 (nodeExtensions.right.length && i < limit / 2) 589 ) { 590 sel.modify("extend", direction[0], "character"); 591 if (sel.focusOffset === 1) { 592 selEndNode = sel.focusNode; 593 selEndOffset = sel.focusOffset; 594 } 595 } else if (i % 2 === 0) { 596 break; 597 } 598 599 i++; 600 } 601 602 return stringifyContent(sel.toString().trim()); 603 }; 604 605 var getExtendedContext = function (context, element, method) { 606 var contentPrepend = "", 607 contentAppend = "", 608 e = element, 609 i; 610 method = method || "textContent"; 611 612 for (i = 0; i < 20; i++) { 613 if (contentPrepend || (e = e.previousSibling) === null) { 614 break; 615 } 616 617 if ((contentPrepend = stringifyContent(e[method].trim())).length) { 618 context = contentPrepend + context; 619 } 620 } 621 622 // reset element 623 e = element; 624 625 for (i = 0; i < 20; i++) { 626 if (contentAppend || (e = e.nextSibling) === null) { 627 break; 628 } 629 if ((contentAppend = stringifyContent(e[method]).trim()).length) { 630 context += contentAppend; 631 } else if (context.slice(-1) !== " ") { 632 context += " "; 633 } 634 } 635 636 return { 637 contents: context, 638 extensions: { 639 left: contentPrepend, 640 right: contentAppend, 641 }, 642 }; 643 }; 644 645 // check that getSelection() has a modify() method. IE has both selection APIs but no modify() method. 646 // this works on modern browsers following standards 647 if ((sel = window.getSelection()).modify) { 648 // check if there is any text selected 649 if (!sel.isCollapsed) { 650 /** 651 * So the first step is to get selection extended to the boundaries of words 652 * 653 * e.g. if the sentence is "What a wonderful life!" and selection is "rful li", 654 * we get "wonderful life" stored in selWord variable 655 */ 656 657 selChars = sel.toString(); 658 659 // return early if no selection to work with or if its length exceeds the limit 660 if (!selChars || selChars.length > maxContextLength) { 661 return; 662 } 663 664 // here we get the nearest parent node which is common for the whole selection 665 if (sel.rangeCount) { 666 parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode; 667 while (parentEl.textContent == sel.toString()) { 668 parentEl = parentEl.parentNode; 669 } 670 } 671 672 // Detect if selection was made backwards 673 // further logic depends on it 674 var range = document.createRange(); 675 range.setStart(sel.anchorNode, sel.anchorOffset); 676 range.setEnd(sel.focusNode, sel.focusOffset); 677 var backwards = range.collapsed; 678 range = null; 679 680 // save initial selection to restore in the end 681 var initialSel = { 682 focusNode: sel.focusNode, 683 focusOffset: sel.focusOffset, 684 anchorNode: sel.anchorNode, 685 anchorOffset: sel.anchorOffset, 686 }; 687 688 // modify() works on the focus of the selection (not virtually) so we manipulate it 689 var endNode = sel.focusNode, 690 endOffset = sel.focusOffset; 691 692 // determine second char of selection and the one before last 693 // they will be our starting point for word boundaries detection 694 var direction, secondChar, oneBeforeLastChar; 695 if (backwards) { 696 direction = ["backward", "forward"]; 697 secondChar = selChars.charAt(selChars.length - 1); 698 oneBeforeLastChar = selChars.charAt(0); 699 } else { 700 direction = ["forward", "backward"]; 701 secondChar = selChars.charAt(0); 702 oneBeforeLastChar = selChars.charAt(selChars.length - 1); 703 } 704 705 // collapse the cursor to the first char 706 sel.collapse(sel.anchorNode, sel.anchorOffset); 707 // move it one char forward 708 sel.modify("move", direction[0], "character"); 709 710 // if the second character was a letter or digit, move cursor another step further 711 // this way we are certain that we are in the middle of the word 712 if (null === secondChar.match(/'[\w\d]'/)) { 713 sel.modify("move", direction[0], "character"); 714 } 715 716 // and now we can determine the beginning position of the word 717 sel.modify("move", direction[1], "word"); 718 719 // then extend the selection up to the initial point 720 // thus assure that selection starts with the beginning of the word 721 sel.extend(endNode, endOffset); 722 723 // do the same trick with the ending--extending it precisely up to the end of the word 724 sel.modify("extend", direction[1], "character"); 725 if (null === oneBeforeLastChar.match(/'[\w\d]'/)) { 726 sel.modify("extend", direction[1], "character"); 727 } 728 sel.modify("extend", direction[0], "word"); 729 if (!backwards && sel.focusOffset === 1) { 730 sel.modify("extend", "backward", "character"); 731 } 732 733 // since different browser extend by "word" differently and some of them extend beyond the word 734 // covering spaces and punctuation, we need to collapse the selection back so it ends with the word 735 var i = 0, 736 lengthBefore, 737 lengthAfter; 738 while ( 739 i < 5 && 740 ( 741 sel 742 .toString() 743 .slice(-1) 744 .match(/[\s\n\t]/) || "" 745 ).length 746 ) { 747 lengthBefore = sel.toString().length; 748 if (backwards) { 749 endNode = 750 sel.anchorOffset == 0 751 ? sel.anchorNode.previousSibling 752 : sel.anchorNode; 753 endOffset = 754 sel.anchorOffset == 0 755 ? sel.anchorNode.previousSibling.length 756 : sel.anchorOffset; 757 sel.modify("move", "backward", "character"); 758 sel.extend(endNode, endOffset); 759 backwards = false; 760 direction = ["forward", "backward"]; 761 } else { 762 sel.modify("extend", "backward", "character"); 763 } 764 lengthAfter = sel.toString().length; 765 766 // workaround for WebKit quirk: undo last iteration 767 if (lengthBefore - lengthAfter > 1) { 768 sel.modify("extend", "forward", "character"); 769 break; 770 } 771 } 772 773 // finally, we've got a modified selection which is bound to words 774 // save it to highlight it later 775 selWord = stringifyContent(sel.toString().trim()); 776 } 777 } 778 // this one is for IE11 779 else if ((sel = window.getSelection())) { 780 var startOffset, startNode, endNode; 781 selChars = sel.toString(); 782 range = document.createRange(); 783 if (range.collapsed) { 784 startNode = sel.focusNode; 785 endNode = sel.anchorNode; 786 startOffset = sel.focusOffset; 787 endOffset = sel.anchorOffset; 788 } else { 789 startNode = sel.anchorNode; 790 endNode = sel.focusNode; 791 startOffset = sel.anchorOffset; 792 endOffset = sel.focusOffset; 793 } 794 795 while ( 796 startOffset && 797 !startNode.textContent 798 .slice(startOffset - 1, startOffset) 799 .match(/[\s\n\t]/) 800 ) { 801 startOffset--; 802 } 803 while ( 804 endOffset < endNode.length && 805 !endNode.textContent.slice(endOffset, endOffset + 1).match(/[\s\n\t]/) 806 ) { 807 endOffset++; 808 } 809 810 // here we get the nearest parent node which is common for the whole selection 811 if (sel.rangeCount) { 812 parentEl = sel.getRangeAt(0).commonAncestorContainer.parentNode; 813 while (parentEl.textContent == sel.toString()) { 814 parentEl = parentEl.parentNode; 815 } 816 } 817 818 selWord = stringifyContent(sel.toString().trim()); 819 820 // this logic is for IE<10 821 // } else if ((sel = document.selection) && sel.type != "Control") { 822 // var textRange = sel.createRange(); 823 // 824 // if (!textRange || textRange.text.length > maxContextLength) { 825 // return; 826 // } 827 // 828 // if (textRange.text) { 829 // selChars = textRange.text; 830 // textRange.expand("word"); 831 // // Move the end back to not include the word's trailing space(s), if necessary 832 // while (/\s$/.test(textRange.text)) { 833 // textRange.moveEnd("character", -1); 834 // } 835 // selWord = textRange.text; 836 // parentEl = textRange.parentNode; 837 // } 838 } 839 840 if (typeof parentEl == "undefined") { 841 return; 842 } 843 844 var selToFindInContext, 845 contextsToCheck = { 846 // different browsers implement different methods, we try them by turn 847 textContent: parentEl.textContent, 848 innerText: parentEl.innerText, 849 }; 850 851 textToHighlight = selWord; 852 853 for (var method in contextsToCheck) { 854 if ( 855 contextsToCheck.hasOwnProperty(method) && 856 typeof contextsToCheck[method] != "undefined" 857 ) { 858 // start with counting selected word occurrences in context 859 var scope = { selection: "word", context: "initial" }; 860 var context = stringifyContent(contextsToCheck[method].trim()); 861 var selWithContext = stringifyContent(sel.toString().trim()); 862 mixtape.contextBuffer = context; 863 mixtape.selBuffer = selWithContext; 864 var selPos; // this is what we are going to find 865 var selExactMatch = false; 866 867 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 868 selExactMatch = true; 869 selToFindInContext = selWithContext; 870 break; 871 } 872 873 // if there is more than one occurrence, extend the selection 874 selWithContext = getExtendedSelection(40); 875 scope.selection = "word extended"; 876 877 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 878 selExactMatch = true; 879 selToFindInContext = selWithContext; 880 break; 881 } 882 883 // if still have duplicates, extend the context and selection, and try again 884 var initialContext = context; 885 var extContext = getExtendedContext(context, parentEl, method); 886 context = extContext.contents; 887 selWithContext = getExtendedSelection(40, extContext.extensions); 888 scope.context = "extended"; 889 890 if ((selPos = getExactSelPos(selWithContext, context)) != -1) { 891 selExactMatch = true; 892 selToFindInContext = selWithContext; 893 break; 894 } 895 896 // skip to next context getting method and start over, or exit 897 if (!selWithContext) { 898 continue; 899 } 900 901 if ( 902 isSubstrUnique(selWord, selWithContext) || 903 selWord == selChars.trim() 904 ) { 905 context = selWithContext; 906 selWithContext = selWord; 907 textToHighlight = selWord; 908 scope.selection = "word"; 909 scope.context = "extended"; 910 } else { 911 context = selWord; 912 selWithContext = selChars.trim(); 913 textToHighlight = selChars.trim(); 914 scope.selection = "initial"; 915 scope.context = "word"; 916 } 917 918 selPos = context.indexOf(selWithContext); 919 920 if (selPos !== -1) { 921 selToFindInContext = selWithContext; 922 } else if ((selPos = context.indexOf(selWord)) !== -1) { 923 selToFindInContext = selWord; 924 } else if ((selPos = context.indexOf(selChars)) !== -1) { 925 selToFindInContext = selChars; 926 } else { 927 continue; 928 } 929 break; 930 } 931 } 932 933 if (selToFindInContext) { 934 sel.removeAllRanges(); 935 } else { 936 mixtape.restoreInitSelection(sel, initialSel); 937 return; 938 } 939 940 if (scope.context === "extended") { 941 context = 942 extContext.extensions.left + 943 initialContext + 944 " " + 945 extContext.extensions.right; 946 } 947 948 var contExcerptStartPos, 949 contExcerptEndPos, 950 selPosInContext, 951 highlightedChars, 952 previewText; 953 maxContextLength = Math.min(context.length, maxContextLength); 954 955 var truncatedContext = context; 956 957 if (context.length > maxContextLength) { 958 if (selPos + selToFindInContext.length / 2 < maxContextLength / 2) { 959 selPosInContext = "beginning"; 960 contExcerptStartPos = 0; 961 contExcerptEndPos = Math.max( 962 selPos + selToFindInContext.length, 963 context.indexOf(" ", maxContextLength - 10) 964 ); 965 } else if ( 966 selPos + selToFindInContext.length / 2 > 967 context.length - maxContextLength / 2 968 ) { 969 selPosInContext = "end"; 970 contExcerptStartPos = Math.min( 971 selPos, 972 context.indexOf(" ", context.length - maxContextLength + 10) 973 ); 974 contExcerptEndPos = context.length; 975 } else { 976 selPosInContext = "middle"; 977 var centerPos = selPos + Math.round(selToFindInContext.length / 2); 978 contExcerptStartPos = Math.min( 979 selPos, 980 context.indexOf(" ", centerPos - maxContextLength / 2 - 10) 981 ); 982 contExcerptEndPos = Math.max( 983 selPos + selToFindInContext.length, 984 context.indexOf(" ", centerPos + maxContextLength / 2 - 10) 985 ); 986 } 987 988 truncatedContext = context 989 .substring(contExcerptStartPos, contExcerptEndPos) 990 .trim(); 991 992 if ( 993 selPosInContext !== "beginning" && 994 context.charAt(contExcerptStartPos - 1) !== "." 995 ) { 996 truncatedContext = "... " + truncatedContext; 997 } 998 if ( 999 selPosInContext !== "end" && 1000 context.charAt(contExcerptStartPos + contExcerptEndPos - 1) !== "." 1001 ) { 1002 truncatedContext = truncatedContext + " ..."; 1003 } 1004 } 1005 1006 if (isSubstrUnique(selChars, textToHighlight)) { 1007 highlightedChars = textToHighlight.replace( 1008 selChars, 1009 '<span class="mixtape_mistake_inner">' + selChars + "</span>" 1010 ); 1011 } else { 1012 highlightedChars = 1013 '<strong class="mixtape_mistake_inner">' + 1014 textToHighlight + 1015 "</strong>"; 1016 } 1017 1018 var selWithContextHighlighted = selToFindInContext.replace( 1019 textToHighlight, 1020 '<span class="mixtape_mistake_outer">' + highlightedChars + "</span>" 587 }, 588 589 _getExactSelPos(selection, context) { 590 return context.indexOf(selection); 591 }, 592 593 _truncateContext(context, selPos, selLength, maxContextLength) { 594 let start = Math.max(0, selPos - Math.floor(maxContextLength / 2)); 595 let end = Math.min(context.length, start + maxContextLength); 596 597 if (start > 0) { 598 start = context.lastIndexOf(" ", start) + 1; 599 } 600 if (end < context.length) { 601 end = context.indexOf(" ", end); 602 } 603 604 return ( 605 (start > 0 ? "..." : "") + 606 context.slice(start, end) + 607 (end < context.length ? "..." : "") 1021 608 ); 1022 1023 if (selExactMatch && truncatedContext === context) { 1024 previewText = 1025 truncatedContext.substring(0, selPos) + 1026 selWithContextHighlighted + 1027 truncatedContext.substring(selPos + selWithContext.length) || 1028 selWithContextHighlighted; 1029 } else { 1030 previewText = 1031 truncatedContext.replace(selWithContext, selWithContextHighlighted) || 1032 selWithContextHighlighted; 1033 } 1034 1035 return { 1036 selection: selChars, 1037 word: selWord, 1038 replace_context: selToFindInContext, 1039 context: truncatedContext, 1040 preview_text: previewText, 1041 nonce: $('input[name="mixtape_nonce"]').val(), 1042 // post_id: mixtape.getPostId() 1043 }; 1044 }, 1045 1046 restoreInitSelection: function (sel, initialSel) { 609 }, 610 611 _stringifyContent(string) { 612 return typeof string === "string" 613 ? string.replace(/\s+/g, " ").trim() 614 : ""; 615 }, 616 617 restoreInitSelection(sel, initialSel) { 1047 618 sel.collapse(initialSel.anchorNode, initialSel.anchorOffset); 1048 619 sel.extend(initialSel.focusNode, initialSel.focusOffset); 1049 620 }, 1050 621 1051 showReportButton : function() {1052 if (! reportButton) {1053 reportButton = $("<button>")1054 .text( mixtape.reportError)622 showReportButton() { 623 if (!this.reportButton) { 624 this.reportButton = $("<button>") 625 .text(MixtapeLocalize.reportError) 1055 626 .addClass("mixtape-report-button") 1056 .on("click", function (){1057 const report = mixtape.getSelectionData();627 .on("click", () => { 628 const report = Mixtape.getSelectionData(); 1058 629 if (report) { 1059 mixtape.showDialog(report); 630 this.showDialog(report); 631 } else { 632 console.error("No report generated"); 1060 633 } 1061 634 }); 1062 635 1063 $("body").append( reportButton);636 $("body").append(this.reportButton); 1064 637 } 1065 638 1066 639 const selection = window.getSelection(); 640 if (selection.rangeCount === 0) { 641 console.error("No selection range found"); 642 return; 643 } 644 1067 645 const range = selection.getRangeAt(0); 1068 646 const rect = range.getBoundingClientRect(); 1069 1070 const buttonHeight = reportButton.outerHeight(); 1071 647 const buttonHeight = this.reportButton.outerHeight(); 1072 648 const topPosition = rect.top + window.scrollY - 20 - buttonHeight; 1073 649 1074 reportButton.css({1075 top: topPosition + "px",1076 left: rect.left + window.scrollX + "px",650 this.reportButton.css({ 651 top: `${topPosition}px`, 652 left: `${rect.left + window.scrollX}px`, 1077 653 position: "absolute", 1078 654 }); 1079 655 }, 1080 656 1081 hideReportButton: function () { 1082 if (reportButton) { 1083 reportButton.remove(); 1084 reportButton = null; 1085 } 1086 }, 657 hideReportButton() { 658 if (this.reportButton) { 659 this.reportButton.remove(); 660 this.reportButton = null; 661 } 662 }, 663 664 init() { 665 this.onReady(); 666 }, 667 }; 668 669 $(document).ready(function () { 670 Mixtape.init(); 1087 671 }); 1088 1089 $(document).ready(mixtape.onReady); 1090 })(jQuery); 672 }); -
mixtape/trunk/package-lock.json
r2994803 r3109820 9 9 "version": "1.0.0", 10 10 "devDependencies": { 11 "@playwright/test": "^1.45.0", 12 "@types/node": "^20.14.9", 11 13 "gulp": "^4.0.2", 12 14 "gulp-phpcs": "^3.1.0", 13 15 "gulp-wp-pot": "^2.5.0" 16 } 17 }, 18 "node_modules/@playwright/test": { 19 "version": "1.45.0", 20 "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", 21 "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", 22 "dev": true, 23 "dependencies": { 24 "playwright": "1.45.0" 25 }, 26 "bin": { 27 "playwright": "cli.js" 28 }, 29 "engines": { 30 "node": ">=18" 31 } 32 }, 33 "node_modules/@types/node": { 34 "version": "20.14.9", 35 "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", 36 "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", 37 "dev": true, 38 "dependencies": { 39 "undici-types": "~5.26.4" 14 40 } 15 41 }, … … 2859 2885 } 2860 2886 }, 2887 "node_modules/playwright": { 2888 "version": "1.45.0", 2889 "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", 2890 "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", 2891 "dev": true, 2892 "dependencies": { 2893 "playwright-core": "1.45.0" 2894 }, 2895 "bin": { 2896 "playwright": "cli.js" 2897 }, 2898 "engines": { 2899 "node": ">=18" 2900 }, 2901 "optionalDependencies": { 2902 "fsevents": "2.3.2" 2903 } 2904 }, 2905 "node_modules/playwright-core": { 2906 "version": "1.45.0", 2907 "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", 2908 "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", 2909 "dev": true, 2910 "bin": { 2911 "playwright-core": "cli.js" 2912 }, 2913 "engines": { 2914 "node": ">=18" 2915 } 2916 }, 2917 "node_modules/playwright/node_modules/fsevents": { 2918 "version": "2.3.2", 2919 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 2920 "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 2921 "dev": true, 2922 "hasInstallScript": true, 2923 "optional": true, 2924 "os": [ 2925 "darwin" 2926 ], 2927 "engines": { 2928 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2929 } 2930 }, 2861 2931 "node_modules/plugin-error": { 2862 2932 "version": "1.0.1", … … 3820 3890 "node": ">= 0.10" 3821 3891 } 3892 }, 3893 "node_modules/undici-types": { 3894 "version": "5.26.5", 3895 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 3896 "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 3897 "dev": true 3822 3898 }, 3823 3899 "node_modules/union-value": { … … 4141 4217 }, 4142 4218 "dependencies": { 4219 "@playwright/test": { 4220 "version": "1.45.0", 4221 "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", 4222 "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", 4223 "dev": true, 4224 "requires": { 4225 "playwright": "1.45.0" 4226 } 4227 }, 4228 "@types/node": { 4229 "version": "20.14.9", 4230 "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", 4231 "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", 4232 "dev": true, 4233 "requires": { 4234 "undici-types": "~5.26.4" 4235 } 4236 }, 4143 4237 "acorn": { 4144 4238 "version": "8.10.0", … … 6402 6496 } 6403 6497 }, 6498 "playwright": { 6499 "version": "1.45.0", 6500 "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", 6501 "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", 6502 "dev": true, 6503 "requires": { 6504 "fsevents": "2.3.2", 6505 "playwright-core": "1.45.0" 6506 }, 6507 "dependencies": { 6508 "fsevents": { 6509 "version": "2.3.2", 6510 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 6511 "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 6512 "dev": true, 6513 "optional": true 6514 } 6515 } 6516 }, 6517 "playwright-core": { 6518 "version": "1.45.0", 6519 "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", 6520 "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", 6521 "dev": true 6522 }, 6404 6523 "plugin-error": { 6405 6524 "version": "1.0.1", … … 7186 7305 "dev": true 7187 7306 }, 7307 "undici-types": { 7308 "version": "5.26.5", 7309 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 7310 "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 7311 "dev": true 7312 }, 7188 7313 "union-value": { 7189 7314 "version": "1.0.1", -
mixtape/trunk/package.json
r2994803 r3109820 4 4 "scripts": { 5 5 "make-pot": "gulp make-pot", 6 "phpcs": "gulp phpcs" 6 "phpcs": "gulp phpcs", 7 "test": "npx playwright test --ui" 7 8 }, 8 9 "devDependencies": { 10 "@playwright/test": "^1.45.0", 11 "@types/node": "^20.14.9", 9 12 "gulp": "^4.0.2", 10 13 "gulp-phpcs": "^3.1.0", -
mixtape/trunk/readme.md
r2998174 r3109820 7 7 **License:** GPLv2 or later 8 8 **Contributors:** natata7 9 **Stable tag:** 1. 19 **Stable tag:** 1.2 10 10 **License URI:** <http://www.gnu.org/licenses/gpl-2.0.html> 11 11 … … 33 33 1. Go to Settings > Mixtape and set options. 34 34 35 ### 1.2 ### 36 37 * Fix: sending reports on front on some cases. 38 35 39 ### 1.1 ### 36 40 -
mixtape/trunk/readme.txt
r2998174 r3109820 5 5 Tested up to: 6.4.1 6 6 Requires PHP: 7.4 7 Stable tag: 1. 17 Stable tag: 1.2 8 8 Contributors: natata7 9 9 License: GPLv2 or later … … 28 28 2. Activate and follow the settings link in the notice you will see at the top. Tick desired checkboxes, save, and that's it! 29 29 30 == 1.2 == 31 32 * Fix: sending reports on front on some cases. 33 30 34 == 1.1 == 31 35 -
mixtape/trunk/src/class-mixtape-abstract.php
r2998174 r3109820 301 301 302 302 // modernizer 303 wp_enqueue_script( 'modernizr', plugins_url( 'assets/js/modernizr.custom.js', self::$plugin_path ), array( 'jquery' ), self::$version, true );303 //wp_enqueue_script( 'modernizr', plugins_url( 'assets/js/modernizr.custom.js', self::$plugin_path ), array( 'jquery' ), self::$version, true ); 304 304 305 305 // frontend script (combined) … … 309 309 array( 310 310 'jquery', 311 'modernizr',311 //'modernizr', 312 312 ), 313 313 filemtime( plugin_dir_path( self::$plugin_path ) . '/assets/js/mixtape-front.js' ), … … 318 318 'reportError' => __( 'Report an error', 'mixtape' ), 319 319 ); 320 wp_localize_script( 'mixtape-front', ' mixtape', $localized_data );320 wp_localize_script( 'mixtape-front', 'MixtapeLocalize', $localized_data ); 321 321 } 322 322
Note: See TracChangeset
for help on using the changeset viewer.