@lizardbyte/shared-web 2026.219.33142 → 2026.314.32913

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/crowdin.js CHANGED
@@ -1,2 +1,2 @@
1
- (()=>{var e={714(e,t,i){var n=i(836);function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"LizardByte",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;["LizardByte","LizardByte-docs"].includes(e)?["sphinx",null].includes(t)?n("https://website-translator.app.crowdin.net/assets/proxy-translator.js",function(){var i={bg:"Български (Bulgarian)",cs:"Čeština (Czech)",de:"Deutsch (German)",en:"English","en-GB":"English, United Kingdom","en-US":"English, United States","es-ES":"Español (Spanish)",fr:"Français (French)",hu:"Magyar (Hungarian)",it:"Italiano (Italian)",ja:"日本語 (Japanese)",ko:"한국어 (Korean)",pl:"Polski (Polish)","pt-BR":"Português, Brasileiro (Portuguese, Brazilian)","pt-PT":"Português (Portuguese)",ru:"Русский (Russian)","sv-SE":"svenska (Swedish)",tr:"Türkçe (Turkish)",uk:"Українська (Ukranian)",vi:"Tiếng Việt (Vietnamese)","zh-CN":"简体中文 (Chinese Simplified)","zh-TW":"繁體中文 (Chinese Traditional)"};i=Object.fromEntries(Object.entries(i).sort(function(e,t){return e[1].localeCompare(t[1])}));var n=globalThis.location.origin;if(globalThis.proxyTranslator.init({baseUrl:n,distribution:{LizardByte:{baseUrl:"https://app.lizardbyte.dev",distribution:"458f881791aebba1d4dde491bw4"},"LizardByte-docs":{baseUrl:"https://docs.lizardbyte.dev",distribution:"d6c830ba4b41106fefe5d391bw4"}}[e].distribution,defaultLanguage:"en",languageTitles:i,showDefaultLanguageInUrl:!1,languageRoutingMethod:"query",position:"bottom-left",submenuPosition:"top-left",poweredBy:!1}),null!==t){var o=document.getElementById("crowdin-language-picker"),r=document.getElementsByClassName("cr-picker-button")[0];"sphinx"===t&&(o.classList.remove("cr-position-bottom-left"),o.style.width=r.offsetWidth+10+"px",o.style.position="relative",o.style.left="10px",o.style.bottom="10px",document.getElementsByClassName("sidebar-sticky")[0].appendChild(o))}}):console.error('Invalid UI. Must be "sphinx", or null'):console.error('Invalid project. Must be "LizardByte" or "LizardByte-docs"')}"undefined"!=typeof globalThis&&void 0!==globalThis.window&&(globalThis.initCrowdIn=o),e.exports=o},836(e){function t(e,t){var i=document.createElement("script");i.src=e,i.async=!0,i.onload=function(){t&&t(null,i)},i.onerror=function(){t&&t(new Error("Failed to load script: ".concat(e)))},document.head.appendChild(i)}"undefined"!=typeof window&&(window.loadScript=t),e.exports=t}},t={};!function i(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}(714)})();
1
+ (()=>{var e={714(e,t,i){var n=i(836);function o(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"LizardByte",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;["LizardByte","LizardByte-docs"].includes(e)?["sphinx",null].includes(t)?(function(){if("function"==typeof globalThis.fetch&&!globalThis._crowdinMirrorInstalled){globalThis._crowdinMirrorInstalled=!0;var e=globalThis.fetch.bind(globalThis);globalThis.fetch=function(t,i){if("string"==typeof t){var n,o;try{o=new URL(t)}catch(e){}if("https:"===(null===(n=o)||void 0===n?void 0:n.protocol)&&"distributions.crowdin.net"===o.hostname){var r="https://cdn.jsdelivr.net/gh/LizardByte/shared-web@crowdin-dist"+o.pathname+o.search+o.hash;return e(r,i)}}return e(t,i)}}}(),n("https://website-translator.app.crowdin.net/assets/proxy-translator.js",function(){var i={bg:"Български (Bulgarian)",cs:"Čeština (Czech)",de:"Deutsch (German)",en:"English","en-GB":"English, United Kingdom","en-US":"English, United States","es-ES":"Español (Spanish)",fr:"Français (French)",hu:"Magyar (Hungarian)",it:"Italiano (Italian)",ja:"日本語 (Japanese)",ko:"한국어 (Korean)",pl:"Polski (Polish)","pt-BR":"Português, Brasileiro (Portuguese, Brazilian)","pt-PT":"Português (Portuguese)",ru:"Русский (Russian)","sv-SE":"svenska (Swedish)",tr:"Türkçe (Turkish)",uk:"Українська (Ukranian)",vi:"Tiếng Việt (Vietnamese)","zh-CN":"简体中文 (Chinese Simplified)","zh-TW":"繁體中文 (Chinese Traditional)"};i=Object.fromEntries(Object.entries(i).sort(function(e,t){return e[1].localeCompare(t[1])}));var n=globalThis.location.origin;if(globalThis.proxyTranslator.init({baseUrl:n,distribution:{LizardByte:{baseUrl:"https://app.lizardbyte.dev",distribution:"458f881791aebba1d4dde491bw4"},"LizardByte-docs":{baseUrl:"https://docs.lizardbyte.dev",distribution:"d6c830ba4b41106fefe5d391bw4"}}[e].distribution,defaultLanguage:"en",languageTitles:i,showDefaultLanguageInUrl:!1,languageRoutingMethod:"query",position:"bottom-left",submenuPosition:"top-left",poweredBy:!1}),null!==t){var o=document.getElementById("crowdin-language-picker"),r=document.getElementsByClassName("cr-picker-button")[0];"sphinx"===t&&(o.classList.remove("cr-position-bottom-left"),o.style.width=r.offsetWidth+10+"px",o.style.position="relative",o.style.left="10px",o.style.bottom="10px",document.getElementsByClassName("sidebar-sticky")[0].appendChild(o))}})):console.error('Invalid UI. Must be "sphinx", or null'):console.error('Invalid project. Must be "LizardByte" or "LizardByte-docs"')}"undefined"!=typeof globalThis&&void 0!==globalThis.window&&(globalThis.initCrowdIn=o),e.exports=o},836(e){function t(e,t){var i=document.createElement("script");i.src=e,i.async=!0,i.onload=function(){t&&t(null,i)},i.onerror=function(){t&&t(new Error("Failed to load script: ".concat(e)))},document.head.appendChild(i)}void 0!==globalThis.window&&(globalThis.loadScript=t),e.exports=t}},t={};!function i(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}(714)})();
2
2
  //# sourceMappingURL=crowdin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crowdin.js","mappings":"wBAAA,IAAMA,EAAaC,EAAQ,KAO3B,SAASC,IAAqD,IAAzCC,EAAOC,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,aAAcG,EAAQH,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,KAE/C,CAAC,aAAc,mBAAmBI,SAASL,GAI3C,CAAC,SAAU,MAAMK,SAASD,GAK/BP,EAAW,wEAAyE,WAEhF,IAWIS,EAAiB,CACjB,GAAM,wBACN,GAAM,kBACN,GAAM,mBACN,GAAM,UACN,QAAS,0BACT,QAAS,yBACT,QAAS,oBACT,GAAM,oBACN,GAAM,qBACN,GAAM,qBACN,GAAM,iBACN,GAAM,eACN,GAAM,kBACN,QAAS,gDACT,QAAS,yBACT,GAAM,oBACN,QAAS,oBACT,GAAM,mBACN,GAAM,wBACN,GAAM,0BACN,QAAS,4BACT,QAAS,8BAGbA,EAAiBC,OAAOC,YAAYD,OAAOE,QAAQH,GAAgBI,KAAK,SAACC,EAAGC,GAAC,OAAKD,EAAE,GAAGE,cAAcD,EAAE,GAAG,IAG1G,IAAIE,EAAiBC,WAAWC,SAASC,OAgBzC,GAbAF,WAAWG,gBAAgBC,KAAK,CAC5BC,QAASN,EACTO,aA5CoB,CACpB,WAAc,CACVD,QAAS,6BACTC,aAAc,+BAElB,kBAAmB,CACfD,QAAS,8BACTC,aAAc,gCAqCYrB,GAASqB,aACvCC,gBAAiB,KACjBhB,eAAgBA,EAChBiB,0BAA0B,EAC1BC,sBAAuB,QACvBC,SAAU,cACVC,gBAAiB,WACjBC,WAAW,IAIE,OAAbvB,EAAJ,CAIA,IAAMwB,EAAYC,SAASC,eAAe,2BACpCC,EAASF,SAASG,uBAAuB,oBAAoB,GAElD,WAAb5B,IACAwB,EAAUK,UAAUC,OAAO,2BAC3BN,EAAUO,MAAMC,MAAQL,EAAOM,YAAc,GAAK,KAClDT,EAAUO,MAAMV,SAAW,WAC3BG,EAAUO,MAAMG,KAAO,OACvBV,EAAUO,MAAMI,OAAS,OAGTV,SAASG,uBAAuB,kBAAkB,GAG1DQ,YAAYZ,GAhBxB,CAkBJ,GAjFIa,QAAQC,MAAM,yCAJdD,QAAQC,MAAM,6DAsFtB,CAG0B,oBAAf3B,iBAAoDZ,IAAtBY,WAAW4B,SAChD5B,WAAWhB,YAAcA,GAG7B6C,EAAOC,QAAU9C,C,SClGjB,SAASF,EAAWiD,EAAKC,GACrB,IAAMC,EAASnB,SAASoB,cAAc,UACtCD,EAAOE,IAAMJ,EACbE,EAAOG,OAAQ,EAEfH,EAAOI,OAAS,WACRL,GAAUA,EAAS,KAAMC,EACjC,EAEAA,EAAOK,QAAU,WACTN,GAAUA,EAAS,IAAIO,MAAM,0BAADC,OAA2BT,IAC/D,EAEAjB,SAAS2B,KAAKhB,YAAYQ,EAC9B,CAGsB,oBAAXL,SACPA,OAAO9C,WAAaA,GAGxB+C,EAAOC,QAAUhD,C,GCzBb4D,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBxD,IAAjByD,EACH,OAAOA,EAAaf,QAGrB,IAAID,EAASa,EAAyBE,GAAY,CAGjDd,QAAS,CAAC,GAOX,OAHAgB,EAAoBF,GAAUf,EAAQA,EAAOC,QAASa,GAG/Cd,EAAOC,OACf,CCnB0Ba,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/crowdin.js","webpack://@lizardbyte/shared-web/./src/js/load-script.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["const loadScript = require('./load-script');\n\n/**\n * Initializes Crowdin translation widget based on project and UI platform.\n * @param {string} project - Project name ('LizardByte' or 'LizardByte-docs').\n * @param {string|null} platform - UI platform ('sphinx', or null).\n */\nfunction initCrowdIn(project = 'LizardByte', platform = null) {\n // Input validation\n if (!['LizardByte', 'LizardByte-docs'].includes(project)) {\n console.error('Invalid project. Must be \"LizardByte\" or \"LizardByte-docs\"');\n return;\n }\n if (!['sphinx', null].includes(platform)) {\n console.error('Invalid UI. Must be \"sphinx\", or null');\n return;\n }\n\n loadScript('https://website-translator.app.crowdin.net/assets/proxy-translator.js', function() {\n // Configure base settings based on project\n const projectSettings = {\n 'LizardByte': {\n baseUrl: \"https://app.lizardbyte.dev\",\n distribution: \"458f881791aebba1d4dde491bw4\",\n },\n 'LizardByte-docs': {\n baseUrl: \"https://docs.lizardbyte.dev\",\n distribution: \"d6c830ba4b41106fefe5d391bw4\",\n }\n };\n\n let languageTitles = {\n \"bg\": \"Български (Bulgarian)\",\n \"cs\": \"Čeština (Czech)\",\n \"de\": \"Deutsch (German)\",\n \"en\": \"English\",\n \"en-GB\": \"English, United Kingdom\",\n \"en-US\": \"English, United States\",\n \"es-ES\": \"Español (Spanish)\",\n \"fr\": \"Français (French)\",\n \"hu\": \"Magyar (Hungarian)\",\n \"it\": \"Italiano (Italian)\",\n \"ja\": \"日本語 (Japanese)\",\n \"ko\": \"한국어 (Korean)\",\n \"pl\": \"Polski (Polish)\",\n \"pt-BR\": \"Português, Brasileiro (Portuguese, Brazilian)\",\n \"pt-PT\": \"Português (Portuguese)\",\n \"ru\": \"Русский (Russian)\",\n \"sv-SE\": \"svenska (Swedish)\",\n \"tr\": \"Türkçe (Turkish)\",\n \"uk\": \"Українська (Ukranian)\",\n \"vi\": \"Tiếng Việt (Vietnamese)\",\n \"zh-CN\": \"简体中文 (Chinese Simplified)\",\n \"zh-TW\": \"繁體中文 (Chinese Traditional)\",\n };\n // sort languages by name\n languageTitles = Object.fromEntries(Object.entries(languageTitles).sort((a, b) => a[1].localeCompare(b[1])));\n\n // use this to allow translations to work on PR preview builds\n let currentBaseUrl = globalThis.location.origin;\n\n // Initialize Crowdin translator\n globalThis.proxyTranslator.init({\n baseUrl: currentBaseUrl,\n distribution: projectSettings[project].distribution,\n defaultLanguage: \"en\",\n languageTitles: languageTitles,\n showDefaultLanguageInUrl: false,\n languageRoutingMethod: \"query\",\n position: \"bottom-left\",\n submenuPosition: \"top-left\",\n poweredBy: false,\n });\n\n // Apply styling based on UI framework\n if (platform === null) {\n return;\n }\n\n const container = document.getElementById('crowdin-language-picker');\n const button = document.getElementsByClassName('cr-picker-button')[0];\n\n if (platform === 'sphinx') {\n container.classList.remove('cr-position-bottom-left')\n container.style.width = button.offsetWidth + 10 + 'px';\n container.style.position = 'relative';\n container.style.left = '10px';\n container.style.bottom = '10px';\n\n // get rst versions\n const sidebar = document.getElementsByClassName('sidebar-sticky')[0];\n\n // move button to related pages\n sidebar.appendChild(container);\n }\n });\n}\n\n// Expose to the global scope\nif (typeof globalThis !== 'undefined' && globalThis.window !== undefined) {\n globalThis.initCrowdIn = initCrowdIn;\n}\n\nmodule.exports = initCrowdIn;\n","/**\n * Load a script asynchronously, and add it to the DOM. Optionally, call a callback when the script has loaded.\n * @param {string} url The URL of the script to load.\n * @param {Function} callback An optional callback to call when the script has loaded.\n */\nfunction loadScript(url, callback) {\n const script = document.createElement('script');\n script.src = url;\n script.async = true;\n\n script.onload = () => {\n if (callback) callback(null, script);\n };\n\n script.onerror = () => {\n if (callback) callback(new Error(`Failed to load script: ${url}`));\n };\n\n document.head.appendChild(script);\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.loadScript = loadScript;\n}\n\nmodule.exports = loadScript;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(714);\n"],"names":["loadScript","require","initCrowdIn","project","arguments","length","undefined","platform","includes","languageTitles","Object","fromEntries","entries","sort","a","b","localeCompare","currentBaseUrl","globalThis","location","origin","proxyTranslator","init","baseUrl","distribution","defaultLanguage","showDefaultLanguageInUrl","languageRoutingMethod","position","submenuPosition","poweredBy","container","document","getElementById","button","getElementsByClassName","classList","remove","style","width","offsetWidth","left","bottom","appendChild","console","error","window","module","exports","url","callback","script","createElement","src","async","onload","onerror","Error","concat","head","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"crowdin.js","mappings":"wBAAA,IAAMA,EAAaC,EAAQ,KAqD3B,SAASC,IAAqD,IAAzCC,EAAOC,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,aAAcG,EAAQH,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,KAE/C,CAAC,aAAc,mBAAmBI,SAASL,GAI3C,CAAC,SAAU,MAAMK,SAASD,IAtCnC,WACI,GAAgC,mBAArBE,WAAWC,QAClBD,WAAWE,wBAAf,CACAF,WAAWE,yBAA0B,EAErC,IAAMC,EAAaH,WAAWC,MAAMG,KAAKJ,YAEzCA,WAAWC,MAAQ,SAA4BI,EAAKC,GAChD,GAAmB,iBAARD,EAAkB,KAAAE,EACrBC,EACJ,IACIA,EAAS,IAAIC,IAAIJ,EACrB,CAAE,MAAAK,GACE,CAKJ,GAAyB,YAAf,QAANH,EAAAC,SAAM,IAAAD,OAAA,EAANA,EAAQI,WAA6C,8BAApBH,EAAOI,SAA0C,CAClF,IAAMC,EA9BM,iEA8B8BL,EAAOM,SAAWN,EAAOO,OAASP,EAAOQ,KACnF,OAAOb,EAAWU,EAAaP,EACnC,CACJ,CACA,OAAOH,EAAWE,EAAKC,EAC3B,CAtB8C,CAuBlD,CAoBIW,GAEA1B,EAAW,wEAAyE,WAEhF,IAWI2B,EAAiB,CACjB,GAAM,wBACN,GAAM,kBACN,GAAM,mBACN,GAAM,UACN,QAAS,0BACT,QAAS,yBACT,QAAS,oBACT,GAAM,oBACN,GAAM,qBACN,GAAM,qBACN,GAAM,iBACN,GAAM,eACN,GAAM,kBACN,QAAS,gDACT,QAAS,yBACT,GAAM,oBACN,QAAS,oBACT,GAAM,mBACN,GAAM,wBACN,GAAM,0BACN,QAAS,4BACT,QAAS,8BAGbA,EAAiBC,OAAOC,YAAYD,OAAOE,QAAQH,GAAgBI,KAAK,SAACC,EAAGC,GAAC,OAAKD,EAAE,GAAGE,cAAcD,EAAE,GAAG,IAG1G,IAAIE,EAAiB1B,WAAW2B,SAASC,OAgBzC,GAbA5B,WAAW6B,gBAAgBC,KAAK,CAC5BC,QAASL,EACTM,aA5CoB,CACpB,WAAc,CACVD,QAAS,6BACTC,aAAc,+BAElB,kBAAmB,CACfD,QAAS,8BACTC,aAAc,gCAqCYtC,GAASsC,aACvCC,gBAAiB,KACjBf,eAAgBA,EAChBgB,0BAA0B,EAC1BC,sBAAuB,QACvBC,SAAU,cACVC,gBAAiB,WACjBC,WAAW,IAIE,OAAbxC,EAAJ,CAIA,IAAMyC,EAAYC,SAASC,eAAe,2BACpCC,EAASF,SAASG,uBAAuB,oBAAoB,GAElD,WAAb7C,IACAyC,EAAUK,UAAUC,OAAO,2BAC3BN,EAAUO,MAAMC,MAAQL,EAAOM,YAAc,GAAK,KAClDT,EAAUO,MAAMV,SAAW,WAC3BG,EAAUO,MAAMG,KAAO,OACvBV,EAAUO,MAAMI,OAAS,OAGTV,SAASG,uBAAuB,kBAAkB,GAG1DQ,YAAYZ,GAhBxB,CAkBJ,IArFIa,QAAQC,MAAM,yCAJdD,QAAQC,MAAM,6DA0FtB,CAG0B,oBAAfrD,iBAAoDH,IAAtBG,WAAWsD,SAChDtD,WAAWP,YAAcA,GAG7B8D,EAAOC,QAAU/D,C,SCpJjB,SAASF,EAAWc,EAAKoD,GACrB,IAAMC,EAASlB,SAASmB,cAAc,UACtCD,EAAOE,IAAMvD,EACbqD,EAAOG,OAAQ,EAEfH,EAAOI,OAAS,WACRL,GAAUA,EAAS,KAAMC,EACjC,EAEAA,EAAOK,QAAU,WACTN,GAAUA,EAAS,IAAIO,MAAM,0BAADC,OAA2B5D,IAC/D,EAEAmC,SAAS0B,KAAKf,YAAYO,EAC9B,MAG0B7D,IAAtBG,WAAWsD,SACXtD,WAAWT,WAAaA,GAG5BgE,EAAOC,QAAUjE,C,GCzBb4E,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBxE,IAAjByE,EACH,OAAOA,EAAad,QAGrB,IAAID,EAASY,EAAyBE,GAAY,CAGjDb,QAAS,CAAC,GAOX,OAHAe,EAAoBF,GAAUd,EAAQA,EAAOC,QAASY,GAG/Cb,EAAOC,OACf,CCnB0BY,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/crowdin.js","webpack://@lizardbyte/shared-web/./src/js/load-script.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["const loadScript = require('./load-script');\n\n/**\n * jsDelivr CDN URL serving Crowdin distribution files from the crowdin-dist\n * git branch. jsDelivr unconditionally sets Access-Control-Allow-Origin: *,\n * so cross-origin fetch() calls succeed without any browser plugin.\n * The branch is refreshed daily by the \"Sync Crowdin Distribution\" workflow.\n * Structure mirrors https://distributions.crowdin.net/<hash>/… exactly.\n * @type {string}\n */\nconst CROWDIN_DIST_MIRROR = 'https://cdn.jsdelivr.net/gh/LizardByte/shared-web@crowdin-dist';\n\n/**\n * Monkey-patches globalThis.fetch to redirect Crowdin distribution requests to\n * the self-hosted GitHub Pages mirror.\n *\n * Must be called BEFORE proxy-translator.js is loaded so that every fetch()\n * the script makes is already intercepted.\n *\n * Idempotent – installs the interceptor at most once per page.\n */\nfunction _installCrowdinFetchInterceptor() {\n if (typeof globalThis.fetch !== 'function') return;\n if (globalThis._crowdinMirrorInstalled) return;\n globalThis._crowdinMirrorInstalled = true;\n\n const _origFetch = globalThis.fetch.bind(globalThis);\n\n globalThis.fetch = function crowdinMirrorFetch(url, options) {\n if (typeof url === 'string') {\n let parsed;\n try {\n parsed = new URL(url);\n } catch {\n // Not a valid absolute URL – pass through unchanged.\n }\n // Use exact hostname comparison to avoid prefix-match bypasses\n // (e.g. distributions.crowdin.net.evil.com) that would be flagged\n // by incomplete URL sanitisation checks.\n if (parsed?.protocol === 'https:' && parsed.hostname === 'distributions.crowdin.net') {\n const mirroredUrl = CROWDIN_DIST_MIRROR + parsed.pathname + parsed.search + parsed.hash;\n return _origFetch(mirroredUrl, options);\n }\n }\n return _origFetch(url, options);\n };\n}\n\n/**\n * Initializes Crowdin translation widget based on project and UI platform.\n * @param {string} project - Project name ('LizardByte' or 'LizardByte-docs').\n * @param {string|null} platform - UI platform ('sphinx', or null).\n */\nfunction initCrowdIn(project = 'LizardByte', platform = null) {\n // Input validation\n if (!['LizardByte', 'LizardByte-docs'].includes(project)) {\n console.error('Invalid project. Must be \"LizardByte\" or \"LizardByte-docs\"');\n return;\n }\n if (!['sphinx', null].includes(platform)) {\n console.error('Invalid UI. Must be \"sphinx\", or null');\n return;\n }\n\n // Redirect distribution CDN requests to our self-hosted GitHub Pages mirror\n // before the script is even loaded so every fetch() it makes is intercepted.\n _installCrowdinFetchInterceptor();\n\n loadScript('https://website-translator.app.crowdin.net/assets/proxy-translator.js', function() {\n // Configure base settings based on project\n const projectSettings = {\n 'LizardByte': {\n baseUrl: \"https://app.lizardbyte.dev\",\n distribution: \"458f881791aebba1d4dde491bw4\",\n },\n 'LizardByte-docs': {\n baseUrl: \"https://docs.lizardbyte.dev\",\n distribution: \"d6c830ba4b41106fefe5d391bw4\",\n }\n };\n\n let languageTitles = {\n \"bg\": \"Български (Bulgarian)\",\n \"cs\": \"Čeština (Czech)\",\n \"de\": \"Deutsch (German)\",\n \"en\": \"English\",\n \"en-GB\": \"English, United Kingdom\",\n \"en-US\": \"English, United States\",\n \"es-ES\": \"Español (Spanish)\",\n \"fr\": \"Français (French)\",\n \"hu\": \"Magyar (Hungarian)\",\n \"it\": \"Italiano (Italian)\",\n \"ja\": \"日本語 (Japanese)\",\n \"ko\": \"한국어 (Korean)\",\n \"pl\": \"Polski (Polish)\",\n \"pt-BR\": \"Português, Brasileiro (Portuguese, Brazilian)\",\n \"pt-PT\": \"Português (Portuguese)\",\n \"ru\": \"Русский (Russian)\",\n \"sv-SE\": \"svenska (Swedish)\",\n \"tr\": \"Türkçe (Turkish)\",\n \"uk\": \"Українська (Ukranian)\",\n \"vi\": \"Tiếng Việt (Vietnamese)\",\n \"zh-CN\": \"简体中文 (Chinese Simplified)\",\n \"zh-TW\": \"繁體中文 (Chinese Traditional)\",\n };\n // sort languages by name\n languageTitles = Object.fromEntries(Object.entries(languageTitles).sort((a, b) => a[1].localeCompare(b[1])));\n\n // use this to allow translations to work on PR preview builds\n let currentBaseUrl = globalThis.location.origin;\n\n // Initialize Crowdin translator\n globalThis.proxyTranslator.init({\n baseUrl: currentBaseUrl,\n distribution: projectSettings[project].distribution,\n defaultLanguage: \"en\",\n languageTitles: languageTitles,\n showDefaultLanguageInUrl: false,\n languageRoutingMethod: \"query\",\n position: \"bottom-left\",\n submenuPosition: \"top-left\",\n poweredBy: false,\n });\n\n // Apply styling based on UI framework\n if (platform === null) {\n return;\n }\n\n const container = document.getElementById('crowdin-language-picker');\n const button = document.getElementsByClassName('cr-picker-button')[0];\n\n if (platform === 'sphinx') {\n container.classList.remove('cr-position-bottom-left')\n container.style.width = button.offsetWidth + 10 + 'px';\n container.style.position = 'relative';\n container.style.left = '10px';\n container.style.bottom = '10px';\n\n // get rst versions\n const sidebar = document.getElementsByClassName('sidebar-sticky')[0];\n\n // move button to related pages\n sidebar.appendChild(container);\n }\n });\n}\n\n// Expose to the global scope\nif (typeof globalThis !== 'undefined' && globalThis.window !== undefined) {\n globalThis.initCrowdIn = initCrowdIn;\n}\n\nmodule.exports = initCrowdIn;\n","/**\n * Load a script asynchronously, and add it to the DOM. Optionally, call a callback when the script has loaded.\n * @param {string} url The URL of the script to load.\n * @param {Function} callback An optional callback to call when the script has loaded.\n */\nfunction loadScript(url, callback) {\n const script = document.createElement('script');\n script.src = url;\n script.async = true;\n\n script.onload = () => {\n if (callback) callback(null, script);\n };\n\n script.onerror = () => {\n if (callback) callback(new Error(`Failed to load script: ${url}`));\n };\n\n document.head.appendChild(script);\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.loadScript = loadScript;\n}\n\nmodule.exports = loadScript;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(714);\n"],"names":["loadScript","require","initCrowdIn","project","arguments","length","undefined","platform","includes","globalThis","fetch","_crowdinMirrorInstalled","_origFetch","bind","url","options","_parsed","parsed","URL","_unused","protocol","hostname","mirroredUrl","pathname","search","hash","_installCrowdinFetchInterceptor","languageTitles","Object","fromEntries","entries","sort","a","b","localeCompare","currentBaseUrl","location","origin","proxyTranslator","init","baseUrl","distribution","defaultLanguage","showDefaultLanguageInUrl","languageRoutingMethod","position","submenuPosition","poweredBy","container","document","getElementById","button","getElementsByClassName","classList","remove","style","width","offsetWidth","left","bottom","appendChild","console","error","window","module","exports","callback","script","createElement","src","async","onload","onerror","Error","concat","head","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- (()=>{var e={415(e){function r(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;return e>=1e6?(e/1e6).toFixed(r)+"M":e>=1e3?(e/1e3).toFixed(r)+"k":e.toString()}"undefined"!=typeof window&&(window.formatNumber=r),e.exports=r}},r={};!function t(o){var n=r[o];if(void 0!==n)return n.exports;var i=r[o]={exports:{}};return e[o](i,i.exports,t),i.exports}(415)})();
