akkd-common

Common functions

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/474549/1263250/akkd-common.js

  1. // ==UserScript==
  2.  
  3. // #region Info
  4.  
  5. // @namespace https://gf.qytechs.cn/en/users/1123632-93akkord
  6. // @exclude *
  7. // @author Michael Barros (https://gf.qytechs.cn/en/users/1123632-93akkord)
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNvyMY98AAA5DSURBVGhDtVr5T1zXGeVviPJDq/bHNq2ZN8MyrGEx4C2JY6tqo+SXRGoVKYqqSlXaqFUjNUlTRXWUWE5iXC/YmNWAjdmXGRgDNruNYciwG4aBYR0wY+x4AWbe1/PdN282PwO21HGO333bfed893zfve85Efx7q6zrpd9UDX32er295bDZ2f6GeVbgdZMT4G1Q2+zwbWfbD6F9SGydvu1s+0GcU3HAh5B2fTgc7QeALLSzGmfbM4GsRuwD+8RWOZaB84y06smW/RUTnx7MMb0kyB860/jT1xrvjO+3LFGWZZmymhcps3kB4C3vL9E+XzsT7UzLImU0LVEGjqVb5gFscVy0cV9a0wKlmucFXjUvUopZ2ed2MpCE/cRwmOYpqWGREoGERmwbcaxxnuJxPN60II7FNywIxKFtNK1Qaq19ZG92608iDtYO5WSBRNa1ZcpgQEQGxDCYNO/vBQTJHZAGpEIckw1HcpMiIBmEkkxLfiSCEBPUQrwpgDiIMjYuUYxpmWLNAO5NLx/PicismZrLsLhE9DMF+TABwN4wolpIw4ikgmTKjgIwCir55xKAyPsEGCGAkVA1MRuRWe9wZzSv+CzyNIRdAC3SwUiFfVJgHy3yDFVAcPS1SAcjOPpxIM8CYnkLEXEQEFcz5Y7IUgXARsLjKnGArbOb6DPYOikgqUWeoWUhLdKM4MirApi8H75RSKix7yxAi6wWhIAw0sF4UQFK9BeUyAcJ4DxIqMYI7K+3uzObXX7iDBH5XZIX3od9tEgzBHGAq08S9lXyuxGgkg8XwO0Y3B9fBQH7ICDDJ2C3CasiDYme6kteLfIMvwAQEsCx5xPA20VBWkWMELAMAWyhBghAFRKR1yC5HRQBin20yDMCApg0C9i9hQICAuQDgIAKhxtVaNK9F+S1CD4LPFmlgdTufL/w3LVfbW8nwCgEzLyoALXmaxNXoQgAOPLPIUAAbVWA6vtQAZwDYgSmnluAv2Q28RJh+wROMgeIP5cAgAUYcSycPCewP4l3OwK7tQ1DjbwgzFuI1CKrBZW4gkDV0RYwqQhI36UAts12FUfF/1uACiEgYwcBXGlEtQmxzdOkVTB5tR1sGy2ywQi3DkOLdDB8AqZ2ELCMycoF8iwCiauuebD/qtlFqajHDEGaI++LfrKPeDLO8Qgkm+boVZBKQvVIwHGxkGNhPtJCBKCQ5y28j+MxAFecuIYlHzAL7yRA+B1E0zFJHb3uoqNtynkeAbFo45nX5KTqmfv0w+ojKpx0UXLDrCiZwXVeRB4l9JOBVbLefUgDdx/RG/UOsfYPnH+WAJDEvTF4L/hL3yIN4l6+/6jJIcizMDGRZWAtxETTEWWOdBomNcUu8/Rep5Mey0SbRPRp7xyi5xTR57qeXDdLI2uPCaepY85FUUUDlFg/I84FC0isX6DjtmXy4rot4LXLt8hYOY3VpM8+PgEKEGGQM6LN5IWIeid9YV0Uz9nEX0eqhshYNYXzeNkRAuocbl7PpGEU0ppgB0EeD4fyCxNruE35tcy4yFDSBxs4QRJvULUzNIyICAFOF0m5PaQrGaSEBuQIHpwMCFJ1c/Q1CPDPAxws7SF98QAZq+/4vK++dYE87BYHESwgxjwHu6CMVjvo84EFkmUvbeFhh8ut4DFEUTXTEIAyml7rcLM1uDSqEH6uddD4+gYJhsD61hal5neQvnQIr34zlIQOhlYf+gXoLnSRruA2RV8aJIwq7aufpfRGPKTGTl/1z4lOPMCB4i4I6KOokn5KqR7DtTOUiWuT6+cFcUY8bJTR6KDfWpy0t2qE/tXH92ME8bAjLOCSjfZcGaPYKyPuiFQI4MkoFTf6yaODd6/N0hMZj8RNMv7y4s/HljGKzMPDy2yUUDFOw34By6Q/30VSwS26MOKkex6Z3F6ZTlqn5JjSH+ir27O4DhEUAjrJUNhLHzQN0+qml9bRwa3V+5RZOYJknaFUiM8bWxV98PXr2I6sPRAC0KQ3Lw+QoXgQAoZJX2LVFpCAhDw94hKPnFt/IPc55mXCEJqmFkm62CmISgU3aWjlR3QrU+fsMkWfa6Fsq528uI5RPmz3Rp+7Lu+52EvHeibFdTwC+ws76L2afnI/AT0Exu5+KB8qaJP1Rf1kuDJEZ8dWxHUcuU3gsdcr9jlQqoCoYivpcG2IABF5QCQhhn3kHicoiFinvMeuDcgIKN19vEnJBbBAQQ9F5naSzXWfA0NdM4t0vGcMyc53EJlGZ7zRZ8xe6WIHRV7spv90T4jrtkDoT9W95EI/TMZ1/0f5SH6rR3+xiyIL+yi1tJ/WNjhTvDSwuCof/G/lVkZ29Vbr9Dx3GyRgkKTwEVAFcPK+Y3HAPmwamf5Y2e19M9dCG2wjJtAE5XndpDvfTkOude6X7m8wITwDD+hyLHhjTpm8ugsdZMB1Ul4PHWMBOIf/aH1DifzKoyf0O5CXzt/AaPaQVHSLPjSNKpbFk/9ce1OWTlso8vQ1+mfrkDiuCtAjz0IF8MTE0cfCK7HBSdlDKFu4YxOkznUOyd/esNH9TcQXHVSMz5H+QjtJOTdo2HVPYeX78UAX2ablyJxWIdKQ30sScKxLGQH/D/c43A8oLbcdOYXrim+LxP779SnRHY/B0bJ20uXewEi30+cdd8RtqgAJ1U4qH0YyswCU0RSUUfY/z5BJSCKb+yFGUYzaU7/5xxuUgI6lnOs0vBwQ8ACjwKI9wD/aRklf2ENRhUquHOtWCPCIPsQIsJWYZOvsKkXhfNQlK2zRTx9fGxfHeb54pwLFIh8lN6+XvujkHFIEHLliJQmFgUdACEiBgGSenCCAZ8K3mh2wC5OXaXzprrdzet7TMb3gsc6tIA0Uj79fP0i6nDayCQEyTbrW5H0ny7Ym7q7jTpnWUF3erkC1KLqJfMEIqAJA4PdFTVv5N0fZjejLS9+hQhlAngW8c7WPNvgp6OWbW1MkIS+kgn46dtMu4sTC3kQZVQQgSEJArd0tpn/YJ75+jk7YlElnCx0dPGP26I5XyhKQ8F29d+0Jz8lEpaNOMpxtoUEIYEkd9gV5z7d19HbhNe/DTXgcKifcP1JKaS9s1OXPAa7j+842yTHf18jWxRXWQI/wnA8tmJiK+siIwjDkWhMjeW/TQ6dQ1U72zdLqI8xH6IAFvHEVFajUBgFjsBIEvFrjcCci+rywiqudxprlEXOkkZV78p7sGvr1KQvtQTL9KtuMMqpMKAsPNsh45poQwKW2bXaJ9KfMpMu20Il2m5dtxOSq7asUndtLxzkHcIAtcCDvOhlON9ORglavSGiInQfBQ1d6hNh3r/TK95BvXDp5xGTctIX5iIsp93u4oh8jgAQun8CkOsgCZhQBsM/euikqGJjyFg9MeP5ad9OrQ4XQ53aRgcscqs77Vb1ykXXSkzcw6dmfd0P+un3YW2S94/mybcgbhZzQc0U53SSf7B7xXMJ1BcDh8pvyH6r75IKBO558XJuc3ylK6x4Ugg9rur3FuKbIOuX5pGVYjobn9+R10eGCZk9J/4Snd8blvYrzH1V0eopwfyHa6aWw1eWxYAEOWMj3RRjrm1isM3TnWlEmQQYPYg9HFcLLXFEuYBI710bS2TYyMGG0BXLacR4Jh4SMhGAujXq+5hy2EC/BGhKukc53wNPoB8mtB6IwexvOtWOL80WwGy8xYCUpF8/F9bqTzbLhdIvvOddxXTdyhf0P+6gCkqodbl4RipUh1iOxdQ4ylo+L5NEjicQMiQQzFN0WFUWX302RIKsTwHyALR/n83ydHlsdCwFRHSch9wHouCJha8BCzoA6rkcp5L6lQvQL6EtwHMlpgL/1l/g4RBbwvXwfAghxhhL4//II6Zjf1Tt8vTsisdoOAYFXPv4Gn9CAlSAWayhTYuXI4AdzpeCaLUgIoHpcwnEug6WDAYCcAYgqsQE/iGOiLyZ9GRNhmQ9oS1hX6QDDZZRFkNNzdbk8inYopCsgLcDRHyd9BQSUQUAS1tQJsI8qIBjGOqdIGAkkWL0gBiIKuA1ygmQo9BzFcDBxkNQzySBwOZTKgwmGgqOtQro6IWBA9PUVkwEB7H8tAQLICz1EcESVIeaIMnFt8gYghDiiKyDIKwKkYAgBiKoGeYEwARx5Ju8XkLyTAMCIty8DD7mPlBZxRjh5cY+IPCMo6oDwMrfZElrEVfjJB4iHCAjPAUbgFS8AI146+MHhAjjpGE8LCCUdTpyhLwN5lERN4oAg7o98KPkQAeo/pG0nIN48T3F1sBMeHC5AFaW2BUT0txOAZBUCQkkHI9j3OwrQJB0E9VuNERUqumyEohFhRihpIMwuKoIjL5J2F4mrZRuByiky4MU+uszmjkjAi3GwAIWo8nUgFPx9RkE8EjsKQ294UQHC99rW2ck2AhCgDwjgEQiNsraAoM98EByDnIjiqvLCAkKJq9jJNgLBAuIr7G7lQxIT931Q2iWi6+ZBCGR4rmCUhdb5ENLCNtsTV8kHl8pQgHiFHQKmIWAaAoYDApgQR3i7j6laMNY78YINYiAvITe0BajkdxP57cj7UAkRVXYIGIWFKibmOPoxjfzNMfDdcdeA9aKR2KJec2V5SsAosLvIP5s8A6SFALQrAVgo5pLVGRF9sTc3FgKYPP/jmSbJnYCciMVkJ10FEV+F4TWNUmmeTV7Ab5tgsloICJCqsV/lwOr0xvmIX/zt2M+ji/sn+csv/6OBMgrPCb6PRcBO+gpE0i9gB/IMLIt3K0ACDLBOFPwfe773zi8/Ov4z8X+svPLBNy8bjtd8GZXbbonJ7+18EURf7OrRn2m16k+1WA3Zptv6E3WD0vfm29KJepv03dPQAZHfY3uywSadbNwWOgGTLTK72Rp9utUifVP571c++OzliIiIiP8B4VfIttF+iOkAAAAASUVORK5CYII=
  9.  
  10. // #endregion Info
  11.  
  12. // ==UserLibrary==
  13.  
  14. // @name akkd-common
  15. // @description Common functions
  16. // @copyright 2022+, Michael Barros (https://gf.qytechs.cn/en/users/1123632-93akkord)
  17. // @license CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
  18. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
  19. // @version 0.0.20
  20.  
  21. // ==/UserScript==
  22.  
  23. // ==/UserLibrary==
  24.  
  25. // ==OpenUserJS==
  26.  
  27. // @author 93Akkord
  28.  
  29. // ==/OpenUserJS==
  30.  
  31. /// <reference path='C:/Users/mbarros/Documents/DevProjects/Web/Tampermonkey/Palantir/@types/__fullReferencePaths__.js' />
  32.  
  33. /*
  34.  
  35. # akkd-common
  36.  
  37. A collection of commonly used classes and functions.
  38.  
  39. ## Required requires:
  40.  
  41. - https://code.jquery.com/jquery-3.2.1.min.js
  42. - https://gf.qytechs.cn/scripts/474546-loglevel/code/loglevel.js
  43.  
  44. */
  45.  
  46. // #region Events
  47.  
  48. // Setup location change events
  49. /**
  50. *
  51. * Example usage:
  52. * ```javascript
  53. * window.addEventListener('popstate', () => {
  54. * window.dispatchEvent(new Event('locationchange'));
  55. * });
  56. */
  57. (() => {
  58. class LocationChangeEvent extends Event {
  59. constructor(type, prevUrl, newUrl) {
  60. super(type);
  61.  
  62. this.prevUrl = prevUrl;
  63. this.newUrl = newUrl;
  64. }
  65. }
  66.  
  67. let prevUrl = document.location.href;
  68. let oldPushState = history.pushState;
  69.  
  70. history.pushState = function pushState() {
  71. let ret = oldPushState.apply(this, arguments);
  72. let newUrl = document.location.href;
  73.  
  74. window.dispatchEvent(new LocationChangeEvent('pushstate', prevUrl, newUrl));
  75. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  76.  
  77. prevUrl = newUrl;
  78.  
  79. return ret;
  80. };
  81.  
  82. let oldReplaceState = history.replaceState;
  83.  
  84. history.replaceState = function replaceState() {
  85. let ret = oldReplaceState.apply(this, arguments);
  86. let newUrl = document.location.href;
  87.  
  88. window.dispatchEvent(new LocationChangeEvent('replacestate', prevUrl, newUrl));
  89. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  90.  
  91. prevUrl = newUrl;
  92.  
  93. return ret;
  94. };
  95.  
  96. window.addEventListener('popstate', () => {
  97. let newUrl = document.location.href;
  98.  
  99. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  100.  
  101. prevUrl = newUrl;
  102. });
  103. })();
  104.  
  105. // #endregion Events
  106.  
  107. // #region Helper Classes
  108.  
  109. class Logger {
  110. /**
  111. * Creates an instance of Logger.
  112. *
  113. * @author Michael Barros <michaelcbarros@gmail.com>
  114. * @param {Window} _window
  115. * @param {string | null} devTag
  116. * @memberof Logger
  117. */
  118. constructor(_window = null, devTag = null) {
  119. /**
  120. * @type {Window}
  121. * @private
  122. */
  123. this.window = _window || getWindow();
  124.  
  125. /** @type {string | null} */
  126. this.devTag = devTag;
  127.  
  128. /** @type {string[]} */
  129. this._additionalTags = [];
  130. }
  131.  
  132. /**
  133. *
  134. * @author Michael Barros <michaelcbarros@gmail.com>
  135. * @type {string[]}
  136. * @public
  137. * @memberof Logger
  138. */
  139. get additionalTags() {
  140. return this._additionalTags;
  141. }
  142.  
  143. /**
  144. *
  145. *
  146. * @author Michael Barros <michaelcbarros@gmail.com>
  147. * @param {string[]} value
  148. * @memberof Logger
  149. */
  150. set additionalTags(value) {
  151. if (getType(value) != 'array') {
  152. value = [value];
  153. }
  154.  
  155. this._additionalTags = value;
  156. }
  157.  
  158. /** @type {string} */
  159. get label() {
  160. return [].concat([this.devTag], this.additionalTags).join(' ');
  161. }
  162.  
  163. /** @type {(...data: any[]) => void} */
  164. get log() {
  165. if (this.devTag) {
  166. return console.log.bind(console, `${this.label}`);
  167. } else {
  168. return console.log.bind(console);
  169. }
  170. }
  171.  
  172. /** @type {(...data: any[]) => void} */
  173. get info() {
  174. if (this.devTag) {
  175. return console.info.bind(console, `${this.label}`);
  176. } else {
  177. return console.info.bind(console);
  178. }
  179. }
  180.  
  181. /** @type {(...data: any[]) => void} */
  182. get error() {
  183. if (this.devTag) {
  184. return console.error.bind(console, `${this.label}`);
  185. } else {
  186. return console.error.bind(console);
  187. }
  188. }
  189.  
  190. /** @type {(...data: any[]) => void} */
  191. get debug() {
  192. if (this.devTag) {
  193. return console.debug.bind(console, `${this.label}`);
  194. } else {
  195. return console.debug.bind(console);
  196. }
  197. }
  198.  
  199. /** @type {(...data: any[]) => void} */
  200. get warn() {
  201. if (this.devTag) {
  202. return console.warn.bind(console, `${this.label}`);
  203. } else {
  204. return console.warn.bind(console);
  205. }
  206. }
  207.  
  208. /**
  209. * Maybe use later?
  210. *
  211. * @memberof Logger
  212. */
  213. _setupFunctions() {
  214. let self = this;
  215. let funcs = ['log', 'info', 'error', 'debug', 'warn'];
  216.  
  217. for (let i = 0; i < funcs.length; i++) {
  218. let func = funcs[i];
  219.  
  220. self[func] = function () {
  221. let args = [...arguments];
  222.  
  223. if (self.devTag) args.unshift(self.label);
  224.  
  225. self.window.console.debug.bind(self.window.console, ...args);
  226. };
  227. }
  228. }
  229. }
  230.  
  231. class Base64 {
  232. static keyStr = 'ABCDEFGHIJKLMNOP' + 'QRSTUVWXYZabcdef' + 'ghijklmnopqrstuv' + 'wxyz0123456789+/' + '=';
  233.  
  234. /**
  235. *
  236. *
  237. * @static
  238. * @param {string} input
  239. * @returns {string}
  240. * @memberof Base64
  241. */
  242. static encode(input) {
  243. input = escape(input);
  244.  
  245. let output = '';
  246.  
  247. let chr1;
  248. let chr2;
  249. let chr3 = '';
  250.  
  251. let enc1;
  252. let enc2;
  253. let enc3;
  254. let enc4 = '';
  255.  
  256. let i = 0;
  257.  
  258. do {
  259. chr1 = input.charCodeAt(i++);
  260. chr2 = input.charCodeAt(i++);
  261. chr3 = input.charCodeAt(i++);
  262. enc1 = chr1 >> 2;
  263. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  264. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  265. enc4 = chr3 & 63;
  266.  
  267. if (isNaN(chr2)) {
  268. enc3 = enc4 = 64;
  269. } else if (isNaN(chr3)) {
  270. enc4 = 64;
  271. }
  272.  
  273. output = output + Base64.keyStr.charAt(enc1) + Base64.keyStr.charAt(enc2) + Base64.keyStr.charAt(enc3) + Base64.keyStr.charAt(enc4);
  274. chr1 = chr2 = chr3 = '';
  275. enc1 = enc2 = enc3 = enc4 = '';
  276. } while (i < input.length);
  277.  
  278. return output;
  279. }
  280.  
  281. /**
  282. *
  283. *
  284. * @static
  285. * @param {string} input
  286. * @returns {string}
  287. * @memberof Base64
  288. */
  289. static decode(input) {
  290. let output = '';
  291.  
  292. let chr1;
  293. let chr2;
  294. let chr3 = '';
  295.  
  296. let enc1;
  297. let enc2;
  298. let enc3;
  299. let enc4 = '';
  300.  
  301. let i = 0;
  302.  
  303. let base64test = /[^A-Za-z0-9\+\/\=]/g;
  304.  
  305. if (base64test.exec(input)) {
  306. throw new Error(`There were invalid base64 characters in the input text. Valid base64 characters are: ['A-Z', 'a-z', '0-9,' '+', '/', '=']`);
  307. }
  308.  
  309. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
  310.  
  311. do {
  312. enc1 = Base64.keyStr.indexOf(input.charAt(i++));
  313. enc2 = Base64.keyStr.indexOf(input.charAt(i++));
  314. enc3 = Base64.keyStr.indexOf(input.charAt(i++));
  315. enc4 = Base64.keyStr.indexOf(input.charAt(i++));
  316. chr1 = (enc1 << 2) | (enc2 >> 4);
  317. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  318. chr3 = ((enc3 & 3) << 6) | enc4;
  319. output = output + String.fromCharCode(chr1);
  320.  
  321. if (enc3 != 64) output = output + String.fromCharCode(chr2);
  322.  
  323. if (enc4 != 64) output = output + String.fromCharCode(chr3);
  324.  
  325. chr1 = chr2 = chr3 = '';
  326. enc1 = enc2 = enc3 = enc4 = '';
  327. } while (i < input.length);
  328.  
  329. return unescape(output);
  330. }
  331. }
  332.  
  333. class MultiRegExp {
  334. constructor(baseRegExp) {
  335. const { regexp, groupIndexMapper, previousGroupsForGroup } = this._fillGroups(baseRegExp);
  336.  
  337. this.regexp = regexp;
  338. this.groupIndexMapper = groupIndexMapper;
  339. this.previousGroupsForGroup = previousGroupsForGroup;
  340. }
  341.  
  342. execForAllGroups(str, includeFullMatch) {
  343. let matches = RegExp.prototype.exec.call(this.regexp, str);
  344.  
  345. if (!matches) return matches;
  346.  
  347. let firstIndex = matches.index;
  348. let indexMapper = includeFullMatch
  349. ? Object.assign(
  350. {
  351. 0: 0,
  352. },
  353. this.groupIndexMapper
  354. )
  355. : this.groupIndexMapper;
  356. let previousGroups = includeFullMatch
  357. ? Object.assign(
  358. {
  359. 0: [],
  360. },
  361. this.previousGroupsForGroup
  362. )
  363. : this.previousGroupsForGroup;
  364.  
  365. let res = Object.keys(indexMapper).map((group) => {
  366. let mapped = indexMapper[group];
  367. let match = matches[mapped];
  368. let start = firstIndex + previousGroups[group].reduce((sum, i) => sum + (matches[i] ? matches[i].length : 0), 0);
  369. let end = start + (matches[mapped] ? matches[mapped].length : 0);
  370. let lineColumnStart = LineColumnFinder(str).fromIndex(start);
  371. let lineColumnEnd = LineColumnFinder(str).fromIndex(end - 1);
  372.  
  373. return {
  374. match,
  375. start,
  376. end,
  377. startLineNumber: lineColumnStart.line,
  378. startColumnNumber: lineColumnStart.col,
  379. endLineNumber: lineColumnEnd.line,
  380. endColumnNumber: lineColumnEnd.col,
  381. };
  382. });
  383.  
  384. return res;
  385. }
  386.  
  387. execForGroup(string, group) {
  388. const matches = RegExp.prototype.exec.call(this.regexp, string);
  389.  
  390. if (!matches) return matches;
  391.  
  392. const firstIndex = matches.index;
  393.  
  394. const mapped = group == 0 ? 0 : this.groupIndexMapper[group];
  395. const previousGroups = group == 0 ? [] : this.previousGroupsForGroup[group];
  396.  
  397. let r = {
  398. match: matches[mapped],
  399. start: firstIndex + previousGroups.reduce((sum, i) => sum + (matches[i] ? matches[i].length : 0), 0),
  400. };
  401.  
  402. r.end = r.start + (matches[mapped] ? matches[mapped].length : 0);
  403.  
  404. return r;
  405. }
  406.  
  407. /**
  408. * Adds brackets before and after a part of string
  409. * @param str string the hole regex string
  410. * @param start int marks the position where ( should be inserted
  411. * @param end int marks the position where ) should be inserted
  412. * @param groupsAdded int defines the offset to the original string because of inserted brackets
  413. * @return {string}
  414. */
  415. _addGroupToRegexString(str, start, end, groupsAdded) {
  416. start += groupsAdded * 2;
  417. end += groupsAdded * 2;
  418.  
  419. return str.substring(0, start) + '(' + str.substring(start, end + 1) + ')' + str.substring(end + 1);
  420. }
  421.  
  422. /**
  423. * converts the given regex to a regex where all not captured string are going to be captured
  424. * it along sides generates a mapper which maps the original group index to the shifted group offset and
  425. * generates a list of groups indexes (including new generated capturing groups)
  426. * which have been closed before a given group index (unshifted)
  427. *
  428. * Example:
  429. * regexp: /a(?: )bc(def(ghi)xyz)/g => /(a(?: )bc)((def)(ghi)(xyz))/g
  430. * groupIndexMapper: {'1': 2, '2', 4}
  431. * previousGroupsForGroup: {'1': [1], '2': [1, 3]}
  432. *
  433. * @param regex RegExp
  434. * @return {{regexp: RegExp, groupIndexMapper: {}, previousGroupsForGroup: {}}}
  435. */
  436. _fillGroups(regex) {
  437. let regexString;
  438. let modifier;
  439.  
  440. if (regex.source && regex.flags) {
  441. regexString = regex.source;
  442. modifier = regex.flags;
  443. } else {
  444. regexString = regex.toString();
  445. modifier = regexString.substring(regexString.lastIndexOf(regexString[0]) + 1); // sometimes order matters ;)
  446. regexString = regexString.substr(1, regex.toString().lastIndexOf(regexString[0]) - 1);
  447. }
  448.  
  449. // regexp is greedy so it should match (? before ( right?
  450. // brackets may be not quoted by \
  451. // closing bracket may look like: ), )+, )+?, ){1,}?, ){1,1111}?
  452. const tester = /(\\\()|(\\\))|(\(\?)|(\()|(\)(?:\{\d+,?\d*}|[*+?])?\??)/g;
  453.  
  454. let modifiedRegex = regexString;
  455.  
  456. let lastGroupStartPosition = -1;
  457. let lastGroupEndPosition = -1;
  458. let lastNonGroupStartPosition = -1;
  459. let lastNonGroupEndPosition = -1;
  460. let groupsAdded = 0;
  461. let groupCount = 0;
  462. let matchArr;
  463. const nonGroupPositions = [];
  464. const groupPositions = [];
  465. const groupNumber = [];
  466. let currentLengthIndexes = [];
  467. const groupIndexMapper = {};
  468. const previousGroupsForGroup = {};
  469.  
  470. while ((matchArr = tester.exec(regexString)) !== null) {
  471. if (matchArr[1] || matchArr[2]) {
  472. // ignore escaped brackets \(, \)
  473. }
  474.  
  475. if (matchArr[3]) {
  476. // non capturing group (?
  477. let index = matchArr.index + matchArr[0].length - 1;
  478.  
  479. lastNonGroupStartPosition = index;
  480. nonGroupPositions.push(index);
  481. } else if (matchArr[4]) {
  482. // capturing group (
  483. let index = matchArr.index + matchArr[0].length - 1;
  484. let lastGroupPosition = Math.max(lastGroupStartPosition, lastGroupEndPosition);
  485.  
  486. // if a (? is found add ) before it
  487. if (lastNonGroupStartPosition > lastGroupPosition) {
  488. // check if between ) of capturing group lies a non capturing group
  489. if (lastGroupPosition < lastNonGroupEndPosition) {
  490. // add groups for x1 and x2 on (?:()x1)x2(?:...
  491. if (lastNonGroupEndPosition - 1 - (lastGroupPosition + 1) > 0) {
  492. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, lastNonGroupEndPosition - 1, groupsAdded);
  493. groupsAdded++;
  494. lastGroupEndPosition = lastNonGroupEndPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  495. currentLengthIndexes.push(groupCount + groupsAdded);
  496. }
  497.  
  498. if (lastNonGroupStartPosition - 1 - (lastNonGroupEndPosition + 1) > 0) {
  499. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastNonGroupEndPosition + 1, lastNonGroupStartPosition - 2, groupsAdded);
  500. groupsAdded++;
  501. lastGroupEndPosition = lastNonGroupStartPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  502. currentLengthIndexes.push(groupCount + groupsAdded);
  503. }
  504. } else {
  505. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, lastNonGroupStartPosition - 2, groupsAdded);
  506. groupsAdded++;
  507. lastGroupEndPosition = lastNonGroupStartPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  508. currentLengthIndexes.push(groupCount + groupsAdded);
  509. }
  510.  
  511. // if necessary also add group between (? and opening bracket
  512. if (index > lastNonGroupStartPosition + 2) {
  513. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastNonGroupStartPosition + 2, index - 1, groupsAdded);
  514. groupsAdded++;
  515. lastGroupEndPosition = index - 1; // imaginary position as it is not in regex but modifiedRegex
  516. currentLengthIndexes.push(groupCount + groupsAdded);
  517. }
  518. } else if (lastGroupPosition < index - 1) {
  519. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, index - 1, groupsAdded);
  520. groupsAdded++;
  521. lastGroupEndPosition = index - 1; // imaginary position as it is not in regex but modifiedRegex
  522. currentLengthIndexes.push(groupCount + groupsAdded);
  523. }
  524.  
  525. groupCount++;
  526. lastGroupStartPosition = index;
  527. groupPositions.push(index);
  528. groupNumber.push(groupCount + groupsAdded);
  529. groupIndexMapper[groupCount] = groupCount + groupsAdded;
  530. previousGroupsForGroup[groupCount] = currentLengthIndexes.slice();
  531. } else if (matchArr[5]) {
  532. // closing bracket ), )+, )+?, ){1,}?, ){1,1111}?
  533. let index = matchArr.index + matchArr[0].length - 1;
  534.  
  535. if ((groupPositions.length && !nonGroupPositions.length) || groupPositions[groupPositions.length - 1] > nonGroupPositions[nonGroupPositions.length - 1]) {
  536. if (lastGroupStartPosition < lastGroupEndPosition && lastGroupEndPosition < index - 1) {
  537. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupEndPosition + 1, index - 1, groupsAdded);
  538. groupsAdded++;
  539.  
  540. //lastGroupEndPosition = index - 1; will be set anyway
  541. currentLengthIndexes.push(groupCount + groupsAdded);
  542. }
  543.  
  544. groupPositions.pop();
  545. lastGroupEndPosition = index;
  546.  
  547. let toPush = groupNumber.pop();
  548. currentLengthIndexes.push(toPush);
  549. currentLengthIndexes = currentLengthIndexes.filter((index) => index <= toPush);
  550. } else if (nonGroupPositions.length) {
  551. nonGroupPositions.pop();
  552. lastNonGroupEndPosition = index;
  553. }
  554. }
  555. }
  556.  
  557. return {
  558. regexp: new RegExp(modifiedRegex, modifier),
  559. groupIndexMapper,
  560. previousGroupsForGroup,
  561. };
  562. }
  563. }
  564.  
  565. class MoveableElement {
  566. /**
  567. * Creates an instance of MoveableElement.
  568. * @param {HTMLElement} element
  569. * @param {boolean} requireKeyDown
  570. * @memberof MoveableElement
  571. */
  572. constructor(element, requireKeyDown) {
  573. this.element = element;
  574. this.requireKeyDown = requireKeyDown || false;
  575. this.handleMouseDown = this.handleMouseDown.bind(this);
  576. this.handleMouseUp = this.handleMouseUp.bind(this);
  577. this.handleMouseMove = this.handleMouseMove.bind(this);
  578.  
  579. this.moving = false;
  580. this.keyPressed = false;
  581. this.originalCursor = getStyle(this.element, 'cursor');
  582.  
  583. this.setupEvents();
  584. }
  585.  
  586. setupEvents() {
  587. if (!document.body) {
  588. setTimeout(() => {
  589. this.setupEvents();
  590. }, 250);
  591. } else {
  592. document.body.addEventListener('keydown', (ev) => {
  593. if (ev.which == '17') {
  594. this.keyPressed = true;
  595. }
  596. });
  597.  
  598. document.body.addEventListener('keyup', (ev) => {
  599. this.keyPressed = false;
  600. });
  601. }
  602. }
  603.  
  604. /**
  605. *
  606. *
  607. * @author Michael Barros <michaelcbarros@gmail.com>
  608. * @param {MouseEvent} ev
  609. * @memberof MoveableElement
  610. */
  611. handleMouseDown(ev) {
  612. if (this.keyPressed || !this.requireKeyDown) {
  613. ev.preventDefault();
  614.  
  615. this.element.style.cursor = 'move';
  616.  
  617. this.changePointerEvents('none');
  618.  
  619. document.body.removeEventListener('mouseup', this.handleMouseUp);
  620. document.body.addEventListener('mouseup', this.handleMouseUp);
  621.  
  622. document.body.removeEventListener('mousemove', this.handleMouseMove);
  623. document.body.removeEventListener('mouseleave', this.handleMouseUp);
  624.  
  625. document.body.addEventListener('mousemove', this.handleMouseMove);
  626. document.body.addEventListener('mouseleave', this.handleMouseUp);
  627.  
  628. try {
  629. document.querySelectorAll('iframe')[0].style.pointerEvents = 'none';
  630. } catch (error) {}
  631. }
  632. }
  633.  
  634. changePointerEvents(value) {
  635. for (let i = 0; i < this.element.children.length; i++) {
  636. const child = this.element.children[i];
  637.  
  638. child.style.pointerEvents = value;
  639. }
  640. }
  641.  
  642. /**
  643. *
  644. *
  645. * @author Michael Barros <michaelcbarros@gmail.com>
  646. * @param {MouseEvent} ev
  647. * @memberof MoveableElement
  648. */
  649. handleMouseUp(ev) {
  650. this.moving = false;
  651. this.element.style.cursor = this.originalCursor;
  652. this.changePointerEvents('auto');
  653.  
  654. document.body.removeEventListener('mouseup', this.handleMouseUp);
  655. document.body.removeEventListener('mousemove', this.handleMouseMove);
  656. document.body.removeEventListener('mouseleave', this.handleMouseUp);
  657.  
  658. try {
  659. document.querySelectorAll('iframe')[0].style.pointerEvents = '';
  660. } catch (error) {}
  661. }
  662.  
  663. /**
  664. *
  665. *
  666. * @author Michael Barros <michaelcbarros@gmail.com>
  667. * @param {MouseEvent} ev
  668. * @memberof MoveableElement
  669. */
  670. handleMouseMove(ev) {
  671. this.moving = true;
  672.  
  673. let top = ev.clientY - getStyle(this.element, 'height') / 2;
  674. let bottom = ev.clientX - getStyle(this.element, 'width') / 2;
  675.  
  676. this.element.style.top = `${top}px`;
  677. this.element.style.left = `${bottom}px`;
  678. }
  679.  
  680. padCoord(coord) {
  681. return coord.toString().padStart(5, ' ');
  682. }
  683.  
  684. init() {
  685. this.element.addEventListener('mousedown', this.handleMouseDown);
  686. }
  687. }
  688.  
  689. class CurrentLine {
  690. /**
  691. * @typedef ILineInfo
  692. * @prop {string} method
  693. * @prop {number} line
  694. * @prop {string} file
  695. * @prop {string} filename
  696. */
  697.  
  698. /**
  699. * Returns a single item
  700. *
  701. * @param {number} [level] Useful to return levels up on the stack. If not informed, the first (0, zero index) element of the stack will be returned
  702. * @returns {ILineInfo}
  703. */
  704. get(level = 0) {
  705. const stack = getStack();
  706. const i = Math.min(level + 1, stack.length - 1);
  707. const item = stack[i];
  708. const result = CurrentLine.parse(item);
  709.  
  710. return result;
  711. }
  712.  
  713. /**
  714. * Returns all stack
  715. *
  716. * @returns {ILineInfo[]}
  717. */
  718. all() {
  719. const stack = getStack();
  720. const result = [];
  721.  
  722. for (let i = 1; i < stack.length; i++) {
  723. const item = stack[i];
  724.  
  725. result.push(CurrentLine.parse(item));
  726. }
  727.  
  728. return result;
  729. }
  730.  
  731. /**
  732. *
  733. *
  734. * @param {NodeJS.CallSite} item
  735. * @returns {ILineInfo}
  736. */
  737. static parse(item) {
  738. const result = {
  739. method: item.getMethodName() || item.getFunctionName(),
  740. line: item.getLineNumber(),
  741. file: item.getFileName() || item.getScriptNameOrSourceURL(),
  742. };
  743.  
  744. result.filename = result.file ? result.file.replace(/^.*\/|\\/gm, '').replace(/\.\w+$/gm, '') : null;
  745.  
  746. return result;
  747. }
  748. }
  749.  
  750. /**
  751. *
  752. *
  753. * @returns {NodeJS.CallSite[]}
  754. */
  755. function getStack() {
  756. const orig = Error.prepareStackTrace;
  757.  
  758. Error.prepareStackTrace = function (_, stack) {
  759. return stack;
  760. };
  761.  
  762. const err = new Error();
  763.  
  764. Error.captureStackTrace(err, arguments.callee);
  765.  
  766. const stack = err.stack;
  767.  
  768. Error.prepareStackTrace = orig;
  769.  
  770. return stack;
  771. }
  772.  
  773. class ProgressTimer {
  774. /**
  775. * Creates an instance of ProgressTimer.
  776. * @param {number} total
  777. * @memberof ProgressTimer
  778. */
  779. constructor(total) {
  780. this.startTime;
  781. this.total = total;
  782. this.loaded = 0;
  783. this.estimatedFinishDt = '';
  784. this.progressMessage = '';
  785. }
  786.  
  787. /**
  788. *
  789. *
  790. * @memberof ProgressTimer
  791. */
  792. start() {
  793. this.startTime = new Date();
  794. }
  795.  
  796. /**
  797. *
  798. *
  799. * @param {number} loaded
  800. * @param {string} msg
  801. * @memberof ProgressTimer
  802. */
  803. updateProgress(loaded, msg) {
  804. this.loaded = loaded;
  805.  
  806. this.progress = `${((this.loaded * 100) / this.total).toFixed(2)}%`;
  807. this.timeRemaining = this._estimatedTimeRemaining(this.startTime, this.loaded, this.total);
  808. this.downloaded = `${this.loaded}/${this.total}`;
  809. this.completionTime = `${this._dateToISOLikeButLocal(this.estimatedFinishDt)}`;
  810. this.totalRuntime = `${this._ms2Timestamp(this.timeTaken)}`;
  811.  
  812. this.updateProgressMessage(msg);
  813. this.printProgress();
  814. }
  815.  
  816. /**
  817. *
  818. *
  819. * @param {string} msg
  820. * @memberof ProgressTimer
  821. */
  822. updateProgressMessage(msg) {
  823. let msgLines = [];
  824.  
  825. msgLines.push(` completed: ${this.progress}`);
  826. msgLines.push(` downloaded: ${this.downloaded}`);
  827. msgLines.push(` total runtime: ${this.totalRuntime}`);
  828. msgLines.push(` time remaining: ${this.timeRemaining}`);
  829. msgLines.push(`completion time: ${this.completionTime}`);
  830.  
  831. if (msg) {
  832. msgLines.push(msg);
  833. }
  834.  
  835. this.progressMessage = msgLines.join('\n');
  836. }
  837.  
  838. /**
  839. *
  840. *
  841. * @memberof ProgressTimer
  842. */
  843. printProgress() {
  844. console.clear();
  845. console.debug(this.progressMessage);
  846. }
  847.  
  848. /**
  849. *
  850. *
  851. * @param {Date} startTime
  852. * @param {number} itemsProcessed
  853. * @param {number} totalItems
  854. * @returns {string}
  855. * @memberof ProgressTimer
  856. */
  857. _estimatedTimeRemaining(startTime, itemsProcessed, totalItems) {
  858. // if (itemsProcessed == 0) {
  859. // return '';
  860. // }
  861.  
  862. let currentTime = new Date();
  863. this.timeTaken = currentTime - startTime;
  864. this.timeLeft = itemsProcessed == 0 ? this.timeTaken * (totalItems - itemsProcessed) : (this.timeTaken / itemsProcessed) * (totalItems - itemsProcessed);
  865. this.estimatedFinishDt = new Date(currentTime.getTime() + this.timeLeft);
  866.  
  867. return this._ms2Timestamp(this.timeLeft);
  868. }
  869.  
  870. /**
  871. *
  872. *
  873. * @param {number} ms
  874. * @returns {string}
  875. * @memberof ProgressTimer
  876. */
  877. _ms2Timestamp(ms) {
  878. // 1- Convert to seconds:
  879. let seconds = ms / 1000;
  880.  
  881. // 2- Extract hours:
  882. let hours = parseInt(seconds / 3600); // 3,600 seconds in 1 hour
  883. seconds = seconds % 3600; // seconds remaining after extracting hours
  884.  
  885. // 3- Extract minutes:
  886. let minutes = parseInt(seconds / 60); // 60 seconds in 1 minute
  887.  
  888. // 4- Keep only seconds not extracted to minutes:
  889. seconds = seconds % 60;
  890.  
  891. let parts = seconds.toString().split('.');
  892.  
  893. seconds = parseInt(parts[0]);
  894. let milliseconds = parts.length > 1 ? parts[1].substring(0, 3).padEnd(3, 0) : '000';
  895.  
  896. hours = hours.toString().padStart(2, '0');
  897. minutes = minutes.toString().padStart(2, '0');
  898. seconds = seconds.toString().padStart(2, '0');
  899.  
  900. return `${hours}:${minutes}:${seconds}.${milliseconds}`; // hours + ':' + minutes + ':' + seconds;
  901. }
  902.  
  903. /**
  904. *
  905. *
  906. * @author Michael Barros <michaelcbarros@gmail.com>
  907. * @param {Date} date
  908. * @returns {string}
  909. * @memberof ProgressTimer
  910. */
  911. _dateToISOLikeButLocal(date) {
  912. let offsetMs = date.getTimezoneOffset() * 60 * 1000;
  913. let msLocal = date.getTime() - offsetMs;
  914. let dateLocal = new Date(msLocal);
  915. let iso = dateLocal.toISOString();
  916. let isoLocal = iso.slice(0, 19);
  917.  
  918. return isoLocal.replace(/T/g, ' ');
  919. }
  920. }
  921.  
  922. class Benchmark {
  923. constructor({ logger, printResults } = {}) {
  924. this.namedPerformances = {};
  925. this.defaultName = 'default';
  926. this.logger = logger;
  927. this.printResults = printResults == undefined ? (this.logger ? true : false) : printResults;
  928. }
  929.  
  930. /**
  931. *
  932. *
  933. * @author Michael Barros <michaelcbarros@gmail.com>
  934. * @param {string=} name
  935. * @memberof Benchmark
  936. */
  937. start(name) {
  938. name = name || this.defaultName;
  939.  
  940. this.namedPerformances[name] = {
  941. startAt: this._hrtime(),
  942. };
  943. }
  944.  
  945. /**
  946. *
  947. *
  948. * @author Michael Barros <michaelcbarros@gmail.com>
  949. * @param {string=} name
  950. * @memberof Benchmark
  951. */
  952. stop(name) {
  953. name = name || this.defaultName;
  954.  
  955. const startAt = this.namedPerformances[name] && this.namedPerformances[name].startAt;
  956.  
  957. if (!startAt) throw new Error(`Namespace: ${name} doesnt exist`);
  958.  
  959. const diff = this._hrtime(startAt);
  960. const time = diff[0] * 1e3 + diff[1] * 1e-6;
  961. const words = this.getWords(diff);
  962. const preciseWords = this.getPreciseWords(diff);
  963. const verboseWords = this.getVerboseWords(diff);
  964. const verboseAbbrWords = this.getVerboseAbbrWords(diff);
  965.  
  966. if (this.printResults) {
  967. let output = name != 'default' ? `[${name}] execution time:` : `execution time:`;
  968.  
  969. this.logger(output, time); // words
  970. }
  971.  
  972. return {
  973. name,
  974. time,
  975. words,
  976. preciseWords,
  977. verboseWords,
  978. verboseAbbrWords,
  979. diff,
  980. };
  981. }
  982.  
  983. /**
  984. *
  985. *
  986. * @author Michael Barros <michaelcbarros@gmail.com>
  987. * @param {T} func
  988. * @param {{name: string, measure: boolean}=} { name, measure }
  989. * @returns {T}
  990. * @memberof Benchmark
  991. * @template T
  992. */
  993. wrapFunc(func, { name, measure = true } = {}) {
  994. name = this._getFuncName(func, name);
  995.  
  996. let self = this;
  997.  
  998. wrappedFunc.measure = measure;
  999. wrappedFunc.benchmark = {
  1000. name,
  1001. results: {
  1002. runs: [],
  1003. avg: null,
  1004. min: null,
  1005. max: null,
  1006. total: null,
  1007. times: null,
  1008. runCount: 0,
  1009. },
  1010. reset: function () {
  1011. this.results.runs = [];
  1012. this.results.avg = null;
  1013. this.results.min = null;
  1014. this.results.max = null;
  1015. this.results.total = null;
  1016. this.results.times = null;
  1017. this.results.runCount = 0;
  1018. },
  1019. printResults: function (logger = console.debug) {
  1020. let output = this.name != 'default' ? `[${this.name}] execution summary:` : `execution summary:`;
  1021.  
  1022. let times = wrappedFunc.benchmark.results.runs.map((run) => {
  1023. return run.time;
  1024. });
  1025.  
  1026. wrappedFunc.benchmark.results.times = times;
  1027. wrappedFunc.benchmark.results.avg = self._getAvgTime(times);
  1028. wrappedFunc.benchmark.results.total = self._getSumTime(times);
  1029. wrappedFunc.benchmark.results.min = Math.min(...times);
  1030. wrappedFunc.benchmark.results.max = Math.max(...times);
  1031.  
  1032. logger(output, `times: ${this.results.runCount} total: ${self.getWords(this.results.total)} min: ${self.getWords(this.results.min)} max: ${self.getWords(this.results.max)} avg: ${self.getWords(this.results.avg)} total: ${self.getWords(this.results.total)}`);
  1033. },
  1034. };
  1035.  
  1036. function wrappedFunc() {
  1037. if (wrappedFunc.measure) {
  1038. self.start(name);
  1039. }
  1040.  
  1041. let res = func(...arguments);
  1042.  
  1043. if (wrappedFunc.measure) {
  1044. wrappedFunc.benchmark.results.runCount++;
  1045.  
  1046. wrappedFunc.benchmark.results.runs.push(self.stop(name));
  1047. }
  1048.  
  1049. return res;
  1050. }
  1051.  
  1052. return this._defineWrappedFuncProperties(wrappedFunc, name);
  1053. }
  1054.  
  1055. /**
  1056. *
  1057. *
  1058. * @author Michael Barros <michaelcbarros@gmail.com>
  1059. * @param {T} func
  1060. * @param {{name: string, measure: boolean}=} { name, measure }
  1061. * @returns {T}
  1062. * @memberof Benchmark
  1063. * @template T
  1064. */
  1065. wrapAsyncFunc(func, { name, measure = true } = {}) {
  1066. name = this._getFuncName(func, name);
  1067.  
  1068. let self = this;
  1069.  
  1070. wrappedFunc.measure = measure;
  1071. wrappedFunc.benchmark = {
  1072. name,
  1073. results: {
  1074. runs: [],
  1075. avg: null,
  1076. min: null,
  1077. max: null,
  1078. total: null,
  1079. times: null,
  1080. runCount: 0,
  1081. },
  1082. reset: function () {
  1083. this.results.runs = [];
  1084. this.results.avg = null;
  1085. this.results.min = null;
  1086. this.results.max = null;
  1087. this.results.total = null;
  1088. this.results.times = null;
  1089. this.results.runCount = 0;
  1090. },
  1091. printResults: function (logger = console.debug) {
  1092. let output = this.name != 'default' ? `[${this.name}] execution summary:` : `execution summary:`;
  1093.  
  1094. let times = wrappedFunc.benchmark.results.runs.map((run) => {
  1095. return run.time;
  1096. });
  1097.  
  1098. wrappedFunc.benchmark.results.times = times;
  1099. wrappedFunc.benchmark.results.avg = self._getAvgTime(times);
  1100. wrappedFunc.benchmark.results.total = self._getSumTime(times);
  1101. wrappedFunc.benchmark.results.min = Math.min(...times);
  1102. wrappedFunc.benchmark.results.max = Math.max(...times);
  1103.  
  1104. logger(output, `times: ${this.results.runCount} total: ${self.getWords(this.results.total)} min: ${self.getWords(this.results.min)} max: ${self.getWords(this.results.max)} avg: ${self.getWords(this.results.avg)} total: ${self.getWords(this.results.total)}`);
  1105. },
  1106. };
  1107.  
  1108. async function wrappedFunc() {
  1109. if (wrappedFunc.measure) {
  1110. self.start(name);
  1111. }
  1112.  
  1113. let res = await func(...arguments);
  1114.  
  1115. if (wrappedFunc.measure) {
  1116. wrappedFunc.benchmark.results.runCount++;
  1117.  
  1118. wrappedFunc.benchmark.results.runs.push(self.stop(name));
  1119. }
  1120.  
  1121. return res;
  1122. }
  1123.  
  1124. return this._defineWrappedFuncProperties(wrappedFunc, name);
  1125. }
  1126.  
  1127. getWords(diff) {
  1128. let ms = typeof diff === 'number' ? diff : this._hrtime2Ms(diff);
  1129. return this._msToHms(ms);
  1130.  
  1131. // return this._prettyHrtime(diff);
  1132. }
  1133.  
  1134. getPreciseWords(diff) {
  1135. return this._prettyHrtime(diff, { precise: true });
  1136. }
  1137.  
  1138. getVerboseWords(diff) {
  1139. return this._prettyHrtime(diff, { verbose: true });
  1140. }
  1141.  
  1142. getVerboseAbbrWords(diff) {
  1143. return this._prettyHrtime(diff, { verbose: true, verboseAbbrv: true, precise: true });
  1144. }
  1145.  
  1146. _msToHms(ms) {
  1147. // Extract days
  1148. let days = Math.floor(ms / (86400 * 1000));
  1149. ms %= (86400 * 1000);
  1150. // Extract hours
  1151. let hours = Math.floor(ms / (3600 * 1000));
  1152. ms %= (3600 * 1000);
  1153. // Extract minutes
  1154. let minutes = Math.floor(ms / (60 * 1000));
  1155. ms = ms % (60 * 1000);
  1156.  
  1157. // Extract seconds
  1158. let seconds = Math.floor(ms / 1000);
  1159. ms = ms % 1000;
  1160.  
  1161. let result = [];
  1162. if (days > 0) {
  1163. result.push(`${days} day${days === 1 ? '' : 's'}`);
  1164. }
  1165. if (hours > 0) {
  1166. result.push(`${hours} hr${hours === 1 ? '' : 's'}`);
  1167. }
  1168. if (minutes > 0) {
  1169. result.push(`${minutes} min${minutes === 1 ? '' : 's'}`);
  1170. }
  1171. if (seconds > 0) {
  1172. result.push(`${seconds} sec${seconds === 1 ? '' : 's'}`);
  1173. }
  1174.  
  1175. if (ms > 0) {
  1176. result.push(`${ms} ms`);
  1177. }
  1178.  
  1179. return result.join(', ');
  1180. }
  1181.  
  1182. /**
  1183. *
  1184. *
  1185. * @author Michael Barros <michaelcbarros@gmail.com>
  1186. * @param {number[][]} times
  1187. * @returns {number}
  1188. */
  1189. _getAvgTime(times) {
  1190. return this._getSumTime(times) / times.length;
  1191. }
  1192.  
  1193. /**
  1194. *
  1195. *
  1196. * @author Michael Barros <michaelcbarros@gmail.com>
  1197. * @param {number[][]} times
  1198. * @returns {number}
  1199. */
  1200. _getSumTime(times) {
  1201. return times.reduce((a, b) => a + b);
  1202. }
  1203.  
  1204. /**
  1205. *
  1206. *
  1207. * @author Michael Barros <michaelcbarros@gmail.com>
  1208. * @param {number} ms
  1209. * @returns {number[][]}
  1210. * @memberof Benchmark
  1211. */
  1212. _ms2Hrtime(ms) {
  1213. let seconds = Math.round(ms / 1000);
  1214. let nanoSeconds = Math.round(ms * 1000000 - seconds * 1000000 * 1000);
  1215.  
  1216. return [seconds, nanoSeconds];
  1217. }
  1218.  
  1219. /**
  1220. *
  1221. *
  1222. * @author Michael Barros <michaelcbarros@gmail.com>
  1223. * @param {[number, number]} hrtime
  1224. * @returns {number}
  1225. * @memberof Benchmark
  1226. */
  1227. _hrtime2Ms(hrtime) {
  1228. return hrtime[0] * 1000 + hrtime[1] / 1000000;
  1229. }
  1230.  
  1231. /**
  1232. *
  1233. *
  1234. * @author Michael Barros <michaelcbarros@gmail.com>
  1235. * @param {T} func
  1236. * @param {string=} name
  1237. * @returns {T}
  1238. * @memberof Benchmark
  1239. * @template T
  1240. */
  1241. _getFuncName(func, name) {
  1242. return name ? name : 'name' in func && func.name.trim() !== '' ? func.name : '[wrapped.func]';
  1243. }
  1244.  
  1245. /**
  1246. *
  1247. *
  1248. * @author Michael Barros <michaelcbarros@gmail.com>
  1249. * @param {Function} wrappedFunc
  1250. * @param {string} name
  1251. * @returns {Function}
  1252. * @memberof Benchmark
  1253. */
  1254. _defineWrappedFuncProperties(wrappedFunc, name) {
  1255. Object.defineProperty(wrappedFunc, 'name', {
  1256. value: name,
  1257. writable: false,
  1258. configurable: false,
  1259. enumerable: false,
  1260. });
  1261.  
  1262. Object.defineProperty(wrappedFunc, 'toString', {
  1263. value: () => func.toString(),
  1264. writable: false,
  1265. configurable: false,
  1266. enumerable: false,
  1267. });
  1268.  
  1269. return wrappedFunc;
  1270. }
  1271.  
  1272. /**
  1273. *
  1274. *
  1275. * @author Michael Barros <michaelcbarros@gmail.com>
  1276. * @param {[number, number]=} time
  1277. * @returns {[number, number]}
  1278. * @memberof Benchmark
  1279. */
  1280. _hrtime(time) {
  1281. if (typeof process !== 'undefined') return process.hrtime(time);
  1282.  
  1283. var performance = typeof performance !== 'undefined' ? performance : {};
  1284. let performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || (() => new Date().getTime());
  1285.  
  1286. let clocktime = performanceNow.call(performance) * 1e-3;
  1287. let seconds = Math.floor(clocktime);
  1288. let nanoseconds = Math.floor((clocktime % 1) * 1e9);
  1289.  
  1290. if (time) {
  1291. seconds = seconds - time[0];
  1292. nanoseconds = nanoseconds - time[1];
  1293.  
  1294. if (nanoseconds < 0) {
  1295. seconds--;
  1296. nanoseconds += 1e9;
  1297. }
  1298. }
  1299.  
  1300. return [seconds, nanoseconds];
  1301. }
  1302.  
  1303. /**
  1304. *
  1305. *
  1306. * @author Michael Barros <michaelcbarros@gmail.com>
  1307. * @param {[number, number]=} time
  1308. * @param {{verbose: boolean; verboseAbbrv: boolean; precise: boolean}} { verbose = false, verboseAbbrv = false, precise = false }
  1309. * @returns {string}
  1310. * @memberof Benchmark
  1311. */
  1312. _prettyHrtime(time, { verbose = false, verboseAbbrv = false, precise = false } = {}) {
  1313. let i, spot, sourceAtStep, valAtStep, decimals, strAtStep, results, totalSeconds;
  1314.  
  1315. let minimalDesc = ['h', 'min', 's', 'ms', 'μs', 'ns'];
  1316. let verboseDesc = !verboseAbbrv ? ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'] : minimalDesc;
  1317. let convert = [60 * 60, 60, 1, 1e6, 1e3, 1];
  1318.  
  1319. if (typeof time === 'number') {
  1320. time = this._ms2Hrtime(time);
  1321. }
  1322.  
  1323. if (!Array.isArray(time) || time.length !== 2) return '';
  1324.  
  1325. if (typeof time[0] !== 'number' || typeof time[1] !== 'number') return '';
  1326.  
  1327. // normalize source array due to changes in node v5.4+
  1328. if (time[1] < 0) {
  1329. totalSeconds = time[0] + time[1] / 1e9;
  1330. time[0] = parseInt(totalSeconds);
  1331. time[1] = parseFloat((totalSeconds % 1).toPrecision(9)) * 1e9;
  1332. }
  1333.  
  1334. results = '';
  1335.  
  1336. for (i = 0; i < 6; i++) {
  1337. // grabbing first or second spot in source array
  1338. spot = i < 3 ? 0 : 1;
  1339. sourceAtStep = time[spot];
  1340.  
  1341. if (i !== 3 && i !== 0) {
  1342. // trim off previous portions
  1343. sourceAtStep = sourceAtStep % convert[i - 1];
  1344. }
  1345.  
  1346. if (i === 2) {
  1347. // get partial seconds from other portion of the array
  1348. sourceAtStep += time[1] / 1e9;
  1349. }
  1350.  
  1351. // val at this unit
  1352. valAtStep = sourceAtStep / convert[i];
  1353.  
  1354. if (valAtStep >= 1) {
  1355. if (verbose) {
  1356. // deal in whole units, subsequent laps will get the decimal portion
  1357. valAtStep = Math.floor(valAtStep);
  1358. }
  1359.  
  1360. if (!precise) {
  1361. // don't fling too many decimals
  1362. decimals = valAtStep >= 10 ? 0 : 2;
  1363. strAtStep = valAtStep.toFixed(decimals);
  1364. } else {
  1365. strAtStep = valAtStep.toString();
  1366. }
  1367.  
  1368. if (strAtStep.indexOf('.') > -1 && strAtStep[strAtStep.length - 1] === '0') {
  1369. // remove trailing zeros
  1370. strAtStep = strAtStep.replace(/\.?0+$/, '');
  1371. }
  1372.  
  1373. if (results) {
  1374. // append space if we have a previous value
  1375. results += ' ';
  1376. }
  1377.  
  1378. // append the value
  1379. results += strAtStep;
  1380.  
  1381. // append units
  1382. if (verbose) {
  1383. results += verboseAbbrv ? `${verboseDesc[i]}` : ` ${verboseDesc[i]}`;
  1384.  
  1385. if (!verboseAbbrv && strAtStep !== '1') {
  1386. results += 's';
  1387. }
  1388. } else {
  1389. results += ` ${minimalDesc[i]}`;
  1390. }
  1391.  
  1392. if (!verbose) {
  1393. // verbose gets as many groups as necessary, the rest get only one
  1394. break;
  1395. }
  1396. }
  1397. }
  1398.  
  1399. return results;
  1400. }
  1401. }
  1402.  
  1403. class ArrayStat {
  1404. /**
  1405. * Creates an instance of ArrayStat.
  1406. * @author Michael Barros <michaelcbarros@gmail.com>
  1407. * @param {number[]} array
  1408. * @memberof ArrayStat
  1409. */
  1410. constructor(array) {
  1411. this.array = array;
  1412. }
  1413.  
  1414. _getCloned() {
  1415. return this.array.slice(0);
  1416. }
  1417.  
  1418. min() {
  1419. return Math.min.apply(null, this.array);
  1420. }
  1421.  
  1422. max() {
  1423. return Math.max.apply(null, this.array);
  1424. }
  1425.  
  1426. range() {
  1427. return this.max(this.array) - this.min(this.array);
  1428. }
  1429.  
  1430. midrange() {
  1431. return this.range(this.array) / 2;
  1432. }
  1433.  
  1434. sum(array) {
  1435. array = array || this.array;
  1436.  
  1437. let total = 0;
  1438.  
  1439. for (let i = 0, l = array.length; i < l; i++) total += array[i];
  1440.  
  1441. return total;
  1442. }
  1443.  
  1444. mean(array) {
  1445. array = array || this.array;
  1446.  
  1447. return this.sum(array) / array.length;
  1448. }
  1449.  
  1450. median() {
  1451. let array = this._getCloned();
  1452.  
  1453. array.sort(function (a, b) {
  1454. return a - b;
  1455. });
  1456.  
  1457. let mid = array.length / 2;
  1458.  
  1459. return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
  1460. }
  1461.  
  1462. modes() {
  1463. if (!this.array.length) return [];
  1464.  
  1465. let modeMap = {};
  1466. let maxCount = 0;
  1467. let modes = [];
  1468.  
  1469. this.array.forEach(function (val) {
  1470. if (!modeMap[val]) modeMap[val] = 1;
  1471. else modeMap[val]++;
  1472.  
  1473. if (modeMap[val] > maxCount) {
  1474. modes = [val];
  1475. maxCount = modeMap[val];
  1476. } else if (modeMap[val] === maxCount) {
  1477. modes.push(val);
  1478.  
  1479. maxCount = modeMap[val];
  1480. }
  1481. });
  1482.  
  1483. return modes;
  1484. }
  1485.  
  1486. letiance() {
  1487. let mean = this.mean();
  1488.  
  1489. return this.mean(
  1490. this._getCloned().map(function (num) {
  1491. return Math.pow(num - mean, 2);
  1492. })
  1493. );
  1494. }
  1495.  
  1496. standardDeviation() {
  1497. return Math.sqrt(this.letiance());
  1498. }
  1499.  
  1500. meanAbsoluteDeviation() {
  1501. let mean = this.mean();
  1502.  
  1503. return this.mean(
  1504. this._getCloned().map(function (num) {
  1505. return Math.abs(num - mean);
  1506. })
  1507. );
  1508. }
  1509.  
  1510. zScores() {
  1511. let mean = this.mean();
  1512. let standardDeviation = this.standardDeviation();
  1513.  
  1514. return this._getCloned().map(function (num) {
  1515. return (num - mean) / standardDeviation;
  1516. });
  1517. }
  1518.  
  1519. withinStd(val, stdev) {
  1520. let low = this.mean() - stdev * this.standardDeviation(); // x.deviation;
  1521. let hi = this.mean() + stdev * this.standardDeviation(); // x.deviation;
  1522. let res = val > low && val < hi;
  1523.  
  1524. console.log(`val: ${val.toString().padEnd(5, ' ')} mean: ${this.mean()} stdev: ${this.standardDeviation()} hi: ${hi} low: ${low} res: ${res}`);
  1525.  
  1526. return res;
  1527. }
  1528. }
  1529.  
  1530. memoizeClass(ArrayStat);
  1531.  
  1532. let LineColumnFinder = (function LineColumnFinder() {
  1533. let isArray = Array.isArray;
  1534. let isObject = (val) => val != null && typeof val === 'object' && Array.isArray(val) === false;
  1535. let slice = Array.prototype.slice;
  1536.  
  1537. /**
  1538. * Finder for index and line-column from given string.
  1539. *
  1540. * You can call this without `new` operator as it returns an instance anyway.
  1541. *
  1542. * @class
  1543. * @param {string} str - A string to be parsed.
  1544. * @param {Object|number} [options] - Options.
  1545. * This can be an index in the string for shorthand of `lineColumn(str, index)`.
  1546. * @param {number} [options.origin=1] - The origin value of line and column.
  1547. */
  1548. function LineColumnFinder(str, options) {
  1549. if (!(this instanceof LineColumnFinder)) {
  1550. if (typeof options === 'number') {
  1551. return new LineColumnFinder(str).fromIndex(options);
  1552. }
  1553.  
  1554. return new LineColumnFinder(str, options);
  1555. }
  1556.  
  1557. this.str = str || '';
  1558. this.lineToIndex = buildLineToIndex(this.str);
  1559.  
  1560. options = options || {};
  1561.  
  1562. this.origin = typeof options.origin === 'undefined' ? 1 : options.origin;
  1563. }
  1564.  
  1565. /**
  1566. * Find line and column from index in the string.
  1567. *
  1568. * @param {number} index - Index in the string. (0-origin)
  1569. * @return {Object|null}
  1570. * Found line number and column number in object `{ line: X, col: Y }`.
  1571. * If the given index is out of range, it returns `null`.
  1572. */
  1573. LineColumnFinder.prototype.fromIndex = function (index) {
  1574. if (index < 0 || index >= this.str.length || isNaN(index)) {
  1575. return null;
  1576. }
  1577.  
  1578. let line = findLowerIndexInRangeArray(index, this.lineToIndex);
  1579.  
  1580. return {
  1581. line: line + this.origin,
  1582. col: index - this.lineToIndex[line] + this.origin,
  1583. };
  1584. };
  1585.  
  1586. /**
  1587. * Find index from line and column in the string.
  1588. *
  1589. * @param {number|Object|Array} line - Line number in the string.
  1590. * This can be an Object of `{ line: X, col: Y }`, or
  1591. * an Array of `[line, col]`.
  1592. * @param {number} [column] - Column number in the string.
  1593. * This must be omitted or undefined when Object or Array is given
  1594. * to the first argument.
  1595. * @return {number}
  1596. * Found index in the string. (always 0-origin)
  1597. * If the given line or column is out of range, it returns `-1`.
  1598. */
  1599. LineColumnFinder.prototype.toIndex = function (line, column) {
  1600. if (typeof column === 'undefined') {
  1601. if (isArray(line) && line.length >= 2) {
  1602. return this.toIndex(line[0], line[1]);
  1603. }
  1604.  
  1605. if (isObject(line) && 'line' in line && ('col' in line || 'column' in line)) {
  1606. return this.toIndex(line.line, 'col' in line ? line.col : line.column);
  1607. }
  1608.  
  1609. return -1;
  1610. }
  1611.  
  1612. if (isNaN(line) || isNaN(column)) {
  1613. return -1;
  1614. }
  1615.  
  1616. line -= this.origin;
  1617. column -= this.origin;
  1618.  
  1619. if (line >= 0 && column >= 0 && line < this.lineToIndex.length) {
  1620. let lineIndex = this.lineToIndex[line];
  1621. let nextIndex = line === this.lineToIndex.length - 1 ? this.str.length : this.lineToIndex[line + 1];
  1622.  
  1623. if (column < nextIndex - lineIndex) {
  1624. return lineIndex + column;
  1625. }
  1626. }
  1627.  
  1628. return -1;
  1629. };
  1630.  
  1631. /**
  1632. * Build an array of indexes of each line from a string.
  1633. *
  1634. * @private
  1635. * @param str {string} An input string.
  1636. * @return {number[]} Built array of indexes. The key is line number.
  1637. */
  1638. function buildLineToIndex(str) {
  1639. let lines = str.split('\n');
  1640. let lineToIndex = new Array(lines.length);
  1641. let index = 0;
  1642.  
  1643. for (let i = 0, l = lines.length; i < l; i++) {
  1644. lineToIndex[i] = index;
  1645. index += lines[i].length + /* "\n".length */ 1;
  1646. }
  1647.  
  1648. return lineToIndex;
  1649. }
  1650.  
  1651. /**
  1652. * Find a lower-bound index of a value in a sorted array of ranges.
  1653. *
  1654. * Assume `arr = [0, 5, 10, 15, 20]` and
  1655. * this returns `1` for `value = 7` (5 <= value < 10),
  1656. * and returns `3` for `value = 18` (15 <= value < 20).
  1657. *
  1658. * @private
  1659. * @param arr {number[]} An array of values representing ranges.
  1660. * @param value {number} A value to be searched.
  1661. * @return {number} Found index. If not found `-1`.
  1662. */
  1663. function findLowerIndexInRangeArray(value, arr) {
  1664. if (value >= arr[arr.length - 1]) {
  1665. return arr.length - 1;
  1666. }
  1667.  
  1668. let min = 0,
  1669. max = arr.length - 2,
  1670. mid;
  1671.  
  1672. while (min < max) {
  1673. mid = min + ((max - min) >> 1);
  1674.  
  1675. if (value < arr[mid]) {
  1676. max = mid - 1;
  1677. } else if (value >= arr[mid + 1]) {
  1678. min = mid + 1;
  1679. } else {
  1680. // value >= arr[mid] && value < arr[mid + 1]
  1681. min = mid;
  1682. break;
  1683. }
  1684. }
  1685.  
  1686. return min;
  1687. }
  1688.  
  1689. return LineColumnFinder;
  1690. })();
  1691.  
  1692. class CustomContextMenu {
  1693. /**
  1694. * Example menuItems
  1695. *
  1696. * ```javascript
  1697. * let menuItems = [
  1698. * {
  1699. * type: 'item',
  1700. * label: 'Test1',
  1701. * onClick: () => {
  1702. * alert('test1');
  1703. * },
  1704. * },
  1705. * {
  1706. * type: 'item',
  1707. * label: 'Test2',
  1708. * onClick: () => {
  1709. * console.debug('test2');
  1710. * },
  1711. * },
  1712. * {
  1713. * type: 'break',
  1714. * },
  1715. * {
  1716. * type: 'item',
  1717. * label: 'Test3',
  1718. * onClick: () => {
  1719. * console.debug('test3');
  1720. * },
  1721. * },
  1722. * ];
  1723. * ```
  1724. * @author Michael Barros <michaelcbarros@gmail.com>
  1725. * @param {HTMLElement} elemToAttachTo
  1726. * @param {*} menuItems
  1727. * @memberof CustomContextMenu
  1728. */
  1729. constructor(elemToAttachTo, menuItems, onContextMenu) {
  1730. this.elem = elemToAttachTo;
  1731. this.menuItems = menuItems;
  1732. this.menu = null;
  1733. this.onContextMenu = onContextMenu;
  1734.  
  1735. this._createMenu();
  1736. this._setupEvents();
  1737.  
  1738. this.hide = debounce(this.hide.bind(this), 500, true);
  1739. }
  1740.  
  1741. /**
  1742. *
  1743. *
  1744. * @author Michael Barros <michaelcbarros@gmail.com>
  1745. * @param {number} top
  1746. * @param {number} left
  1747. * @memberof CustomContextMenu
  1748. */
  1749. show(top, left) {
  1750. document.body.appendChild(this.menu);
  1751.  
  1752. this.menu.style.display = 'block';
  1753.  
  1754. this.menu.style.top = `${top}px`;
  1755. this.menu.style.left = `${left}px`;
  1756.  
  1757. this.menu.setAttribute('tabindex', '0');
  1758. this.menu.focus();
  1759. }
  1760.  
  1761. hide() {
  1762. this.menu.style.display = 'none';
  1763.  
  1764. if (document.body.contains(this.menu)) {
  1765. this.menu.remove();
  1766. }
  1767. }
  1768.  
  1769. _setupEvents() {
  1770. this.elem.addEventListener('contextmenu', (ev) => {
  1771. ev.preventDefault();
  1772.  
  1773. if (this.onContextMenu) {
  1774. this.onContextMenu(ev);
  1775. }
  1776.  
  1777. this.show(ev.pageY, ev.pageX);
  1778. });
  1779.  
  1780. document.addEventListener('click', (ev) => {
  1781. if (document.body.contains(this.menu) && !this._isHover(this.menu)) {
  1782. this.hide();
  1783. }
  1784. });
  1785.  
  1786. window.addEventListener('blur', (ev) => {
  1787. this.hide();
  1788. });
  1789.  
  1790. this.menu.addEventListener('blur', (ev) => {
  1791. this.hide();
  1792. });
  1793. }
  1794.  
  1795. _createMenu() {
  1796. this.menu = this._createMenuContainer();
  1797.  
  1798. for (let i = 0; i < this.menuItems.length; i++) {
  1799. let itemConfig = this.menuItems[i];
  1800.  
  1801. switch (itemConfig.type) {
  1802. case 'item':
  1803. this.menu.appendChild(this._createItem(itemConfig));
  1804.  
  1805. break;
  1806.  
  1807. case 'break':
  1808. this.menu.appendChild(this._createBreak());
  1809.  
  1810. break;
  1811.  
  1812. default:
  1813. break;
  1814. }
  1815. }
  1816.  
  1817. // document.body.appendChild(this.menu);
  1818. }
  1819.  
  1820. /**
  1821. *
  1822. *
  1823. * @author Michael Barros <michaelcbarros@gmail.com>
  1824. * @returns {HTMLElement}
  1825. * @memberof CustomContextMenu
  1826. */
  1827. _createMenuContainer() {
  1828. let html = `<div class="context" hidden></div>`;
  1829.  
  1830. let elem = this._createElementsFromHTML(html);
  1831.  
  1832. return elem;
  1833. }
  1834.  
  1835. /**
  1836. *
  1837. *
  1838. * @author Michael Barros <michaelcbarros@gmail.com>
  1839. * @param {*} itemConfig
  1840. * @returns {HTMLElement}
  1841. * @memberof CustomContextMenu
  1842. */
  1843. _createItem(itemConfig) {
  1844. let html = `<div class="context_item">
  1845. <div class="inner_item">
  1846. ${itemConfig.label}
  1847. </div>
  1848. </div>`;
  1849.  
  1850. let elem = this._createElementsFromHTML(html);
  1851.  
  1852. if (itemConfig.id) {
  1853. elem.id = itemConfig.id;
  1854. }
  1855.  
  1856. if (itemConfig.onClick) {
  1857. elem.addEventListener('click', (ev) => {
  1858. itemConfig.onClick(ev);
  1859.  
  1860. this.hide();
  1861. });
  1862. }
  1863.  
  1864. return elem;
  1865. }
  1866.  
  1867. /**
  1868. *
  1869. *
  1870. * @author Michael Barros <michaelcbarros@gmail.com>
  1871. * @returns {HTMLElement}
  1872. * @memberof CustomContextMenu
  1873. */
  1874. _createBreak() {
  1875. let html = `<div class="context_hr"></div>`;
  1876.  
  1877. let elem = this._createElementsFromHTML(html);
  1878.  
  1879. return elem;
  1880. }
  1881.  
  1882. /**
  1883. *
  1884. *
  1885. * @author Michael Barros <michaelcbarros@gmail.com>
  1886. * @param {string} htmlStr
  1887. * @returns {HTMLElement}
  1888. */
  1889. _createElementsFromHTML(htmlStr) {
  1890. let div = document.createElement('div');
  1891.  
  1892. div.innerHTML = htmlStr.trim();
  1893.  
  1894. return div.firstChild;
  1895. }
  1896.  
  1897. _isHover(elem) {
  1898. return elem.parentElement.querySelector(':hover') === elem;
  1899. }
  1900. }
  1901.  
  1902. /**
  1903. *
  1904. *
  1905. * @author Michael Barros <michaelcbarros@gmail.com>
  1906. * @class LocalStorageEx
  1907. */
  1908. class LocalStorageEx {
  1909. /**
  1910. * Creates an instance of LocalStorageEx.
  1911. *
  1912. * @author Michael Barros <michaelcbarros@gmail.com>
  1913. * @memberof LocalStorageEx
  1914. */
  1915. constructor() {
  1916. this.__storage = localStorage;
  1917. }
  1918.  
  1919. /**
  1920. *
  1921. *
  1922. * @author Michael Barros <michaelcbarros@gmail.com>
  1923. * @readonly
  1924. * @memberof LocalStorageEx
  1925. */
  1926. get UNDEFINED_SAVED_VALUE() {
  1927. return '__** undefined **__';
  1928. }
  1929.  
  1930. /**
  1931. *
  1932. *
  1933. * @author Michael Barros <michaelcbarros@gmail.com>
  1934. * @readonly
  1935. * @memberof LocalStorageEx
  1936. */
  1937. get size() {
  1938. let total = 0;
  1939.  
  1940. for (let x in this.__storage) {
  1941. // Value is multiplied by 2 due to data being stored in `utf-16` format, which requires twice the space.
  1942. let amount = this.__storage[x].length * 2;
  1943.  
  1944. if (!isNaN(amount) && this.__storage.hasOwnProperty(x)) {
  1945. total += amount;
  1946. }
  1947. }
  1948.  
  1949. return total;
  1950. }
  1951.  
  1952. /**
  1953. * Determine if browser supports local storage.
  1954. *
  1955. * @author Michael Barros <michaelcbarros@gmail.com>
  1956. * @returns {boolean}
  1957. * @memberof LocalStorageEx
  1958. */
  1959. isSupported() {
  1960. return typeof Storage !== 'undefined';
  1961. }
  1962.  
  1963. /**
  1964. * Check if key exists in local storage.
  1965. *
  1966. * @author Michael Barros <michaelcbarros@gmail.com>
  1967. * @param {*} key
  1968. * @returns {boolean}
  1969. * @memberof LocalStorageEx
  1970. */
  1971. has(key) {
  1972. if (typeof key === 'object') {
  1973. key = JSON.stringify(key);
  1974. }
  1975.  
  1976. return this.__storage.hasOwnProperty(key);
  1977. }
  1978.  
  1979. /**
  1980. * Retrieve an object from local storage.
  1981. *
  1982. * @author Michael Barros <michaelcbarros@gmail.com>
  1983. * @param {*} key
  1984. * @param {*} [defaultValue=null]
  1985. * @returns {*}
  1986. * @memberof LocalStorageEx
  1987. */
  1988. get(key, defaultValue = null) {
  1989. if (typeof key === 'object') {
  1990. key = JSON.stringify(key);
  1991. }
  1992.  
  1993. if (!this.has(key)) {
  1994. return defaultValue;
  1995. }
  1996.  
  1997. let item = this.__storage.getItem(key);
  1998.  
  1999. try {
  2000. if (item === '__** undefined **__') {
  2001. return undefined;
  2002. } else {
  2003. return JSON.parse(item);
  2004. }
  2005. } catch (error) {
  2006. return item;
  2007. }
  2008. }
  2009.  
  2010. /**
  2011. * Save some value to local storage.
  2012. *
  2013. * @author Michael Barros <michaelcbarros@gmail.com>
  2014. * @param {string} key
  2015. * @param {*} value
  2016. * @returns {void}
  2017. * @memberof LocalStorageEx
  2018. */
  2019. set(key, value) {
  2020. if (typeof key === 'object') {
  2021. key = JSON.stringify(key);
  2022. }
  2023.  
  2024. if (value === undefined) {
  2025. value = this.UNDEFINED_SAVED_VALUE;
  2026. } else if (typeof value === 'object') {
  2027. value = JSON.stringify(value);
  2028. }
  2029.  
  2030. this.__storage.setItem(key, value);
  2031. }
  2032.  
  2033. /**
  2034. * Remove element from local storage.
  2035. *
  2036. * @author Michael Barros <michaelcbarros@gmail.com>
  2037. * @param {*} key
  2038. * @returns {void}
  2039. * @memberof LocalStorageEx
  2040. */
  2041. remove(key) {
  2042. if (typeof key === 'object') {
  2043. key = JSON.stringify(key);
  2044. }
  2045.  
  2046. this.__storage.removeItem(key);
  2047. }
  2048.  
  2049. toString() {
  2050. return JSON.parse(JSON.stringify(this.__storage));
  2051. }
  2052. }
  2053.  
  2054. /**
  2055. *
  2056. *
  2057. * @author Michael Barros <michaelcbarros@gmail.com>
  2058. * @class SessionStorageEx
  2059. * @extends {LocalStorageEx}
  2060. */
  2061. class SessionStorageEx extends LocalStorageEx {
  2062. /**
  2063. * Creates an instance of SessionStorageEx.
  2064. *
  2065. * @author Michael Barros <michaelcbarros@gmail.com>
  2066. * @memberof SessionStorageEx
  2067. */
  2068. constructor() {
  2069. super();
  2070.  
  2071. this.__storage = sessionStorage;
  2072. }
  2073. }
  2074.  
  2075. class IgnoreCaseMap extends Map {
  2076. /**
  2077. *
  2078. *
  2079. * @author Michael Barros <michaelcbarros@gmail.com>
  2080. * @param {string} key
  2081. * @param {*} value
  2082. * @returns {this}
  2083. * @memberof IgnoreCaseMap
  2084. */
  2085. set(key, value) {
  2086. return super.set(key.toLocaleLowerCase(), value);
  2087. }
  2088.  
  2089. /**
  2090. *
  2091. *
  2092. * @author Michael Barros <michaelcbarros@gmail.com>
  2093. * @param {string} key
  2094. * @returns {*}
  2095. * @memberof IgnoreCaseMap
  2096. */
  2097. get(key) {
  2098. return super.get(key.toLocaleLowerCase());
  2099. }
  2100. }
  2101.  
  2102. // #endregion Helper Classes
  2103.  
  2104. // #region Helper Functions
  2105.  
  2106. /**
  2107. *
  2108. *
  2109. * @author Michael Barros <michaelcbarros@gmail.com>
  2110. * @param {string} name
  2111. * @param {{logLevel: log.LogLevelDesc, tag: string}} logLevel
  2112. * @return {log.Logger}
  2113. */
  2114. function getLogger(name, { logLevel, tag }) {
  2115. prefix.reg(log);
  2116.  
  2117. const colors = {
  2118. TRACE: '220;86;220',
  2119. DEBUG: '86;86;220',
  2120. INFO: '134;134;221',
  2121. WARN: '220;220;86',
  2122. ERROR: '220;86;86',
  2123. };
  2124.  
  2125. /** @type {prefix.LoglevelPluginPrefixOptions} */
  2126. let options = {
  2127. // template: tag ? `[%t] %l [${tag}] %n:` : '[%t] %l %n:',
  2128. levelFormatter: function (level) {
  2129. return level.toUpperCase();
  2130. },
  2131. nameFormatter: function (name) {
  2132. return name || 'root';
  2133. },
  2134. timestampFormatter: function (date) {
  2135. return date.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
  2136. },
  2137. format: function (level, name, timestamp) {
  2138. let _timestamp = `\x1B[90m[${timestamp}]\x1B[m`;
  2139. let _level = `\x1B[38;2;${colors[level.toUpperCase()]}m${level.toUpperCase()}\x1B[m`;
  2140. let _name = `\x1B[38;2;38;177;38m${tag ? `[${tag}-` : '['}${name}]\x1B[m`;
  2141.  
  2142. let _format = `${_timestamp} ${_level} ${_name}:`;
  2143.  
  2144. return _format;
  2145. },
  2146. };
  2147.  
  2148. const logger = log.getLogger(name);
  2149.  
  2150. prefix.apply(logger, options);
  2151.  
  2152. logger.setLevel(logLevel || 'WARN');
  2153.  
  2154. return logger;
  2155. }
  2156.  
  2157. function pp(obj, fn) {
  2158. fn = fn || console.log;
  2159.  
  2160. fn(pformat(obj));
  2161. }
  2162.  
  2163. function pformat(obj, space = 4) {
  2164. return JSON.stringify(obj, null, space);
  2165. }
  2166.  
  2167. function removeAllButLastStrPattern(string, token) {
  2168. let parts = string.split(token);
  2169.  
  2170. if (parts[1] === undefined) return string;
  2171. else return parts.slice(0, -1).join('') + token + parts.slice(-1);
  2172. }
  2173.  
  2174. /**
  2175. *
  2176. *
  2177. * @author Michael Barros <michaelcbarros@gmail.com>
  2178. * @param {Array.<T> | Array} arr
  2179. * @param {?function(T, T): boolean} callbackObjs
  2180. * @return {T[]}
  2181. * @template T
  2182. */
  2183. function dedupeArr(arr, callbackObjs) {
  2184. if (callbackObjs) {
  2185. let tempArr = /** @type {[]} */ (arr).filter((value, index) => {
  2186. return (
  2187. index ===
  2188. arr.findIndex((other) => {
  2189. return callbackObjs(value, other);
  2190. })
  2191. );
  2192. });
  2193.  
  2194. return tempArr;
  2195. } else {
  2196. return [...new Set(arr)];
  2197. }
  2198. }
  2199.  
  2200. /**
  2201. *
  2202. *
  2203. * @author Michael Barros <michaelcbarros@gmail.com>
  2204. * @param {any} obj
  2205. * @returns {boolean}
  2206. */
  2207. function isClass(obj) {
  2208. return typeof obj === 'function' && /^\s*class\s+/.test(obj.toString());
  2209. }
  2210.  
  2211. /**
  2212. * Checks whether a variable is a class or an instance created with `new`.
  2213. *
  2214. * @author Michael Barros <michaelcbarros@gmail.com>
  2215. * @param {*} value The variable to check.
  2216. * @returns {boolean} `true` if the variable is a class or an instance created with `new`, `false` otherwise.
  2217. */
  2218. function isClassOrInstance(value) {
  2219. // prettier-ignore
  2220. if (typeof value === 'function' &&
  2221. value.prototype &&
  2222. typeof value.prototype.constructor === 'function' &&
  2223. value.prototype.constructor !== Array &&
  2224. value.prototype.constructor !== Object) {
  2225. return true; // It's a class
  2226. } else if (typeof value === 'object' &&
  2227. value.constructor &&
  2228. typeof value.constructor === 'function' &&
  2229. value.constructor.prototype &&
  2230. typeof value.constructor.prototype.constructor === 'function' &&
  2231. value.constructor !== Array &&
  2232. value.constructor !== Object) {
  2233. return true; // It's an instance created with new
  2234. }
  2235.  
  2236. return false;
  2237. }
  2238.  
  2239. /**
  2240. *
  2241. *
  2242. * @author Michael Barros <michaelcbarros@gmail.com>
  2243. * @param {*} value The variable to check.
  2244. * @returns {boolean}
  2245. */
  2246. function isFunction(value) {
  2247. try {
  2248. return typeof value == 'function';
  2249. } catch (error) {
  2250. return false;
  2251. }
  2252. }
  2253.  
  2254. /**
  2255. *
  2256. *
  2257. * @author Michael Barros <michaelcbarros@gmail.com>
  2258. * @param {object} obj
  2259. * @param {{ propsToExclude?: string[]; namesOnly: boolean; removeDuplicates: boolean; asObject: boolean }} [{ propsToExclude = [], namesOnly = false, removeDuplicates = true, asObject = false } = {}]
  2260. * @returns
  2261. */
  2262. function getObjProps(obj, { propsToExclude = [], namesOnly = false, removeDuplicates = true, asObject = false } = {}) {
  2263. // Default
  2264. let _propsToExclude = [
  2265. //
  2266. '__defineGetter__',
  2267. '__defineSetter__',
  2268. '__lookupSetter__',
  2269. '__lookupGetter__',
  2270. '__proto__',
  2271. '__original__',
  2272.  
  2273. 'caller',
  2274. 'callee',
  2275. 'arguments',
  2276.  
  2277. 'toString',
  2278. 'valueOf',
  2279. 'constructor',
  2280. 'hasOwnProperty',
  2281. 'isPrototypeOf',
  2282. 'propertyIsEnumerable',
  2283. 'toLocaleString',
  2284. ];
  2285.  
  2286. _propsToExclude = propsToExclude && Array.isArray(propsToExclude) ? _propsToExclude.concat(propsToExclude) : _propsToExclude;
  2287.  
  2288. let objHierarchy = getObjHierarchy(obj);
  2289. let propNames = getPropNames(objHierarchy);
  2290. let plainObj = {};
  2291. let objKeys = [];
  2292.  
  2293. /**
  2294. *
  2295. *
  2296. * @author Michael Barros <michaelcbarros@gmail.com>
  2297. * @param {any} obj
  2298. * @returns {Array<any>}
  2299. */
  2300. function getObjHierarchy(obj) {
  2301. let objs = [obj];
  2302.  
  2303. obj = isClassOrInstance(obj) ? obj.prototype || obj.__proto__ : obj;
  2304.  
  2305. do {
  2306. objs.push(obj);
  2307. } while ((obj = Object.getPrototypeOf(obj)));
  2308.  
  2309. return objs;
  2310. }
  2311.  
  2312. /**
  2313. *
  2314. *
  2315. * @author Michael Barros <michaelcbarros@gmail.com>
  2316. * @param {Array<any>} objHierarchy
  2317. * @returns {string[]}
  2318. */
  2319. function getPropNames(objHierarchy) {
  2320. /** @type {string[]} */
  2321. let propNames = [];
  2322.  
  2323. for (let i = 0; i < objHierarchy.length; i++) {
  2324. const _obj = objHierarchy[i];
  2325.  
  2326. let getPropFuncs = [Object.getOwnPropertyNames, Object.getOwnPropertySymbols];
  2327.  
  2328. getPropFuncs.forEach((func) => {
  2329. let _propNames = func(_obj);
  2330.  
  2331. _propNames.forEach((propName) => {
  2332. if (!_propsToExclude.includes(propName) && !propNames.includes(propName)) {
  2333. propNames.push(propName);
  2334. }
  2335. });
  2336. });
  2337. }
  2338.  
  2339. return propNames;
  2340. }
  2341.  
  2342. /**
  2343. *
  2344. *
  2345. * @author Michael Barros <michaelcbarros@gmail.com>
  2346. * @param {{ name: string, value: any }[]} props
  2347. * @return {{ name: string, value: any }[]}
  2348. */
  2349. function dedupeProps(props) {
  2350. function findNonNullProp(props, name) {
  2351. let res = props.find((prop) => prop.name == name && prop.value != null);
  2352.  
  2353. if (!res) {
  2354. res = props.find((prop) => prop.name == name);
  2355. }
  2356.  
  2357. return res;
  2358. }
  2359.  
  2360. function propsContains(props, name) {
  2361. return props.some((prop) => prop.name == name);
  2362. }
  2363.  
  2364. let newProps = [];
  2365.  
  2366. for (let i = 0; i < props.length; i++) {
  2367. const prop = props[i];
  2368.  
  2369. let tempProp = findNonNullProp(props, prop.name);
  2370.  
  2371. if (!propsContains(newProps, tempProp.name)) {
  2372. newProps.push(tempProp);
  2373. }
  2374. }
  2375.  
  2376. return newProps;
  2377. }
  2378.  
  2379. function getProps(objHierarchy, doFuncs = false) {
  2380. /** @type {{ name: string, value: any }} */
  2381. let props = [];
  2382.  
  2383. for (let o = 0; o < objHierarchy.length; o++) {
  2384. const _obj = objHierarchy[o];
  2385.  
  2386. for (let p = 0; p < propNames.length; p++) {
  2387. const propName = propNames[p];
  2388. let value;
  2389.  
  2390. try {
  2391. value = _obj[propName];
  2392. } catch (error) {}
  2393.  
  2394. if (!_propsToExclude.includes(propName)) {
  2395. if (asObject) {
  2396. if (!objKeys.includes(propName)) {
  2397. objKeys.push(propName);
  2398.  
  2399. plainObj[propName] = value;
  2400. }
  2401. } else {
  2402. props.push({
  2403. name: propName,
  2404. value: value,
  2405. });
  2406. }
  2407. }
  2408. }
  2409. }
  2410.  
  2411. if (!asObject) {
  2412. if (removeDuplicates) {
  2413. props = dedupeProps(props);
  2414. }
  2415.  
  2416. props = props.filter(function (prop, i, props) {
  2417. let exprs = [
  2418. //
  2419. !_propsToExclude.includes(prop.name),
  2420. // props[i + 1] && prop.name != props[i + 1].name,
  2421. ...(doFuncs ? [isFunction(prop.value)] : [!isFunction(prop.value)]),
  2422. ];
  2423.  
  2424. return exprs.every(Boolean);
  2425. });
  2426. }
  2427.  
  2428. if (asObject) {
  2429. return plainObj;
  2430. } else {
  2431. return props.sort(function (a, b) {
  2432. let aName = typeof a.name == 'symbol' ? a.name.toString() : a.name;
  2433. let bName = typeof b.name == 'symbol' ? b.name.toString() : b.name;
  2434.  
  2435. if (aName < bName) return -1;
  2436. if (aName > bName) return 1;
  2437.  
  2438. return 0;
  2439. });
  2440. }
  2441. }
  2442.  
  2443. let res;
  2444.  
  2445. if (asObject) {
  2446. getProps(objHierarchy, true);
  2447. getProps(objHierarchy);
  2448.  
  2449. res = plainObj;
  2450. } else {
  2451. res = {
  2452. funcs: getProps(objHierarchy, true),
  2453. props: getProps(objHierarchy),
  2454. };
  2455.  
  2456. if (namesOnly) {
  2457. res.funcs = res.funcs.filter((func) => func.name.toString() != 'Symbol(Symbol.hasInstance)').map((func) => func.name);
  2458. res.props = res.props.filter((prop) => prop.name.toString() != 'Symbol(Symbol.hasInstance)').map((prop) => prop.name);
  2459. }
  2460. }
  2461.  
  2462. objHierarchy = null;
  2463.  
  2464. return res;
  2465. }
  2466.  
  2467. /**
  2468. *
  2469. *
  2470. * @author Michael Barros <michaelcbarros@gmail.com>
  2471. * @param {Window} [_window=window]
  2472. * @param {{ namesOnly: boolean; asObject: boolean }} [{ namesOnly = false, asObject = false } = {}]
  2473. * @returns
  2474. */
  2475. function getUserDefinedGlobalProps(_window = null, { namesOnly = false, asObject = false } = {}) {
  2476. _window = _window || getWindow();
  2477.  
  2478. let iframe = document.createElement('iframe');
  2479.  
  2480. iframe.style.display = 'none';
  2481.  
  2482. document.body.appendChild(iframe);
  2483.  
  2484. let plainObj = {};
  2485. let objKeys = [];
  2486.  
  2487. function getProps(obj, doFuncs = false) {
  2488. let props = [];
  2489. let _obj = obj;
  2490.  
  2491. let getPropFuncs = [Object.getOwnPropertyNames, Object.getOwnPropertySymbols];
  2492.  
  2493. getPropFuncs.forEach((func) => {
  2494. let propNames = func(_obj);
  2495.  
  2496. for (let i = 0; i < propNames.length; i++) {
  2497. const propName = propNames[i];
  2498. let value;
  2499.  
  2500. try {
  2501. value = _obj[propName];
  2502. } catch (error) {}
  2503.  
  2504. if (isNumber(propName) && value?.constructor?.name == 'Window') continue;
  2505.  
  2506. if (!iframe.contentWindow.hasOwnProperty(propName)) {
  2507. if (asObject) {
  2508. if (!objKeys.includes(propName)) {
  2509. objKeys.push(propName);
  2510.  
  2511. plainObj[propName] = value;
  2512. }
  2513. } else {
  2514. props.push({
  2515. name: propName,
  2516. value: value,
  2517. });
  2518. }
  2519. }
  2520. }
  2521. });
  2522.  
  2523. if (!asObject) {
  2524. props = props.filter(function (prop, i, props) {
  2525. let propName1 = prop.name;
  2526. let propName2 = props[i + 1] ? props[i + 1].name : undefined;
  2527. let propValue1 = prop.value;
  2528. let propValue2 = props[i + 1] ? props[i + 1].value : undefined;
  2529.  
  2530. let exprs = [
  2531. //
  2532. // props[i + 1] && propName1 != propName2,
  2533. (props[i + 1] && propName1.constructor.name == 'Symbol' && propName2.constructor.name == 'Symbol' && propValue1 != propValue2) || propName1 != propName2,
  2534. ...(doFuncs ? [isFunction(obj[propName1])] : [!isFunction(obj[propName1])]),
  2535. ];
  2536.  
  2537. return exprs.every(Boolean);
  2538. });
  2539. }
  2540.  
  2541. if (asObject) {
  2542. return plainObj;
  2543. } else {
  2544. return props.sort(function (a, b) {
  2545. let aName = typeof a.name == 'symbol' ? a.name.toString() : a.name;
  2546. let bName = typeof b.name == 'symbol' ? b.name.toString() : b.name;
  2547.  
  2548. if (aName < bName) return -1;
  2549. if (aName > bName) return 1;
  2550.  
  2551. return 0;
  2552. });
  2553. }
  2554. }
  2555.  
  2556. let res;
  2557.  
  2558. if (asObject) {
  2559. getProps(_window, true);
  2560. getProps(_window);
  2561.  
  2562. res = plainObj;
  2563. } else {
  2564. res = {
  2565. funcs: getProps(_window, true),
  2566. props: getProps(_window),
  2567. };
  2568.  
  2569. if (namesOnly) {
  2570. res.funcs = res.funcs.filter((func) => func.name.toString() != 'Symbol(Symbol.hasInstance)').map((func) => func.name);
  2571. res.props = res.props.filter((prop) => prop.name.toString() != 'Symbol(Symbol.hasInstance)').map((prop) => prop.name);
  2572. }
  2573. }
  2574.  
  2575. document.body.removeChild(iframe);
  2576.  
  2577. return res;
  2578. }
  2579.  
  2580. /**
  2581. *
  2582. *
  2583. * @author Michael Barros <michaelcbarros@gmail.com>
  2584. * @param {T} obj
  2585. * @param {T | boolean} thisArg
  2586. * @returns {T}
  2587. * @template T
  2588. */
  2589. function storeObjOriginalFuncs(obj, thisArg = true) {
  2590. let props = getObjProps(obj);
  2591.  
  2592. obj.__original__ = {};
  2593.  
  2594. for (let i = 0; i < props.funcs.length; i++) {
  2595. const func = props.funcs[i];
  2596.  
  2597. if (thisArg == true) {
  2598. obj.__original__[func.name] = func.value.bind(obj);
  2599. } else {
  2600. obj.__original__[func.name] = thisArg != false && thisArg != null && thisArg != undefined ? func.value.bind(thisArg) : func.value;
  2601. }
  2602. }
  2603.  
  2604. return obj;
  2605. }
  2606.  
  2607. function printProps(obj, title) {
  2608. let headerFooterBanner = '*********************************************************';
  2609.  
  2610. console.log(headerFooterBanner);
  2611. console.log(`* ${title || ''}`);
  2612. console.log(headerFooterBanner);
  2613.  
  2614. for (let key in obj) console.log(key + ': ', [obj[key]]);
  2615.  
  2616. console.log(headerFooterBanner);
  2617. }
  2618.  
  2619. function sortObject(o, desc) {
  2620. let sorted = {};
  2621. let key;
  2622. let a = [];
  2623.  
  2624. for (key in o) {
  2625. if (o.hasOwnProperty(key)) a.push(key);
  2626. }
  2627.  
  2628. if (desc) a.sort(sortDescending);
  2629. else a.sort(sortAscending);
  2630.  
  2631. for (key = 0; key < a.length; key++) sorted[a[key]] = o[a[key]];
  2632.  
  2633. return sorted;
  2634. }
  2635.  
  2636. function sortAscending(a, b) {
  2637. if (typeof a == 'string') {
  2638. a = a.toLowerCase();
  2639. b = b.toLowerCase();
  2640. }
  2641.  
  2642. if (a < b) return -1;
  2643. else if (a > b) return 1;
  2644. else return 0;
  2645. }
  2646.  
  2647. function sortDescending(a, b) {
  2648. if (typeof a == 'string') {
  2649. a = a.toLowerCase();
  2650. b = b.toLowerCase();
  2651. }
  2652.  
  2653. if (a > b) return -1;
  2654. else if (a < b) return 1;
  2655. else return 0;
  2656. }
  2657.  
  2658. function getFileExtension(sFile) {
  2659. return sFile.replace(/^(.*)(\.[^/.]+)$/, '$2');
  2660. }
  2661.  
  2662. /**
  2663. * Async wait function.
  2664. * Example:
  2665. * (async () => {
  2666. * await wait(4000).then(() => {
  2667. * console.log(new Date().toLocaleTimeString());
  2668. * }).then(() => {
  2669. * console.log('here');
  2670. * });
  2671. * })();
  2672. *
  2673. * @param {number} ms - Milliseconds to wait.
  2674. * @param {boolean} [synchronous=false] - Wait synchronously.
  2675. */
  2676. async function wait(ms, synchronous = false) {
  2677. let _wait = (ms, synchronous) => {
  2678. if (synchronous) {
  2679. let start = Date.now();
  2680. let now = start;
  2681.  
  2682. while (now - start < ms) now = Date.now();
  2683. } else {
  2684. return new Promise((resolve) => setTimeout(resolve, ms));
  2685. }
  2686. };
  2687.  
  2688. await _wait(ms, synchronous);
  2689. }
  2690.  
  2691. /**
  2692. *
  2693. *
  2694. * @author Michael Barros <michaelcbarros@gmail.com>
  2695. * @param {() => bool} condition
  2696. * @param {{ timeout?: number; callback: () => T; conditionIsAsync: boolean; }} [{ timeout, callback, conditionIsAsync = false } = {}]
  2697. * @returns {T}
  2698. * @template T
  2699. */
  2700. async function waitUntil(condition, { timeout, callback, conditionIsAsync = false } = {}) {
  2701. timeout = timeout || -1;
  2702. let maxTime = timeout == -1 ? 20000 : -1;
  2703. let startTime = new Date();
  2704.  
  2705. let timeRanOut = false;
  2706.  
  2707. let done = (() => {
  2708. let deferred = {};
  2709.  
  2710. deferred.promise = new Promise((resolve, reject) => {
  2711. deferred.resolve = resolve;
  2712. deferred.reject = reject;
  2713. });
  2714.  
  2715. return deferred;
  2716. })();
  2717.  
  2718. /** @type {number} */
  2719. let timeoutId;
  2720.  
  2721. if (timeout && timeout > 0) {
  2722. timeoutId = setTimeout(() => {
  2723. timeRanOut = true;
  2724.  
  2725. return done.reject();
  2726. }, timeout);
  2727. }
  2728.  
  2729. let loop = async () => {
  2730. let endTime = new Date();
  2731. let elapsed = endTime - startTime;
  2732.  
  2733. let conditionResult = conditionIsAsync ? await condition() : condition();
  2734.  
  2735. if (conditionResult || timeRanOut || (maxTime != -1 && elapsed > maxTime)) {
  2736. clearTimeout(timeoutId);
  2737.  
  2738. return done.resolve(callback ? await callback() : undefined);
  2739. }
  2740.  
  2741. setTimeout(loop, 0);
  2742. };
  2743.  
  2744. setTimeout(loop, 0);
  2745.  
  2746. return done.promise;
  2747. }
  2748.  
  2749. /**
  2750. *
  2751. *
  2752. * @author Michael Barros <michaelcbarros@gmail.com>
  2753. * @param {any} obj
  2754. * @param {boolean} [getInherited=false]
  2755. * @returns {string}
  2756. */
  2757. function getType(obj, getInherited = false) {
  2758. let _typeVar = (function (global) {
  2759. let cache = {};
  2760.  
  2761. return function (obj) {
  2762. let key;
  2763.  
  2764. // null
  2765. if (obj == null) return 'null';
  2766.  
  2767. // window/global
  2768. if (obj == global) return 'global';
  2769.  
  2770. // basic: string, boolean, number, undefined
  2771. if (!['object', 'function'].includes((key = typeof obj))) return key;
  2772.  
  2773. if (obj.constructor != undefined && obj.constructor.name != 'Object' && !getInherited) return obj.constructor.name;
  2774.  
  2775. // cached. date, regexp, error, object, array, math
  2776. // and get XXXX from [object XXXX], and cache it
  2777. return cache[(key = {}.toString.call(obj))] || (cache[key] = key.slice(8, -1));
  2778. };
  2779. })(globalThis);
  2780.  
  2781. return _typeVar(obj);
  2782. }
  2783.  
  2784. /**
  2785. * Returns a function, that, as long as it continues to be invoked, will not
  2786. * be triggered. The function will be called after it stops being called for
  2787. * N milliseconds. If `immediate` is passed, trigger the function on the
  2788. * leading edge, instead of the trailing.
  2789. *
  2790. * @param {function} func
  2791. * @param {Number} wait
  2792. * @param {Boolean} immediate
  2793. * @returns
  2794. */
  2795. function debounce(func, wait, immediate) {
  2796. let timeout;
  2797.  
  2798. return function () {
  2799. let context = this,
  2800. args = arguments;
  2801.  
  2802. let later = function () {
  2803. timeout = null;
  2804.  
  2805. if (!immediate) func.apply(context, args);
  2806. };
  2807.  
  2808. let callNow = immediate && !timeout;
  2809.  
  2810. clearTimeout(timeout);
  2811. timeout = setTimeout(later, wait);
  2812.  
  2813. if (callNow) func.apply(context, args);
  2814. };
  2815. }
  2816.  
  2817. function equals(x, y) {
  2818. if (x === y) return true;
  2819. // if both x and y are null or undefined and exactly the same
  2820.  
  2821. if (!(x instanceof Object) || !(y instanceof Object)) return false;
  2822. // if they are not strictly equal, they both need to be Objects
  2823.  
  2824. if (x.constructor !== y.constructor) return false;
  2825. // they must have the exact same prototype chain, the closest we can do is
  2826. // test there constructor.
  2827.  
  2828. for (let p in x) {
  2829. if (!x.hasOwnProperty(p)) continue;
  2830. // other properties were tested using x.constructor === y.constructor
  2831.  
  2832. if (!y.hasOwnProperty(p)) return false;
  2833. // allows to compare x[ p ] and y[ p ] when set to undefined
  2834.  
  2835. if (x[p] === y[p]) continue;
  2836. // if they have the same strict value or identity then they are equal
  2837.  
  2838. if (typeof x[p] !== 'object') return false;
  2839. // Numbers, Strings, Functions, Booleans must be strictly equal
  2840.  
  2841. if (!equals(x[p], y[p])) return false;
  2842. // Objects and Arrays must be tested recursively
  2843. }
  2844.  
  2845. for (p in y) {
  2846. if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) return false;
  2847. // allows x[ p ] to be set to undefined
  2848. }
  2849. return true;
  2850. }
  2851.  
  2852. function isEncoded(uri) {
  2853. uri = uri || '';
  2854.  
  2855. return uri !== decodeURIComponent(uri);
  2856. }
  2857.  
  2858. function fullyDecodeURI(uri) {
  2859. while (isEncoded(uri)) uri = decodeURIComponent(uri);
  2860.  
  2861. return uri;
  2862. }
  2863.  
  2864. /**
  2865. * Get difference in days between two dates.
  2866. *
  2867. * @param {Date} a
  2868. * @param {Date} b
  2869. * @returns
  2870. */
  2871. function dateDiffInDays(a, b) {
  2872. let _MS_PER_DAY = 1000 * 60 * 60 * 24;
  2873.  
  2874. // Discard the time and time-zone information.
  2875. let utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
  2876. let utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
  2877.  
  2878. return Math.floor((utc1 - utc2) / _MS_PER_DAY);
  2879. }
  2880.  
  2881. function randomInt(min, max) {
  2882. return Math.floor(Math.random() * (max - min + 1) + min);
  2883. }
  2884.  
  2885. function randomFloat(min, max) {
  2886. return Math.random() * (max - min + 1) + min;
  2887. }
  2888.  
  2889. function keySort(keys, desc) {
  2890. return function (a, b) {
  2891. let aVal = null;
  2892. let bVal = null;
  2893.  
  2894. for (let i = 0; i < keys.length; i++) {
  2895. const key = keys[i];
  2896.  
  2897. if (i == 0) {
  2898. aVal = a[key];
  2899. bVal = b[key];
  2900. } else {
  2901. aVal = aVal[key];
  2902. bVal = bVal[key];
  2903. }
  2904. }
  2905. return desc ? ~~(aVal < bVal) : ~~(aVal > bVal);
  2906. };
  2907. }
  2908.  
  2909. function observe(obj, handler) {
  2910. return new Proxy(obj, {
  2911. get(target, key) {
  2912. return target[key];
  2913. },
  2914. set(target, key, value) {
  2915. target[key] = value;
  2916.  
  2917. if (handler) handler();
  2918. },
  2919. });
  2920. }
  2921.  
  2922. /**
  2923. *
  2924. *
  2925. * @author Michael Barros <michaelcbarros@gmail.com>
  2926. * @param {Console} console
  2927. */
  2928. function addSaveToConsole(console) {
  2929. console.save = function (data, filename) {
  2930. if (!data) {
  2931. console.error('Console.save: No data');
  2932.  
  2933. return;
  2934. }
  2935.  
  2936. if (!filename) filename = 'console.json';
  2937.  
  2938. if (typeof data === 'object') data = JSON.stringify(data, undefined, 4);
  2939.  
  2940. let blob = new Blob([data], {
  2941. type: 'text/json',
  2942. });
  2943. let event = document.createEvent('MouseEvents');
  2944. let tempElem = document.createElement('a');
  2945.  
  2946. tempElem.download = filename;
  2947. tempElem.href = window.URL.createObjectURL(blob);
  2948. tempElem.dataset.downloadurl = ['text/json', tempElem.download, tempElem.href].join(':');
  2949.  
  2950. event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  2951. tempElem.dispatchEvent(event);
  2952. };
  2953. }
  2954.  
  2955. /**
  2956. *
  2957. *
  2958. * @author Michael Barros <michaelcbarros@gmail.com>
  2959. * @param {Window} _window
  2960. * @param {string} propName
  2961. * @param {{} | [] | any} value
  2962. */
  2963. function setupWindowProps(_window, propName, value) {
  2964. if (getType(value) == 'object') {
  2965. if (typeof _window[propName] === 'undefined' || _window[propName] == null) {
  2966. _window[propName] = {};
  2967. }
  2968.  
  2969. let keys = Object.keys(value);
  2970.  
  2971. for (let i = 0; i < keys.length; i++) {
  2972. const key = keys[i];
  2973.  
  2974. if (!(/** @type {{}} */ (_window[propName].hasOwnProperty(key)))) {
  2975. _window[propName][key] = null;
  2976. }
  2977.  
  2978. if (_window[propName][key] == null) {
  2979. _window[propName][key] = value[key];
  2980. }
  2981. }
  2982. } else {
  2983. if (typeof _window[propName] === 'undefined' || _window[propName] == null) {
  2984. _window[propName] = value;
  2985. }
  2986. }
  2987. }
  2988.  
  2989. /**
  2990. *
  2991. *
  2992. * @author Michael Barros <michaelcbarros@gmail.com>
  2993. * @param {{ name: string; value: any; }[]} variables
  2994. */
  2995. function exposeGlobalVariables(variables) {
  2996. variables.forEach((variable, index, variables) => {
  2997. try {
  2998. setupWindowProps(getWindow(), variable.name, variable.value);
  2999. } catch (error) {
  3000. logger.error(`Unable to expose variable ${variable.name} into the global scope.`);
  3001. }
  3002. });
  3003. }
  3004.  
  3005. /**
  3006. *
  3007. *
  3008. * @author Michael Barros <michaelcbarros@gmail.com>
  3009. * @param {string} str
  3010. * @returns {string}
  3011. */
  3012. function htmlEntitiesDecode(str) {
  3013. return str
  3014. .replace(/&amp;/g, '&')
  3015. .replace(/&lt;/g, '<')
  3016. .replace(/&gt;/g, '>')
  3017. .replace(/&quot;/g, '"');
  3018. }
  3019.  
  3020. /**
  3021. *
  3022. *
  3023. * @author Michael Barros <michaelcbarros@gmail.com>
  3024. * @param {string} metaName
  3025. * @returns {string}
  3026. */
  3027. function getMeta(metaName) {
  3028. const metas = document.getElementsByTagName('meta');
  3029.  
  3030. for (let i = 0; i < metas.length; i++) {
  3031. if (metas[i].getAttribute('name') === metaName) {
  3032. return metas[i].getAttribute('content');
  3033. }
  3034. }
  3035.  
  3036. return '';
  3037. }
  3038.  
  3039. /**
  3040. *
  3041. *
  3042. * @returns {Window & typeof globalThis}
  3043. */
  3044. function getWindow() {
  3045. return globalThis.GM_info && GM_info.script.grant.includes('unsafeWindow') ? unsafeWindow : globalThis;
  3046. }
  3047.  
  3048. /**
  3049. *
  3050. *
  3051. * @param {Window} _window
  3052. */
  3053. function getTopWindow(_window = null) {
  3054. _window = _window || getWindow();
  3055.  
  3056. try {
  3057. if (_window.self !== _window.top) {
  3058. _window = getTopWindow(_window.parent);
  3059. }
  3060. } catch (e) {}
  3061.  
  3062. return _window;
  3063. }
  3064.  
  3065. /**
  3066. * Setup global error handler
  3067. *
  3068. * **Example:**
  3069. * ```javascript
  3070. * setupGlobalErrorHandler({
  3071. * callback: (error) => console.error('Error:', error),
  3072. * continuous: true,
  3073. * prevent_default: true,
  3074. * tag: '[test-global-error-handler]',
  3075. * });
  3076. * ```
  3077. *
  3078. * @author Michael Barros <michaelcbarros@gmail.com>
  3079. * @param {{ callback: (error: ErrorEx) => void; continuous?: boolean; prevent_default?: boolean; tag?: string; logFunc?: (...data: any[]) => void; _window: Window; }} [{ callback, continuous = true, prevent_default = false, tag = '[akkd]', logFunc = console.error, _window = window } = {}]
  3080. */
  3081. function setupGlobalErrorHandler({ callback, continuous = true, prevent_default = false, tag = null, logFunc = console.error, _window = window } = {}) {
  3082. // respect existing onerror handlers
  3083. let _onerror_original = _window.onerror;
  3084.  
  3085. // install our new error handler
  3086. _window.onerror = function (event, source, lineno, colno, error) {
  3087. if (_onerror_original) {
  3088. _onerror_original(event, source, lineno, colno, error);
  3089. }
  3090.  
  3091. // unset onerror to prevent loops and spamming
  3092. let _onerror = _window.onerror;
  3093.  
  3094. _window.onerror = null;
  3095.  
  3096. // now deal with the error
  3097. let errorObject = new ErrorEx(event, source, lineno, colno, error);
  3098. let errorMessage = createErrorMessage(errorObject);
  3099.  
  3100. if (tag) {
  3101. let rgb = '38;177;38';
  3102.  
  3103. tag = `\x1B[38;2;${rgb}m${tag}\x1B[m`;
  3104.  
  3105. logFunc(tag, errorMessage);
  3106. } else {
  3107. logFunc(errorMessage);
  3108. }
  3109.  
  3110. // run callback if provided
  3111. if (callback) {
  3112. callback(errorObject);
  3113. }
  3114.  
  3115. // re-install this error handler again if continuous mode
  3116. if (continuous) {
  3117. _window.onerror = _onerror;
  3118. }
  3119.  
  3120. // true if normal error propagation should be suppressed
  3121. // (i.e. normally console.error is logged by the browser)
  3122. return prevent_default;
  3123. };
  3124.  
  3125. class ErrorEx {
  3126. /**
  3127. * Creates an instance of ErrorEx.
  3128. * @author Michael Barros <michaelcbarros@gmail.com>
  3129. * @param {string | Event} event
  3130. * @param {string} source
  3131. * @param {number} lineno
  3132. * @param {number} colno
  3133. * @param {Error} error
  3134. * @memberof ErrorEx
  3135. */
  3136. constructor(event, source, lineno, colno, error) {
  3137. this.name = error.name;
  3138. this.message = error && error.message ? error.message : null;
  3139. this.stack = error && error.stack ? error.stack : null;
  3140. this.event = event;
  3141. this.location = document.location.href;
  3142. this.url = source;
  3143. this.lineno = lineno;
  3144. this.colno = colno;
  3145. this.useragent = navigator.userAgent;
  3146. this.fileName = error && error.fileName ? error.fileName : null;
  3147. this.description = error && error.description ? error.description : null;
  3148. this.name = error && error.name ? error.name : null;
  3149. this.error = error;
  3150. }
  3151. }
  3152.  
  3153. /**
  3154. *
  3155. *
  3156. * @author Michael Barros <michaelcbarros@gmail.com>
  3157. * @param {ErrorEx} error
  3158. * @returns {string}
  3159. */
  3160. function createErrorMessage(error) {
  3161. let name = error && error.name ? error.name : 'Error';
  3162. let message = error && error.message ? error.message : 'Unknown error occured';
  3163. let stack = error && error.stack ? error.stack.split('\n').splice(1).join('\n') : 'Error';
  3164.  
  3165. let errorMessage = `Uncaught Global ${name}: ${message}\n${stack}`;
  3166.  
  3167. return errorMessage;
  3168. }
  3169. }
  3170.  
  3171. function applyCss(cssFiles) {
  3172. /** @type {{ css: string, node?: HTMLElement }[]} */
  3173. let cssArr = [];
  3174.  
  3175. for (let i = 0; i < cssFiles.length; i++) {
  3176. let cssStr = GM_getResourceText(cssFiles[i]);
  3177.  
  3178. cssArr.push({
  3179. css: cssStr,
  3180. });
  3181. }
  3182.  
  3183. addStyles(cssArr);
  3184. }
  3185.  
  3186. function applyCss2(cssFiles) {
  3187. /**
  3188. *
  3189. *
  3190. * @author Michael Barros <michaelcbarros@gmail.com>
  3191. * @param {string} cssStyleStr
  3192. * @returns {HTMLStyleElement}
  3193. */
  3194. function createStyleElementFromCss(cssStyleStr) {
  3195. let style = document.createElement('style');
  3196.  
  3197. style.innerHTML = cssStyleStr.trim();
  3198.  
  3199. return style;
  3200. }
  3201.  
  3202. let ranOnce = false;
  3203.  
  3204. /** @type {HTMLStyleElement[]} */
  3205. getWindow().akkd.styleElements = [];
  3206.  
  3207. function removeStyleElements() {
  3208. for (let i = 0; i < getWindow().akkd.styleElements.length; i++) {
  3209. let styleElement = getWindow().akkd.styleElements[i];
  3210.  
  3211. styleElement.remove();
  3212. }
  3213.  
  3214. getWindow().akkd.styleElements = [];
  3215. }
  3216.  
  3217. function _editStyleSheets() {
  3218. $(document).arrive('style, link', async function () {
  3219. if (this.tagName == 'LINK' && this.href.includes('.css')) {
  3220. removeStyleElements();
  3221.  
  3222. for (let i = 0; i < cssFiles.length; i++) {
  3223. let cssFile = cssFiles[i];
  3224. let css = GM_getResourceText(cssFile);
  3225.  
  3226. let styleElem = createStyleElementFromCss(css);
  3227.  
  3228. styleElem.id = `akkd-transform-style-${(i + 1).toString().padStart(2, '0')}`;
  3229.  
  3230. getWindow().akkd.styleElements.push(styleElem);
  3231.  
  3232. document.body.appendChild(styleElem);
  3233. }
  3234. }
  3235. });
  3236.  
  3237. if (!ranOnce) {
  3238. for (let i = 0; i < cssFiles.length; i++) {
  3239. let cssFile = cssFiles[i];
  3240. let css = GM_getResourceText(cssFile);
  3241.  
  3242. let styleElem = createStyleElementFromCss(css);
  3243.  
  3244. styleElem.id = `akkd-transform-style-${(i + 1).toString().padStart(2, '0')}`;
  3245.  
  3246. getWindow().akkd.styleElements.push(styleElem);
  3247.  
  3248. document.body.appendChild(styleElem);
  3249. }
  3250.  
  3251. ranOnce = true;
  3252. }
  3253. }
  3254.  
  3255. _editStyleSheets();
  3256. }
  3257.  
  3258. /**
  3259. *
  3260. *
  3261. * @author Michael Barros <michaelcbarros@gmail.com>
  3262. * @param {{ css: string, node?: HTMLElement }[]} cssArr
  3263. */
  3264. let addStyles = (function () {
  3265. /** @type {string[]} */
  3266. const addedStyleIds = [];
  3267.  
  3268. /**
  3269. *
  3270. *
  3271. * @author Michael Barros <michaelcbarros@gmail.com>
  3272. * @param {{ css: string, node?: HTMLElement }[]} cssArr
  3273. * @param {{ useGM: boolean }} cssArr { useGM = true } = {}
  3274. */
  3275. function _addStyles(cssArr, { useGM = true } = {}) {
  3276. /**
  3277. *
  3278. *
  3279. * @author Michael Barros <michaelcbarros@gmail.com>
  3280. * @param {string} css
  3281. * @returns {HTMLStyleElement}
  3282. */
  3283. function createStyleElementFromCss(css) {
  3284. let style = document.createElement('style');
  3285.  
  3286. style.innerHTML = css.trim();
  3287.  
  3288. return style;
  3289. }
  3290.  
  3291. function removeStyleElements() {
  3292. for (let i = addedStyleIds.length - 1; i >= 0; i--) {
  3293. /** @type {HTMLStyleElement} */
  3294. let styleElem = document.getElementById(addedStyleIds[i]);
  3295.  
  3296. if (styleElem) {
  3297. styleElem.remove();
  3298.  
  3299. addedStyleIds.splice(i, 1);
  3300. }
  3301. }
  3302. }
  3303.  
  3304. function addStyleElements() {
  3305. for (let i = 0; i < cssArr.length; i++) {
  3306. try {
  3307. const css = cssArr[i].css;
  3308. const node = cssArr[i].node || document.head;
  3309.  
  3310. /** @type {HTMLStyleElement} */
  3311. let elem = useGM ? GM_addStyle(css) : createStyleElementFromCss(css);
  3312.  
  3313. elem.id = `akkd-custom-style-${(i + 1).toString().padStart(2, '0')}`;
  3314.  
  3315. node.append(elem);
  3316.  
  3317. addedStyleIds.push(elem.id);
  3318. } catch (error) {
  3319. console.error(error);
  3320. }
  3321. }
  3322. }
  3323.  
  3324. removeStyleElements();
  3325. addStyleElements();
  3326.  
  3327. return addedStyleIds;
  3328. }
  3329.  
  3330. return _addStyles;
  3331. })();
  3332.  
  3333. /**
  3334. * Return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  3335. *
  3336. * @author Michael Barros <michaelcbarros@gmail.com>
  3337. * @returns {string}
  3338. */
  3339. function uuid4() {
  3340. let uuid = '';
  3341. let ii;
  3342.  
  3343. for (ii = 0; ii < 32; ii += 1) {
  3344. switch (ii) {
  3345. case 8:
  3346. case 20:
  3347. uuid += '-';
  3348. uuid += ((Math.random() * 16) | 0).toString(16);
  3349.  
  3350. break;
  3351.  
  3352. case 12:
  3353. uuid += '-';
  3354. uuid += '4';
  3355.  
  3356. break;
  3357.  
  3358. case 16:
  3359. uuid += '-';
  3360. uuid += ((Math.random() * 4) | 8).toString(16);
  3361.  
  3362. break;
  3363.  
  3364. default:
  3365. uuid += ((Math.random() * 16) | 0).toString(16);
  3366. }
  3367. }
  3368.  
  3369. return uuid;
  3370. }
  3371.  
  3372. /**
  3373. *
  3374. *
  3375. * @author Michael Barros <michaelcbarros@gmail.com>
  3376. * @param {{} | []} obj
  3377. * @param {string | {oldName: string, newName: string}[]} oldName
  3378. * @param {string=} newName
  3379. * @returns
  3380. */
  3381. function renameProperty(obj, oldName, newName) {
  3382. function _renameProperty(obj, oldName, newName) {
  3383. let keys = Object.keys(obj);
  3384.  
  3385. for (let i = 0; i < keys.length; i++) {
  3386. let key = keys[i];
  3387. let value = obj[key];
  3388.  
  3389. if (value && typeof value == 'object') {
  3390. obj[key] = _renameProperty(value, oldName, newName);
  3391. }
  3392.  
  3393. if (obj.hasOwnProperty(oldName)) {
  3394. obj[newName] = obj[oldName];
  3395.  
  3396. delete obj[oldName];
  3397. }
  3398. }
  3399.  
  3400. return obj;
  3401. }
  3402.  
  3403. let renames = Array.isArray(oldName) ? oldName : [{ oldName, newName }];
  3404.  
  3405. for (let i = 0; i < renames.length; i++) {
  3406. const rename = renames[i];
  3407.  
  3408. obj = _renameProperty(obj, rename.oldName, rename.newName);
  3409. }
  3410.  
  3411. return obj;
  3412. }
  3413.  
  3414. /**
  3415. *
  3416. *
  3417. * @author Michael Barros <michaelcbarros@gmail.com>
  3418. * @param {Promise<T>} fn
  3419. * @param {{ retries?: number; interval?: number; maxTime?: number; throwError?: boolean; }} { retries = 3, interval = 100, maxTime = null, throwError = false }
  3420. * @returns {Promise<T>}
  3421. * @template T
  3422. */
  3423. async function retry(fn, { retries = 3, interval = 100, maxTime = null, throwError = false }) {
  3424. let start = new Date();
  3425. let timeLapsed;
  3426.  
  3427. async function _retry() {
  3428. try {
  3429. return await fn;
  3430. } catch (error) {
  3431. timeLapsed = new Date() - start;
  3432.  
  3433. await wait(interval);
  3434.  
  3435. if (maxTime) {
  3436. if (timeLapsed >= maxTime) {
  3437. if (throwError) {
  3438. throw error;
  3439. } else {
  3440. return null;
  3441. }
  3442. }
  3443. } else {
  3444. --retries;
  3445.  
  3446. if (retries === 0) {
  3447. if (throwError) {
  3448. throw error;
  3449. } else {
  3450. return null;
  3451. }
  3452. }
  3453. }
  3454.  
  3455. return await _retry();
  3456. }
  3457. }
  3458.  
  3459. return await _retry();
  3460. }
  3461.  
  3462. /**
  3463. *
  3464. *
  3465. * @author Michael Barros <michaelcbarros@gmail.com>
  3466. * @param {string} htmlStr
  3467. * @returns {NodeListOf<ChildNode>}
  3468. */
  3469. function createElementsFromHTML(htmlStr) {
  3470. let div = document.createElement('div');
  3471.  
  3472. div.innerHTML = htmlStr.trim();
  3473.  
  3474. return div.childNodes;
  3475. }
  3476.  
  3477. /**
  3478. * Checks if a variable is a number.
  3479. *
  3480. * @param {*} variable - The variable to check.
  3481. * @returns {boolean} - `true` if the variable is a number or a number represented as a string, `false` otherwise.
  3482. */
  3483. function isNumber(variable) {
  3484. return (typeof variable == 'string' || typeof variable == 'number') && !isNaN(variable - 0) && variable !== '';
  3485. }
  3486.  
  3487. /**
  3488. *
  3489. *
  3490. * @author Michael Barros <michaelcbarros@gmail.com>
  3491. * @param {string | number} val
  3492. * @returns
  3493. */
  3494. function parseNumberSafe(val) {
  3495. if (isNumber(val)) {
  3496. val = parseFloat(val);
  3497. }
  3498.  
  3499. return val;
  3500. }
  3501.  
  3502. /**
  3503. *
  3504. *
  3505. * @author Michael Barros <michaelcbarros@gmail.com>
  3506. * @param {number} num
  3507. * @returns {boolean}
  3508. */
  3509. function isInt(num) {
  3510. return Number(num) === num && num % 1 === 0;
  3511. }
  3512.  
  3513. /**
  3514. *
  3515. *
  3516. * @author Michael Barros <michaelcbarros@gmail.com>
  3517. * @param {number} num
  3518. * @returns {boolean}
  3519. */
  3520. function isFloat(num) {
  3521. return Number(num) === num && num % 1 !== 0;
  3522. }
  3523.  
  3524. /*
  3525. *
  3526. *
  3527. * @author Michael Barros <michaelcbarros@gmail.com>
  3528. * @param {HTMLElement} elem
  3529. * @param {string} prop
  3530. * @param {Window=} _window
  3531. * @returns {string | number | null}
  3532. */
  3533. /**
  3534. *
  3535. *
  3536. * @author Michael Barros <michaelcbarros@gmail.com>
  3537. * @param {HTMLElement} elem
  3538. * @param {string} prop
  3539. * @param {Window} [_window=getWindow()]
  3540. * @returns {string | number | null}
  3541. */
  3542. function getStyle(elem, prop, _window = null) {
  3543. _window = _window || getWindow();
  3544.  
  3545. let value = parseNumberSafe(
  3546. window
  3547. .getComputedStyle(elem, null)
  3548. .getPropertyValue(prop)
  3549. .replace(/^(\d+)px$/, '$1')
  3550. );
  3551.  
  3552. return value;
  3553. }
  3554.  
  3555. /**
  3556. *
  3557. *
  3558. * @author Michael Barros <michaelcbarros@gmail.com>
  3559. * @param {HTMLElement} beforeElem
  3560. * @param {HTMLElement=} afterElem
  3561. */
  3562. function attachHorizontalResizer(beforeElem, afterElem) {
  3563. let resizer = document.createElement('span');
  3564.  
  3565. resizer.className = 'akkd-horz-resizer';
  3566.  
  3567. beforeElem.after(resizer);
  3568.  
  3569. afterElem = afterElem ? afterElem : resizer.nextElementSibling;
  3570.  
  3571. // resizer.addEventListener('mousedown', init, false);
  3572.  
  3573. // /**
  3574. // *
  3575. // *
  3576. // * @author Michael Barros <michaelcbarros@gmail.com>
  3577. // * @param {MouseEvent} ev
  3578. // */
  3579. // function init(ev) {
  3580. // getWindow().addEventListener('mousemove', resize, false);
  3581. // getWindow().addEventListener('mouseup', stopResize, false);
  3582. // }
  3583.  
  3584. // /**
  3585. // *
  3586. // *
  3587. // * @author Michael Barros <michaelcbarros@gmail.com>
  3588. // * @param {MouseEvent} ev
  3589. // */
  3590. // function resize(ev) {
  3591. // beforeElem.style.height = `${ev.clientY - beforeElem.offsetTop}px`;
  3592. // }
  3593.  
  3594. // /**
  3595. // *
  3596. // *
  3597. // * @author Michael Barros <michaelcbarros@gmail.com>
  3598. // * @param {MouseEvent} ev
  3599. // */
  3600. // function stopResize(ev) {
  3601. // getWindow().removeEventListener('mousemove', resize, false);
  3602. // getWindow().removeEventListener('mouseup', stopResize, false);
  3603. // }
  3604.  
  3605. let prevX = -1;
  3606. let prevY = -1;
  3607. let dir = null;
  3608.  
  3609. $(resizer).on('mousedown', function (e) {
  3610. prevX = e.clientX;
  3611. prevY = e.clientY;
  3612. dir = 'n'; // $(this).attr('id');
  3613.  
  3614. $(document).on('mousemove', resize);
  3615. $(document).on('mouseup', stopResize);
  3616. });
  3617.  
  3618. /**
  3619. *
  3620. *
  3621. * @author Michael Barros <michaelcbarros@gmail.com>
  3622. * @param {JQuery.MouseMoveEvent<Document, undefined, Document, Document>} ev
  3623. */
  3624. function resize(ev) {
  3625. if (prevX == -1) return;
  3626.  
  3627. let boxX = $(afterElem).position().left;
  3628. let boxY = $(afterElem).position().top;
  3629. let boxW = $(afterElem).width();
  3630. let boxH = $(afterElem).height();
  3631.  
  3632. let dx = ev.clientX - prevX;
  3633. let dy = ev.clientY - prevY;
  3634.  
  3635. switch (dir) {
  3636. case 'n':
  3637. // north
  3638. boxY += dy;
  3639. boxH -= dy;
  3640.  
  3641. break;
  3642.  
  3643. case 's':
  3644. // south
  3645. boxH += dy;
  3646.  
  3647. break;
  3648.  
  3649. case 'w':
  3650. // west
  3651. boxX += dx;
  3652. boxW -= dx;
  3653.  
  3654. break;
  3655.  
  3656. case 'e':
  3657. // east
  3658. boxW += dx;
  3659.  
  3660. break;
  3661.  
  3662. default:
  3663. break;
  3664. }
  3665.  
  3666. $(afterElem).css({
  3667. // top: boxY + 'px',
  3668. // left: boxX + 'px',
  3669. // width: boxW + 'px',
  3670. height: boxH + 'px',
  3671. });
  3672.  
  3673. let lines = [
  3674. //
  3675. // ['newHeight', newHeight],
  3676. ['clientY', ev.clientY],
  3677. ['beforeElem.top', roundNumber($(beforeElem).position().top)],
  3678. ['beforeElem.height', $(beforeElem).height()],
  3679. '',
  3680. ['afterElem.top', roundNumber($(afterElem).position().top)],
  3681. ['afterElem.height', $(afterElem).height()],
  3682. ];
  3683.  
  3684. // writeDebugMsg(lines);
  3685. console.debug([`y: ${ev.clientY}`, `b.top: ${roundNumber($(beforeElem).position().top)}`, `b.height: ${$(beforeElem).height()}`, `a.top: ${roundNumber($(afterElem).position().top)}`, `a.height: ${$(afterElem).height()}`].join(' '));
  3686.  
  3687. function writeDebugMsg(lines) {
  3688. let outputLines = ['*'.repeat(60)];
  3689.  
  3690. let tags = lines.map((line) => (Array.isArray(line) ? line[0] : line));
  3691.  
  3692. lines.forEach((line) => {
  3693. if (Array.isArray(line)) {
  3694. // need to require lpad-align
  3695. // outputLines.push(`${lpadAlign(line[0], tags)}: ${line[1]}`);
  3696. } else {
  3697. outputLines.push(line);
  3698. }
  3699. });
  3700.  
  3701. outputLines.push('*'.repeat(60));
  3702.  
  3703. console.debug(outputLines.join('\n'));
  3704. }
  3705.  
  3706. function roundNumber(num, places = 2) {
  3707. return parseFloat(parseFloat(num.toString()).toFixed(places));
  3708. // Math.round(num * 100) / 100
  3709. }
  3710.  
  3711. prevX = ev.clientX;
  3712. prevY = ev.clientY;
  3713. }
  3714.  
  3715. /**
  3716. *
  3717. *
  3718. * @author Michael Barros <michaelcbarros@gmail.com>
  3719. * @param {JQuery.MouseMoveEvent<Document, undefined, Document, Document>} ev
  3720. */
  3721. function stopResize(ev) {
  3722. prevX = -1;
  3723. prevY = -1;
  3724.  
  3725. $(document).off('mousemove', resize);
  3726. $(document).off('mouseup', stopResize);
  3727. }
  3728. }
  3729.  
  3730. function traceMethodCalls(obj) {
  3731. /** @type {ProxyHandler} */
  3732. let handler = {
  3733. get(target, propKey, receiver) {
  3734. if (propKey == 'isProxy') return true;
  3735.  
  3736. const prop = target[propKey];
  3737.  
  3738. if (typeof prop == 'undefined') return;
  3739.  
  3740. if (typeof prop === 'object' && target[propKey] !== null) {
  3741. if (!prop.isProxy) {
  3742. target[propKey] = new Proxy(prop, handler);
  3743.  
  3744. return target[propKey];
  3745. } else {
  3746. return target[propKey];
  3747. }
  3748. }
  3749.  
  3750. if (typeof target[propKey] == 'function') {
  3751. const origMethod = target[propKey];
  3752.  
  3753. return function (...args) {
  3754. let result = origMethod.apply(this, args);
  3755.  
  3756. console.log(propKey + JSON.stringify(args) + ' -> ' + JSON.stringify(result));
  3757.  
  3758. return result;
  3759. };
  3760. } else {
  3761. return target[propKey];
  3762. }
  3763. },
  3764. };
  3765.  
  3766. return new Proxy(obj, handler);
  3767. }
  3768.  
  3769. /**
  3770. *
  3771. *
  3772. * @author Michael Barros <michaelcbarros@gmail.com>
  3773. * @param {HTMLElement} elem
  3774. * @param {number} [topOffset=0]
  3775. * @returns {boolean}
  3776. */
  3777. function isVisible(elem, topOffset = 0) {
  3778. /**
  3779. * Checks if a DOM element is visible. Takes into
  3780. * consideration its parents and overflow.
  3781. *
  3782. * @param {HTMLElement} el the DOM element to check if is visible
  3783. *
  3784. * These params are optional that are sent in recursively,
  3785. * you typically won't use these:
  3786. *
  3787. * @param {number} top Top corner position number
  3788. * @param {number} right Right corner position number
  3789. * @param {number} bottom Bottom corner position number
  3790. * @param {number} left Left corner position number
  3791. * @param {number} width Element width number
  3792. * @param {number} height Element height number
  3793. * @returns {boolean}
  3794. */
  3795. function _isVisible(el, top, right, bottom, left, width, height) {
  3796. let parent = el.parentNode;
  3797. let VISIBLE_PADDING = 2;
  3798.  
  3799. if (!_elementInDocument(el)) {
  3800. return false;
  3801. }
  3802.  
  3803. // Return true for document node
  3804. if (9 === parent.nodeType) {
  3805. return true;
  3806. }
  3807.  
  3808. // Return false if our element is invisible
  3809. if ('0' === _getStyle(el, 'opacity') || 'none' === _getStyle(el, 'display') || 'hidden' === _getStyle(el, 'visibility')) {
  3810. return false;
  3811. }
  3812.  
  3813. if ('undefined' === typeof top || 'undefined' === typeof right || 'undefined' === typeof bottom || 'undefined' === typeof left || 'undefined' === typeof width || 'undefined' === typeof height) {
  3814. top = el.offsetTop + topOffset;
  3815. left = el.offsetLeft;
  3816. bottom = top + el.offsetHeight;
  3817. right = left + el.offsetWidth;
  3818. width = el.offsetWidth;
  3819. height = el.offsetHeight;
  3820. }
  3821.  
  3822. // If we have a parent, let's continue:
  3823. if (parent) {
  3824. // Check if the parent can hide its children.
  3825. if ('hidden' === _getStyle(parent, 'overflow') || 'scroll' === _getStyle(parent, 'overflow')) {
  3826. // Only check if the offset is different for the parent
  3827. if (
  3828. // If the target element is to the right of the parent elm
  3829. left + VISIBLE_PADDING > parent.offsetWidth + parent.scrollLeft ||
  3830. // If the target element is to the left of the parent elm
  3831. left + width - VISIBLE_PADDING < parent.scrollLeft ||
  3832. // If the target element is under the parent elm
  3833. top + VISIBLE_PADDING > parent.offsetHeight + parent.scrollTop ||
  3834. // If the target element is above the parent elm
  3835. top + height - VISIBLE_PADDING < parent.scrollTop
  3836. ) {
  3837. // Our target element is out of bounds:
  3838. return false;
  3839. }
  3840. }
  3841. // Add the offset parent's left/top coords to our element's offset:
  3842. if (el.offsetParent === parent) {
  3843. left += parent.offsetLeft;
  3844. top += parent.offsetTop;
  3845. }
  3846. // Let's recursively check upwards:
  3847. return _isVisible(parent, top, right, bottom, left, width, height);
  3848. }
  3849.  
  3850. return true;
  3851. }
  3852.  
  3853. // Cross browser method to get style properties:
  3854. /**
  3855. *
  3856. *
  3857. * @author Michael Barros <michaelcbarros@gmail.com>
  3858. * @param {HTMLElement} el
  3859. * @param {string} property
  3860. * @returns
  3861. */
  3862. function _getStyle(el, property) {
  3863. let value;
  3864.  
  3865. if (window.getComputedStyle) {
  3866. value = document.defaultView.getComputedStyle(el, null)[property];
  3867. }
  3868.  
  3869. if (el.currentStyle) {
  3870. value = el.currentStyle[property];
  3871. }
  3872.  
  3873. return value;
  3874. }
  3875.  
  3876. /**
  3877. *
  3878. *
  3879. * @author Michael Barros <michaelcbarros@gmail.com>
  3880. * @param {HTMLElement} element
  3881. * @returns {boolean}
  3882. */
  3883. function _elementInDocument(element) {
  3884. while ((element = element.parentNode)) {
  3885. if (element == document) {
  3886. return true;
  3887. }
  3888. }
  3889.  
  3890. return false;
  3891. }
  3892.  
  3893. return _isVisible(elem);
  3894. }
  3895.  
  3896. /**
  3897. * @summary
  3898. * High-order function that memoizes a function, by creating a scope
  3899. * to store the result of each function call, returning the cached
  3900. * result when the same inputs is given.
  3901. *
  3902. * @description
  3903. * Memoization is an optimization technique used primarily to speed up
  3904. * functions by storing the results of expensive function calls, and returning
  3905. * the cached result when the same inputs occur again.
  3906. *
  3907. * Each time a memoized function is called, its parameters are used as keys to index the cache.
  3908. * If the index (key) is present, then it can be returned, without executing the entire function.
  3909. * If the index is not cached, then all the body of the function is executed, and the result is
  3910. * added to the cache.
  3911. *
  3912. * @see https://www.sitepoint.com/implementing-memoization-in-javascript/
  3913. *
  3914. * @export
  3915. * @param {Function} func: function to memoize
  3916. * @returns {Function}
  3917. */
  3918. function memoize(func) {
  3919. const cache = {};
  3920.  
  3921. function memoized(...args) {
  3922. const key = JSON.stringify(args);
  3923.  
  3924. if (key in cache) return cache[key];
  3925.  
  3926. if (globalThis instanceof this.constructor) {
  3927. return (cache[key] = func.apply(null, args));
  3928. } else {
  3929. return (cache[key] = func.apply(this, args));
  3930. }
  3931. }
  3932.  
  3933. memoized.toString = () => func.toString();
  3934.  
  3935. return memoized;
  3936. }
  3937.  
  3938. function memoizeClass(clazz, options = { toIgnore: [] }) {
  3939. let funcs = getObjProps(clazz, { namesOnly: true }).funcs;
  3940.  
  3941. for (let i = 0; i < funcs.length; i++) {
  3942. let funcName = funcs[i];
  3943.  
  3944. if (options.toIgnore.includes(funcName)) continue;
  3945.  
  3946. let func = Object.getOwnPropertyDescriptor(clazz.prototype, funcName);
  3947.  
  3948. let memFunc = memoize(func.value);
  3949.  
  3950. Object.defineProperty(clazz.prototype, funcName, {
  3951. get: function () {
  3952. return memFunc;
  3953. },
  3954. });
  3955. }
  3956.  
  3957. let props = getObjProps(clazz, { namesOnly: true }).props;
  3958.  
  3959. for (let i = 0; i < props.length; i++) {
  3960. let propName = props[i];
  3961.  
  3962. if (options.toIgnore.includes(propName)) continue;
  3963.  
  3964. let prop = Object.getOwnPropertyDescriptor(clazz.prototype, propName);
  3965. let cacheKey = `_${propName}-cache_`;
  3966.  
  3967. Object.defineProperty(clazz.prototype, propName, {
  3968. get: function () {
  3969. return (this[cacheKey] = this[cacheKey] || prop.get.call(this));
  3970. },
  3971. });
  3972. }
  3973. }
  3974.  
  3975. /**
  3976. *
  3977. *
  3978. * @author Michael Barros <michaelcbarros@gmail.com>
  3979. * @param {any[]} objects
  3980. * @returns {object}
  3981. */
  3982. function merge(...objects) {
  3983. const isObject = (obj) => Object.prototype.toString.call(obj) == '[object Object]' && obj.constructor && obj.constructor.name == 'Object';
  3984.  
  3985. let _merge = (_target, _source, _isMergingArrays) => {
  3986. if (!isObject(_target) || !isObject(_source)) {
  3987. return _source;
  3988. }
  3989.  
  3990. Object.keys(_source).forEach((key) => {
  3991. const targetValue = _target[key];
  3992. const sourceValue = _source[key];
  3993.  
  3994. if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
  3995. if (_isMergingArrays) {
  3996. _target[key] = targetValue.map((x, i) => (sourceValue.length <= i ? x : _merge(x, sourceValue[i], _isMergingArrays)));
  3997.  
  3998. if (sourceValue.length > targetValue.length) {
  3999. _target[key] = _target[key].concat(sourceValue.slice(targetValue.length));
  4000. }
  4001. } else {
  4002. _target[key] = targetValue.concat(sourceValue);
  4003. }
  4004. } else if (isObject(targetValue) && isObject(sourceValue)) {
  4005. _target[key] = _merge(Object.assign({}, targetValue), sourceValue, _isMergingArrays);
  4006. } else {
  4007. _target[key] = sourceValue;
  4008. }
  4009. });
  4010.  
  4011. return _target;
  4012. };
  4013.  
  4014. const isMergingArrays = typeof objects[objects.length - 1] == 'boolean' ? objects.pop() : false;
  4015.  
  4016. if (objects.length < 2) throw new Error('mergeEx: this function expects at least 2 objects to be provided');
  4017.  
  4018. if (objects.some((object) => !isObject(object))) throw new Error('mergeEx: all values should be of type "object"');
  4019.  
  4020. const target = objects.shift();
  4021. let source;
  4022.  
  4023. while ((source = objects.shift())) {
  4024. _merge(target, source, isMergingArrays);
  4025. }
  4026.  
  4027. return target;
  4028. }
  4029.  
  4030. /**
  4031. *
  4032. *
  4033. * @author Michael Barros <michaelcbarros@gmail.com>
  4034. * @param {Window} [windowOrFrame=getTopWindow()]
  4035. * @param {Window[]} [allFrameArray=[]]
  4036. * @returns {Window[]}
  4037. */
  4038. function getAllFrames(windowOrFrame = getTopWindow(), allFrameArray = []) {
  4039. allFrameArray.push(windowOrFrame.frames);
  4040.  
  4041. for (var i = 0; i < windowOrFrame.frames.length; i++) {
  4042. getAllFrames(windowOrFrame.frames[i], allFrameArray);
  4043. }
  4044.  
  4045. return allFrameArray;
  4046. }
  4047.  
  4048. /**
  4049. *
  4050. *
  4051. * @author Michael Barros <michaelcbarros@gmail.com>
  4052. * @returns {boolean}
  4053. */
  4054. function windowIsFocused() {
  4055. let frames = getAllFrames();
  4056.  
  4057. for (let i = 0; i < frames.length; i++) {
  4058. const frame = frames[i];
  4059.  
  4060. try {
  4061. if (frame.document.hasFocus()) {
  4062. return true;
  4063. }
  4064. } catch (error) {}
  4065. }
  4066.  
  4067. return false;
  4068. }
  4069.  
  4070. function setupWindowHasFocused() {
  4071. let frames = getAllFrames();
  4072.  
  4073. for (let i = 0; i < frames.length; i++) {
  4074. const frame = frames[i];
  4075.  
  4076. try {
  4077. frame.hasFocus = windowIsFocused;
  4078. } catch (error) {}
  4079. }
  4080. }
  4081.  
  4082. /**
  4083. *
  4084. *
  4085. * @author Michael Barros <michaelcbarros@gmail.com>
  4086. * @param {[]} array
  4087. * @param {any} element
  4088. * @param {number} index
  4089. */
  4090. function moveArrayElement(array, filter, index) {
  4091. let item = array.filter((item) => item === filter)[0];
  4092.  
  4093. if (item) {
  4094. array = array.filter((item) => item !== filter);
  4095.  
  4096. array.unshift(item);
  4097. }
  4098.  
  4099. return array;
  4100. }
  4101.  
  4102. /**
  4103. *
  4104. *
  4105. * @author Michael Barros <michaelcbarros@gmail.com>
  4106. * @param {HTMLElement} elem
  4107. * @param {(elem: HTMLElement, level: number) => void} callback
  4108. * @param {number} [level=0]
  4109. */
  4110. function walkDom(elem, callback, level = 0) {
  4111. let children = elem.children;
  4112.  
  4113. callback(elem, level);
  4114.  
  4115. for (let i = 0; i < children.length; i++) {
  4116. /** @type {HTMLElement} */
  4117. let child = children[i];
  4118.  
  4119. walkDom(child, callback, level + 1);
  4120.  
  4121. if (child.shadowRoot) {
  4122. walkDom(child.shadowRoot, callback, level + 2);
  4123. }
  4124. }
  4125. }
  4126.  
  4127. /**
  4128. *
  4129. *
  4130. * @author Michael Barros <michaelcbarros@gmail.com>
  4131. * @param {T} obj
  4132. * @param {(key: string | number, value: any, keyPath: string, callbackRes: { doBreak: boolean, returnValue: any | null }, obj: T) => boolean} callback
  4133. * @template T
  4134. * @returns {{ dottedObj: T, returnValue: any }}
  4135. */
  4136. function walkObj(obj, callback) {
  4137. let callbackRes = {
  4138. doBreak: false,
  4139. returnValue: null,
  4140. };
  4141.  
  4142. /**
  4143. *
  4144. *
  4145. * @author Michael Barros <michaelcbarros@gmail.com>
  4146. * @param {{}} _obj
  4147. * @param {string[]} keyPath
  4148. * @param {{}} newObj
  4149. */
  4150. function _walk(_obj, keyPath, newObj) {
  4151. keyPath = typeof keyPath === 'undefined' ? [] : keyPath;
  4152. newObj = typeof newObj === 'undefined' ? {} : newObj;
  4153.  
  4154. for (let key in _obj) {
  4155. if (_obj.hasOwnProperty(key)) {
  4156. let value = _obj[key];
  4157.  
  4158. keyPath.push(key);
  4159.  
  4160. callback.apply(this, [key, value, keyPath.join('.'), callbackRes, obj]);
  4161.  
  4162. if (typeof value === 'object' && value !== null) {
  4163. newObj = _walk(value, keyPath, newObj);
  4164. } else {
  4165. let newKey = keyPath.join('.');
  4166.  
  4167. newObj[newKey] = value;
  4168. }
  4169.  
  4170. keyPath.pop();
  4171.  
  4172. if (callbackRes.doBreak) {
  4173. break;
  4174. }
  4175. }
  4176. }
  4177.  
  4178. return newObj;
  4179. }
  4180.  
  4181. let newObj = _walk(obj);
  4182.  
  4183. return {
  4184. dottedObj: newObj,
  4185. returnValue: callbackRes.returnValue,
  4186. };
  4187. }
  4188.  
  4189. /**
  4190. * A function to take a string written in dot notation style, and use it to
  4191. * find a nested object property inside of an object.
  4192. *
  4193. * Useful in a plugin or module that accepts a JSON array of objects, but
  4194. * you want to let the user specify where to find various bits of data
  4195. * inside of each custom object instead of forcing a standardized
  4196. * property list.
  4197. *
  4198. * @author Michael Barros <michaelcbarros@gmail.com>
  4199. * @param {{}} obj
  4200. * @param {string} dotPath
  4201. * @returns {*}
  4202. */
  4203. function getNestedDot(obj, dotPath) {
  4204. let parts = dotPath.split('.');
  4205. let length = parts.length;
  4206. let property = obj || this;
  4207.  
  4208. for (let i = 0; i < length; i++) {
  4209. property = property[parts[i]];
  4210. }
  4211.  
  4212. return property;
  4213. }
  4214.  
  4215. /**
  4216. *
  4217. *
  4218. * @author Michael Barros <michaelcbarros@gmail.com>
  4219. * @param {{}} obj
  4220. * @param {number} maxLevel
  4221. */
  4222. function getDottedObj(obj, maxLevel = 50) {
  4223. /**
  4224. *
  4225. *
  4226. * @author Michael Barros <michaelcbarros@gmail.com>
  4227. * @param {{}} _obj
  4228. * @param {string[]} keyPath
  4229. * @param {{}} newObj
  4230. * @param {number} level
  4231. */
  4232. function _worker(_obj, keyPath, newObj, level = 0) {
  4233. keyPath = typeof keyPath === 'undefined' ? [] : keyPath;
  4234. newObj = typeof newObj === 'undefined' ? {} : newObj;
  4235.  
  4236. for (let key in _obj) {
  4237. if (_obj.hasOwnProperty(key)) {
  4238. let value = _obj[key];
  4239.  
  4240. keyPath.push(key);
  4241.  
  4242. if (typeof value === 'object' && value !== null) {
  4243. newObj = _worker(value, keyPath, newObj, level++);
  4244. } else {
  4245. let newKey = keyPath.join('.');
  4246.  
  4247. newObj[newKey] = value;
  4248. }
  4249.  
  4250. keyPath.pop();
  4251.  
  4252. if (maxLevel > 0 && level >= maxLevel) {
  4253. break;
  4254. }
  4255. }
  4256. }
  4257.  
  4258. return newObj;
  4259. }
  4260.  
  4261. let dottedObj = _worker(obj);
  4262.  
  4263. return dottedObj;
  4264. }
  4265.  
  4266. function isNode() {
  4267. return !(typeof window !== 'undefined' && typeof window.document !== 'undefined');
  4268. }
  4269.  
  4270. /**
  4271. *
  4272. *
  4273. * @author Michael Barros <michaelcbarros@gmail.com>
  4274. * @returns {number}
  4275. */
  4276. function getCurrentTimeMs() {
  4277. if (isNode()) {
  4278. const NS_PER_MS = 1e6;
  4279.  
  4280. let time = process.hrtime();
  4281.  
  4282. return time[0] * 1000 + time[1] / NS_PER_MS;
  4283. } else {
  4284. return performance.now();
  4285. }
  4286. }
  4287.  
  4288. const intervalIDsMap = new Map();
  4289. const timeoutIDsMap = new Map();
  4290.  
  4291. /**
  4292. * Schedules the repeated execution of a function (callback) with a fixed time delay between each call.
  4293. * @param {TimerHandler} handler - A function to be executed repeatedly.
  4294. * @param {number} [timeout] - The time, in milliseconds, between each function call. Default is 0.
  4295. * @param {...any} [args] - Additional arguments to be passed to the function.
  4296. * @returns {number} - An identifier representing the interval. This value can be used with clearInterval to cancel the interval.
  4297. */
  4298. function setIntervalEx(handler, timeout, ...args) {
  4299. if (isNode()) {
  4300. return setInterval(handler, timeout, ...args);
  4301. } else {
  4302. let startTime = getCurrentTimeMs();
  4303. let elapsedTime = 0;
  4304. /** @type {number} */
  4305. let intervalId;
  4306. let intervalIdTemp;
  4307.  
  4308. function loop(currentTime) {
  4309. if (intervalIDsMap.get(intervalId)) {
  4310. const deltaTime = currentTime - startTime;
  4311.  
  4312. elapsedTime += deltaTime;
  4313.  
  4314. if (elapsedTime >= timeout) {
  4315. handler(...args);
  4316.  
  4317. elapsedTime = 0;
  4318. }
  4319.  
  4320. startTime = currentTime;
  4321.  
  4322. intervalIdTemp = window.requestAnimationFrame(loop);
  4323. } else {
  4324. window.cancelAnimationFrame(intervalIdTemp);
  4325. intervalIDsMap.delete(intervalId);
  4326. }
  4327. }
  4328.  
  4329. intervalId = window.requestAnimationFrame(loop);
  4330.  
  4331. intervalIDsMap.set(intervalId, true);
  4332.  
  4333. return intervalId;
  4334. }
  4335. }
  4336.  
  4337. /**
  4338. *
  4339. *
  4340. * @author Michael Barros <michaelcbarros@gmail.com>
  4341. * @param {number} intervalId
  4342. */
  4343. function clearIntervalEx(intervalId) {
  4344. if (isNode()) {
  4345. clearInterval(intervalId);
  4346. } else {
  4347. intervalIDsMap.set(intervalId, false);
  4348.  
  4349. window.cancelAnimationFrame(intervalId);
  4350. }
  4351. }
  4352.  
  4353. /**
  4354. * Schedules the execution of a function (callback) after a specified time delay.
  4355. * @param {TimerHandler} handler - A function to be executed.
  4356. * @param {number} [timeout] - The time, in milliseconds, to wait before executing the function. Default is 0.
  4357. * @param {...any} [args] - Additional arguments to be passed to the function.
  4358. * @returns {number} - An identifier representing the timeout. This value can be used with clearTimeout to cancel the timeout.
  4359. */
  4360. function setTimeoutEx(handler, timeout, ...args) {
  4361. if (isNode()) {
  4362. return setTimeout(handler, timeout, ...args);
  4363. } else {
  4364. let startTime = getCurrentTimeMs();
  4365. /** @type {number} */
  4366. let timeoutId;
  4367. let timeoutIdTemp;
  4368.  
  4369. function loop(currentTime) {
  4370. if (timeoutIDsMap.get(timeoutId)) {
  4371. const deltaTime = currentTime - startTime;
  4372.  
  4373. if (deltaTime >= timeout) {
  4374. handler(...args);
  4375. } else {
  4376. timeoutIdTemp = window.requestAnimationFrame(loop);
  4377. }
  4378. } else {
  4379. window.cancelAnimationFrame(timeoutIdTemp);
  4380. timeoutIDsMap.delete(timeoutId);
  4381. }
  4382. }
  4383.  
  4384. timeoutId = window.requestAnimationFrame(loop);
  4385.  
  4386. timeoutIDsMap.set(timeoutId, true);
  4387.  
  4388. return timeoutId;
  4389. }
  4390. }
  4391.  
  4392. /**
  4393. *
  4394. *
  4395. * @author Michael Barros <michaelcbarros@gmail.com>
  4396. * @param {number} timeoutId
  4397. */
  4398. function clearTimeoutEx(timeoutId) {
  4399. if (isNode()) {
  4400. clearTimeout(timeoutId);
  4401. } else {
  4402. timeoutIDsMap.set(timeoutId, false);
  4403.  
  4404. window.cancelAnimationFrame(timeoutId);
  4405. }
  4406. }
  4407.  
  4408. /**
  4409. *
  4410. *
  4411. * @author Michael Barros <michaelcbarros@gmail.com>
  4412. * @param {string} attribute
  4413. * @param {string} value
  4414. * @param {string} elementType
  4415. * @returns {HTMLElement[]}
  4416. */
  4417. function findByAttributeValue(attribute, value, elementType) {
  4418. elementType = elementType || '*';
  4419.  
  4420. let all = document.getElementsByTagName(elementType);
  4421. let foundElements = [];
  4422.  
  4423. for (let i = 0; i < all.length; i++) {
  4424. if (all[i].getAttribute(attribute).includes(value)) {
  4425. foundElements.push(all[i]);
  4426. }
  4427. }
  4428.  
  4429. return foundElements;
  4430. }
  4431.  
  4432. /**
  4433. *
  4434. *
  4435. * @author Michael Barros <michaelcbarros@gmail.com>
  4436. * @returns {number}
  4437. */
  4438. function getLocalStorageSize() {
  4439. let total = 0;
  4440.  
  4441. for (let x in localStorage) {
  4442. // Value is multiplied by 2 due to data being stored in `utf-16` format, which requires twice the space.
  4443. let amount = localStorage[x].length * 2;
  4444.  
  4445. if (!isNaN(amount) && localStorage.hasOwnProperty(x)) {
  4446. total += amount;
  4447. }
  4448. }
  4449.  
  4450. return total;
  4451. }
  4452.  
  4453. /**
  4454. *
  4455. *
  4456. * @author Michael Barros <michaelcbarros@gmail.com>
  4457. * @param {number} bytes
  4458. * @param {boolean} [si=false]
  4459. * @returns {string}
  4460. */
  4461. function bytes2HumanReadable(bytes, si = false) {
  4462. let thresh = si ? 1000 : 1024;
  4463.  
  4464. if (Math.abs(bytes) < thresh) {
  4465. return bytes + ' B';
  4466. }
  4467.  
  4468. let units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  4469. let u = -1;
  4470.  
  4471. while (true) {
  4472. bytes = bytes / thresh;
  4473. u++;
  4474.  
  4475. if (!(Math.abs(bytes) >= thresh && u < units.length - 1)) {
  4476. break;
  4477. }
  4478. }
  4479.  
  4480. return bytes.toFixed(1) + ' ' + units[u];
  4481. }
  4482.  
  4483. async function GM_fetch(url, fetchInit = {}) {
  4484. if (!window.GM_xmlhttpRequest) {
  4485. console.warn('GM_xmlhttpRequest not required. Using native fetch.');
  4486.  
  4487. return await fetch(url, fetchInit);
  4488. }
  4489.  
  4490. let parseHeaders = function (headersString) {
  4491. const headers = new Headers();
  4492.  
  4493. for (const line of headersString.trim().split('\n')) {
  4494. const [key, ...valueParts] = line.split(':');
  4495.  
  4496. if (key) {
  4497. headers.set(key.trim().toLowerCase(), valueParts.join(':').trim());
  4498. }
  4499. }
  4500.  
  4501. return headers;
  4502. };
  4503.  
  4504. const defaultFetchInit = { method: 'get' };
  4505. const { headers, method } = { ...defaultFetchInit, ...fetchInit };
  4506. const isStreamSupported = GM_xmlhttpRequest?.RESPONSE_TYPE_STREAM;
  4507. const HEADERS_RECEIVED = 2;
  4508.  
  4509. if (!isStreamSupported) {
  4510. return new Promise((resolve, _reject) => {
  4511. const blobPromise = new Promise((resolve, reject) => {
  4512. GM_xmlhttpRequest({
  4513. url,
  4514. method,
  4515. headers,
  4516. responseType: 'blob',
  4517. onload: async (response) => resolve(response.response),
  4518. onerror: reject,
  4519. onreadystatechange: onHeadersReceived,
  4520. });
  4521. });
  4522.  
  4523. blobPromise.catch(_reject);
  4524.  
  4525. function onHeadersReceived(gmResponse) {
  4526. const { readyState, responseHeaders, status, statusText } = gmResponse;
  4527.  
  4528. if (readyState === HEADERS_RECEIVED) {
  4529. const headers = parseHeaders(responseHeaders);
  4530.  
  4531. resolve({
  4532. headers,
  4533. status,
  4534. statusText,
  4535. arrayBuffer: () => blobPromise.then((blob) => blob.arrayBuffer()),
  4536. blob: () => blobPromise,
  4537. json: () => blobPromise.then((blob) => blob.text()).then((text) => JSON.parse(text)),
  4538. text: () => blobPromise.then((blob) => blob.text()),
  4539. });
  4540. }
  4541. }
  4542. });
  4543. } else {
  4544. return new Promise((resolve, _reject) => {
  4545. const responsePromise = new Promise((resolve, reject) => {
  4546. void GM_xmlhttpRequest({
  4547. url,
  4548. method,
  4549. headers,
  4550. responseType: 'stream',
  4551. onerror: reject,
  4552. onreadystatechange: onHeadersReceived,
  4553. // onloadstart: (gmResponse) => logDebug('[onloadstart]', gmResponse), // debug
  4554. });
  4555. });
  4556.  
  4557. responsePromise.catch(_reject);
  4558.  
  4559. function onHeadersReceived(gmResponse) {
  4560. const { readyState, responseHeaders, status, statusText, response: readableStream } = gmResponse;
  4561.  
  4562. if (readyState === HEADERS_RECEIVED) {
  4563. const headers = parseHeaders(responseHeaders);
  4564. let newResp;
  4565.  
  4566. if (status === 0) {
  4567. newResp = new Response(readableStream, { headers /*status, statusText*/ });
  4568. } else {
  4569. newResp = new Response(readableStream, { headers, status, statusText });
  4570. }
  4571.  
  4572. resolve(newResp);
  4573. }
  4574. }
  4575. });
  4576. }
  4577. }
  4578.  
  4579. /**
  4580. *
  4581. *
  4582. * @author Michael Barros <michaelcbarros@gmail.com>
  4583. * @param {T} items
  4584. * @param {{name: string, desc: boolean, case_sensitive: boolean}[]} columns
  4585. * @param {{cmpFunc: any}} [{ cmpFunc = cmp }={}]
  4586. * @returns {T}
  4587. * @template T
  4588. */
  4589. function multiKeySort(items, columns, { cmpFunc = null } = {}) {
  4590. function cmp(a, b) {
  4591. if (a < b) {
  4592. return -1;
  4593. } else {
  4594. if (a > b) {
  4595. return 1;
  4596. } else {
  4597. return 0;
  4598. }
  4599. }
  4600. }
  4601.  
  4602. cmpFunc = cmpFunc != null ? cmpFunc : cmp;
  4603.  
  4604. let comparers = [];
  4605.  
  4606. columns.forEach((col) => {
  4607. let column = col.name;
  4608. let desc = 'desc' in col ? col.desc : false;
  4609. let case_sensitive = 'case_sensitive' in col ? col.case_sensitive : true;
  4610.  
  4611. comparers.push([column, desc ? -1 : 1, case_sensitive]);
  4612. });
  4613.  
  4614. function comparer(left, right) {
  4615. for (let i = 0; i < comparers.length; i++) {
  4616. const column = comparers[i][0];
  4617. const polarity = comparers[i][1];
  4618. const case_sensitive = comparers[i][2];
  4619.  
  4620. let result = 0;
  4621.  
  4622. if (case_sensitive) {
  4623. result = cmpFunc(left[column], right[column]);
  4624. } else {
  4625. result = cmpFunc(left[column].toLowerCase(), right[column].toLowerCase());
  4626. }
  4627.  
  4628. if (result) {
  4629. return polarity * result;
  4630. }
  4631. }
  4632.  
  4633. return 0;
  4634. }
  4635.  
  4636. return items.sort(comparer);
  4637. }
  4638.  
  4639. /**
  4640. *
  4641. *
  4642. * @author Michael Barros <michaelcbarros@gmail.com>
  4643. * @param {string} text
  4644. * @param {string} [nodeType='div']
  4645. * @returns {HTMLElement}
  4646. */
  4647. function getElementByTextContent(text, nodeType = 'div') {
  4648. let xpath = `//${nodeType}[text()='${text}']`;
  4649.  
  4650. /** @type {HTMLElement} */
  4651. let elem = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  4652.  
  4653. return elem;
  4654. }
  4655.  
  4656. /**
  4657. *
  4658. *
  4659. * @author Michael Barros <michaelcbarros@gmail.com>
  4660. * @param {string} text
  4661. * @param {string} [nodeType='div']
  4662. * @returns {HTMLElement}
  4663. */
  4664. function getElementByTextContentContains(text, nodeType = 'div') {
  4665. let xpath = `//${nodeType}[contains(text(),'${text}')]`;
  4666.  
  4667. /** @type {HTMLElement} */
  4668. let elem = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  4669.  
  4670. return elem;
  4671. }
  4672.  
  4673. /**
  4674. *
  4675. *
  4676. * @author Michael Barros <michaelcbarros@gmail.com>
  4677. * @param {HTMLElement} element
  4678. * @param {(elem: HTMLElement) => void} callback
  4679. */
  4680. function onRemove(element, callback) {
  4681. const parent = element.parentNode;
  4682.  
  4683. if (!parent) {
  4684. throw new Error('The node must already be attached');
  4685. }
  4686.  
  4687. const observer = new MutationObserver((mutations) => {
  4688. let removed = false;
  4689.  
  4690. for (const mutation of mutations) {
  4691. for (const node of mutation.removedNodes) {
  4692. if (node === element) {
  4693. observer.disconnect();
  4694.  
  4695. callback(element);
  4696.  
  4697. removed = true;
  4698.  
  4699. break;
  4700. }
  4701. }
  4702.  
  4703. if (removed) {
  4704. break;
  4705. }
  4706. }
  4707. });
  4708.  
  4709. observer.observe(parent, {
  4710. childList: true,
  4711. });
  4712. }
  4713.  
  4714. // #endregion Helper Functions
  4715.  
  4716. // #region Prototype Functions
  4717.  
  4718. // #region Array
  4719.  
  4720. /**
  4721. * Function to setup custom prototype functions for the Array class
  4722. * (For no conflicts, Object.defineProperty must be used)
  4723. *
  4724. */
  4725. function setupArrayPrototypes() {
  4726. let funcs = [
  4727. function pushUnique(item) {
  4728. let index = -1;
  4729.  
  4730. for (let i = 0; i < this.length; i++) {
  4731. if (equals(this[i], item)) index = i;
  4732. }
  4733.  
  4734. if (index === -1) this.push(item);
  4735. },
  4736. ];
  4737.  
  4738. for (let i = 0; i < funcs.length; i++) {
  4739. let func = funcs[i];
  4740.  
  4741. Object.defineProperty(Array.prototype, func.name, {
  4742. enumerable: false,
  4743. configurable: true,
  4744. writable: true,
  4745. value: func,
  4746. });
  4747. }
  4748. }
  4749.  
  4750. setupArrayPrototypes();
  4751.  
  4752. // #endregion Array
  4753.  
  4754. // #endregion Prototype Functions
  4755.  
  4756. // #region jQuery
  4757.  
  4758. function setupJqueryExtendedFuncs() {
  4759. if ('jQuery' in getWindow() || 'jQuery' in window) {
  4760. jQuery.fn.extend({
  4761. /**
  4762. *
  4763. *
  4764. * @author Michael Barros <michaelcbarros@gmail.com>
  4765. * @returns {boolean}
  4766. * @this {JQuery<HTMLElement>}
  4767. */
  4768. exists: function exists() {
  4769. return this.length !== 0;
  4770. },
  4771.  
  4772. /**
  4773. *
  4774. *
  4775. * @author Michael Barros <michaelcbarros@gmail.com>
  4776. * @param {() => void} callback
  4777. * @returns {JQuery<HTMLElement>}
  4778. * @this {JQuery<HTMLElement>}
  4779. */
  4780. ready: function ready(callback) {
  4781. let cb = function cb() {
  4782. return setTimeout(callback, 0, jQuery);
  4783. };
  4784.  
  4785. if (document.readyState !== 'loading') {
  4786. cb();
  4787. } else {
  4788. document.addEventListener('DOMContentLoaded', cb);
  4789. }
  4790.  
  4791. return this;
  4792. },
  4793.  
  4794. /**
  4795. *
  4796. *
  4797. * @author Michael Barros <michaelcbarros@gmail.com>
  4798. * @param {string} method
  4799. * @param {{}} options
  4800. * @returns {number | string | null}
  4801. * @this {JQuery<HTMLElement>}
  4802. */
  4803. actual: function actual(method, options) {
  4804. // check if the jQuery method exist
  4805. if (!this[method]) {
  4806. throw '$.actual => The jQuery method "' + method + '" you called does not exist';
  4807. }
  4808.  
  4809. let defaults = {
  4810. absolute: false,
  4811. clone: false,
  4812. includeMargin: false,
  4813. display: 'block',
  4814. };
  4815.  
  4816. let configs = jQuery.extend(defaults, options);
  4817.  
  4818. let $target = this.eq(0);
  4819. let fix;
  4820. let restore;
  4821.  
  4822. if (configs.clone === true) {
  4823. fix = function () {
  4824. let style = 'position: absolute !important; top: -1000 !important; ';
  4825.  
  4826. // this is useful with css3pie
  4827. $target = $target.clone().attr('style', style).appendTo('body');
  4828. };
  4829.  
  4830. restore = function () {
  4831. // remove DOM element after getting the width
  4832. $target.remove();
  4833. };
  4834. } else {
  4835. let tmp = [];
  4836. let style = '';
  4837. let $hidden;
  4838.  
  4839. fix = function () {
  4840. // get all hidden parents
  4841. $hidden = $target.parents().addBack().filter(':hidden');
  4842. style += 'visibility: hidden !important; display: ' + configs.display + ' !important; ';
  4843.  
  4844. if (configs.absolute === true) {
  4845. style += 'position: absolute !important; ';
  4846. }
  4847.  
  4848. // save the origin style props
  4849. // set the hidden el css to be got the actual value later
  4850. $hidden.each(function () {
  4851. // Save original style. If no style was set, attr() returns undefined
  4852. let $this = jQuery(this);
  4853. let thisStyle = $this.attr('style');
  4854.  
  4855. tmp.push(thisStyle);
  4856.  
  4857. // Retain as much of the original style as possible, if there is one
  4858. $this.attr('style', thisStyle ? thisStyle + ';' + style : style);
  4859. });
  4860. };
  4861.  
  4862. restore = function () {
  4863. // restore origin style values
  4864. $hidden.each(function (i) {
  4865. let $this = jQuery(this);
  4866. let _tmp = tmp[i];
  4867.  
  4868. if (_tmp === undefined) {
  4869. $this.removeAttr('style');
  4870. } else {
  4871. $this.attr('style', _tmp);
  4872. }
  4873. });
  4874. };
  4875. }
  4876.  
  4877. fix();
  4878. // get the actual value with user specific methed
  4879. // it can be 'width', 'height', 'outerWidth', 'innerWidth'... etc
  4880. // configs.includeMargin only works for 'outerWidth' and 'outerHeight'
  4881. let actual = /(outer)/.test(method) ? $target[method](configs.includeMargin) : $target[method]();
  4882.  
  4883. restore();
  4884. // IMPORTANT, this plugin only return the value of the first element
  4885. return actual;
  4886. },
  4887.  
  4888. /**
  4889. *
  4890. *
  4891. * @author Michael Barros <michaelcbarros@gmail.com>
  4892. * @returns {DOMRect}
  4893. * @this {JQuery<HTMLElement>}
  4894. */
  4895. rect: function rect() {
  4896. return this[0].getBoundingClientRect();
  4897. },
  4898.  
  4899. /**
  4900. *
  4901. *
  4902. * @author Michael Barros <michaelcbarros@gmail.com>
  4903. * @param {number} levels
  4904. * @returns {JQuery<HTMLElement>}
  4905. * @this {JQuery<HTMLElement>}
  4906. */
  4907. parentEx: function parentEx(levels = 1) {
  4908. let parent = this;
  4909.  
  4910. for (let i = 0; i < levels; i++) {
  4911. if (parent.parent().length == 0) {
  4912. break;
  4913. }
  4914.  
  4915. parent = parent.parent();
  4916. }
  4917.  
  4918. return parent;
  4919. },
  4920. });
  4921. }
  4922. }
  4923.  
  4924. setupJqueryExtendedFuncs();
  4925.  
  4926. // #endregion jQuery

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址