akkd-common

Common functions

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greatest.deepsurf.us/scripts/474549/1263250/akkd-common.js

  1. // ==UserScript==
  2.  
  3. // #region Info
  4.  
  5. // @namespace https://greatest.deepsurf.us/en/users/1123632-93akkord
  6. // @exclude *
  7. // @author Michael Barros (https://greatest.deepsurf.us/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://greatest.deepsurf.us/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://greatest.deepsurf.us/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