1
+ (()=>{var r={415(r){function o(r){var o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;return r>=1e6?(r/1e6).toFixed(o)+"M":r>=1e3?(r/1e3).toFixed(o)+"k":r.toString()}void 0!==globalThis.window&&(globalThis.formatNumber=o),r.exports=o}},o={};!function e(t){var i=o[t];if(void 0!==i)return i.exports;var n=o[t]={exports:{}};return r[t](n,n.exports,e),n.exports}(415)})();
2
2
  //# sourceMappingURL=format-number.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-number.js","mappings":"oBAMA,SAASA,EAAaC,GAAwB,IAAnBC,EAAaC,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,EACvC,OAAIF,GAAO,KACCA,EAAM,KAASK,QAAQJ,GAAiB,IACzCD,GAAO,KACNA,EAAM,KAAMK,QAAQJ,GAAiB,IAEtCD,EAAIM,UAEnB,CAGsB,oBAAXC,SACPA,OAAOR,aAAeA,GAG1BS,EAAOC,QAAUV,C,GCpBbW,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBR,IAAjBS,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/format-number.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Format a number to a human-readable string.\n * @param {number} num The number to format.\n * @param {number} decimalPlaces The number of decimal places to include in the formatted string.\n * @returns {string} The formatted number as a string with suffix (k, M, etc.).\n */\nfunction formatNumber(num, decimalPlaces = 1) {\n if (num >= 1000000) {\n return (num / 1000000).toFixed(decimalPlaces) + 'M';\n } else if (num >= 1000) {\n return (num / 1000).toFixed(decimalPlaces) + 'k';\n } else {\n return num.toString()\n }\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.formatNumber = formatNumber;\n}\n\nmodule.exports = formatNumber;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(415);\n"],"names":["formatNumber","num","decimalPlaces","arguments","length","undefined","toFixed","toString","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"format-number.js","mappings":"oBAMA,SAASA,EAAaC,GAAwB,IAAnBC,EAAaC,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,EACvC,OAAIF,GAAO,KACCA,EAAM,KAASK,QAAQJ,GAAiB,IACzCD,GAAO,KACNA,EAAM,KAAMK,QAAQJ,GAAiB,IAEtCD,EAAIM,UAEnB,MAG0BF,IAAtBG,WAAWC,SACXD,WAAWR,aAAeA,GAG9BU,EAAOC,QAAUX,C,GCpBbY,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBT,IAAjBU,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/format-number.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Format a number to a human-readable string.\n * @param {number} num The number to format.\n * @param {number} decimalPlaces The number of decimal places to include in the formatted string.\n * @returns {string} The formatted number as a string with suffix (k, M, etc.).\n */\nfunction formatNumber(num, decimalPlaces = 1) {\n if (num >= 1000000) {\n return (num / 1000000).toFixed(decimalPlaces) + 'M';\n } else if (num >= 1000) {\n return (num / 1000).toFixed(decimalPlaces) + 'k';\n } else {\n return num.toString()\n }\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.formatNumber = formatNumber;\n}\n\nmodule.exports = formatNumber;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(415);\n"],"names":["formatNumber","num","decimalPlaces","arguments","length","undefined","toFixed","toString","globalThis","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- (()=>{var t={83(t){function n(t,n){if(0===t.length)return n.length;if(0===n.length)return t.length;var e,r,h=[];for(e=0;e<=n.length;e++)h[e]=[e];for(r=0;r<=t.length;r++)h[0][r]=r;for(e=1;e<=n.length;e++)for(r=1;r<=t.length;r++)n.charAt(e-1)===t.charAt(r-1)?h[e][r]=h[e-1][r-1]:h[e][r]=Math.min(h[e-1][r-1]+1,Math.min(h[e][r-1]+1,h[e-1][r]+1));return 100*(1-h[n.length][t.length]/Math.max(t.length,n.length))}"undefined"!=typeof window&&(window.levenshteinDistance=n),t.exports=n}},n={};!function e(r){var h=n[r];if(void 0!==h)return h.exports;var o=n[r]={exports:{}};return t[r](o,o.exports,e),o.exports}(83)})();
1
+ (()=>{var t={83(t){function n(t,n){if(0===t.length)return n.length;if(0===n.length)return t.length;var e,r,h=[];for(e=0;e<=n.length;e++)h[e]=[e];for(r=0;r<=t.length;r++)h[0][r]=r;for(e=1;e<=n.length;e++)for(r=1;r<=t.length;r++)n.charAt(e-1)===t.charAt(r-1)?h[e][r]=h[e-1][r-1]:h[e][r]=Math.min(h[e-1][r-1]+1,Math.min(h[e][r-1]+1,h[e-1][r]+1));return 100*(1-h[n.length][t.length]/Math.max(t.length,n.length))}void 0!==globalThis.window&&(globalThis.levenshteinDistance=n),t.exports=n}},n={};!function e(r){var h=n[r];if(void 0!==h)return h.exports;var l=n[r]={exports:{}};return t[r](l,l.exports,e),l.exports}(83)})();
2
2
  //# sourceMappingURL=levenshtein-distance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"levenshtein-distance.js","mappings":"mBAMA,SAASA,EAAoBC,EAAGC,GAC5B,GAAiB,IAAbD,EAAEE,OAAc,OAAOD,EAAEC,OAC7B,GAAiB,IAAbD,EAAEC,OAAc,OAAOF,EAAEE,OAE7B,IAGIC,EAMAC,EATAC,EAAS,GAIb,IAAKF,EAAI,EAAGA,GAAKF,EAAEC,OAAQC,IACvBE,EAAOF,GAAK,CAACA,GAKjB,IAAKC,EAAI,EAAGA,GAAKJ,EAAEE,OAAQE,IACvBC,EAAO,GAAGD,GAAKA,EAInB,IAAKD,EAAI,EAAGA,GAAKF,EAAEC,OAAQC,IACvB,IAAKC,EAAI,EAAGA,GAAKJ,EAAEE,OAAQE,IACnBH,EAAEK,OAAOH,EAAI,KAAOH,EAAEM,OAAOF,EAAI,GACjCC,EAAOF,GAAGC,GAAKC,EAAOF,EAAI,GAAGC,EAAI,GAEjCC,EAAOF,GAAGC,GAAKG,KAAKC,IAAIH,EAAOF,EAAI,GAAGC,EAAI,GAAK,EAC3CG,KAAKC,IAAIH,EAAOF,GAAGC,EAAI,GAAK,EACxBC,EAAOF,EAAI,GAAGC,GAAK,IAMvC,OAA2E,KAAnE,EAAKC,EAAOJ,EAAEC,QAAQF,EAAEE,QAAUK,KAAKE,IAAIT,EAAEE,OAAQD,EAAEC,QACnE,CAGsB,oBAAXQ,SACPA,OAAOX,oBAAsBA,GAGjCY,EAAOC,QAAUb,C,GC7Cbc,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAM,EAAoBH,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,G","sources":["webpack://@lizardbyte/shared-web/./src/js/levenshtein-distance.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Calculate the levenshtein distance between two strings.\n * @param {string} a - The first string to compare.\n * @param {string} b - The second string to compare.\n * @returns {number|*} - The percentage of the levenshtein distance.\n */\nfunction levenshteinDistance(a, b) {\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n let matrix = [];\n\n // increment along the first column of each row\n let i;\n for (i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n // increment each column in the first row\n let j;\n for (j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n // Fill in the rest of the matrix\n for (i = 1; i <= b.length; i++) {\n for (j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution\n Math.min(matrix[i][j - 1] + 1, // insertion\n matrix[i - 1][j] + 1)); // deletion\n }\n }\n }\n\n // return the percentage of the levenshtein distance\n return (1 - (matrix[b.length][a.length] / Math.max(a.length, b.length))) * 100\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.levenshteinDistance = levenshteinDistance;\n}\n\nmodule.exports = levenshteinDistance;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(83);\n"],"names":["levenshteinDistance","a","b","length","i","j","matrix","charAt","Math","min","max","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"levenshtein-distance.js","mappings":"mBAMA,SAASA,EAAoBC,EAAGC,GAC5B,GAAiB,IAAbD,EAAEE,OAAc,OAAOD,EAAEC,OAC7B,GAAiB,IAAbD,EAAEC,OAAc,OAAOF,EAAEE,OAE7B,IAGIC,EAMAC,EATAC,EAAS,GAIb,IAAKF,EAAI,EAAGA,GAAKF,EAAEC,OAAQC,IACvBE,EAAOF,GAAK,CAACA,GAKjB,IAAKC,EAAI,EAAGA,GAAKJ,EAAEE,OAAQE,IACvBC,EAAO,GAAGD,GAAKA,EAInB,IAAKD,EAAI,EAAGA,GAAKF,EAAEC,OAAQC,IACvB,IAAKC,EAAI,EAAGA,GAAKJ,EAAEE,OAAQE,IACnBH,EAAEK,OAAOH,EAAI,KAAOH,EAAEM,OAAOF,EAAI,GACjCC,EAAOF,GAAGC,GAAKC,EAAOF,EAAI,GAAGC,EAAI,GAEjCC,EAAOF,GAAGC,GAAKG,KAAKC,IAAIH,EAAOF,EAAI,GAAGC,EAAI,GAAK,EAC3CG,KAAKC,IAAIH,EAAOF,GAAGC,EAAI,GAAK,EACxBC,EAAOF,EAAI,GAAGC,GAAK,IAMvC,OAA2E,KAAnE,EAAKC,EAAOJ,EAAEC,QAAQF,EAAEE,QAAUK,KAAKE,IAAIT,EAAEE,OAAQD,EAAEC,QACnE,MAG0BQ,IAAtBC,WAAWC,SACXD,WAAWZ,oBAAsBA,GAGrCc,EAAOC,QAAUf,C,GC7CbgB,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBP,IAAjBQ,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,G","sources":["webpack://@lizardbyte/shared-web/./src/js/levenshtein-distance.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Calculate the levenshtein distance between two strings.\n * @param {string} a - The first string to compare.\n * @param {string} b - The second string to compare.\n * @returns {number|*} - The percentage of the levenshtein distance.\n */\nfunction levenshteinDistance(a, b) {\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n let matrix = [];\n\n // increment along the first column of each row\n let i;\n for (i = 0; i <= b.length; i++) {\n matrix[i] = [i];\n }\n\n // increment each column in the first row\n let j;\n for (j = 0; j <= a.length; j++) {\n matrix[0][j] = j;\n }\n\n // Fill in the rest of the matrix\n for (i = 1; i <= b.length; i++) {\n for (j = 1; j <= a.length; j++) {\n if (b.charAt(i - 1) === a.charAt(j - 1)) {\n matrix[i][j] = matrix[i - 1][j - 1];\n } else {\n matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution\n Math.min(matrix[i][j - 1] + 1, // insertion\n matrix[i - 1][j] + 1)); // deletion\n }\n }\n }\n\n // return the percentage of the levenshtein distance\n return (1 - (matrix[b.length][a.length] / Math.max(a.length, b.length))) * 100\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.levenshteinDistance = levenshteinDistance;\n}\n\nmodule.exports = levenshteinDistance;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(83);\n"],"names":["levenshteinDistance","a","b","length","i","j","matrix","charAt","Math","min","max","undefined","globalThis","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- (()=>{var n={836(n){function o(n,o){var r=document.createElement("script");r.src=n,r.async=!0,r.onload=function(){o&&o(null,r)},r.onerror=function(){o&&o(new Error("Failed to load script: ".concat(n)))},document.head.appendChild(r)}"undefined"!=typeof window&&(window.loadScript=o),n.exports=o}},o={};!function r(e){var t=o[e];if(void 0!==t)return t.exports;var c=o[e]={exports:{}};return n[e](c,c.exports,r),c.exports}(836)})();
1
+ (()=>{var o={836(o){function r(o,r){var n=document.createElement("script");n.src=o,n.async=!0,n.onload=function(){r&&r(null,n)},n.onerror=function(){r&&r(new Error("Failed to load script: ".concat(o)))},document.head.appendChild(n)}void 0!==globalThis.window&&(globalThis.loadScript=r),o.exports=r}},r={};!function n(t){var e=r[t];if(void 0!==e)return e.exports;var a=r[t]={exports:{}};return o[t](a,a.exports,n),a.exports}(836)})();
2
2
  //# sourceMappingURL=load-script.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-script.js","mappings":"oBAKA,SAASA,EAAWC,EAAKC,GACrB,IAAMC,EAASC,SAASC,cAAc,UACtCF,EAAOG,IAAML,EACbE,EAAOI,OAAQ,EAEfJ,EAAOK,OAAS,WACRN,GAAUA,EAAS,KAAMC,EACjC,EAEAA,EAAOM,QAAU,WACTP,GAAUA,EAAS,IAAIQ,MAAM,0BAADC,OAA2BV,IAC/D,EAEAG,SAASQ,KAAKC,YAAYV,EAC9B,CAGsB,oBAAXW,SACPA,OAAOd,WAAaA,GAGxBe,EAAOC,QAAUhB,C,GCzBbiB,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAM,EAAoBH,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/load-script.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Load a script asynchronously, and add it to the DOM. Optionally, call a callback when the script has loaded.\n * @param {string} url The URL of the script to load.\n * @param {Function} callback An optional callback to call when the script has loaded.\n */\nfunction loadScript(url, callback) {\n const script = document.createElement('script');\n script.src = url;\n script.async = true;\n\n script.onload = () => {\n if (callback) callback(null, script);\n };\n\n script.onerror = () => {\n if (callback) callback(new Error(`Failed to load script: ${url}`));\n };\n\n document.head.appendChild(script);\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.loadScript = loadScript;\n}\n\nmodule.exports = loadScript;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(836);\n"],"names":["loadScript","url","callback","script","document","createElement","src","async","onload","onerror","Error","concat","head","appendChild","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"load-script.js","mappings":"oBAKA,SAASA,EAAWC,EAAKC,GACrB,IAAMC,EAASC,SAASC,cAAc,UACtCF,EAAOG,IAAML,EACbE,EAAOI,OAAQ,EAEfJ,EAAOK,OAAS,WACRN,GAAUA,EAAS,KAAMC,EACjC,EAEAA,EAAOM,QAAU,WACTP,GAAUA,EAAS,IAAIQ,MAAM,0BAADC,OAA2BV,IAC/D,EAEAG,SAASQ,KAAKC,YAAYV,EAC9B,MAG0BW,IAAtBC,WAAWC,SACXD,WAAWf,WAAaA,GAG5BiB,EAAOC,QAAUlB,C,GCzBbmB,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBP,IAAjBQ,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/load-script.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Load a script asynchronously, and add it to the DOM. Optionally, call a callback when the script has loaded.\n * @param {string} url The URL of the script to load.\n * @param {Function} callback An optional callback to call when the script has loaded.\n */\nfunction loadScript(url, callback) {\n const script = document.createElement('script');\n script.src = url;\n script.async = true;\n\n script.onload = () => {\n if (callback) callback(null, script);\n };\n\n script.onerror = () => {\n if (callback) callback(new Error(`Failed to load script: ${url}`));\n };\n\n document.head.appendChild(script);\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.loadScript = loadScript;\n}\n\nmodule.exports = loadScript;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(836);\n"],"names":["loadScript","url","callback","script","document","createElement","src","async","onload","onerror","Error","concat","head","appendChild","undefined","globalThis","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
@@ -1,2 +1,2 @@
1
- (()=>{var r={76(r){function n(r,n){return function(t,e){return t[r]>e[r]?-1:t[r]<e[r]||t[n]>e[n]?1:t[n]<e[n]?-1:0}}"undefined"!=typeof window&&(window.rankingSorter=n),r.exports=n}},n={};!function t(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={exports:{}};return r[e](i,i.exports,t),i.exports}(76)})();
1
+ (()=>{var r={76(r){function o(r,o){return function(n,t){return n[r]>t[r]?-1:n[r]<t[r]||n[o]>t[o]?1:n[o]<t[o]?-1:0}}void 0!==globalThis.window&&(globalThis.rankingSorter=o),r.exports=o}},o={};!function n(t){var e=o[t];if(void 0!==e)return e.exports;var i=o[t]={exports:{}};return r[t](i,i.exports,n),i.exports}(76)})();
2
2
  //# sourceMappingURL=ranking-sorter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ranking-sorter.js","mappings":"mBAMA,SAASA,EAAcC,EAAUC,GAC7B,OAAO,SAASC,EAAGC,GACf,OAAID,EAAEF,GAAYG,EAAEH,IACR,EACDE,EAAEF,GAAYG,EAAEH,IAInBE,EAAED,GAAaE,EAAEF,GAHd,EAKIC,EAAED,GAAaE,EAAEF,IAChB,EAED,CAGnB,CACJ,CAGsB,oBAAXG,SACPA,OAAOL,cAAgBA,GAG3BM,EAAOC,QAAUP,C,GC7BbQ,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAM,EAAoBH,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,G","sources":["webpack://@lizardbyte/shared-web/./src/js/ranking-sorter.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Sorts an array of objects by two keys in descending order.\n * @param {string} firstKey The primary key to sort by.\n * @param {string} secondKey The secondary key to sort by, in case the first key is equal.\n * @returns {Function} The sorting function that returns a number.\n */\nfunction rankingSorter(firstKey, secondKey) {\n return function(a, b) {\n if (a[firstKey] > b[firstKey]) {\n return -1;\n } else if (a[firstKey] < b[firstKey]) {\n return 1;\n }\n else {\n if (a[secondKey] > b[secondKey]) {\n return 1;\n } else if (a[secondKey] < b[secondKey]) {\n return -1;\n } else {\n return 0;\n }\n }\n }\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.rankingSorter = rankingSorter;\n}\n\nmodule.exports = rankingSorter;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(76);\n"],"names":["rankingSorter","firstKey","secondKey","a","b","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"ranking-sorter.js","mappings":"mBAMA,SAASA,EAAcC,EAAUC,GAC7B,OAAO,SAASC,EAAGC,GACf,OAAID,EAAEF,GAAYG,EAAEH,IACR,EACDE,EAAEF,GAAYG,EAAEH,IAEhBE,EAAED,GAAaE,EAAEF,GADjB,EAGAC,EAAED,GAAaE,EAAEF,IAChB,EAED,CAEf,CACJ,MAG0BG,IAAtBC,WAAWC,SACXD,WAAWN,cAAgBA,GAG/BQ,EAAOC,QAAUT,C,GC1BbU,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBP,IAAjBQ,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,G","sources":["webpack://@lizardbyte/shared-web/./src/js/ranking-sorter.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Sorts an array of objects by two keys in descending order.\n * @param {string} firstKey The primary key to sort by.\n * @param {string} secondKey The secondary key to sort by, in case the first key is equal.\n * @returns {Function} The sorting function that returns a number.\n */\nfunction rankingSorter(firstKey, secondKey) {\n return function(a, b) {\n if (a[firstKey] > b[firstKey]) {\n return -1;\n } else if (a[firstKey] < b[firstKey]) {\n return 1;\n } else if (a[secondKey] > b[secondKey]) {\n return 1;\n } else if (a[secondKey] < b[secondKey]) {\n return -1;\n } else {\n return 0;\n }\n }\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.rankingSorter = rankingSorter;\n}\n\nmodule.exports = rankingSorter;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(76);\n"],"names":["rankingSorter","firstKey","secondKey","a","b","undefined","globalThis","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
package/dist/sleep.js CHANGED
@@ -1,2 +1,2 @@
1
- (()=>{var e={399(e){function r(e){return new Promise(function(r){return setTimeout(r,e)})}"undefined"!=typeof window&&(window.sleep=r),e.exports=r}},r={};!function n(t){var o=r[t];if(void 0!==o)return o.exports;var i=r[t]={exports:{}};return e[t](i,i.exports,n),i.exports}(399)})();
1
+ (()=>{var r={399(r){function e(r){return new Promise(function(e){return setTimeout(e,r)})}void 0!==globalThis.window&&(globalThis.sleep=e),r.exports=e}},e={};!function o(t){var n=e[t];if(void 0!==n)return n.exports;var i=e[t]={exports:{}};return r[t](i,i.exports,o),i.exports}(399)})();
2
2
  //# sourceMappingURL=sleep.js.map
package/dist/sleep.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sleep.js","mappings":"oBAKA,SAASA,EAAMC,GACX,OAAO,IAAIC,QAAQ,SAAAC,GAAO,OAAIC,WAAWD,EAASF,EAAG,EACzD,CAGsB,oBAAXI,SACPA,OAAOL,MAAQA,GAGnBM,EAAOC,QAAUP,C,GCbbQ,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAM,EAAoBH,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/sleep.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Sleep for a given amount of time.\n * @param {number} ms The time to sleep in milliseconds.\n * @returns {Promise<unknown>} A promise that resolves after the given time.\n */\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// Expose to the global scope\nif (typeof window !== 'undefined') {\n window.sleep = sleep;\n}\n\nmodule.exports = sleep;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(399);\n"],"names":["sleep","ms","Promise","resolve","setTimeout","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","__webpack_modules__"],"sourceRoot":""}
1
+ {"version":3,"file":"sleep.js","mappings":"oBAKA,SAASA,EAAMC,GACX,OAAO,IAAIC,QAAQ,SAAAC,GAAO,OAAIC,WAAWD,EAASF,EAAG,EACzD,MAG0BI,IAAtBC,WAAWC,SACXD,WAAWN,MAAQA,GAGvBQ,EAAOC,QAAUT,C,GCbbU,EAA2B,CAAC,GAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBP,IAAjBQ,EACH,OAAOA,EAAaJ,QAGrB,IAAID,EAASE,EAAyBE,GAAY,CAGjDH,QAAS,CAAC,GAOX,OAHAK,EAAoBF,GAAUJ,EAAQA,EAAOC,QAASE,GAG/CH,EAAOC,OACf,CCnB0BE,CAAoB,I","sources":["webpack://@lizardbyte/shared-web/./src/js/sleep.js","webpack://@lizardbyte/shared-web/webpack/bootstrap","webpack://@lizardbyte/shared-web/webpack/startup"],"sourcesContent":["/**\n * Sleep for a given amount of time.\n * @param {number} ms The time to sleep in milliseconds.\n * @returns {Promise<unknown>} A promise that resolves after the given time.\n */\nfunction sleep(ms) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// Expose to the global scope\nif (globalThis.window !== undefined) {\n globalThis.sleep = sleep;\n}\n\nmodule.exports = sleep;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// startup\n// Load entry module and return exports\n// This entry module is referenced by other modules so it can't be inlined\nvar __webpack_exports__ = __webpack_require__(399);\n"],"names":["sleep","ms","Promise","resolve","setTimeout","undefined","globalThis","window","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__"],"sourceRoot":""}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/LizardByte/shared-web.git"
6
6
  },
7
- "version": "2026.219.33142",
7
+ "version": "2026.314.32913",
8
8
  "description": "Shared web assets for LizardByte projects",
9
9
  "license": "AGPL-3.0-only",
10
10
  "funding": "https://app.lizardbyte.dev",
@@ -28,28 +28,28 @@
28
28
  "@babel/preset-env": "7.29.0",
29
29
  "@codecov/webpack-plugin": "1.9.1",
30
30
  "@eslint/js": "10.0.1",
31
- "@jest/globals": "30.2.0",
32
- "babel-loader": "10.0.0",
31
+ "@jest/globals": "30.3.0",
32
+ "babel-loader": "10.1.1",
33
33
  "clean-jsdoc-theme": "4.3.0",
34
34
  "cross-env": "10.1.0",
35
35
  "css-loader": "7.1.4",
36
- "eslint": "10.0.0",
36
+ "eslint": "10.0.3",
37
37
  "eslint-plugin-jest": "29.15.0",
38
- "globals": "17.3.0",
39
- "jest": "30.2.0",
40
- "jest-environment-jsdom": "30.2.0",
38
+ "globals": "17.4.0",
39
+ "jest": "30.3.0",
40
+ "jest-environment-jsdom": "30.3.0",
41
41
  "jest-junit": "16.0.0",
42
42
  "jsdoc": "4.0.5",
43
- "mini-css-extract-plugin": "2.10.0",
43
+ "mini-css-extract-plugin": "2.10.1",
44
44
  "node-fetch": "3.3.2",
45
45
  "npm-run-all": "4.1.5",
46
- "postcss": "8.5.6",
46
+ "postcss": "8.5.8",
47
47
  "postcss-loader": "8.2.1",
48
- "postcss-preset-env": "11.1.3",
49
- "sass": "1.97.3",
48
+ "postcss-preset-env": "11.2.0",
49
+ "sass": "1.98.0",
50
50
  "sass-loader": "16.0.7",
51
- "webpack": "5.105.2",
52
- "webpack-cli": "6.0.1",
51
+ "webpack": "5.105.4",
52
+ "webpack-cli": "7.0.0",
53
53
  "webpack-dev-server": "5.2.3"
54
54
  },
55
55
  "jest": {
package/src/js/crowdin.js CHANGED
@@ -1,5 +1,51 @@
1
1
  const loadScript = require('./load-script');
2
2
 
3
+ /**
4
+ * jsDelivr CDN URL serving Crowdin distribution files from the crowdin-dist
5
+ * git branch. jsDelivr unconditionally sets Access-Control-Allow-Origin: *,
6
+ * so cross-origin fetch() calls succeed without any browser plugin.
7
+ * The branch is refreshed daily by the "Sync Crowdin Distribution" workflow.
8
+ * Structure mirrors https://distributions.crowdin.net/<hash>/… exactly.
9
+ * @type {string}
10
+ */
11
+ const CROWDIN_DIST_MIRROR = 'https://cdn.jsdelivr.net/gh/LizardByte/shared-web@crowdin-dist';
12
+
13
+ /**
14
+ * Monkey-patches globalThis.fetch to redirect Crowdin distribution requests to
15
+ * the self-hosted GitHub Pages mirror.
16
+ *
17
+ * Must be called BEFORE proxy-translator.js is loaded so that every fetch()
18
+ * the script makes is already intercepted.
19
+ *
20
+ * Idempotent – installs the interceptor at most once per page.
21
+ */
22
+ function _installCrowdinFetchInterceptor() {
23
+ if (typeof globalThis.fetch !== 'function') return;
24
+ if (globalThis._crowdinMirrorInstalled) return;
25
+ globalThis._crowdinMirrorInstalled = true;
26
+
27
+ const _origFetch = globalThis.fetch.bind(globalThis);
28
+
29
+ globalThis.fetch = function crowdinMirrorFetch(url, options) {
30
+ if (typeof url === 'string') {
31
+ let parsed;
32
+ try {
33
+ parsed = new URL(url);
34
+ } catch {
35
+ // Not a valid absolute URL – pass through unchanged.
36
+ }
37
+ // Use exact hostname comparison to avoid prefix-match bypasses
38
+ // (e.g. distributions.crowdin.net.evil.com) that would be flagged
39
+ // by incomplete URL sanitisation checks.
40
+ if (parsed?.protocol === 'https:' && parsed.hostname === 'distributions.crowdin.net') {
41
+ const mirroredUrl = CROWDIN_DIST_MIRROR + parsed.pathname + parsed.search + parsed.hash;
42
+ return _origFetch(mirroredUrl, options);
43
+ }
44
+ }
45
+ return _origFetch(url, options);
46
+ };
47
+ }
48
+
3
49
  /**
4
50
  * Initializes Crowdin translation widget based on project and UI platform.
5
51
  * @param {string} project - Project name ('LizardByte' or 'LizardByte-docs').
@@ -16,6 +62,10 @@ function initCrowdIn(project = 'LizardByte', platform = null) {
16
62
  return;
17
63
  }
18
64
 
65
+ // Redirect distribution CDN requests to our self-hosted GitHub Pages mirror
66
+ // before the script is even loaded so every fetch() it makes is intercepted.
67
+ _installCrowdinFetchInterceptor();
68
+
19
69
  loadScript('https://website-translator.app.crowdin.net/assets/proxy-translator.js', function() {
20
70
  // Configure base settings based on project
21
71
  const projectSettings = {
@@ -15,8 +15,8 @@ function formatNumber(num, decimalPlaces = 1) {
15
15
  }
16
16
 
17
17
  // Expose to the global scope
18
- if (typeof window !== 'undefined') {
19
- window.formatNumber = formatNumber;
18
+ if (globalThis.window !== undefined) {
19
+ globalThis.formatNumber = formatNumber;
20
20
  }
21
21
 
22
22
  module.exports = formatNumber;
@@ -40,8 +40,8 @@ function levenshteinDistance(a, b) {
40
40
  }
41
41
 
42
42
  // Expose to the global scope
43
- if (typeof window !== 'undefined') {
44
- window.levenshteinDistance = levenshteinDistance;
43
+ if (globalThis.window !== undefined) {
44
+ globalThis.levenshteinDistance = levenshteinDistance;
45
45
  }
46
46
 
47
47
  module.exports = levenshteinDistance;
@@ -20,8 +20,8 @@ function loadScript(url, callback) {
20
20
  }
21
21
 
22
22
  // Expose to the global scope
23
- if (typeof window !== 'undefined') {
24
- window.loadScript = loadScript;
23
+ if (globalThis.window !== undefined) {
24
+ globalThis.loadScript = loadScript;
25
25
  }
26
26
 
27
27
  module.exports = loadScript;
@@ -10,22 +10,19 @@ function rankingSorter(firstKey, secondKey) {
10
10
  return -1;
11
11
  } else if (a[firstKey] < b[firstKey]) {
12
12
  return 1;
13
- }
14
- else {
15
- if (a[secondKey] > b[secondKey]) {
16
- return 1;
17
- } else if (a[secondKey] < b[secondKey]) {
18
- return -1;
19
- } else {
20
- return 0;
21
- }
13
+ } else if (a[secondKey] > b[secondKey]) {
14
+ return 1;
15
+ } else if (a[secondKey] < b[secondKey]) {
16
+ return -1;
17
+ } else {
18
+ return 0;
22
19
  }
23
20
  }
24
21
  }
25
22
 
26
23
  // Expose to the global scope
27
- if (typeof window !== 'undefined') {
28
- window.rankingSorter = rankingSorter;
24
+ if (globalThis.window !== undefined) {
25
+ globalThis.rankingSorter = rankingSorter;
29
26
  }
30
27
 
31
28
  module.exports = rankingSorter;
package/src/js/sleep.js CHANGED
@@ -8,8 +8,8 @@ function sleep(ms) {
8
8
  }
9
9
 
10
10
  // Expose to the global scope
11
- if (typeof window !== 'undefined') {
12
- window.sleep = sleep;
11
+ if (globalThis.window !== undefined) {
12
+ globalThis.sleep = sleep;
13
13
  }
14
14
 
15
15
  module.exports = sleep;
@@ -20,7 +20,7 @@ const initCrowdIn = require('../src/js/crowdin');
20
20
  describe('initCrowdIn', () => {
21
21
  beforeEach(() => {
22
22
  // Mock DOM elements
23
- global.document.body.innerHTML = `
23
+ globalThis.document.body.innerHTML = `
24
24
  <div id="crowdin-language-picker">
25
25
  <div class="cr-picker-button"></div>
26
26
  <div class="cr-picker-submenu"></div>
@@ -35,18 +35,22 @@ describe('initCrowdIn', () => {
35
35
  jest.spyOn(console, 'error').mockImplementation(() => {});
36
36
 
37
37
  // Mock window.proxyTranslator
38
- global.window.proxyTranslator = {
38
+ globalThis.window.proxyTranslator = {
39
39
  init: jest.fn()
40
40
  };
41
41
 
42
42
  // Use fake timers to control setTimeout
43
43
  jest.useFakeTimers();
44
+
45
+ // Reset fetch interceptor flag so each test starts clean
46
+ delete globalThis._crowdinMirrorInstalled;
44
47
  });
45
48
 
46
49
  afterEach(() => {
47
50
  jest.clearAllMocks();
48
51
  jest.useRealTimers();
49
- delete global.window.proxyTranslator;
52
+ delete globalThis.window.proxyTranslator;
53
+ delete globalThis._crowdinMirrorInstalled;
50
54
  });
51
55
 
52
56
  it('should validate project parameter', () => {
@@ -65,7 +69,7 @@ describe('initCrowdIn', () => {
65
69
  // Simulate script loading
66
70
  jest.runAllTimers();
67
71
 
68
- expect(window.proxyTranslator.init).toHaveBeenCalledWith(
72
+ expect(globalThis.proxyTranslator.init).toHaveBeenCalledWith(
69
73
  expect.objectContaining({
70
74
  baseUrl: "http://localhost",
71
75
  distribution: "458f881791aebba1d4dde491bw4",
@@ -80,7 +84,7 @@ describe('initCrowdIn', () => {
80
84
  // Simulate script loading
81
85
  jest.runAllTimers();
82
86
 
83
- expect(window.proxyTranslator.init).toHaveBeenCalledWith(
87
+ expect(globalThis.proxyTranslator.init).toHaveBeenCalledWith(
84
88
  expect.objectContaining({
85
89
  baseUrl: "http://localhost",
86
90
  distribution: "d6c830ba4b41106fefe5d391bw4",
@@ -114,3 +118,100 @@ describe('initCrowdIn', () => {
114
118
  expect(sidebar.contains(container)).toBe(true);
115
119
  });
116
120
  });
121
+
122
+ describe('Crowdin fetch interceptor', () => {
123
+ const CROWDIN_CDN = 'https://distributions.crowdin.net';
124
+ const MIRROR = 'https://cdn.jsdelivr.net/gh/LizardByte/shared-web@crowdin-dist';
125
+
126
+ beforeEach(() => {
127
+ // Reset interceptor state so each test gets a fresh install
128
+ delete globalThis._crowdinMirrorInstalled;
129
+
130
+ // Install a controllable mock fetch
131
+ globalThis.fetch = jest.fn().mockResolvedValue({ ok: true });
132
+
133
+ // Minimal DOM + proxyTranslator stub so initCrowdIn callback doesn't crash
134
+ globalThis.window.proxyTranslator = { init: jest.fn() };
135
+ globalThis.document.body.innerHTML = '';
136
+
137
+ jest.useFakeTimers();
138
+ });
139
+
140
+ afterEach(() => {
141
+ jest.clearAllMocks();
142
+ jest.useRealTimers();
143
+ delete globalThis._crowdinMirrorInstalled;
144
+ delete globalThis.window.proxyTranslator;
145
+ });
146
+
147
+ it('should redirect Crowdin CDN fetch calls to the GitHub Pages mirror', async () => {
148
+ // Save mock before initCrowdIn wraps globalThis.fetch
149
+ const mockFetch = globalThis.fetch;
150
+
151
+ initCrowdIn();
152
+ jest.runAllTimers();
153
+
154
+ const testUrl = `${CROWDIN_CDN}/458f881791aebba1d4dde491bw4/manifest.json`;
155
+ await globalThis.fetch(testUrl);
156
+
157
+ // The wrapper should have called the underlying mock with the mirrored URL.
158
+ // The interceptor uses URL parsing, so the path is taken from parsed.pathname.
159
+ expect(mockFetch).toHaveBeenCalled();
160
+ const calledUrl = mockFetch.mock.calls[0][0];
161
+ expect(calledUrl).toBe(`${MIRROR}/458f881791aebba1d4dde491bw4/manifest.json`);
162
+ });
163
+
164
+ it('should preserve query string when redirecting', async () => {
165
+ const mockFetch = globalThis.fetch;
166
+
167
+ initCrowdIn();
168
+ jest.runAllTimers();
169
+
170
+ const testUrl = `${CROWDIN_CDN}/458f881791aebba1d4dde491bw4/languages.json?timestamp=1234`;
171
+ await globalThis.fetch(testUrl);
172
+
173
+ const calledUrl = mockFetch.mock.calls[0][0];
174
+ expect(calledUrl).toBe(`${MIRROR}/458f881791aebba1d4dde491bw4/languages.json?timestamp=1234`);
175
+ });
176
+
177
+ it('should not redirect non-Crowdin fetch calls', async () => {
178
+ const mockFetch = globalThis.fetch;
179
+
180
+ initCrowdIn();
181
+ jest.runAllTimers();
182
+
183
+ const externalUrl = 'https://example.com/data.json';
184
+ await globalThis.fetch(externalUrl);
185
+
186
+ const calledUrl = mockFetch.mock.calls[0][0];
187
+ expect(calledUrl).toBe(externalUrl);
188
+ });
189
+
190
+ it('should not redirect a lookalike hostname (incomplete-URL-sanitisation guard)', async () => {
191
+ // A URL whose hostname merely starts with the CDN hostname must NOT be
192
+ // redirected – this is the exact bypass that CodeQL flags when using
193
+ // startsWith() instead of exact hostname comparison.
194
+ const mockFetch = globalThis.fetch;
195
+
196
+ initCrowdIn();
197
+ jest.runAllTimers();
198
+
199
+ const lookalike = 'https://distributions.crowdin.net.evil.com/steal';
200
+ await globalThis.fetch(lookalike);
201
+
202
+ const calledUrl = mockFetch.mock.calls[0][0];
203
+ expect(calledUrl).toBe(lookalike);
204
+ });
205
+
206
+ it('should install the interceptor only once (idempotent)', () => {
207
+ initCrowdIn();
208
+ const fetchAfterFirst = globalThis.fetch;
209
+ jest.runAllTimers();
210
+
211
+ // Calling initCrowdIn again must NOT wrap fetch a second time
212
+ initCrowdIn();
213
+ jest.runAllTimers();
214
+
215
+ expect(globalThis.fetch).toBe(fetchAfterFirst);
216
+ });
217
+ });
@@ -16,7 +16,7 @@ describe('sleep function', () => {
16
16
  })
17
17
 
18
18
  beforeEach(() => {
19
- jest.spyOn(global, 'setTimeout');
19
+ jest.spyOn(globalThis, 'setTimeout');
20
20
  });
21
21
 
22
22
  test.each([