diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5da5008 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +JS_ENV="development" +JS_ROOT="./" +JS_i18n="../../../src/_locales" \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8bcae8b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: magicoflolis diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index 43b0b0f..ebbcfd9 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -34,6 +34,13 @@ body: description: What browser are you seeing this bug on? validations: required: true + - type: input + id: userjs-manager + attributes: + label: User Script Manager + description: What user script manager are you using? - not required when using Web Extension + validations: + required: false - type: input id: url attributes: @@ -60,4 +67,4 @@ body: 1. Console error '...' 1. etc. validations: - required: false \ No newline at end of file + required: false diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml index 389a6b1..990c982 100644 --- a/.github/ISSUE_TEMPLATE/02_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/02_feature_request.yml @@ -11,4 +11,4 @@ body: label: Feature description description: A clear and concise description, please include if your feature request is related to a problem. validations: - required: true \ No newline at end of file + required: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0c3700d..5e05bdd 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,7 @@ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "mrmlnc.vscode-scss", - "adpyke.vscode-userscript" + "adpyke.vscode-userscript", + "xyz.local-history" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index b524aac..b54e9f0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,14 +23,8 @@ "keepProfileChanges": false, "url": "https://www.google.com/", "addonPath": "${workspaceFolder}/build/firefox", - "reloadOnChange": { - "watch": [ - "${workspaceFolder}/src/**" - ], - "ignore": [ - "${workspaceFolder}/node_modules/**" - ] - }, + "reloadOnChange": "${workspaceFolder}/src/**", + "firefoxArgs": ["--devtools"], "pathMappings": [ { "url": "webpack:///js", diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4f716e0..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "files.exclude": { - "**/build/**": true, - "**/dist/**": true, - "**/node_modules/**": true, - "**/Notes/**": true, - "tests/*": true - }, - "explorer.excludeGitIgnore": true, - "search.exclude": { - "**/build/**": true, - "**/dist/**": true, - "**/node_modules/**": true, - "**/Notes/**": true, - "tests/*": true - } -} diff --git a/CHANGELOG.user.md b/CHANGELOG.user.md index 61e1794..c67f69b 100644 --- a/CHANGELOG.user.md +++ b/CHANGELOG.user.md @@ -1,24 +1,214 @@ # Changelog +**Known Issues:** + +* SVG elements don't load on every webpage: + * _can be blocked by host, external scripts, web extensions, etc._ +* Outlook Website Loading Problem [#70](https://github.com/magicoflolis/Userscript-Plus/issues/70) + * Recommend excluding this userscript on `*.office.com` domains within your userscript manager or switch to the webextension version as it does not injected itself into the page. + * Caused by `Duplicated Default Trusted Types policy`. + +**TODO:** + +* Finish translations. +* Finish blacklist section. +* GitHub engine - filter out UserScript and UserStyles that don't match the host +* Rework TrustedTypePolicy code + +## v7.6.8 + +* Bug fixes: + * ~~Fixed [#70](https://github.com/magicoflolis/Userscript-Plus/issues/70)~~ + +--- + +## v7.6.7 + +* Bug fixes: + * Fixed [#69](https://github.com/magicoflolis/Userscript-Plus/issues/69) + +--- + +## v7.6.6 + +* Config changes: + * `autofetch` is now OFF by default +* Internal changes: + * refactored some code, better readability + * added info icon along with help links +* General changes: + * while `autofetch` is off, clicking the counter for the first time will auto fetch results + * minor bug fixes + +**TODO:** + +* Finish translations. +* Finish blacklist section. +* GitHub engine - filter out UserScript and UserStyles that don't match the host + +--- + +## v7.6.5 + +* fixed [#67](https://github.com/magicoflolis/Userscript-Plus/issues/67) + +--- + +## v7.6.4 + +* fixed `this.hostCache` for real this time +* changed `safeSelf` into a class + +--- + +## v7.6.3 + +* fixed [#66](https://github.com/magicoflolis/Userscript-Plus/issues/66) + * `this.cache` should have been `this.hostCache`, my bad + +--- + +## v7.6.2 + +* Improvements: + * improved multiple UserScript/UserStyle detection + * improved cache system + * _improved_ hostname recognition - somewhat follows web extension version + * improved readablity of "Theme Colors" section + * improved import/export of config and theme + * improved download process of UserScripts and UserStyles +* Config changes: + * GitHub engine: + * if your a UserScript or UserStyle developer and it's hosted on GitHub see [GitHub Detection](https://github.com/magicoflolis/Userscript-Plus/tree/master/wiki#github-detection) + * will now list all UserScripts and UserStyles within a repository - _even if they don't match the host_ + * multiple urls will now be fetched: + * `https://api.github.com/search/repositories?q=topic:{domain}+topic:userstyle` - cannot be changed in the settings menu + * `https://api.github.com/search/repositories?q=topic:{domain}+topic:userscript` - reset your config or manually set this address for changes to take effect + * the repository is removed from the list if no valid UserScripts or UserStyles are found +* Bug fixes: + * fixed "Enabling a engine requires the page to be reloaded, while disabling a engine does not." + * fixed "If an item changes after the list is sorted, the sorting won't be updated" + * fixed menu not timing out when "Fetch on load" was disabled + * fixed tab when searching for all-sites / `*` + * fixed searching settings +* Internal changes: + * removed `Memorize` class + * removed unused \@grants `GM_addValueChangeListener`, `GM_removeValueChangeListener`, and there `GM.*` counter parts + * removed some unused functions, types, css + * shortened some functions, types, css +* i18n additions (still not localized): + * new keys: `code`, `metadata`, `preview_metadata`, `recommend_author`, `recommend_other`, `default_sort` + +**TODO:** + +* Finish translations. +* Finish blacklist section. +* GitHub engine - filter out UserScript and UserStyles that don't match the host + +--- + +## v7.6.1 + +* Fixed sorting + +--- + +## v7.6.0 + +* New feature, `Preview Metadata` - same as `Preview Code` but loads userscripts metadata head +* New feature, `Page` - loads additonal information from userscripts homepage +* Config changes, moved `codePreview` value to `preview: code`, recommended resetting your config to clear any old/unused settings. + * If you had `Preview Code` enabled, you will need to re-enable it +* When `Preview Metadata` and `Preview Code` are both enabled, it will display the entire userscript +* Icon changes, svgs are now from [fontawesome.com](https://fontawesome.com) +* Search engines have been moved to the `Search Engines` section +* Improved mobile UI +* Improved userscript parsing +* Improved load times - added custom event to handle async item changes +* Improved GitHub engine + * Number of stars for a repo = daily installs + * Number of watchers for a repo = good rating +* Cleanup old css +* Minor bug fixes + +**Known Issues:** + +* Enabling a engine requires the page to be reloaded, while disabling a engine does not. + * This is caused by the cache system. +* If an item changes after the list is sorted, the sorting won't be updated + +**TODO:** + +* Finish translations. +* Finish blacklist section. + +--- + +## v7.5.0 + +* Merged [#64](https://github.com/magicoflolis/Userscript-Plus/pull/64) +* Cleaned up old comments + +**Known Issues:** + +* Enabling a engine requires the page to be reloaded, while disabling a engine does not. + * This is caused by the cache system. +* "Filter out other languages" may not filter _every_ userscript that is not in your language. + +**TODO:** + +* Finish translations. +* Finish blacklist section. +* Finish config section. + +--- + +## v7.4.0 + +* Merged [#59](https://github.com/magicoflolis/Userscript-Plus/pull/59) +* Added `githubusercontent.com` to `@connect`. +* Added `autoSort` to config. +* Added sorting to the following: "Total installs", "Ratings", "Created date". + * Still in the early stages, but functional +* Reworked counter system. +* Tweaked cache system. + * Reduced how often the script will cache something. +* Cleaned up code. +* Fixed minor issues. + +**Known Issues:** + +* Enabling a engine requires the page to be reloaded, while disabling a engine does not. + * This is caused by the cache system. +* "Filter out other languages" may not filter _every_ userscript that is not in your language. + +**TODO:** + +* Finish translations. +* Finish blacklist section. +* Finish config section. +* Merge [#64](https://github.com/magicoflolis/Userscript-Plus/pull/64) + +--- + ## v7.3.0 * Re-organized config list. * Added [GreasyFork Bullshit Filter](https://greasyfork.org/scripts/12179) into list. * You can enable it under "List Filters" in the config. * You can also do `filter:` in the search bar to quickly filter the list. - * Full list [here](https://github.com/magicoflolis/Userscript-Plus/tree/master/wiki/README.md) + * Full list here: [Wiki Page](https://github.com/magicoflolis/Userscript-Plus/tree/master/wiki/README.md) * Example: `filter:ascii` => filters out non-ASCII scripts **Known Issues:** * "Filter out other languages" may not filter _every_ userscript that is not in your language. -* SVG elements don't load on every webpage, _can be blocked by the page, external script, or extensions._ **TODO:** * Finish translations. * Finish blacklist section. -* Merge https://github.com/magicoflolis/Userscript-Plus/pull/59 +* Merge [#59](https://github.com/magicoflolis/Userscript-Plus/pull/59) * Add sorting to the following: "Total installs", "Ratings", "Created date" --- @@ -40,19 +230,18 @@ **Known Issues:** * "Filter out other languages" may not filter _every_ userscript that is not in your language. -* SVG elements don't load on every webpage, _can be blocked by the page._ **TODO:** * Add [GreasyFork Bullshit Filter](https://greasyfork.org/scripts/12179) into list -* Merge https://github.com/magicoflolis/Userscript-Plus/pull/59 +* Merge [#59](https://github.com/magicoflolis/Userscript-Plus/pull/59) * Add sorting to the following: "Total installs", "Ratings", "Created date" --- ## v7.1.1 -* Fixed "Error When Opening YouTube" https://github.com/magicoflolis/Userscript-Plus/issues/63 +* Fixed "Error When Opening YouTube" [#63](https://github.com/magicoflolis/Userscript-Plus/issues/63) --- @@ -67,12 +256,11 @@ **Known Issues:** * "Filter out other languages" may not filter _every_ userscript that is not in your language. -* SVG elements don't load on every webpage, _can be blocked by the page._ **TODO:** * Add [GreasyFork Bullshit Filter](https://greasyfork.org/scripts/12179) into list -* Merge https://github.com/magicoflolis/Userscript-Plus/pull/59 +* Merge [#59](https://github.com/magicoflolis/Userscript-Plus/pull/59) * Add sorting to the following: "Total installs", "Ratings", "Created date" * Improve design of blacklist section @@ -99,7 +287,6 @@ **Known Issues:** * "Filter out other languages" may not filter _every_ userscript that is not in your language. -* SVG elements don't load on every webpage, _can be blocked by the page._ **TODO:** @@ -107,3 +294,7 @@ * Improve design of blacklist section --- + +## Previous Changes + +[https://github.com/magicoflolis/Userscript-Plus/releases](https://github.com/magicoflolis/Userscript-Plus/releases) diff --git a/README.md b/README.md index bf9d7a0..80923b7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![Badge Issues](https://img.shields.io/github/issues/magicoflolis/Userscript-Plus?style=flat-square)](https://github.com/magicoflolis/Userscript-Plus/issues) [![Badge Greasy Fork](https://img.shields.io/greasyfork/dt/421603?style=flat-square)](https://greasyfork.org/scripts/421603) [![Badge Stars](https://img.shields.io/github/stars/magicoflolis/Userscript-Plus?style=flat-square)](https://github.com/magicoflolis/Userscript-Plus/stargazers) -[![UserScript](https://img.shields.io/badge/Userscript-Download-brightgreen.svg?style=flat-square&label=Greasy+Fork&logo=javascript&logoColor=white)](https://greasyfork.org/scripts/421603) -[![UserScript](https://img.shields.io/badge/Userscript-Download-brightgreen.svg?style=flat-square&label=GitHub+Repo&logo=javascript&logoColor=white)](https://github.com/magicoflolis/Userscript-Plus/blob/master/dist/magic-userjs.user.js?raw=1) --- @@ -16,10 +14,12 @@ Magic Userscript+ *A complete rewrite of [Userscript+ : Show Site All UserJS](https://github.com/jae-jae/Userscript-Plus)* -Finds available UserScripts for the current webpage, the power of [Greasy Fork](https://greasyfork.org) on the go! +Finds available UserScripts and UserStyles for the current webpage, the power of [Greasy Fork](https://greasyfork.org) on the go! [UserScript Changelog](https://github.com/magicoflolis/Userscript-Plus/blob/master/CHANGELOG.user.md) +[Web Extension Changelog](https://github.com/magicoflolis/Userscript-Plus/releases) + [List of known issues by Web Browser](https://github.com/magicoflolis/Userscript-Plus/blob/master/browser-issues.md) ## **Download** @@ -30,66 +30,74 @@ Finds available UserScripts for the current webpage, the power of [Greasy Fork]( > The UserScript only works on `HTTPS` sites! ([https://example.com](https://example.com)) * [Greasy Fork](https://greasyfork.org/scripts/421603) -* [Open UserJS](https://openuserjs.org/scripts/Magic/Magic_Userscript+_Show_Site_All_UserJS) - outdated * [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/blob/master/dist/magic-userjs.user.js?raw=1) +* [Open UserJS](https://openuserjs.org/scripts/Magic/Magic_Userscript+_Show_Site_All_UserJS) - Outdated **Web Extension:** -*Firefox:* - +* [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/releases) * [Firefox Add-ons](https://addons.mozilla.org/firefox/addon/userscript-plus/) -* [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/releases/tag/webext/latest) - -Works for desktop and mobile versions. - -*Chromium:* +* [Chrome Web Store](https://chromewebstore.google.com/detail/kbelpalpbddhjhoakbjkfookjeiennbo) +* [Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/golkolijobdaldjgefapmcknlmkjlhdh) +* ~~[Opera Add-ons](https://github.com/magicoflolis/Userscript-Plus/releases)~~ - Under review -* ~~[Chrome Web Store](https://github.com/magicoflolis/Userscript-Plus/releases/tag/webext/latest)~~ -* [GitHub Repo](https://github.com/magicoflolis/Userscript-Plus/releases/tag/webext/latest) - -Magic Userscript+ should be compatible with any Chromium-based browser - -**(Not Recommended) Bookmarklet:** +**Bookmarklet (not recommended):** Save this URL as a bookmark, clicking it will cause the **UserScript version** to inject itself into the current webpage. -```JS +```js filename="src/UserJS/main.js" javascript:(function(){['https://cdn.jsdelivr.net/gh/magicoflolis/Userscript-Plus@master/userscript/dist/magic-userjs.user.js'].map(s=>document.body.appendChild(document.createElement('script')).src=s)})(); ``` ## Features -* Designed for mobile and desktop devices -* Support multiple languages - date formats will match your current language -* Query UserScripts from: - * [Greasy Fork](https://greasyfork.org) - * [Sleazy Fork](https://sleazyfork.org) - * [Open UserJS](https://openuserjs.org) ( limited availability, will read `Too many requests...` if limit is reached ) - * [GitHub](https://github.com/search?l=JavaScript&o=desc&q="==UserScript==") ( requires [Personal Access Token](https://github.com/settings/tokens), no permissions are required ) -* Customize the theme -* Import / export config and theme -* Search for UserScripts -* Filter UserScripts which do not match your current language -* Sort UserScripts, default sorting "Daily Installs" -* Preview UserScript code before install -* Built-in UserScript [Greasyfork Search with Sleazyfork Results include](https://greasyfork.org/scripts/23840) -* Save UserScript as a local file -* Highlight UserScripts created by the [author](https://greasyfork.org/users/166061) -* Highlight recommended UserScripts from the [author](https://greasyfork.org/users/166061) +* General: + * UI designed for mobile and desktop devices + * Multiple language support - date formats are based on your current language + * Import / export config and theme + * Customize theme UI +* Query UserScripts and UserStyles from: + * [Greasy Fork](https://greasyfork.org) - enabled by default + * [Sleazy Fork](https://sleazyfork.org) - disabled by default + * [GitHub](https://github.com/search?l=JavaScript&o=desc&q="==UserScript==") ( requires [Personal Access Token](https://github.com/settings/tokens), no permissions are required ) - disabled by default +* Built-in UserScripts: + * [GreasyFork Bullshit Filter](https://greasyfork.org/scripts/12179) - disabled by default + * [Greasyfork Search with Sleazyfork Results include](https://greasyfork.org/scripts/23840) - disabled by default +* Automation: + * Fetch on load - query on page load +* Blacklist: + * Attempts to exclude certain hosts from being queried - localhost, bank, government, etc. +* Menu: + * Search for UserScripts - for shortcuts see [Wiki](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/README.md#shortcuts) + * Filter UserScripts which do not match your current language + * Sort UserScripts, default sorting "Daily Installs" + * Preview UserScripts code before install + * Save UserScript as a local file + * UserScript highlights: + * UserScripts created by the [author](https://greasyfork.org/users/166061) - enabled by default + * UserScript recommendations - enabled by default **UserScript Features:** > Tested and compatible with [TamperMonkey](https://www.tampermonkey.net/) or [ViolentMonkey](https://violentmonkey.github.io/) -* Maximize, minimize, or exit the UserScript list -* Change the position of floating counter -* Sync config with UserScript manager or per host +* General: + * Maximize, minimize, or close menu + * Sync config with UserScript manager or per host + * Customize timeout window - can be re-injected using your managers User Script Commands menu +* Query UserScripts and UserStyles from: + * [Open UserJS](https://openuserjs.org) ( limited availability, will read `Too many requests...` if limit is reached ) - disabled by default +* Automation: + * Inject on load - injects menu on page load + * Automatic fullscreen - maximizes menu when opened +* UserScript Commands via `GM_registerMenuCommand`: + * Inject Userscript+ - injects menu into the page + * Close Userscript+ - removes menu from the page -**Bookmarklet Features:** +**Web Extension Features:** -* Maximize, minimize, or exit the UserScript list -* Change the position of floating counter -* Separate config for every host +* General: + * You can fullscreen the list and it will open it to a new tab ## Previews @@ -136,52 +144,16 @@ javascript:(function(){['https://cdn.jsdelivr.net/gh/magicoflolis/Userscript-Plu * **If nothing appears, this means the script is not executing at all!** * For any additional help, submit a [New Issue](https://github.com/magicoflolis/Userscript-Plus/issues/new/choose) -## Build +## Want to Contribute? -Install [Node.js](https://nodejs.org/) and [pnpm](https://pnpm.io/). *The version of Node.js should match or be greater than the `"node"` key in `package.json`.* - -``` sh -# Install dependencies -$ pnpm i -``` +Fork this repo and open a [pull request](https://github.com/magicoflolis/Userscript-Plus/pulls) - [Rules](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md#contribution-rules) - [Guide](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md#contribution-guide) -### UserScript Version - -**Desktop** - Developed in [ViolentMonkey](https://violentmonkey.github.io/), see [How to edit scripts with your favorite editor?](https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/) - -**Mobile** - Developed in [FireFox Developer Edition](https://www.mozilla.org/firefox/developer/) "Responsive Design Mode" on desktop + [cromite](https://github.com/uazo/cromite) app on Android - -``` sh -# Watch and compile to local HTTP server -$ pnpm run dev:UserJS - -# Compile UserScript to "./dist" -$ pnpm run pub:UserJS -``` - -### WebExtension Version - -``` sh -# Compile WebExtension (developer version) for "Chromium" to "./build/chrome" -$ pnpm run dev:Cr -# Compile WebExtension (developer version) for "Firefox" to "./build/firefox" -$ pnpm run dev:FF -# Compile WebExtension (public version) for "Chromium" to "./build/chrome" -$ pnpm run webpack:Cr -# Compile WebExtension (public version) for "Firefox" to "./build/firefox" -$ pnpm run webpack:FF - -# [ Testing ] -# Browsers can be launched via "Run and Debug" in VSCode. -# Edit runtimeExecutable in ".vscode/launch.json" to your Chrome executable. -# Recommended to create an additional profile "about:profiles" for Firefox. -``` +How to add or edit translations? How to build? - [Build](https://github.com/magicoflolis/Userscript-Plus/blob/master/wiki/Build.md) ### Roadmap -* Finish WebExtension -* Add sorting to the following: "Total installs", "Ratings", "Created date" -* Improve design of blacklist section. +* See TODO section in [UserScript Changelog](https://github.com/magicoflolis/Userscript-Plus/blob/master/CHANGELOG.user.md) +* See TODO section in [Web Extension Changelog](https://github.com/magicoflolis/Userscript-Plus/releases) ### Source Code diff --git a/UserJS.json b/UserJS.json new file mode 100644 index 0000000..a814551 --- /dev/null +++ b/UserJS.json @@ -0,0 +1,69 @@ +{ + "version": "7.6.8", + "name": "Magic Userscript+ : Show Site All UserJS", + "description": "Finds available userscripts for the current webpage.", + "author": "Magic ", + "bugs": "https://github.com/magicoflolis/Userscript-Plus/issues", + "homepage": "https://github.com/magicoflolis/Userscript-Plus", + "icon": "./src/img/icon_64.png", + "downloadURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js", + "updateURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js", + "license": "MIT", + "build": { + "source": { + "metadata": "./src/UserJS/header.js", + "code": "./src/UserJS/main.js", + "mainCSS": "./src/sass/magicuserjs.scss" + }, + "watch": { + "files": ["main.js", "header.js", "_main.scss"], + "dirs": ["src/UserJS", "src/sass"] + }, + "paths": { + "fileName": "magic-userjs", + "dir": "./dist", + "i18n": { + "default": "en", + "dir": "../../../src/_locales" + }, + "dev": { + "fileName": "magic-userjs.dev", + "dir": "./web-server" + } + } + }, + "metadata": { + "compatible": ["chrome", "firefox", "edge", "opera", "safari"], + "connect": [ + "greasyfork.org", + "sleazyfork.org", + "github.com", + "githubusercontent.com", + "openuserjs.org" + ], + "grant": [ + "GM_addElement", + "GM_info", + "GM_getValue", + "GM_openInTab", + "GM_setValue", + "GM_registerMenuCommand", + "GM_xmlhttpRequest", + "GM.addElement", + "GM.info", + "GM.getValue", + "GM.openInTab", + "GM.setValue", + "GM.registerMenuCommand", + "GM.xmlHttpRequest" + ], + "exclude": [], + "include": [], + "exclude-match": [], + "match": ["https://*/*"], + "noframes": true, + "resource": {}, + "require": [], + "run-at": "document-start" + } +} diff --git a/Userscript-Plus.code-workspace b/Userscript-Plus.code-workspace index 35a299a..a90eb82 100644 --- a/Userscript-Plus.code-workspace +++ b/Userscript-Plus.code-workspace @@ -6,38 +6,36 @@ ], "settings": { "search.exclude": { + "**/.vscode/**": true, "**/build/**": true, "**/dist/**": true, "**/node_modules/**": true, "**/Notes/**": true }, - "editor.codeActionsOnSave": { - "source.fixAll": "never", - "source.fixAll.eslint": "explicit" - }, - "editor.formatOnSaveMode": "modifications", "explorer.excludeGitIgnore": true, "files.exclude": { + "**/.vscode/**": true, "**/build/**": true, "**/dist/**": true, "**/node_modules/**": true, "**/Notes/**": true }, - "files.trimTrailingWhitespace": true, "local-history.daysLimit": 3, "local-history.maxDisplay": 10, "local-history.saveDelay": 0, "local-history.dateLocale": "en-US", "local-history.exclude": [ "**/.history/**", - "**/.vscode/**", - "**/node_modules/**", - "**/dist/**", - "**/chrome_dist/**", - "**/build/**", - "**/*.code-workspace", - "**/src/sass/**", + "**/.vscode/**", + "**/node_modules/**", + "**/typings/**", + "**/out/**", + "**/Code/User/**", + "**/*.code-workspace", + "**/build/**", + "**/dist/**", + "**/Notes/**" ], - "local-history.path": "${workspaceFolder}/.vscode", + "local-history.path": "${workspaceFolder}/.vscode" } } \ No newline at end of file diff --git a/dist/.env.example b/dist/.env.example deleted file mode 100644 index ca948fe..0000000 --- a/dist/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Please do not change this .env variable -JS_ENV="production" diff --git a/dist/magic-userjs.meta.js b/dist/magic-userjs.meta.js index c74560c..5924b2f 100644 --- a/dist/magic-userjs.meta.js +++ b/dist/magic-userjs.meta.js @@ -1,5 +1,5 @@ // ==UserScript== -// @version 7.3.0 +// @version 7.6.8 // @name Magic Userscript+ : Show Site All UserJS // @name:ar Magic Userscript+: عرض جميع ملفات UserJS // @name:de Magic Userscript+ : Website anzeigen Alle UserJS @@ -24,13 +24,13 @@ // @description:zh 为当前网页查找可用的用户脚本。 // @description:zh-CN 为当前网页查找可用的用户脚本。 // @description:zh-TW 为当前网页查找可用的用户脚本。 +// @author Magic // @supportURL https://github.com/magicoflolis/Userscript-Plus/issues // @namespace https://github.com/magicoflolis/Userscript-Plus // @homepageURL https://github.com/magicoflolis/Userscript-Plus // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV8TRZFKBzuIOGSoTnZREcGlVLEIFkpboVUHk0u/oElDkuLiKLgWHPxYrDq4OOvq4CoIgh8gzg5Oii5S4v+SQosYD4778e7e4+4dIDSrTLN6YoCm22Y6EZdy+VWp7xUigghhDqLMLCOZWczCd3zdI8DXuyjP8j/35xhUCxYDAhJxjBmmTbxBPLNpG5z3icOsLKvE58QTJl2Q+JHrisdvnEsuCzwzbGbT88RhYqnUxUoXs7KpEU8TR1RNp3wh57HKeYuzVq2z9j35C4MFfSXDdZqjSGAJSaQgQUEdFVRhI0qrToqFNO3Hffwjrj9FLoVcFTByLKAGDbLrB/+D391axalJLykYB3pfHOdjDOjbBVoNx/k+dpzWCSA+A1d6x19rArOfpDc6WuQICG0DF9cdTdkDLneA4SdDNmVXEmkKxSLwfkbflAeGboGBNa+39j5OH4AsdbV8AxwcAuMlyl73eXd/d2//nmn39wOjunK6jS33SAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+gDDBAAJWyXgRAAABRPSURBVHjazVt9VNRl9v9854VhhjdBiHcUaFQEDTVCdk1IstTV0jb1LLZkJZrl4knLTnZaKjdqdXdLXcvfyoHUErPUBBEMLREJ6egJFBUUBF9WMd6R4WW+M/P5/eHMdxkYEJS0e8498MzzzPN87537PM+9n3u/An49kgFwA+AJIACAP4AxAEIA+AFwBzDEPLYJQB2AqwDOATgN4AqAywBubNu2rTk+Pl4EAJIyABAEwTQYDyn8CoIrAEQD+BOAMLPgLgAcAGDs2LGQyWSora1FYGAg6urqUFZWBgCIiYnBqVOn0NDQAAA6T0/Pm/X19ZflcvkZo9G4w2AwHAFgwG+Q5ACCg4KClixZsuT82rVrTTt27CCAHpybm8vq6moWFRXRZDKxpqaGkyZN4q0fl3zyySelsaIoMiYmhg899BD/9re/mQAUAogHEGxe8/5TaGiob0RERMrSpUuvZ2Zmmo4cOcIjR44wLy/PpgIOHjzI2tpaTp06lVqtlhUVFUxLS5MU8MQTT0hjLQqZPHkySRIAFy1aZAwNDa0C8C/z9rpvNBbAPwBctyVob3zw4EEWFBRQJpNJFvH999/3WwE5OTncs2ePZUwVgDjzWXPHB9Wd0GsACuRy+UoXFxev5cuXo7OzEyStWBBsHzGiKMJkunWGmUwmyGQy6X+lUgkAcHBw6HXxoUOHWv4dDiAFwNcAJtzpgTUQUgNYIgjCP2bNmiX7+9//jo0bN2Lz5s3IyspCUFCQJIz5xB7Q5FVVVZg9ezby8vKwYsWKgTzTFACZAFYC+BZA+6ArQKlUOouiuAZAgiAIMq1WixEjRuCjjz6Cq6srPvzwQ1RUVNze5GQyKyV1bW/atAnJycmIj49Henr6/66qLpbUdbzRaMS0adOQm5sLAN4A/g/ARADvAGgZvCNeLvdUq9VbzVcQAXDo0KHcs2cPDQYD29ramJiYSKVSedszICIigpGRkTbb9vb2nDRpEmNiYujk5MQZM2bQ3d2dbm5unDFjhtX4qKgonj59mt7e3t3XEAH82+xnDAp5AThjSxgPDw9++eWXNJlMbGpq4htvvCEdbr82r1ixgr/73e966zeaz4W7VoIzgAyZTMaoqChOnTqVarW6hxIyMzNpMBio0+m4ZMmSflnCPWCj2RKc71R4NYD1AIwODg4sKipiU1MTV69e3UNAT09Pfv311yTJxsZGLl++/J5Zwm1YNMugvhMFxAFoA0CFQsH33nuPHR0dbGtr49KlS6lSqawW8/LyYnZ2Ng0GA2/evMkXXniBCoXit6CEFrNbPiB6SalUtncVwNnZmcnJySTJlpYWvv7665TL5VaL+fj4cN++fSTJ+vp6vvzyy31agrOzc1/7eMAcFRXFIUOG2Oq7NhA/YaRcLr85bNgwuri4WE2kUqn48ccfs7OzkzqdjgkJCT0swdfXl7m5uTQajWxubuaCBQskRYWGhjIrK4uCIBAAY2JiSFJq3y2T5PTp03vrP9wfj1EA8PkjjzzC8+fPMysriz4+PlYTubi4cN26dSTJ5uZmm/vdz8+PBw4cIEnW1tbyxRdfpCAIDA4OpsFgsBo7WML3Y64287buk7x9fX07oqOj2dLSQpPJxMzMzB5KUKvV3LRpE/V6PW/evMmFCxfSzs7Oaoy/vz9/+OEHGo1GNjY2cu7cudRoNExNTeWePXuYkZHBsLAw7t+/n4IgUBAEPv/889y+fTu/+OILvvDCC5JAGRkZjIiI4OrVq/nll19aRYxdOSMjg+PHjycAJiUl8cMPP+TMmTO5Y8cOzpo1yxI7ePYV0v5rzpw51Gg0/Mtf/kKdTkeDwcCcnBx6enpaLebq6sr169dLJ7+t/a7Vann+/HmS5I0bN7hgwQK6urpy1apVLCwslLaASqViSkoKW1tbWVJSwuLiYt68eZNpaWlUKpUkyR9//JE///wzL126xOrqagYFBdncAhblZGdns66ujoWFhTx16hQ7Ozvp7+9PcxRpM5QOBlBu2cdOTk5877332NraSpPJxN27d9PLy8tqQQcHB27ZsoWiKLKlpYULFiyQrkhHR0e+++671Ov1tFB9fT1nz54tKSo6OpokOWbMGDY0NPC1116jWq2mvb09ly9fzsbGRoaHh5MkP/nkE9rb23PMmDESTnA7BVy4cIHBwcF0cXHhTz/9xH/+8580yxhsSwHx5nuT0dHRTEpKokql4sqVK9ne3k5RFLl//356eHhYLerm5sbPPvtMEvDFF1+kvb09165dy/b2dnan69ev86mnnqIgCJJwlnC366Hr7OxMk8nE2NhYkpRcYVvASW8KyM/Pl/qys7OZk5Nj8Q3ibQVFhZbBjz32GHU6HQFQqVQyOTmZOp2ORqOR6enpPZTg5OTErVu30mAwsKmpiSkpKezs7GRvdO7cOXp7e9PHx4cGg4GRkZHs7Ozkww8/LM05fvx4dnR0cNKkST0E7q8Cjh49aqUAC0gjCEJh90Aw1uw6SgqwABAWc37rrbfY0dFBURS5d+9eurm5WS3u7u7OlJQU9of0ej2fffZZOjk5UafT0dPTkydOnGBpaSmfeeYZzpkzh6dOnWJxcTG9vLwGTQGWtqOjo9EsM2Rm/lNXcKQ7kOHs7IwNGzZg48aNEEURTz31FD7++OOuwATa2tpw/vx5GAy3xyyVSiV8fX1hMplgMplw48YNvPTSS7h48SJ27NiB9PR0XL58GQsXLkRNTY3t+7oXsMXyeV9h9/z5861kdgdwvPt92tULtFxTarWaSUlJ1Ov11Ov13LVrF4cMGUKlUsl33nmHOp2uXxYgiiLnz59Pi5ttWUcmk1GhUFChUFjdKAqFwuqO79629blcLrfyVLu2zX+PW6LFUAD/7a+zoVKp+Mknn7C9vZ0Gg4Fbtmzh66+/zo6ODvaXzp07R19f3/sdI/zXLDumA2gdyJcdHByYnJxMURSp1+v7PPC6k8lk4ubNm38LIXMrgOlyM572zEAiJVEUUVBQAHd3d4wbNw4qlar/mRhBwKhRo3DlyhWcPXtWAkfvA9kBOCoHsBDAIwP9tsFgwNGjR6HRaBAZGWl14Nx2ZTs7PProo6irq0NxcfGAwdNBpEsAcOhOzSgoKIhqtZr/+c9/BnQGWKilpYVxcXE2cYO3336b77777q+9DQ4BQNmdfDk0NJS5ubm0s7Ojs7Mz169fT6PRaHPP90W//PIL4+Pje8QRmzZtYkpKyqAIGh0dzRMnTrC5uZm7du2in5+fpa8M5qzsgCaUyWRMT0+3+NbS7fDBBx+wvLyc9fX1rK2t5cmTJ/nSSy/xq6++oiiKvSqhqamJc+fO/VUQpMDAQNbW1jI9PZ0JCQksLy/nd999R3t7ewKow7fffmuqq6tjWlqapBlRFBkfH8+srCzJJe4e6lZUVEggaVlZGbVaLRUKBYOCgjhp0iRGRkbS29ubgiDQ3d2d27dv79MSampqOH/+fOke379/Pw8fPmxz24WHh9vkBx98sMf41atXs7KykgEBAQTAWbNmsba2lsHBwQRgwLx584wrVqxgdXU18/PzOXToUJJkVVUVk5KSGBcX12PShx9+WHJhAXDt2rVWgYeDgwMfe+wxenh48NVXX6W/vz/d3Ny4Z8+ePi2hsbGRs2fPplwuZ0JCAg8dOtRj7cOHD/f6/cLCwh7j09PTWVhYKDlBTk5OFEWR4eHhtKTa6yzwVFtbGydOnEiSfOutt3pFWCzRm+UunzBhAltaWjh69GgC4LJly5iXl0dBEOjm5sbg4GA6OTnxgQce4M6dO/u0hGvXrtGCSSQkJPRYWy6XS95id+6OUQJgZmam1Y8jCAJJWhRQpzArYOjJkyehVquhVt9CkH/++ederyeDwQCDwQCVSgVRFFFWVobS0lIkJCRg5cqVWLFiBdasWQMPDw9s2bIFTzzxBGpraxEXF4dly5ZBo9FgxowZkMt74hLe3t5ISUnB888/j23btvXof+ONNzB27Fibz1VWVob333/f6rOGhgZ4enrCzs4Oer0e7u7u0Ov1aGtrA4A6mUKh+K9SqcTTTz+NlpYWNDc3S0L2Ro2NjWhoaEBQUBAAQKfTYdeuXZgyZQoWL14MOzs7HDt2DFqtFjNnzsTcuXMRFhYGPz8/hIaGYvHixdi3b1+v87u5uWHz5s2IjY3t0Tds2DCEhoba5MDAwB7ji4qK4OPjg5CQEMhkMsyZMwcNDQ1obGwEgKtITEzcn5mZyaamJn7zzTfUaDQkyccff7zXk9XR0ZGFhYVctmyZ9NmQIUN45coV3rhxg/v27aNcLmd4eDibm5uZm5vLvXv3sqCggBcvXpSSKdnZ2TavTgvV1tbyySefvKski6urK8vLy1lSUsLdu3ezvr6eqampljk3YsGCBe/n5+fzzTfflCDu0tJSTpw4sc+Jly5dypycHKt9l5SUxNLSUgmalsvlXLp0KY8dO8ZPP/2U0dHRPHPmDAEwODiYPj4+zMjI6PNMuHTpUp8/Rn94+PDh/OKLL1hUVMQ1a9ZIcsrl8iV3FAxZMrm7d+9mWFjYXT2ct7e3lEfo64qMjY0d7HRbq6+v79MDDod7i7/vhv39/Zmdnd2nJVRVVXHy5MmDGg4LghBqExC5H+zv788jR4706Tpfu3aN0dHRg2UJEiAiM9fZ3PdE5vDhw5mbm9unJVRUVDAqKmow1kvpCgNagaL3WwnHjh3r0xKuXLnC3//+93ez/SRQ1AoWDwwM5PDhwwc84eTJkymKIgEwICCAw4YNs+rXaDSSlwiAo0ePpoODQ6/toKAgHjlypE9LKC8vt4LRB8g9YHFoNJqF5eXlpr/+9a8DntCS4rL46l1dz64wu+UX6w/MrdVqefz48T4tobq6mpGRkQO1BKvEiKwLrJ2/bt26ms8++wwAEBAQgGHDhsHNzQ0jRoyAp+etnKK7uztGjBgBb29vm16c0Wjs4UVaYC+S8Pf3BwD4+fkhJCQEo0aNAgD4+/sjJCQEDg4OCAwMRGVlJf785z/jxx9/7NVjDAgIQFpamuQay2Qy+Pj4YOTIkdBqtXB1dZVgOA8PD/j6+kIQhIsACmwmR11cXDZYNPX999+zsrJSMsWysjJOmTKFRUVF0rVkMeuuFvDdd99JlZ8WtmR3ANzWtKdNm0aSfPPNNxkUFMSQkBCeOHHitgfjhAkTqFarmZKSQpPJRKPRyIKCAoaEhDA0NJR5eXkcNWpUn8lRmFPHVZYS1oaGBs6ZM4djx45ldXU1q6urrdobN260qQCj0cj29naJLaixIAi0s7MjSc6cOVNKhHZty2QykuSZM2c4cuRIqlQqjho1isePH+8TaT59+jRHjx5NjUZDZ2dnhoSEMD8/n3v37uVXX33FxMREm+nx7oWSNwC8bb4i1GfPnkVGRgaMRiMqKythb29v1X7wwQdtavHChQv46KOPJKB05MiRWLVqFQBAr9dLf9vb/1fQ2b29bt06lJeXAwCuXr2KxYsXY9u2bXjooYdsIs1hYWHYu3cv5s2bh6qqKjz99NPo6OjA1KlTcfHiRSQkJLSbZbtxu1rhHACFDQ0N0Ov1MBqN0j7u2jYajbC3t7epgOvXr+Pzzz9HamoqUlNTceDAgQHDtR0dHdL/EyZMQGtrK5577jmcPHmy1+9otVps2bIFSqUSxcXF8PLywoULF/D++++jpaWl0CzbbYulGwCsysrKaureMVjwdUdHBwICAnptA8DLL78MX19feHt7IykpCYmJiaioqMCSJUtQWlraa84hIiICr732GnJychAbG4tFixbhm2++uQ5glVm2flWLn9y5c+erhw8f7uwtIdlbctJWX/f2gQMHsGHDBnR0dCA2NrZHGwBu3ryJkpISVFZWIiwsDPv378fIkSNRX1+P5557DiUlJb2uHx0dDZlMhk2bNuHSpUut5iLqkwMulHR0dPy3pWhixowZ/MMf/iCd7NOnT5fa3t7eXLx4cY/Pu0Z8ln4AfOCBBzhv3jwmJCTQz8/Pqu3r6yv5BY8//jgXLlzIRx55hDKZjF5eXlKhxMSJE3nu3Dmbh+LWrVspCAI1Gs1dFUpaSmX/fa/d5O4vTvTG48ePZ3FxsZXwlZWVluvOBODTuymVtZC7ufDYeC8VMG3atH6NHTFiBNPS0piXl8ddu3Z19QwPDGbFuLvZEsR7oQCNRmMT4e2NlUolhwwZYkl2mABsHUzhu26H9ebaW/5GWXe3FeL9qSD/k7n29p4K5+Pjc7uiimvmZ1PjHtAEc+1t271SwKFDh1hQUGAVLU6dOpX+/v7t5me5Jy9NSX4CgLkApgH4ALfe3vpVyWAwQBRFAMCUKVNgNBrxww8/VBsMhrfNHl7DHb0OdBfP1I5b7/juBCAPDg72GjdunLOXl5csMTERoaGhOH36NERRhEajQVxcHOLj4zF58mSIoojLly9Dq9UiOTkZJ06cwMqVK/Hoo4/i7NmzaGtrg4ODA1555RU8++yz0Ov1GDduHI4ePYqSkhJDfX191blz57aaTKYXzNheO+4zyZVK5YMajWahq6vrT4sWLTLl5eUxNTWVLi4uzMnJYW1tLauqqnj58mXW1dVx1qxZUqlsVlYWq6qq2Nrays2bN1OlUnH79u2sqqriwYMH+corr9DLy8uoUCiOC4Lw23p1tpdtFatSqVLHjx9fEhUV9curr77aYYHaVCoVk5KSWFRUxJiYGOr1es6bN49yuZxxcXFcv349PTw8+Mc//rHd09PzuvkXTjFjeIrBflihl/2mkMlkTgAUwi1HXiDZaPbpXVtbW39xcnKiOUByNcf6wi23X2gAgPj4eHl4ePiI4uJij5qaGv+Wlhbftra2EL1erzWZTN4k3a5everk4+ODixcvNpGs02g01wIDA6uvXr16IiIioik/P/9UZ2dnjZ+fX8OVK1dczOuZBEGQ63S6RkdHR5vRGUlPk8nUJJfLOy1tAI2CIOi7j/1/l0eTL0xHMHkAAAAASUVORK5CYII= // @downloadURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js // @updateURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js -// @author Magic // @license MIT // @compatible chrome // @compatible firefox @@ -40,6 +40,7 @@ // @connect greasyfork.org // @connect sleazyfork.org // @connect github.com +// @connect githubusercontent.com // @connect openuserjs.org // @grant GM_addElement // @grant GM_info diff --git a/dist/magic-userjs.user.js b/dist/magic-userjs.user.js index e46b3fc..ba6c1a6 100644 --- a/dist/magic-userjs.user.js +++ b/dist/magic-userjs.user.js @@ -1,5 +1,5 @@ // ==UserScript== -// @version 7.3.0 +// @version 7.6.8 // @name Magic Userscript+ : Show Site All UserJS // @name:ar Magic Userscript+: عرض جميع ملفات UserJS // @name:de Magic Userscript+ : Website anzeigen Alle UserJS @@ -24,13 +24,13 @@ // @description:zh 为当前网页查找可用的用户脚本。 // @description:zh-CN 为当前网页查找可用的用户脚本。 // @description:zh-TW 为当前网页查找可用的用户脚本。 +// @author Magic // @supportURL https://github.com/magicoflolis/Userscript-Plus/issues // @namespace https://github.com/magicoflolis/Userscript-Plus // @homepageURL https://github.com/magicoflolis/Userscript-Plus // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV8TRZFKBzuIOGSoTnZREcGlVLEIFkpboVUHk0u/oElDkuLiKLgWHPxYrDq4OOvq4CoIgh8gzg5Oii5S4v+SQosYD4778e7e4+4dIDSrTLN6YoCm22Y6EZdy+VWp7xUigghhDqLMLCOZWczCd3zdI8DXuyjP8j/35xhUCxYDAhJxjBmmTbxBPLNpG5z3icOsLKvE58QTJl2Q+JHrisdvnEsuCzwzbGbT88RhYqnUxUoXs7KpEU8TR1RNp3wh57HKeYuzVq2z9j35C4MFfSXDdZqjSGAJSaQgQUEdFVRhI0qrToqFNO3Hffwjrj9FLoVcFTByLKAGDbLrB/+D391axalJLykYB3pfHOdjDOjbBVoNx/k+dpzWCSA+A1d6x19rArOfpDc6WuQICG0DF9cdTdkDLneA4SdDNmVXEmkKxSLwfkbflAeGboGBNa+39j5OH4AsdbV8AxwcAuMlyl73eXd/d2//nmn39wOjunK6jS33SAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+gDDBAAJWyXgRAAABRPSURBVHjazVt9VNRl9v9854VhhjdBiHcUaFQEDTVCdk1IstTV0jb1LLZkJZrl4knLTnZaKjdqdXdLXcvfyoHUErPUBBEMLREJ6egJFBUUBF9WMd6R4WW+M/P5/eHMdxkYEJS0e8498MzzzPN87537PM+9n3u/An49kgFwA+AJIACAP4AxAEIA+AFwBzDEPLYJQB2AqwDOATgN4AqAywBubNu2rTk+Pl4EAJIyABAEwTQYDyn8CoIrAEQD+BOAMLPgLgAcAGDs2LGQyWSora1FYGAg6urqUFZWBgCIiYnBqVOn0NDQAAA6T0/Pm/X19ZflcvkZo9G4w2AwHAFgwG+Q5ACCg4KClixZsuT82rVrTTt27CCAHpybm8vq6moWFRXRZDKxpqaGkyZN4q0fl3zyySelsaIoMiYmhg899BD/9re/mQAUAogHEGxe8/5TaGiob0RERMrSpUuvZ2Zmmo4cOcIjR44wLy/PpgIOHjzI2tpaTp06lVqtlhUVFUxLS5MU8MQTT0hjLQqZPHkySRIAFy1aZAwNDa0C8C/z9rpvNBbAPwBctyVob3zw4EEWFBRQJpNJFvH999/3WwE5OTncs2ePZUwVgDjzWXPHB9Wd0GsACuRy+UoXFxev5cuXo7OzEyStWBBsHzGiKMJkunWGmUwmyGQy6X+lUgkAcHBw6HXxoUOHWv4dDiAFwNcAJtzpgTUQUgNYIgjCP2bNmiX7+9//jo0bN2Lz5s3IyspCUFCQJIz5xB7Q5FVVVZg9ezby8vKwYsWKgTzTFACZAFYC+BZA+6ArQKlUOouiuAZAgiAIMq1WixEjRuCjjz6Cq6srPvzwQ1RUVNze5GQyKyV1bW/atAnJycmIj49Henr6/66qLpbUdbzRaMS0adOQm5sLAN4A/g/ARADvAGgZvCNeLvdUq9VbzVcQAXDo0KHcs2cPDQYD29ramJiYSKVSedszICIigpGRkTbb9vb2nDRpEmNiYujk5MQZM2bQ3d2dbm5unDFjhtX4qKgonj59mt7e3t3XEAH82+xnDAp5AThjSxgPDw9++eWXNJlMbGpq4htvvCEdbr82r1ixgr/73e966zeaz4W7VoIzgAyZTMaoqChOnTqVarW6hxIyMzNpMBio0+m4ZMmSflnCPWCj2RKc71R4NYD1AIwODg4sKipiU1MTV69e3UNAT09Pfv311yTJxsZGLl++/J5Zwm1YNMugvhMFxAFoA0CFQsH33nuPHR0dbGtr49KlS6lSqawW8/LyYnZ2Ng0GA2/evMkXXniBCoXit6CEFrNbPiB6SalUtncVwNnZmcnJySTJlpYWvv7665TL5VaL+fj4cN++fSTJ+vp6vvzyy31agrOzc1/7eMAcFRXFIUOG2Oq7NhA/YaRcLr85bNgwuri4WE2kUqn48ccfs7OzkzqdjgkJCT0swdfXl7m5uTQajWxubuaCBQskRYWGhjIrK4uCIBAAY2JiSFJq3y2T5PTp03vrP9wfj1EA8PkjjzzC8+fPMysriz4+PlYTubi4cN26dSTJ5uZmm/vdz8+PBw4cIEnW1tbyxRdfpCAIDA4OpsFgsBo7WML3Y64287buk7x9fX07oqOj2dLSQpPJxMzMzB5KUKvV3LRpE/V6PW/evMmFCxfSzs7Oaoy/vz9/+OEHGo1GNjY2cu7cudRoNExNTeWePXuYkZHBsLAw7t+/n4IgUBAEPv/889y+fTu/+OILvvDCC5JAGRkZjIiI4OrVq/nll19aRYxdOSMjg+PHjycAJiUl8cMPP+TMmTO5Y8cOzpo1yxI7ePYV0v5rzpw51Gg0/Mtf/kKdTkeDwcCcnBx6enpaLebq6sr169dLJ7+t/a7Vann+/HmS5I0bN7hgwQK6urpy1apVLCwslLaASqViSkoKW1tbWVJSwuLiYt68eZNpaWlUKpUkyR9//JE///wzL126xOrqagYFBdncAhblZGdns66ujoWFhTx16hQ7Ozvp7+9PcxRpM5QOBlBu2cdOTk5877332NraSpPJxN27d9PLy8tqQQcHB27ZsoWiKLKlpYULFiyQrkhHR0e+++671Ov1tFB9fT1nz54tKSo6OpokOWbMGDY0NPC1116jWq2mvb09ly9fzsbGRoaHh5MkP/nkE9rb23PMmDESTnA7BVy4cIHBwcF0cXHhTz/9xH/+8580yxhsSwHx5nuT0dHRTEpKokql4sqVK9ne3k5RFLl//356eHhYLerm5sbPPvtMEvDFF1+kvb09165dy/b2dnan69ev86mnnqIgCJJwlnC366Hr7OxMk8nE2NhYkpRcYVvASW8KyM/Pl/qys7OZk5Nj8Q3ibQVFhZbBjz32GHU6HQFQqVQyOTmZOp2ORqOR6enpPZTg5OTErVu30mAwsKmpiSkpKezs7GRvdO7cOXp7e9PHx4cGg4GRkZHs7Ozkww8/LM05fvx4dnR0cNKkST0E7q8Cjh49aqUAC0gjCEJh90Aw1uw6SgqwABAWc37rrbfY0dFBURS5d+9eurm5WS3u7u7OlJQU9of0ej2fffZZOjk5UafT0dPTkydOnGBpaSmfeeYZzpkzh6dOnWJxcTG9vLwGTQGWtqOjo9EsM2Rm/lNXcKQ7kOHs7IwNGzZg48aNEEURTz31FD7++OOuwATa2tpw/vx5GAy3xyyVSiV8fX1hMplgMplw48YNvPTSS7h48SJ27NiB9PR0XL58GQsXLkRNTY3t+7oXsMXyeV9h9/z5861kdgdwvPt92tULtFxTarWaSUlJ1Ov11Ov13LVrF4cMGUKlUsl33nmHOp2uXxYgiiLnz59Pi5ttWUcmk1GhUFChUFjdKAqFwuqO79629blcLrfyVLu2zX+PW6LFUAD/7a+zoVKp+Mknn7C9vZ0Gg4Fbtmzh66+/zo6ODvaXzp07R19f3/sdI/zXLDumA2gdyJcdHByYnJxMURSp1+v7PPC6k8lk4ubNm38LIXMrgOlyM572zEAiJVEUUVBQAHd3d4wbNw4qlar/mRhBwKhRo3DlyhWcPXtWAkfvA9kBOCoHsBDAIwP9tsFgwNGjR6HRaBAZGWl14Nx2ZTs7PProo6irq0NxcfGAwdNBpEsAcOhOzSgoKIhqtZr/+c9/BnQGWKilpYVxcXE2cYO3336b77777q+9DQ4BQNmdfDk0NJS5ubm0s7Ojs7Mz169fT6PRaHPP90W//PIL4+Pje8QRmzZtYkpKyqAIGh0dzRMnTrC5uZm7du2in5+fpa8M5qzsgCaUyWRMT0+3+NbS7fDBBx+wvLyc9fX1rK2t5cmTJ/nSSy/xq6++oiiKvSqhqamJc+fO/VUQpMDAQNbW1jI9PZ0JCQksLy/nd999R3t7ewKow7fffmuqq6tjWlqapBlRFBkfH8+srCzJJe4e6lZUVEggaVlZGbVaLRUKBYOCgjhp0iRGRkbS29ubgiDQ3d2d27dv79MSampqOH/+fOke379/Pw8fPmxz24WHh9vkBx98sMf41atXs7KykgEBAQTAWbNmsba2lsHBwQRgwLx584wrVqxgdXU18/PzOXToUJJkVVUVk5KSGBcX12PShx9+WHJhAXDt2rVWgYeDgwMfe+wxenh48NVXX6W/vz/d3Ny4Z8+ePi2hsbGRs2fPplwuZ0JCAg8dOtRj7cOHD/f6/cLCwh7j09PTWVhYKDlBTk5OFEWR4eHhtKTa6yzwVFtbGydOnEiSfOutt3pFWCzRm+UunzBhAltaWjh69GgC4LJly5iXl0dBEOjm5sbg4GA6OTnxgQce4M6dO/u0hGvXrtGCSSQkJPRYWy6XS95id+6OUQJgZmam1Y8jCAJJWhRQpzArYOjJkyehVquhVt9CkH/++ederyeDwQCDwQCVSgVRFFFWVobS0lIkJCRg5cqVWLFiBdasWQMPDw9s2bIFTzzxBGpraxEXF4dly5ZBo9FgxowZkMt74hLe3t5ISUnB888/j23btvXof+ONNzB27Fibz1VWVob333/f6rOGhgZ4enrCzs4Oer0e7u7u0Ov1aGtrA4A6mUKh+K9SqcTTTz+NlpYWNDc3S0L2Ro2NjWhoaEBQUBAAQKfTYdeuXZgyZQoWL14MOzs7HDt2DFqtFjNnzsTcuXMRFhYGPz8/hIaGYvHixdi3b1+v87u5uWHz5s2IjY3t0Tds2DCEhoba5MDAwB7ji4qK4OPjg5CQEMhkMsyZMwcNDQ1obGwEgKtITEzcn5mZyaamJn7zzTfUaDQkyccff7zXk9XR0ZGFhYVctmyZ9NmQIUN45coV3rhxg/v27aNcLmd4eDibm5uZm5vLvXv3sqCggBcvXpSSKdnZ2TavTgvV1tbyySefvKski6urK8vLy1lSUsLdu3ezvr6eqampljk3YsGCBe/n5+fzzTfflCDu0tJSTpw4sc+Jly5dypycHKt9l5SUxNLSUgmalsvlXLp0KY8dO8ZPP/2U0dHRPHPmDAEwODiYPj4+zMjI6PNMuHTpUp8/Rn94+PDh/OKLL1hUVMQ1a9ZIcsrl8iV3FAxZMrm7d+9mWFjYXT2ct7e3lEfo64qMjY0d7HRbq6+v79MDDod7i7/vhv39/Zmdnd2nJVRVVXHy5MmDGg4LghBqExC5H+zv788jR4706Tpfu3aN0dHRg2UJEiAiM9fZ3PdE5vDhw5mbm9unJVRUVDAqKmow1kvpCgNagaL3WwnHjh3r0xKuXLnC3//+93ez/SRQ1AoWDwwM5PDhwwc84eTJkymKIgEwICCAw4YNs+rXaDSSlwiAo0ePpoODQ6/toKAgHjlypE9LKC8vt4LRB8g9YHFoNJqF5eXlpr/+9a8DntCS4rL46l1dz64wu+UX6w/MrdVqefz48T4tobq6mpGRkQO1BKvEiKwLrJ2/bt26ms8++wwAEBAQgGHDhsHNzQ0jRoyAp+etnKK7uztGjBgBb29vm16c0Wjs4UVaYC+S8Pf3BwD4+fkhJCQEo0aNAgD4+/sjJCQEDg4OCAwMRGVlJf785z/jxx9/7NVjDAgIQFpamuQay2Qy+Pj4YOTIkdBqtXB1dZVgOA8PD/j6+kIQhIsACmwmR11cXDZYNPX999+zsrJSMsWysjJOmTKFRUVF0rVkMeuuFvDdd99JlZ8WtmR3ANzWtKdNm0aSfPPNNxkUFMSQkBCeOHHitgfjhAkTqFarmZKSQpPJRKPRyIKCAoaEhDA0NJR5eXkcNWpUn8lRmFPHVZYS1oaGBs6ZM4djx45ldXU1q6urrdobN260qQCj0cj29naJLaixIAi0s7MjSc6cOVNKhHZty2QykuSZM2c4cuRIqlQqjho1isePH+8TaT59+jRHjx5NjUZDZ2dnhoSEMD8/n3v37uVXX33FxMREm+nx7oWSNwC8bb4i1GfPnkVGRgaMRiMqKythb29v1X7wwQdtavHChQv46KOPJKB05MiRWLVqFQBAr9dLf9vb/1fQ2b29bt06lJeXAwCuXr2KxYsXY9u2bXjooYdsIs1hYWHYu3cv5s2bh6qqKjz99NPo6OjA1KlTcfHiRSQkJLSbZbtxu1rhHACFDQ0N0Ov1MBqN0j7u2jYajbC3t7epgOvXr+Pzzz9HamoqUlNTceDAgQHDtR0dHdL/EyZMQGtrK5577jmcPHmy1+9otVps2bIFSqUSxcXF8PLywoULF/D++++jpaWl0CzbbYulGwCsysrKaureMVjwdUdHBwICAnptA8DLL78MX19feHt7IykpCYmJiaioqMCSJUtQWlraa84hIiICr732GnJychAbG4tFixbhm2++uQ5glVm2flWLn9y5c+erhw8f7uwtIdlbctJWX/f2gQMHsGHDBnR0dCA2NrZHGwBu3ryJkpISVFZWIiwsDPv378fIkSNRX1+P5557DiUlJb2uHx0dDZlMhk2bNuHSpUut5iLqkwMulHR0dPy3pWhixowZ/MMf/iCd7NOnT5fa3t7eXLx4cY/Pu0Z8ln4AfOCBBzhv3jwmJCTQz8/Pqu3r6yv5BY8//jgXLlzIRx55hDKZjF5eXlKhxMSJE3nu3Dmbh+LWrVspCAI1Gs1dFUpaSmX/fa/d5O4vTvTG48ePZ3FxsZXwlZWVluvOBODTuymVtZC7ufDYeC8VMG3atH6NHTFiBNPS0piXl8ddu3Z19QwPDGbFuLvZEsR7oQCNRmMT4e2NlUolhwwZYkl2mABsHUzhu26H9ebaW/5GWXe3FeL9qSD/k7n29p4K5+Pjc7uiimvmZ1PjHtAEc+1t271SwKFDh1hQUGAVLU6dOpX+/v7t5me5Jy9NSX4CgLkApgH4ALfe3vpVyWAwQBRFAMCUKVNgNBrxww8/VBsMhrfNHl7DHb0OdBfP1I5b7/juBCAPDg72GjdunLOXl5csMTERoaGhOH36NERRhEajQVxcHOLj4zF58mSIoojLly9Dq9UiOTkZJ06cwMqVK/Hoo4/i7NmzaGtrg4ODA1555RU8++yz0Ov1GDduHI4ePYqSkhJDfX191blz57aaTKYXzNheO+4zyZVK5YMajWahq6vrT4sWLTLl5eUxNTWVLi4uzMnJYW1tLauqqnj58mXW1dVx1qxZUqlsVlYWq6qq2Nrays2bN1OlUnH79u2sqqriwYMH+corr9DLy8uoUCiOC4Lw23p1tpdtFatSqVLHjx9fEhUV9curr77aYYHaVCoVk5KSWFRUxJiYGOr1es6bN49yuZxxcXFcv349PTw8+Mc//rHd09PzuvkXTjFjeIrBflihl/2mkMlkTgAUwi1HXiDZaPbpXVtbW39xcnKiOUByNcf6wi23X2gAgPj4eHl4ePiI4uJij5qaGv+Wlhbftra2EL1erzWZTN4k3a5everk4+ODixcvNpGs02g01wIDA6uvXr16IiIioik/P/9UZ2dnjZ+fX8OVK1dczOuZBEGQ63S6RkdHR5vRGUlPk8nUJJfLOy1tAI2CIOi7j/1/l0eTL0xHMHkAAAAASUVORK5CYII= // @downloadURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js // @updateURL https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js -// @author Magic // @license MIT // @compatible chrome // @compatible firefox @@ -40,6 +40,7 @@ // @connect greasyfork.org // @connect sleazyfork.org // @connect github.com +// @connect githubusercontent.com // @connect openuserjs.org // @grant GM_addElement // @grant GM_info @@ -62,645 +63,719 @@ (() => { 'use strict'; /******************************************************************************/ -const inIframe = () => { +const inIframe = (() => { try { return window.self !== window.top; - } catch (e) { + } catch { return true; } -} -if (inIframe()) { - return; -} +})(); +if (inIframe) return; let userjs = self.userjs; /** * Skip text/plain documents, based on uBlock Origin `vapi.js` file * - * [Source Code](https://github.com/gorhill/uBlock/blob/master/platform/common/vapi.js) + * [source code](https://github.com/gorhill/uBlock/blob/68962453ff6eec7ff109615a738beb8699b9844a/platform/common/vapi.js#L35) */ if ( (document instanceof Document || (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && - /^image\/|^text\/plain/.test(document.contentType || '') === false && + /^text\/html|^application\/(xhtml|xml)/.test(document.contentType || '') === true && (self.userjs instanceof Object === false || userjs.UserJS !== true) ) { userjs = self.userjs = { UserJS: true }; -} -if (!(typeof userjs === 'object' && userjs.UserJS)) { - return; -} -/** - * Uncompressed locales + compiler - * - * [_locales](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) - * - * [languageLoader.js](https://github.com/magicoflolis/Userscript-Plus/blob/master/tools/languageLoader.js) - */ +} else { + console.error('[%cMagic Userscript+%c] %cERROR','color: rgb(29, 155, 240);','','color: rgb(249, 24, 128);', `MIME type is not a document, got "${document.contentType || ''}"`); +} +if (!(typeof userjs === 'object' && userjs.UserJS)) return; +{ + /** Native implementation exists */ + const excludePolicy = [ + 'outlook.office.com' + ]; + const hostname = location?.hostname || ''; + if (window.trustedTypes && window.trustedTypes.createPolicy && !hostname.includes(excludePolicy)) window.trustedTypes.createPolicy('default', { createHTML: (string) => string, createScript: (string) => string, createScriptURL: (string) => string }); +} +/** [i18n directory](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) */ const translations = { - "ar": { - "createdby": "انشأ من قبل", - "name": "اسم", - "daily_installs": "التثبيت اليومي", - "close": "يغلق", - "filterA": "منقي", - "max": "تحقيق أقصى قدر", - "min": "تصغير", - "search": "يبحث", - "search_placeholder": "بحث في البرامج النصية", - "install": "تثبيت", - "issue": "إصدار جديد", - "version_number": "الإصدار", - "updated": "آخر تحديث", - "total_installs": "إجمالي التثبيت", - "ratings": "التقييمات", - "good": "جيد", - "ok": "جيد", - "bad": "سيء", - "created_date": "تم إنشاؤه", - "redirect": "شوكة دهنية للكبار", - "filter": "تصفية اللغات الأخرى", - "dtime": "عرض المهلة", - "save": "حفظ", - "reset": "إعادة تعيين", - "preview_code": "كود المعاينة", - "saveFile": "احفظ الملف", - "newTab": "علامة تبويب جديدة", - "applies_to": "ينطبق على", - "license": "الترخيص", - "no_license": "لا يوجد", - "antifeatures": "إعلانات", - "userjs_fullscreen": "ملء الشاشة الكاملة التلقائي", - "listing_none": "(لا يوجد)", - "export_config": "تهيئة التصدير", - "export_theme": "تصدير السمة", - "import_config": "استيراد تهيئة الاستيراد", - "import_theme": "استيراد النسق", - "code_size": "حجم الرمز", - "prmpt_css": "التثبيت كأسلوب المستخدم؟", - "userjs_inject": "حقن Userscript+", - "userjs_close": "إغلاق Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'ar': { + 'createdby': 'انشأ من قبل', + 'name': 'اسم', + 'daily_installs': 'التثبيت اليومي', + 'close': 'يغلق', + 'filterA': 'منقي', + 'max': 'تحقيق أقصى قدر', + 'min': 'تصغير', + 'search': 'يبحث', + 'search_placeholder': 'بحث في البرامج النصية', + 'install': 'تثبيت', + 'issue': 'إصدار جديد', + 'version_number': 'الإصدار', + 'updated': 'آخر تحديث', + 'total_installs': 'إجمالي التثبيت', + 'ratings': 'التقييمات', + 'good': 'جيد', + 'ok': 'جيد', + 'bad': 'سيء', + 'created_date': 'تم إنشاؤه', + 'redirect': 'شوكة دهنية للكبار', + 'filter': 'تصفية اللغات الأخرى', + 'dtime': 'عرض المهلة', + 'save': 'حفظ', + 'reset': 'إعادة تعيين', + 'preview_code': 'كود المعاينة', + 'saveFile': 'احفظ الملف', + 'newTab': 'علامة تبويب جديدة', + 'applies_to': 'ينطبق على', + 'license': 'الترخيص', + 'no_license': 'لا يوجد', + 'antifeatures': 'إعلانات', + 'userjs_fullscreen': 'ملء الشاشة الكاملة التلقائي', + 'listing_none': '(لا يوجد)', + 'export_config': 'تهيئة التصدير', + 'export_theme': 'تصدير السمة', + 'import_config': 'استيراد تهيئة الاستيراد', + 'import_theme': 'استيراد النسق', + 'code_size': 'حجم الرمز', + 'prmpt_css': 'التثبيت كأسلوب المستخدم؟', + 'userjs_inject': 'حقن Userscript+', + 'userjs_close': 'إغلاق Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Code', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "de": { - "createdby": "Erstellt von", - "name": "Name", - "daily_installs": "Tägliche Installationen", - "close": "Schließen Sie", - "filterA": "Filter", - "max": "Maximieren Sie", - "min": "minimieren", - "search": "Suche", - "search_placeholder": "Suche nach Userscripts", - "install": "Installieren Sie", - "issue": "Neue Ausgabe", - "version_number": "Version", - "updated": "Zuletzt aktualisiert", - "total_installs": "Installationen insgesamt", - "ratings": "Bewertungen", - "good": "Gut", - "ok": "Okay", - "bad": "Schlecht", - "created_date": "Erstellt", - "redirect": "Greasy Fork für Erwachsene", - "filter": "Andere Sprachen herausfiltern", - "dtime": "Zeitüberschreitung anzeigen", - "save": "Speichern Sie", - "reset": "Zurücksetzen", - "preview_code": "Vorschau Code", - "saveFile": "Datei speichern", - "newTab": "Neue Registerkarte", - "applies_to": "Gilt für", - "license": "Lizenz", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatischer Vollbildmodus", - "listing_none": "(Keine)", - "export_config": "Konfig exportieren", - "export_theme": "Thema exportieren", - "import_config": "Konfig importieren", - "import_theme": "Thema importieren", - "code_size": "Code Größe", - "prmpt_css": "Als UserStyle installieren?", - "userjs_inject": "Userscript+ einfügen", - "userjs_close": "Userscript+ schließen", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'de': { + 'createdby': 'Erstellt von', + 'name': 'Name', + 'daily_installs': 'Tägliche Installationen', + 'close': 'Schließen Sie', + 'filterA': 'Filter', + 'max': 'Maximieren Sie', + 'min': 'minimieren', + 'search': 'Suche', + 'search_placeholder': 'Suche nach Userscripts', + 'install': 'Installieren Sie', + 'issue': 'Neue Ausgabe', + 'version_number': 'Version', + 'updated': 'Zuletzt aktualisiert', + 'total_installs': 'Installationen insgesamt', + 'ratings': 'Bewertungen', + 'good': 'Gut', + 'ok': 'Okay', + 'bad': 'Schlecht', + 'created_date': 'Erstellt', + 'redirect': 'Greasy Fork für Erwachsene', + 'filter': 'Andere Sprachen herausfiltern', + 'dtime': 'Zeitüberschreitung anzeigen', + 'save': 'Speichern Sie', + 'reset': 'Zurücksetzen', + 'preview_code': 'Vorschau Code', + 'saveFile': 'Datei speichern', + 'newTab': 'Neue Registerkarte', + 'applies_to': 'Gilt für', + 'license': 'Lizenz', + 'no_license': 'N/A', + 'antifeatures': 'Antifeatures', + 'userjs_fullscreen': 'Automatischer Vollbildmodus', + 'listing_none': '(Keine)', + 'export_config': 'Konfig exportieren', + 'export_theme': 'Thema exportieren', + 'import_config': 'Konfig importieren', + 'import_theme': 'Thema importieren', + 'code_size': 'Code Größe', + 'prmpt_css': 'Als UserStyle installieren?', + 'userjs_inject': 'Userscript+ einfügen', + 'userjs_close': 'Userscript+ schließen', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Quelltext', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "en": { - "createdby": "Created by", - "name": "Name", - "daily_installs": "Daily Installs", - "close": "Close", - "filterA": "Filter", - "max": "Maximize", - "min": "Minimize", - "search": "Search", - "search_placeholder": "Search for userscripts", - "install": "Install", - "issue": "New Issue", - "version_number": "Version", - "updated": "Last Updated", - "total_installs": "Total Installs", - "ratings": "Ratings", - "good": "Good", - "ok": "Okay", - "bad": "Bad", - "created_date": "Created", - "redirect": "Greasy Fork for adults", - "filter": "Filter out other languages", - "dtime": "Display Timeout", - "save": "Save", - "reset": "Reset", - "preview_code": "Preview Code", - "saveFile": "Save File", - "newTab": "New Tab", - "applies_to": "Applies to", - "license": "License", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatic Fullscreen", - "listing_none": "(None)", - "export_config": "Export Config", - "export_theme": "Export Theme", - "import_config": "Import Config", - "import_theme": "Import Theme", - "code_size": "Code Size", - "prmpt_css": "Install as UserStyle?", - "userjs_inject": "Inject Userscript+", - "userjs_close": "Close Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'en': { + 'createdby': 'Created by', + 'name': 'Name', + 'daily_installs': 'Daily Installs', + 'close': 'Close', + 'filterA': 'Filter', + 'max': 'Maximize', + 'min': 'Minimize', + 'search': 'Search', + 'search_placeholder': 'Search for userscripts', + 'install': 'Install', + 'issue': 'New Issue', + 'version_number': 'Version', + 'updated': 'Last Updated', + 'total_installs': 'Total Installs', + 'ratings': 'Ratings', + 'good': 'Good', + 'ok': 'Okay', + 'bad': 'Bad', + 'created_date': 'Created', + 'redirect': 'Greasy Fork for adults', + 'filter': 'Filter out other languages', + 'dtime': 'Display Timeout', + 'save': 'Save', + 'reset': 'Reset', + 'preview_code': 'Preview Code', + 'saveFile': 'Download', + 'newTab': 'New Tab', + 'applies_to': 'Applies to', + 'license': 'License', + 'no_license': 'N/A', + 'antifeatures': 'Antifeatures', + 'userjs_fullscreen': 'Automatic Fullscreen', + 'listing_none': '(None)', + 'export_config': 'Export Config', + 'export_theme': 'Export Theme', + 'import_config': 'Import Config', + 'import_theme': 'Import Theme', + 'code_size': 'Code Size', + 'prmpt_css': 'Install as UserStyle?', + 'userjs_inject': 'Inject Userscript+', + 'userjs_close': 'Close Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Code', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "en_GB": { - "createdby": "Created by", - "name": "Name", - "daily_installs": "Daily Installs", - "close": "Close", - "filterA": "Filter", - "max": "Maximize", - "min": "Minimize", - "search": "Search", - "search_placeholder": "Search scripts", - "install": "Install", - "issue": "New Issue", - "version_number": "Version", - "updated": "Last Updated", - "total_installs": "Total Installs", - "ratings": "Ratings", - "good": "Good", - "ok": "Ok", - "bad": "Bad", - "created_date": "Created", - "redirect": "Greasy Fork for adults", - "filter": "Filter out other languages", - "dtime": "Display Timeout", - "save": "Save", - "reset": "Reset", - "preview_code": "Preview Code", - "saveFile": "Save File", - "newTab": "New Tab", - "applies_to": "Applies to", - "license": "License", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Automatic Fullscreen", - "listing_none": "(None)", - "export_config": "Export Config", - "export_theme": "Export Theme", - "import_config": "Import Config", - "import_theme": "Import Theme", - "code_size": "Code Size", - "prmpt_css": "Install as UserStyle?", - "userjs_inject": "Inject Userscript+", - "userjs_close": "Close Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'en_GB': { + 'createdby': 'Created by', + 'name': 'Name', + 'daily_installs': 'Daily Installs', + 'close': 'Close', + 'filterA': 'Filter', + 'max': 'Maximize', + 'min': 'Minimize', + 'search': 'Search', + 'search_placeholder': 'Search for userscripts', + 'install': 'Install', + 'issue': 'New Issue', + 'version_number': 'Version', + 'updated': 'Last Updated', + 'total_installs': 'Total Installs', + 'ratings': 'Ratings', + 'good': 'Good', + 'ok': 'Okay', + 'bad': 'Bad', + 'created_date': 'Created', + 'redirect': 'Greasy Fork for adults', + 'filter': 'Filter out other languages', + 'dtime': 'Display Timeout', + 'save': 'Save', + 'reset': 'Reset', + 'preview_code': 'Preview Code', + 'saveFile': 'Download', + 'newTab': 'New Tab', + 'applies_to': 'Applies to', + 'license': 'License', + 'no_license': 'N/A', + 'antifeatures': 'Antifeatures', + 'userjs_fullscreen': 'Automatic Fullscreen', + 'listing_none': '(None)', + 'export_config': 'Export Config', + 'export_theme': 'Export Theme', + 'import_config': 'Import Config', + 'import_theme': 'Import Theme', + 'code_size': 'Code Size', + 'prmpt_css': 'Install as UserStyle?', + 'userjs_inject': 'Inject Userscript+', + 'userjs_close': 'Close Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Code', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "es": { - "createdby": "Creado por", - "name": "Nombre", - "daily_installs": "Instalaciones diarias", - "close": "Ya no se muestra", - "filterA": "Filtro", - "max": "Maximizar", - "min": "Minimizar", - "search": "Busque en", - "search_placeholder": "Buscar userscripts", - "install": "Instalar", - "issue": "Nueva edición", - "version_number": "Versión", - "updated": "Última actualización", - "total_installs": "Total de instalaciones", - "ratings": "Clasificaciones", - "good": "Bueno", - "ok": "Ok", - "bad": "Malo", - "created_date": "Creado", - "redirect": "Greasy Fork para adultos", - "filter": "Filtrar otros idiomas", - "dtime": "Mostrar el tiempo de espera", - "save": "Guardar", - "reset": "Reiniciar", - "preview_code": "Vista previa del código", - "saveFile": "Guardar archivo", - "newTab": "Guardar archivo", - "applies_to": "Se aplica a", - "license": "Licencia", - "no_license": "Desconocida", - "antifeatures": "Características indeseables", - "userjs_fullscreen": "Pantalla completa automática", - "listing_none": "(Ninguno)", - "export_config": "Exportar configuración", - "export_theme": "Exportar tema", - "import_config": "Importar configuración", - "import_theme": "Importar tema", - "code_size": "Código Tamaño", - "prmpt_css": "¿Instalar como UserStyle?", - "userjs_inject": "Inyectar Userscript+", - "userjs_close": "Cerrar Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'es': { + 'createdby': 'Creado por', + 'name': 'Nombre', + 'daily_installs': 'Instalaciones diarias', + 'close': 'Ya no se muestra', + 'filterA': 'Filtro', + 'max': 'Maximizar', + 'min': 'Minimizar', + 'search': 'Busque en', + 'search_placeholder': 'Buscar userscripts', + 'install': 'Instalar', + 'issue': 'Nueva edición', + 'version_number': 'Versión', + 'updated': 'Última actualización', + 'total_installs': 'Total de instalaciones', + 'ratings': 'Clasificaciones', + 'good': 'Bueno', + 'ok': 'Ok', + 'bad': 'Malo', + 'created_date': 'Creado', + 'redirect': 'Greasy Fork para adultos', + 'filter': 'Filtrar otros idiomas', + 'dtime': 'Mostrar el tiempo de espera', + 'save': 'Guardar', + 'reset': 'Reiniciar', + 'preview_code': 'Vista previa del código', + 'saveFile': 'Guardar archivo', + 'newTab': 'Guardar archivo', + 'applies_to': 'Se aplica a', + 'license': 'Licencia', + 'no_license': 'Desconocida', + 'antifeatures': 'Características indeseables', + 'userjs_fullscreen': 'Pantalla completa automática', + 'listing_none': '(Ninguno)', + 'export_config': 'Exportar configuración', + 'export_theme': 'Exportar tema', + 'import_config': 'Importar configuración', + 'import_theme': 'Importar tema', + 'code_size': 'Código Tamaño', + 'prmpt_css': '¿Instalar como UserStyle?', + 'userjs_inject': 'Inyectar Userscript+', + 'userjs_close': 'Cerrar Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Código', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "fr": { - "createdby": "Créé par", - "name": "Nom", - "daily_installs": "Installations quotidiennes", - "close": "Ne plus montrer", - "filterA": "Filtre", - "max": "Maximiser", - "min": "Minimiser", - "search": "Recherche", - "search_placeholder": "Rechercher des userscripts", - "install": "Installer", - "issue": "Nouveau numéro", - "version_number": "Version", - "updated": "Dernière mise à jour", - "total_installs": "Total des installations", - "ratings": "Notations", - "good": "Bon", - "ok": "Ok", - "bad": "Mauvais", - "created_date": "Créé", - "redirect": "Greasy Fork pour les adultes", - "filter": "Filtrer les autres langues", - "dtime": "Délai d'affichage", - "save": "Sauvez", - "reset": "Réinitialiser", - "preview_code": "Prévisualiser le code", - "saveFile": "Enregistrer le fichier", - "newTab": "Nouvel onglet", - "applies_to": "S'applique à", - "license": "Licence", - "no_license": "N/A", - "antifeatures": "Antifeatures", - "userjs_fullscreen": "Plein écran automatique", - "listing_none": "(Aucun)", - "export_config": "Export Config", - "export_theme": "Exporter le thème", - "import_config": "Importer la configuration", - "import_theme": "Importer le thème", - "code_size": "Code Taille", - "prmpt_css": "Installer comme UserStyle ?", - "userjs_inject": "Injecter Userscript+", - "userjs_close": "Fermer Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'fr': { + 'createdby': 'Créé par', + 'name': 'Nom', + 'daily_installs': 'Installations quotidiennes', + 'close': 'Ne plus montrer', + 'filterA': 'Filtre', + 'max': 'Maximiser', + 'min': 'Minimiser', + 'search': 'Recherche', + 'search_placeholder': 'Rechercher des userscripts', + 'install': 'Installer', + 'issue': 'Nouveau numéro', + 'version_number': 'Version', + 'updated': 'Dernière mise à jour', + 'total_installs': 'Total des installations', + 'ratings': 'Notations', + 'good': 'Bon', + 'ok': 'Ok', + 'bad': 'Mauvais', + 'created_date': 'Créé', + 'redirect': 'Greasy Fork pour les adultes', + 'filter': 'Filtrer les autres langues', + 'dtime': "Délai d'affichage", + 'save': 'Sauvez', + 'reset': 'Réinitialiser', + 'preview_code': 'Prévisualiser le code', + 'saveFile': 'Enregistrer le fichier', + 'newTab': 'Nouvel onglet', + 'applies_to': "S'applique à", + 'license': 'Licence', + 'no_license': 'N/A', + 'antifeatures': 'Antifeatures', + 'userjs_fullscreen': 'Plein écran automatique', + 'listing_none': '(Aucun)', + 'export_config': 'Export Config', + 'export_theme': 'Exporter le thème', + 'import_config': 'Importer la configuration', + 'import_theme': 'Importer le thème', + 'code_size': 'Code Taille', + 'prmpt_css': 'Installer comme UserStyle ?', + 'userjs_inject': 'Injecter Userscript+', + 'userjs_close': 'Fermer Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Code', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "ja": { - "createdby": "によって作成された", - "name": "名前", - "daily_installs": "デイリーインストール", - "close": "表示されなくなりました", - "filterA": "フィルター", - "max": "最大化", - "min": "ミニマム", - "search": "検索", - "search_placeholder": "ユーザースクリプトの検索", - "install": "インストール", - "issue": "新刊のご案内", - "version_number": "バージョン", - "updated": "最終更新日", - "total_installs": "総インストール数", - "ratings": "レーティング", - "good": "グッド", - "ok": "良い", - "bad": "悪い", - "created_date": "作成", - "redirect": "大人のGreasyfork", - "filter": "他の言語をフィルタリングする", - "dtime": "表示タイムアウト", - "save": "拯救", - "reset": "リセット", - "preview_code": "コードのプレビュー", - "saveFile": "ファイルを保存", - "newTab": "新しいタブ", - "applies_to": "適用対象", - "license": "ライセンス", - "no_license": "不明", - "antifeatures": "アンチ機能", - "userjs_fullscreen": "自動フルスクリーン", - "listing_none": "(なし)", - "export_config": "エクスポート設定", - "export_theme": "テーマのエクスポート", - "import_config": "設定のインポート", - "import_theme": "テーマのインポート", - "code_size": "コード・サイズ", - "prmpt_css": "UserStyleとしてインストールしますか?", - "userjs_inject": "Userscript+ を挿入", - "userjs_close": "Userscript+ を閉じる", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'ja': { + 'createdby': 'によって作成された', + 'name': '名前', + 'daily_installs': 'デイリーインストール', + 'close': '表示されなくなりました', + 'filterA': 'フィルター', + 'max': '最大化', + 'min': 'ミニマム', + 'search': '検索', + 'search_placeholder': 'ユーザースクリプトの検索', + 'install': 'インストール', + 'issue': '新刊のご案内', + 'version_number': 'バージョン', + 'updated': '最終更新日', + 'total_installs': '総インストール数', + 'ratings': 'レーティング', + 'good': 'グッド', + 'ok': '良い', + 'bad': '悪い', + 'created_date': '作成', + 'redirect': '大人のGreasyfork', + 'filter': '他の言語をフィルタリングする', + 'dtime': '表示タイムアウト', + 'save': '拯救', + 'reset': 'リセット', + 'preview_code': 'コードのプレビュー', + 'saveFile': 'ファイルを保存', + 'newTab': '新しいタブ', + 'applies_to': '適用対象', + 'license': 'ライセンス', + 'no_license': '不明', + 'antifeatures': 'アンチ機能', + 'userjs_fullscreen': '自動フルスクリーン', + 'listing_none': '(なし)', + 'export_config': 'エクスポート設定', + 'export_theme': 'テーマのエクスポート', + 'import_config': '設定のインポート', + 'import_theme': 'テーマのインポート', + 'code_size': 'コード・サイズ', + 'prmpt_css': 'UserStyleとしてインストールしますか?', + 'userjs_inject': 'Userscript+ を挿入', + 'userjs_close': 'Userscript+ を閉じる', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'コード', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "nl": { - "createdby": "Gemaakt door", - "name": "Naam", - "daily_installs": "Dagelijkse Installaties", - "close": "Sluit", - "filterA": "Filter", - "max": "Maximaliseer", - "min": "Minimaliseer", - "search": "Zoek", - "search_placeholder": "Zoeken naar gebruikersscripts", - "install": "Installeer", - "issue": "Nieuw Issue", - "version_number": "Versie", - "updated": "Laatste Update", - "total_installs": "Totale Installaties", - "ratings": "Beoordeling", - "good": "Goed", - "ok": "Ok", - "bad": "Slecht", - "created_date": "Aangemaakt", - "redirect": "Greasy Fork voor volwassenen", - "filter": "Filter andere talen", - "dtime": "Weergave timeout", - "save": "Opslaan", - "reset": "Opnieuw instellen", - "preview_code": "Voorbeeldcode", - "saveFile": "Bestand opslaan", - "newTab": "Nieuw tabblad", - "applies_to": "Geldt voor", - "license": "Licentie", - "no_license": "N.v.t.", - "antifeatures": "Functies voor eigen gewin", - "userjs_fullscreen": "Automatisch volledig scherm", - "listing_none": "(Geen)", - "export_config": "Configuratie exporteren", - "export_theme": "Thema exporteren", - "import_config": "Configuratie importeren", - "import_theme": "Thema importeren", - "code_size": "Code Grootte", - "prmpt_css": "Installeren als UserStyle?", - "userjs_inject": "Injecteer Userscript+", - "userjs_close": "Sluit Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'nl': { + 'createdby': 'Gemaakt door', + 'name': 'Naam', + 'daily_installs': 'Dagelijkse Installaties', + 'close': 'Sluit', + 'filterA': 'Filter', + 'max': 'Maximaliseer', + 'min': 'Minimaliseer', + 'search': 'Zoek', + 'search_placeholder': 'Zoeken naar gebruikersscripts', + 'install': 'Installeer', + 'issue': 'Nieuw Issue', + 'version_number': 'Versie', + 'updated': 'Laatste Update', + 'total_installs': 'Totale Installaties', + 'ratings': 'Beoordeling', + 'good': 'Goed', + 'ok': 'Ok', + 'bad': 'Slecht', + 'created_date': 'Aangemaakt', + 'redirect': 'Greasy Fork voor volwassenen', + 'filter': 'Filter andere talen', + 'dtime': 'Weergave timeout', + 'save': 'Opslaan', + 'reset': 'Opnieuw instellen', + 'preview_code': 'Voorbeeldcode', + 'saveFile': 'Bestand opslaan', + 'newTab': 'Nieuw tabblad', + 'applies_to': 'Geldt voor', + 'license': 'Licentie', + 'no_license': 'N.v.t.', + 'antifeatures': 'Functies voor eigen gewin', + 'userjs_fullscreen': 'Automatisch volledig scherm', + 'listing_none': '(Geen)', + 'export_config': 'Configuratie exporteren', + 'export_theme': 'Thema exporteren', + 'import_config': 'Configuratie importeren', + 'import_theme': 'Thema importeren', + 'code_size': 'Code Grootte', + 'prmpt_css': 'Installeren als UserStyle?', + 'userjs_inject': 'Injecteer Userscript+', + 'userjs_close': 'Sluit Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Code', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "pl": { - "createdby": "Stworzony przez", - "name": "Nazwa", - "daily_installs": "Codzienne instalacje", - "close": "Zamknij", - "filterA": "Filtr", - "max": "Maksymalizuj", - "min": "Minimalizuj", - "search": "Wyszukiwanie", - "search_placeholder": "Wyszukiwanie skryptów użytkownika", - "install": "Instalacja", - "issue": "Nowy numer", - "version_number": "Wersja", - "updated": "Ostatnia aktualizacja", - "total_installs": "Łączna liczba instalacji", - "ratings": "Oceny", - "good": "Dobry", - "ok": "Ok", - "bad": "Zły", - "created_date": "Utworzony", - "redirect": "Greasy Fork dla dorosłych", - "filter": "Odfiltruj inne języki", - "dtime": "Limit czasu wyświetlania", - "save": "Zapisz", - "reset": "Reset", - "preview_code": "Kod podglądu", - "saveFile": "Zapisz plik", - "newTab": "Nowa karta", - "applies_to": "Dotyczy", - "license": "Licencja", - "no_license": "N/A", - "antifeatures": "Antywzorce", - "userjs_fullscreen": "Automatyczny pełny ekran", - "listing_none": "(Brak)", - "export_config": "Konfiguracja eksportu", - "export_theme": "Motyw eksportu", - "import_config": "Importuj konfigurację", - "import_theme": "Importuj motyw", - "code_size": "Kod Rozmiar", - "prmpt_css": "Zainstalować jako UserStyle?", - "userjs_inject": "Wstrzyknij Userscript+", - "userjs_close": "Zamknij Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'pl': { + 'createdby': 'Stworzony przez', + 'name': 'Nazwa', + 'daily_installs': 'Codzienne instalacje', + 'close': 'Zamknij', + 'filterA': 'Filtr', + 'max': 'Maksymalizuj', + 'min': 'Minimalizuj', + 'search': 'Wyszukiwanie', + 'search_placeholder': 'Wyszukiwanie skryptów użytkownika', + 'install': 'Instalacja', + 'issue': 'Nowy numer', + 'version_number': 'Wersja', + 'updated': 'Ostatnia aktualizacja', + 'total_installs': 'Łączna liczba instalacji', + 'ratings': 'Oceny', + 'good': 'Dobry', + 'ok': 'Ok', + 'bad': 'Zły', + 'created_date': 'Utworzony', + 'redirect': 'Greasy Fork dla dorosłych', + 'filter': 'Odfiltruj inne języki', + 'dtime': 'Limit czasu wyświetlania', + 'save': 'Zapisz', + 'reset': 'Reset', + 'preview_code': 'Kod podglądu', + 'saveFile': 'Zapisz plik', + 'newTab': 'Nowa karta', + 'applies_to': 'Dotyczy', + 'license': 'Licencja', + 'no_license': 'N/A', + 'antifeatures': 'Antywzorce', + 'userjs_fullscreen': 'Automatyczny pełny ekran', + 'listing_none': '(Brak)', + 'export_config': 'Konfiguracja eksportu', + 'export_theme': 'Motyw eksportu', + 'import_config': 'Importuj konfigurację', + 'import_theme': 'Importuj motyw', + 'code_size': 'Kod Rozmiar', + 'prmpt_css': 'Zainstalować jako UserStyle?', + 'userjs_inject': 'Wstrzyknij Userscript+', + 'userjs_close': 'Zamknij Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Kod', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "ru": { - "createdby": "Сделано", - "name": "Имя", - "daily_installs": "Ежедневные установки", - "close": "Больше не показывать", - "filterA": "Фильтр", - "max": "Максимизировать", - "min": "Минимизировать", - "search": "Поиск", - "search_placeholder": "Поиск юзерскриптов", - "install": "Установите", - "issue": "Новый выпуск", - "version_number": "Версия", - "updated": "Последнее обновление", - "total_installs": "Всего установок", - "ratings": "Рейтинги", - "good": "Хорошо", - "ok": "Хорошо", - "bad": "Плохо", - "created_date": "Создано", - "redirect": "Greasy Fork для взрослых", - "filter": "Отфильтровать другие языки", - "dtime": "Тайм-аут отображения", - "save": "Сохранить", - "reset": "Перезагрузить", - "preview_code": "Предварительный просмотр кода", - "saveFile": "Сохранить файл", - "newTab": "Новая вкладка", - "applies_to": "Применяется к", - "license": "Лицензия", - "no_license": "Недоступно", - "antifeatures": "Нежелательная функциональность", - "userjs_fullscreen": "Автоматический полноэкранный режим", - "listing_none": "(нет)", - "export_config": "Экспорт конфигурации", - "export_theme": "Экспорт темы", - "import_config": "Импорт конфигурации", - "import_theme": "Импортировать тему", - "code_size": "Код Размер", - "prmpt_css": "Установить как UserStyle?", - "userjs_inject": "Вставить Userscript+", - "userjs_close": "Закрыть Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'ru': { + 'createdby': 'Сделано', + 'name': 'Имя', + 'daily_installs': 'Ежедневные установки', + 'close': 'Больше не показывать', + 'filterA': 'Фильтр', + 'max': 'Максимизировать', + 'min': 'Минимизировать', + 'search': 'Поиск', + 'search_placeholder': 'Поиск юзерскриптов', + 'install': 'Установите', + 'issue': 'Новый выпуск', + 'version_number': 'Версия', + 'updated': 'Последнее обновление', + 'total_installs': 'Всего установок', + 'ratings': 'Рейтинги', + 'good': 'Хорошо', + 'ok': 'Хорошо', + 'bad': 'Плохо', + 'created_date': 'Создано', + 'redirect': 'Greasy Fork для взрослых', + 'filter': 'Отфильтровать другие языки', + 'dtime': 'Тайм-аут отображения', + 'save': 'Сохранить', + 'reset': 'Перезагрузить', + 'preview_code': 'Предварительный просмотр кода', + 'saveFile': 'Сохранить файл', + 'newTab': 'Новая вкладка', + 'applies_to': 'Применяется к', + 'license': 'Лицензия', + 'no_license': 'Недоступно', + 'antifeatures': 'Нежелательная функциональность', + 'userjs_fullscreen': 'Автоматический полноэкранный режим', + 'listing_none': '(нет)', + 'export_config': 'Экспорт конфигурации', + 'export_theme': 'Экспорт темы', + 'import_config': 'Импорт конфигурации', + 'import_theme': 'Импортировать тему', + 'code_size': 'Код Размер', + 'prmpt_css': 'Установить как UserStyle?', + 'userjs_inject': 'Вставить Userscript+', + 'userjs_close': 'Закрыть Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': 'Исходный код', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "zh": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "安装为用户风格?", - "userjs_inject": "注入 Userscript+", - "userjs_close": "关闭 Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'zh': { + 'createdby': '由...制作', + 'name': '姓名', + 'daily_installs': '日常安装', + 'close': '不再显示', + 'filterA': '过滤器', + 'max': '最大化', + 'min': '最小化', + 'search': '搜索', + 'search_placeholder': '搜索用户脚本', + 'install': '安装', + 'issue': '新问题', + 'version_number': '版本', + 'updated': '最后更新', + 'total_installs': '总安装量', + 'ratings': '评级', + 'good': '好的', + 'ok': '好的', + 'bad': '不好', + 'created_date': '创建', + 'redirect': '大人的Greasyfork', + 'filter': '过滤掉其他语言', + 'dtime': '显示超时', + 'save': '拯救', + 'reset': '重置', + 'preview_code': '预览代码', + 'saveFile': '保存存档', + 'newTab': '新标签', + 'applies_to': '适用于', + 'license': '许可证', + 'no_license': '暂无', + 'antifeatures': '可能不受欢迎的功能', + 'userjs_fullscreen': '自动全屏', + 'listing_none': '(无)', + 'export_config': '导出配置', + 'export_theme': '导出主题', + 'import_config': '导入配置', + 'import_theme': '导入主题', + 'code_size': '代码 尺寸', + 'prmpt_css': '安装为用户风格?', + 'userjs_inject': '注入 Userscript+', + 'userjs_close': '关闭 Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': '代码', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "zh_CN": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "安装为用户风格?", - "userjs_inject": "注入 Userscript+", - "userjs_close": "关闭 Userscript+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'zh_CN': { + 'createdby': '由...制作', + 'name': '姓名', + 'daily_installs': '日常安装', + 'close': '不再显示', + 'filterA': '过滤器', + 'max': '最大化', + 'min': '最小化', + 'search': '搜索', + 'search_placeholder': '搜索用户脚本', + 'install': '安装', + 'issue': '新问题', + 'version_number': '版本', + 'updated': '最后更新', + 'total_installs': '总安装量', + 'ratings': '评级', + 'good': '好的', + 'ok': '好的', + 'bad': '不好', + 'created_date': '创建', + 'redirect': '大人的Greasyfork', + 'filter': '过滤掉其他语言', + 'dtime': '显示超时', + 'save': '拯救', + 'reset': '重置', + 'preview_code': '预览代码', + 'saveFile': '保存存档', + 'newTab': '新标签', + 'applies_to': '适用于', + 'license': '许可证', + 'no_license': '暂无', + 'antifeatures': '可能不受欢迎的功能', + 'userjs_fullscreen': '自动全屏', + 'listing_none': '(无)', + 'export_config': '导出配置', + 'export_theme': '导出主题', + 'import_config': '导入配置', + 'import_theme': '导入主题', + 'code_size': '代码 尺寸', + 'prmpt_css': '安装为用户风格?', + 'userjs_inject': '注入 Userscript+', + 'userjs_close': '关闭 Userscript+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': '代码', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' }, - "zh_TW": { - "createdby": "由...制作", - "name": "姓名", - "daily_installs": "日常安装", - "close": "不再显示", - "filterA": "过滤器", - "max": "最大化", - "min": "最小化", - "search": "搜索", - "search_placeholder": "搜索用户脚本", - "install": "安装", - "issue": "新问题", - "version_number": "版本", - "updated": "最后更新", - "total_installs": "总安装量", - "ratings": "评级", - "good": "好的", - "ok": "好的", - "bad": "不好", - "created_date": "创建", - "redirect": "大人的Greasyfork", - "filter": "过滤掉其他语言", - "dtime": "显示超时", - "save": "拯救", - "reset": "重置", - "preview_code": "预览代码", - "saveFile": "保存存档", - "newTab": "新标签", - "applies_to": "适用于", - "license": "许可证", - "no_license": "暂无", - "antifeatures": "可能不受欢迎的功能", - "userjs_fullscreen": "自动全屏", - "listing_none": "(无)", - "export_config": "导出配置", - "export_theme": "导出主题", - "import_config": "导入配置", - "import_theme": "导入主题", - "code_size": "代码 尺寸", - "prmpt_css": "作為使用者樣式安裝?", - "userjs_inject": "注入用戶腳本+", - "userjs_close": "關閉用戶腳本+", - "userjs_sync": "Sync with UserScript Manager", - "userjs_autoinject": "Inject on load", - "auto_fetch": "Fetch on load" + 'zh_TW': { + 'createdby': '由...制作', + 'name': '姓名', + 'daily_installs': '日常安装', + 'close': '不再显示', + 'filterA': '过滤器', + 'max': '最大化', + 'min': '最小化', + 'search': '搜索', + 'search_placeholder': '搜索用户脚本', + 'install': '安装', + 'issue': '新问题', + 'version_number': '版本', + 'updated': '最后更新', + 'total_installs': '总安装量', + 'ratings': '评级', + 'good': '好的', + 'ok': '好的', + 'bad': '不好', + 'created_date': '创建', + 'redirect': '大人的Greasyfork', + 'filter': '过滤掉其他语言', + 'dtime': '显示超时', + 'save': '拯救', + 'reset': '重置', + 'preview_code': '预览代码', + 'saveFile': '保存存档', + 'newTab': '新标签', + 'applies_to': '适用于', + 'license': '许可证', + 'no_license': '暂无', + 'antifeatures': '可能不受欢迎的功能', + 'userjs_fullscreen': '自动全屏', + 'listing_none': '(无)', + 'export_config': '导出配置', + 'export_theme': '导出主题', + 'import_config': '导入配置', + 'import_theme': '导入主题', + 'code_size': '代码 尺寸', + 'prmpt_css': '作為使用者樣式安裝?', + 'userjs_inject': '注入用戶腳本+', + 'userjs_close': '關閉用戶腳本+', + 'userjs_sync': 'Sync', + 'userjs_autoinject': 'Inject on load', + 'auto_fetch': 'Fetch on load', + 'code': '代碼', + 'metadata': 'Metadata', + 'preview_metadata': 'Preview Metadata', + 'recommend_author': 'Recommend Author', + 'recommend_other': 'Recommend Others', + 'default_sort': 'Default Sort' } }; -/** - * To compile this CSS `pnpm run build:Sass` - * - * [Uncompiled Cascading Style Sheet](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/sass) - */ +/** [source code](https://github.com/magicoflolis/Userscript-Plus/blob/master/src/sass/_main.scss) */ const main_css = `mujs-root { --mujs-even-row: hsl(222, 14%, 22%); --mujs-odd-row: hsl(222, 14%, 11%); @@ -732,6 +807,9 @@ const main_css = `mujs-root { } mujs-root * { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; scrollbar-color: var(--mujs-txt-color, hsl(0, 0%, 100%)) hsl(224, 14%, 21%); scrollbar-width: thin; } @@ -778,10 +856,6 @@ td.mujs-list { font-weight: 500; } -.mujs-sty-flex > mujs-btn { - margin: auto; -} - .mujs-invalid { border-radius: 8px !important; border-width: 2px !important; @@ -801,11 +875,6 @@ mujs-row { gap: 0.5em; } -@media screen and (max-width: 800px) { - mujs-column { - flex-flow: row wrap; - } -} mujs-column count-frame[data-counter=greasyfork] { background: var(--mujs-gf-color, hsl(204, 100%, 40%)); } @@ -818,6 +887,11 @@ mujs-column count-frame[data-counter=github] { mujs-column count-frame[data-counter=openuserjs] { background: hsla(12, 86%, 50%, 0.568); } +@media screen and (max-width: 800px) { + mujs-column { + flex-flow: row wrap; + } +} mujs-row { flex-flow: column wrap; @@ -915,6 +989,11 @@ mujs-main mujs-tabs mujs-tab { border-radius: 4px; background: transparent; } +@media screen and (max-width: 800px) { + mujs-main mujs-tabs mujs-tab { + min-width: 6em !important; + } +} mujs-main mujs-tabs mujs-tab.active { background: var(--mujs-even-row, hsl(222, 14%, 18%)); } @@ -957,17 +1036,25 @@ mujs-main input:not([type=checkbox]) { border: transparent; outline: none !important; } +mujs-main mujs-page, mujs-main textarea { background: inherit; overflow-y: auto; - color: var(--mujs-placeholder, hsl(81, 56%, 54%)); border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - border-radius: 10px; - resize: vertical; + border-radius: 5px; outline: none; font-family: monospace; font-size: 14px; } +mujs-main mujs-page { + padding: 0.5em; + margin: 0.5em; +} +mujs-main textarea { + overflow-y: auto; + color: var(--mujs-placeholder, hsl(81, 56%, 54%)); + resize: vertical; +} mujs-main textarea:focus { outline: none; } @@ -997,8 +1084,7 @@ mujs-main .mujs-prompt { align-items: center; justify-content: center; } -mujs-main .mujs-prompt svg, -mujs-main .mujs-prompt img { +mujs-main .mujs-prompt svg { width: 14px; height: 14px; background: transparent; @@ -1021,19 +1107,19 @@ mujs-main .mujs-prompt > .prompt .prompt-body { grid-gap: 0.5em; padding-top: 0.5em; } -mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-deny] { +mujs-main .mujs-prompt > .prompt mujs-btn.prompt-deny { background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); } -mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-deny]:hover { +mujs-main .mujs-prompt > .prompt mujs-btn.prompt-deny:hover { background: var(--mujs-sf-txt-color, hsl(12, 79%, 55%)); border-color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%)); } -mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-confirm] { +mujs-main .mujs-prompt > .prompt mujs-btn.prompt-confirm { background: var(--mujs-gf-color, hsl(204, 100%, 40%)); border-color: var(--mujs-gf-color, hsl(204, 100%, 40%)); } -mujs-main .mujs-prompt > .prompt mujs-btn[data-command=prompt-confirm]:hover { +mujs-main .mujs-prompt > .prompt mujs-btn.prompt-confirm:hover { background: var(--mujs-gf-btn-color, hsl(211, 87%, 56%)); border-color: var(--mujs-gf-btn-color, hsl(211, 87%, 56%)); } @@ -1114,9 +1200,6 @@ mujs-header mujs-url > input { mujs-header .rate-container { order: 1; } -mujs-header .mujs-sty-flex { - order: 2; -} mujs-header .btn-frame { order: 999999999999; } @@ -1163,13 +1246,11 @@ mujs-body table { display: flex; flex-flow: row wrap; align-items: center; - border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); padding-top: 0.5em; padding-bottom: 0.5em; } mujs-body table .frame:not(.webext-page) td { margin: auto; - border-bottom: 1px solid transparent; } mujs-body table .frame:not(.webext-page) td > mujs-a, mujs-body table .frame:not(.webext-page) td > mu-js, @@ -1177,9 +1258,32 @@ mujs-body table { text-align: center; justify-content: center; } + mujs-body table .frame:not(.webext-page) td > mujs-a { + width: 100%; + } +} +@media screen and (max-width: 1180px) and (max-width: 800px) { + mujs-body table .frame:not(.webext-page) td > mujs-column { + flex-flow: column wrap; + } + mujs-body table .frame:not(.webext-page) td > mujs-column > mujs-row { + align-content: center; + } + mujs-body table .frame:not(.webext-page) td > mujs-column mujs-column { + justify-content: center; + } +} +@media screen and (max-width: 1180px) { mujs-body table .frame:not(.webext-page) td:not(.mujs-name, .install-btn) { width: 25%; } +} +@media screen and (max-width: 1180px) and (max-width: 800px) { + mujs-body table .frame:not(.webext-page) td.install-btn { + width: 100%; + } +} +@media screen and (max-width: 1180px) { mujs-body table .frame:not(.webext-page) .mujs-name { width: 100%; } @@ -1192,15 +1296,12 @@ mujs-body table { width: auto !important; } } -mujs-body table th, -mujs-body table td { - border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); -} mujs-body table th { position: -webkit-sticky; position: sticky; top: 0; background: hsla(222, 14%, 33%, 0.75); + border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); } mujs-body table th.mujs-header-name { width: 50%; @@ -1213,9 +1314,15 @@ mujs-body table th.mujs-header-name { mujs-body table .frame:nth-child(even) { background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; } +mujs-body table .frame:nth-child(even) textarea { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; +} mujs-body table .frame:nth-child(odd) { background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; } +mujs-body table .frame:nth-child(odd) textarea { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; +} mujs-body table .frame:not([data-engine=sleazyfork], [data-engine=greasyfork]) mujs-a { color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%)); } @@ -1290,12 +1397,9 @@ mujs-body table .frame .mujs-ratings[data-el=bad] { color: hsl(0, 100%, 50%); } mujs-body table .frame svg { + width: 12px; + height: 12px; fill: currentColor; -} -mujs-body table .frame svg, -mujs-body table .frame img { - width: 14px; - height: 14px; background: transparent; } mujs-body table .frame > td:not(.mujs-name) { @@ -1318,6 +1422,11 @@ mujs-body table .frame > .mujs-name > mu-jsbtn { mujs-body table .frame > .mujs-name > mujs-column > mu-jsbtn { padding: 0px 7px; } +@media screen and (max-width: 800px) { + mujs-body table .frame > .mujs-name > mujs-column > mu-jsbtn { + width: 100%; + } +} mujs-body table .frame > .mujs-uframe > mujs-a { font-size: 16px; font-weight: 500; @@ -1358,6 +1467,27 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a::after { mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after { content: ""; } +@media screen and (max-width: 800px) { + mujs-body table .frame [data-el=matches] { + align-self: center; + width: 30em !important; + } +} +mujs-body table .frame [data-name=license] { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; +} +@media screen and (max-width: 800px) { + mujs-body table .frame [data-name=license] { + width: 100% !important; + width: -moz-available !important; + width: -webkit-fill-available !important; + } +} @media screen and (max-width: 1150px) { .mujs-cfg { @@ -1369,18 +1499,6 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after height: -moz-fit-content; height: -webkit-fit-content; } -@media screen and (max-height: 720px) { - .mujs-cfg:not(.webext-page) { - height: 100%; - height: -moz-available; - height: -webkit-fill-available; - width: 100%; - width: -moz-available; - width: -webkit-fill-available; - overflow-x: auto; - padding: 0.5em; - } -} .mujs-cfg mujs-section { border-radius: 16px; padding: 0.5em; @@ -1388,15 +1506,68 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after .mujs-cfg mujs-section:nth-child(even) { background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; } +.mujs-cfg mujs-section:nth-child(even) input, +.mujs-cfg mujs-section:nth-child(even) select { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); +} +.mujs-cfg mujs-section:nth-child(even) select option { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); +} +.mujs-cfg mujs-section:nth-child(even) select option:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; +} .mujs-cfg mujs-section:nth-child(odd) { background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; } -.mujs-cfg mujs-section[data-name=theme], .mujs-cfg mujs-section[data-name=blacklist] { +.mujs-cfg mujs-section:nth-child(odd) input, +.mujs-cfg mujs-section:nth-child(odd) select { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); +} +.mujs-cfg mujs-section:nth-child(odd) select option { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); +} +.mujs-cfg mujs-section:nth-child(odd) select option:hover { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; +} +.mujs-cfg mujs-section[data-name=theme] .sub-section { + border-radius: 4px; +} +.mujs-cfg mujs-section[data-name=theme] .sub-section:nth-child(even) { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); +} +.mujs-cfg mujs-section[data-name=theme] .sub-section:nth-child(odd) { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); +} +.mujs-cfg mujs-section[data-name=theme] input, +.mujs-cfg mujs-section[data-name=theme] select { + background: inherit; +} +.mujs-cfg mujs-section[data-name=theme] select option { + background: inherit; +} +.mujs-cfg mujs-section[data-name=theme] select option:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; +} +.mujs-cfg mujs-section svg { + width: 14px; + height: 14px; + fill: currentColor; + background: transparent; +} +.mujs-cfg mujs-section[data-name=exp], .mujs-cfg mujs-section[data-name=blacklist] { display: flex; justify-content: space-between; flex-direction: column; gap: 0.25em; } +.mujs-cfg mujs-section[data-name=exp] > mujs-btn, .mujs-cfg mujs-section[data-name=blacklist] > mujs-btn { + width: 100%; + width: -moz-available; + width: -webkit-fill-available; +} +.mujs-cfg mujs-section[data-name=exp] > mujs-btn:hover, .mujs-cfg mujs-section[data-name=blacklist] > mujs-btn:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; +} .mujs-cfg mujs-section input[type=text]::-webkit-input-placeholder { color: var(--mujs-placeholder, hsl(81, 56%, 54%)); } @@ -1429,22 +1600,37 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after .mujs-cfg mujs-section > label.new-list mujs-add { font-size: 20px; } -.mujs-cfg mujs-section > label.sub-section { - padding: 0.2em; -} .mujs-cfg mujs-section > label input:not([type=checkbox]) { font-size: 14px; position: relative; border-radius: 4px; border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); } -.mujs-cfg mujs-section > input { +.mujs-cfg mujs-section select, +.mujs-cfg mujs-section select option { + color: var(--mujs-txt-color, hsl(0, 0%, 100%)); + border: 1px solid transparent; + list-style: none; + outline-style: none; + pointer-events: auto; +} +.mujs-cfg mujs-section select { + text-align: center; + border-radius: 4px; +} +.mujs-cfg mujs-section > *.sub-section { + padding: 0.2em; +} +.mujs-cfg mujs-section > *.sub-section[data-engine] { + flex-wrap: wrap; +} +.mujs-cfg mujs-section > *.sub-section[data-engine] input { width: 100%; width: -moz-available; width: -webkit-fill-available; - margin-top: 5px; - border-radius: 4px; - border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); +} +.mujs-cfg mujs-section > *.sub-section input[type=text] { + margin: 0.2em 0px; } .mujs-cfg .mujs-inlab { position: relative; @@ -1460,8 +1646,11 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after .mujs-cfg .mujs-inlab input[type=checkbox]:checked + label:before { right: 0px; } -.mujs-cfg .mujs-inlab input[type=checkbox][data-name=greasyfork]:checked + label, .mujs-cfg .mujs-inlab input[type=checkbox][data-name=sleazyfork]:checked + label { - background: var(--mujs-chck-gf, hsla(197, 100%, 50%, 0.568)); +.mujs-cfg .mujs-inlab input[type=checkbox][data-name=greasyfork]:checked + label { + background: var(--mujs-gf-color, hsl(204, 100%, 40%)); +} +.mujs-cfg .mujs-inlab input[type=checkbox][data-name=sleazyfork]:checked + label { + background: var(--mujs-sf-color, hsl(12, 86%, 50%)); } .mujs-cfg .mujs-inlab input[type=checkbox][data-name=openuserjs]:checked + label { background: var(--mujs-chck-open, hsla(12, 86%, 50%, 0.568)); @@ -1489,6 +1678,9 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after right: 20px; border-radius: 20px; } +.mujs-cfg .mujs-sty-flex mujs-btn { + margin: auto; +} .mujs-cfg .mujs-sty-flex mujs-btn[data-command=reset] { background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); @@ -1508,6 +1700,18 @@ mujs-body table .frame [data-el=matches] .mujs-grants > mujs-a:last-child::after .mujs-cfg:not(.webext-page) { margin: 1rem 25rem; } +@media screen and (max-height: 720px) { + .mujs-cfg:not(.webext-page) { + height: 100%; + height: -moz-available; + height: -webkit-fill-available; + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + overflow-x: auto; + padding: 0.5em; + } +} mujs-a { display: inline-block; @@ -1536,12 +1740,9 @@ mujs-btn { padding: 6px 15px; } mujs-btn svg { - fill: var(--mujs-txt-color, hsl(0, 0%, 100%)); -} -mujs-btn svg, -mujs-btn img { width: 14px; height: 14px; + fill: var(--mujs-txt-color, hsl(0, 0%, 100%)); } mu-jsbtn { @@ -1559,165 +1760,223 @@ mu-jsbtn { mujs-a, mu-jsbtn, .mujs-pointer, -.mujs-cfg mujs-section *:not(input[type=password], input[type=text], input[type=number], [data-theme], [data-blacklist]), +.mujs-cfg mujs-section *:not(input[type=text], input[type=number], [data-theme], [data-blacklist]), .mainbtn, .mainframe, mujs-btn { cursor: pointer !important; -} -`; +}`; /******************************************************************************/ // #region Console -const dbg = (...msg) => { - const dt = new Date(); - console.debug( - '[%cMagic Userscript+%c] %cDBG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(255, 212, 0);', - `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, - ...msg - ); -}; -const err = (...msg) => { - console.error( - '[%cMagic Userscript+%c] %cERROR', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(249, 24, 128);', - ...msg - ); - const a = typeof alert !== 'undefined' && alert; - for (const ex of msg) { - if (typeof ex === 'object' && 'cause' in ex && a) { - a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`); +const con = { + title: '[%cMagic Userscript+%c]', + color: 'color: rgb(29, 155, 240);', + dbg(...msg) { + const dt = new Date(); + console.debug( + `${con.title} %cDBG`, + con.color, + '', + 'color: rgb(255, 212, 0);', + `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, + ...msg + ); + }, + err(...msg) { + console.error(`${con.title} %cERROR`, con.color, '', 'color: rgb(249, 24, 128);', ...msg); + const a = typeof alert !== 'undefined' && alert; + const t = con.title.replace(/%c/g, ''); + for (const ex of msg) { + if (typeof ex === 'object' && 'cause' in ex && a) { + a(`${t} (${ex.cause}) ${ex.message}`); + } } + }, + info(...msg) { + console.info(`${con.title} %cINF`, con.color, '', 'color: rgb(0, 186, 124);', ...msg); + }, + log(...msg) { + console.log(`${con.title} %cLOG`, con.color, '', 'color: rgb(219, 160, 73);', ...msg); } }; -const info = (...msg) => { - console.info( - '[%cMagic Userscript+%c] %cINF', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(0, 186, 124);', - ...msg - ); -}; -const log = (...msg) => { - console.log( - '[%cMagic Userscript+%c] %cLOG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(219, 160, 73);', - ...msg - ); -}; +const { err, info } = con; // #endregion - /** * @type { import("../typings/types.d.ts").config } */ -let cfg = {}; - +let cfg; /** - * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js - * @returns {typeof globalThis} + * @type {URL | undefined} */ -function globalWin() { - const check = function (it) { - return it && it.Math === Math && it; - }; +let url; +try { + // for some reason `window.location.href` isn't always the same as `location.href` + url = new URL(window.location.href ?? BLANK_PAGE); +} catch { + /* empty */ +} +//#region Placeholders +const BLANK_FN = function () {}; +const BLANK_ASYNC_FN = async function () {}; +const BLANK_PAGE = 'about:blank'; +//#endregion +/** + * @template {string} S + * @param {S} hn + */ +const normalizedHostname = (hn) => hn.replace(/^www\./, ''); +/** + * @template {string} S + * @param {S} txt + */ +const formatURL = (txt) => + txt + .split('.') + .splice(-2) + .join('.') + .replace(/\/|https:/g, ''); +/** + * @template {string} S + * @param {S} str + */ +const getHostname = (str) => formatURL(normalizedHostname(str)); +// #region Validators +/** + * @type { import("../typings/types.d.ts").objToStr } + */ +const objToStr = (obj) => Object.prototype.toString.call(obj).match(/\[object (.*)\]/)[1]; +/** + * @type { import("../typings/types.d.ts").isRegExp } + */ +const isRegExp = (obj) => /RegExp/.test(objToStr(obj)); +/** + * @type { import("../typings/types.d.ts").isElem } + */ +const isElem = (obj) => /Element/.test(objToStr(obj)); +/** + * @type { import("../typings/types.d.ts").isHTML } + */ +const isHTML = (obj) => /HTML/.test(objToStr(obj)); +/** + * @type { import("../typings/types.d.ts").isObj } + */ +const isObj = (obj) => /Object/.test(objToStr(obj)); +/** + * @type { import("../typings/types.d.ts").isFN } + */ +const isFN = (obj) => /Function/.test(objToStr(obj)); +const isUserCSS = (str) => /\.user\.css$/.test(str); +const isUserJS = (str) => /\.user\.js$/.test(str); +/** + * @type { import("../typings/types.d.ts").isNull } + */ +const isNull = (obj) => { + return Object.is(obj, null) || Object.is(obj, undefined); +}; +/** + * @type { import("../typings/types.d.ts").isBlank } + */ +const isBlank = (obj) => { return ( - check(typeof globalThis == 'object' && globalThis) || - check(typeof window == 'object' && window) || - check(typeof self == 'object' && self) || - check(typeof this == 'object' && this) || - (function () { - return this; - })() || - Function('return this')() + (typeof obj === 'string' && Object.is(obj.trim(), '')) || + ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || + (Array.isArray(obj) && Object.is(obj.length, 0)) || + (isObj(obj) && Object.is(Object.keys(obj).length, 0)) ); -} - -/** @type { import("../typings/UserJS.d.ts").safeSelf } */ -function safeSelf() { - if (userjs.safeSelf) { - return userjs.safeSelf; - } +}; +/** + * @type { import("../typings/types.d.ts").isEmpty } + */ +const isEmpty = (obj) => { + return isNull(obj) || isBlank(obj); +}; +// #endregion +// #region Globals +/** @type { import("../typings/UserJS.d.ts").safeHandles } */ +let _self; +{ + /** + * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js + * @returns {typeof globalThis} + */ + const globalWin = () => { + const check = (it) => it && it.Math === Math && it; + return ( + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + check(typeof self == 'object' && self) + ); + }; const g = globalWin(); - /** @type { import("../typings/UserJS.d.ts").safeHandles } */ - const safe = { - XMLHttpRequest: g.XMLHttpRequest, - createElement: g.document.createElement.bind(g.document), - createElementNS: g.document.createElementNS.bind(g.document), - createTextNode: g.document.createTextNode.bind(g.document), - setTimeout: g.setTimeout, - clearTimeout: g.clearTimeout, - navigator: g.navigator, - scheduler: { - postTask(callback, options) { - if ('scheduler' in g && 'postTask' in g.scheduler) { - return g.scheduler.postTask(callback, options); - } - - options = Object.assign({}, options); - - if (options.delay === undefined) options.delay = 0; - options.delay = Number(options.delay); - if (options.delay < 0) { - return Promise.reject(new TypeError('"delay" must be a positive number.')); + try { + /** @type { import("../typings/UserJS.d.ts").safeHandles } */ + const safe = { + XMLHttpRequest: g.XMLHttpRequest, + CustomEvent: g.CustomEvent, + createElement: g.document.createElement.bind(g.document), + createElementNS: g.document.createElementNS.bind(g.document), + createTextNode: g.document.createTextNode.bind(g.document), + setTimeout: g.setTimeout, + clearTimeout: g.clearTimeout, + navigator: g.navigator, + scheduler: { + postTask(callback, options) { + if ('scheduler' in g && 'postTask' in g.scheduler) { + return g.scheduler.postTask(callback, options); + } + options = Object.assign({}, options); + if (options.delay === undefined) options.delay = 0; + options.delay = Number(options.delay); + if (options.delay < 0) { + return Promise.reject(new TypeError('"delay" must be a positive number.')); + } + return new Promise((resolve) => { + g.setTimeout(() => { + resolve(callback()); + }, options.delay); + }); + }, + yield() { + if ('scheduler' in g && 'yield' in g.scheduler) { + return g.scheduler.yield(); + } + return new Promise((resolve) => { + g.setTimeout(resolve, 0); + }); } - return new Promise((resolve) => { - g.setTimeout(() => { - resolve(callback()); - }, options.delay); - }); }, - yield() { - if ('scheduler' in g && 'yield' in g.scheduler) { - scheduler.yield(); - return g.scheduler.yield(); + groupBy(items, keySelector) { + if ('groupBy' in Object) { + return Object.groupBy(items, keySelector); } - return new Promise((resolve) => { - g.setTimeout(resolve, 0); - }); - } - } - }; - userjs.safeSelf = safe; - return userjs.safeSelf; -} - -const trustedTypes = { - policy: false, - init() { - if (this.policy) { - return this.policy; - } - this.policy = true; - try { - // Native implementation exists - if (window.trustedTypes && window.trustedTypes.createPolicy) { - window.trustedTypes.createPolicy('default', { - createHTML: (string) => string - }); + /** [Object.groupBy polyfill](https://gist.github.com/gtrabanco/7c97bd41aa74af974fa935bfb5044b6e) */ + return items.reduce((acc = {}, ...args) => { + const key = keySelector(...args); + acc[key] ??= []; + acc[key].push(args[0]); + return acc; + }, {}); } - } catch (ex) { - ex.cause = 'trustedTypes.init()'; - err(ex); + }; + for (const [k, v] of Object.entries(safe)) { + if (/scheduler|navigator/.test(k) || isFN(v)) continue; + throw new Error(`Safe "${k}" returned "${v}"`, { cause: '_self' }); } - return this.policy; + _self = safe; + } catch (e) { + err(e); + _self = null; } -}; -trustedTypes.init(); - -const BLANK_PAGE = 'about:blank'; -// Lets highlight me :) +} +// #endregion +// #region Constants +/** Lets highlight me :) */ const authorID = 166061; /** - * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{{id}}` + * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{id}` */ const goodUserJS = [ + 33005, 394820, 438684, 4870, @@ -1749,30 +2008,28 @@ const goodUserJS = [ 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' ]; -/** - * Remove UserJS from banned accounts - */ +/** Remove UserJS from banned accounts */ const badUserJS = [478597]; -// Unsupport webpages for each engine +/** Unsupport host for search engines */ const engineUnsupported = { greasyfork: ['pornhub.com'], sleazyfork: ['pornhub.com'], openuserjs: [], github: [] }; -const getUAData = () => { +const isMobile = (() => { if (userjs.isMobile !== undefined) { return userjs.isMobile; } try { - const { navigator } = safeSelf(); + const { navigator } = _self; if (navigator) { - const { userAgent, userAgentData } = navigator; - const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; + const { userAgent = '', userAgentData = {} } = navigator; + const { platform = '', mobile = false } = Object(userAgentData); userjs.isMobile = - /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') || + /Mobile|Tablet/.test(String(userAgent)) || Boolean(mobile) || - /Android|Apple/.test(platform ? String(platform) : ''); + /Android|Apple/.test(String(platform)); } else { userjs.isMobile = false; } @@ -1782,9 +2039,8 @@ const getUAData = () => { err(ex); } return userjs.isMobile; -}; -const isMobile = getUAData(); -const isGM = typeof GM !== 'undefined'; +})(); +const isGM = typeof GM !== 'undefined' || typeof GM_xmlhttpRequest !== 'undefined'; const builtinList = { local: /localhost|router|gov|(\d+\.){3}\d+/, finance: @@ -1795,19 +2051,26 @@ const builtinList = { pathname: '/hentai/.+/read/page/.+' } }; +// #endregion +// #region DEFAULT_CONFIG /** * @type { import("../typings/types.d.ts").config } */ const DEFAULT_CONFIG = { - autofetch: true, + autofetch: false, autoinject: true, + autoSort: 'daily_installs', + clearTabCache: true, cache: true, - codePreview: false, autoexpand: false, filterlang: false, sleazyredirect: false, time: 10000, blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], + preview: { + code: false, + metadata: false + }, engines: [ { enabled: true, @@ -1829,7 +2092,7 @@ const DEFAULT_CONFIG = { name: 'github', token: '', query: encodeURIComponent( - 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' + 'https://api.github.com/search/repositories?q=topic:{domain}+topic:userscript' ) } ], @@ -1895,140 +2158,63 @@ const DEFAULT_CONFIG = { } } }; -//#region i18n -class i18nHandler { - constructor() { - /** @type { string[] } */ - this.cache = []; - - const { navigator } = safeSelf(); - const { languages } = navigator; - for (const nlang of languages) { - const lg = nlang.split('-')[0]; - if (this.cache.indexOf(lg) === -1) { - this.cache.push(lg); - } - } - - const current = navigator.language.split('-')[0] ?? 'en'; - if (!this.cache.includes(current)) { - this.cache.push(current); - } - } - toDate(str = '') { - const { navigator } = safeSelf(); - return new Intl.DateTimeFormat(navigator.language).format(new Date(str)); +// #endregion +// #region i18n +const Language = class { + static i18nMap = new Map(Object.entries(translations)); + /** + * @param {string | Date | number} str + */ + static toDate(str = '') { + const { + navigator: { language } + } = _self; + return new Intl.DateTimeFormat(language).format(typeof str === 'string' ? new Date(str) : str); } - toNumber(number) { - const { navigator } = safeSelf(); - return new Intl.NumberFormat(navigator.language).format(number); + /** + * @param {number | bigint} number + */ + static toNumber(number) { + const { + navigator: { language } + } = _self; + return new Intl.NumberFormat(language).format(number); } - i18n$(...keys) { - const { navigator } = safeSelf(); - const current = navigator.language.split('-')[0] ?? 'en'; - const list = translations[cfg.language] ?? translations[current]; - const arr = []; - for (const key of keys) { - arr.push(list[key]); + /** + * @type { import("../typings/UserJS.d.ts").i18n$ } + */ + static i18n$(key) { + try { + return Language.i18nMap.get(Language.current)?.[key] ?? 'Invalid Key'; + } catch (e) { + err(e); + return 'error'; } - return arr.length !== 1 ? arr : arr[0]; - } -} -const language = new i18nHandler(); -const { i18n$ } = language; -//#endregion -// #region Utilities -const union = (...arr) => [...new Set(arr.flat())]; -/** - * @type { import("../typings/types.d.ts").qs } - */ -const qs = (selector, root) => { - try { - return (root || document).querySelector(selector); - } catch (ex) { - err(ex); } - return null; -}; -/** - * @type { import("../typings/types.d.ts").qsA } - */ -const qsA = (selectors, root) => { - try { - return (root || document).querySelectorAll(selectors); - } catch (ex) { - err(ex); + static get current() { + const { + navigator: { language } + } = _self; + const [current = 'en'] = language.split('-'); + return current; } - return []; }; +const { i18n$ } = Language; +// #endregion +// #region Utilities +const union = (...arr) => [...new Set(arr.flat())]; /** - * @type { import("../typings/types.d.ts").objToStr } - */ -const objToStr = (obj) => Object.prototype.toString.call(obj); -/** - * @type { import("../typings/types.d.ts").strToURL } + * @param {string} str */ -const strToURL = (str) => { - const WIN_LOCATION = window.location ?? BLANK_PAGE; +const decode = (str) => { try { - str = str ?? WIN_LOCATION; - return objToStr(str).includes('URL') ? str : new URL(str); + if (decodeURI(str) !== decodeURIComponent(str)) { + return decode(decodeURIComponent(str)); + } } catch (ex) { - ex.cause = 'strToURL'; err(ex); } - return WIN_LOCATION; -}; -/** - * @type { import("../typings/types.d.ts").isRegExp } - */ -const isRegExp = (obj) => { - const s = objToStr(obj); - return s.includes('RegExp'); -}; -/** - * @type { import("../typings/types.d.ts").isElem } - */ -const isElem = (obj) => { - const s = objToStr(obj); - return s.includes('Element'); -}; -/** - * @type { import("../typings/types.d.ts").isObj } - */ -const isObj = (obj) => { - const s = objToStr(obj); - return s.includes('Object'); -}; -/** - * @type { import("../typings/types.d.ts").isFN } - */ -const isFN = (obj) => { - const s = objToStr(obj); - return s.includes('Function'); -}; -/** - * @type { import("../typings/types.d.ts").isNull } - */ -const isNull = (obj) => { - return Object.is(obj, null) || Object.is(obj, undefined); -}; -/** - * @type { import("../typings/types.d.ts").isBlank } - */ -const isBlank = (obj) => { - return ( - (typeof obj === 'string' && Object.is(obj.trim(), '')) || - ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || - (Array.isArray(obj) && Object.is(obj.length, 0)) || - (isObj(obj) && Object.is(Object.keys(obj).length, 0)) - ); -}; -/** - * @type { import("../typings/types.d.ts").isEmpty } - */ -const isEmpty = (obj) => { - return isNull(obj) || isBlank(obj); + return str; }; /** * @type { import("../typings/types.d.ts").normalizeTarget } @@ -2041,52 +2227,67 @@ const normalizeTarget = (target, toQuery = true, root) => { return target; } if (typeof target === 'string') { - return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; + return toQuery ? Array.from((root || document).querySelectorAll(target)) : Array.of(target); } - if (isElem(target)) { - return [target]; + if (/object HTML/.test(Object.prototype.toString.call(target))) { + return Array.of(target); } return Array.from(target); }; /** - * @type { import("../typings/types.d.ts").ael } + * @type { import("../typings/types.d.ts").qs } */ -const ael = (el, type, listener, options = {}) => { +const qs = (selector, root) => { try { - for (const elem of normalizeTarget(el)) { - if (!elem) { - continue; - } - if (isMobile && type === 'click') { - elem.addEventListener('touchstart', listener, options); - continue; - } - elem.addEventListener(type, listener, options); - } + return (root || document).querySelector(selector); + } catch (ex) { + err(ex); + } + return null; +}; +/** + * @type { import("../typings/types.d.ts").qsA } + */ +const qsA = (selectors, root) => { + try { + return (root || document).querySelectorAll(selectors); } catch (ex) { err(ex); } + return []; +}; +/** + * @type { import("../typings/types.d.ts").ael } + */ +const ael = (el, type, listener, options = {}) => { + for (const elem of normalizeTarget(el).filter(isHTML)) { + if (isMobile && type === 'click') { + elem.addEventListener('touchstart', listener, options); + continue; + } + elem.addEventListener(type, listener, options); + } }; /** * @type { import("../typings/types.d.ts").formAttrs } */ const formAttrs = (elem, attr = {}) => { - if (!elem) { - return elem; - } - for (const key in attr) { - if (typeof attr[key] === 'object') { - formAttrs(elem[key], attr[key]); - } else if (isFN(attr[key])) { + if (!elem) return elem; + for (const [key, value] of Object.entries(attr)) { + if (/^_mujs/i.test(key)) { + elem[key] = value; + } else if (typeof value === 'object') { + formAttrs(elem[key], value); + } else if (isFN(value)) { if (/^on/.test(key)) { - elem[key] = attr[key]; + elem[key] = value; continue; } - ael(elem, key, attr[key]); - } else if (key === 'class') { - elem.className = attr[key]; + ael(elem, key, value); + } else if (/^class/i.test(key)) { + dom.cl.add(elem, value); } else { - elem[key] = attr[key]; + elem[key] = value; } } return elem; @@ -2097,33 +2298,19 @@ const formAttrs = (elem, attr = {}) => { const make = (tagName, cname, attrs) => { let el; try { - const { createElement } = safeSelf(); - el = createElement(tagName); - if (!isEmpty(cname)) { - if (typeof cname === 'string') { - el.className = cname; - } else if (isObj(cname)) { - formAttrs(el, cname); - } - } - if (!isEmpty(attrs)) { - if (typeof attrs === 'string') { - el.textContent = attrs; - } else if (isObj(attrs)) { - formAttrs(el, attrs); - } - } + el = _self.createElement(tagName.toLowerCase()); + if ((typeof cname === 'string' || Array.isArray(cname)) && !isEmpty(cname)) + dom.cl.add(el, cname); + if (typeof attrs === 'string' && !isEmpty(attrs)) el.textContent = attrs; + formAttrs(el, (isObj(cname) && cname) || (isObj(attrs) && attrs) || {}); } catch (ex) { + if (ex instanceof DOMException) throw new Error(`${ex.name}: ${ex.message}`, { cause: 'make' }); ex.cause = 'make'; err(ex); } return el; }; - -/** - * @type { import("../typings/UserJS.d.ts").getGMInfo } - */ -const getGMInfo = () => { +const $info = (() => { if (isGM) { if (isObj(GM.info)) { return GM.info; @@ -2136,20 +2323,19 @@ const getGMInfo = () => { icon: '', name: 'Magic Userscript+', namespace: 'https://github.com/magicoflolis/Userscript-Plus', - updateURL: 'https://github.com/magicoflolis/Userscript-Plus/releases', + updateURL: 'https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.js', version: 'Bookmarklet', bugs: 'https://github.com/magicoflolis/Userscript-Plus/issues' } }; -}; -const $info = getGMInfo(); +})(); // #endregion /** * @type { import("../typings/types.d.ts").dom } */ const dom = { attr(target, attr, value = undefined) { - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { if (value === undefined) { return elem.getAttribute(attr); } @@ -2161,7 +2347,7 @@ const dom = { } }, prop(target, prop, value = undefined) { - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { if (value === undefined) { return elem[prop]; } @@ -2169,7 +2355,7 @@ const dom = { } }, text(target, text) { - const targets = normalizeTarget(target); + const targets = normalizeTarget(target).filter(isHTML); if (text === undefined) { return targets.length !== 0 ? targets[0].textContent : undefined; } @@ -2177,135 +2363,95 @@ const dom = { elem.textContent = text; } }, + remove(target) { + normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.remove()); + }, cl: { add(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.add(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.add(token); - } - } + token = normalizeTarget((typeof token === 'string' && token.split(' ')) || token, false); + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.add(...token)); }, remove(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(token); - } - } + token = normalizeTarget(token, false); + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.remove(...token)); }, toggle(target, token, force) { let r; - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { r = elem.classList.toggle(token, force); } return r; }, has(target, token) { - for (const elem of normalizeTarget(target)) { - if (elem.classList.contains(token)) { - return true; - } - } - return false; + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.contains(token)); } } }; -class Memorize { - constructor() { - /** - * @type {Map>} - */ - this.store = new Map(); - /** - * @type { { [key: string]: Map; userjs: Map } } - */ - this.maps = {}; - this.create('cfg', 'container', 'userjs'); - } - /** - * @template { string } S - * @param { ...S } maps - * @returns { S | S[] } - */ - create(...maps) { - const resp = []; - for (const key of maps) { - if (this.store.has(key)) { - return this.store.get(key); - } - const m = new Map(); - this.store.set(key, m); - this.maps[key] = m; - resp.push(this.store.get(key)); - } - return resp.length >= 2 ? resp : resp[0]; - } -} -const memory = new Memorize(); //#region Icon SVGs const iconSVG = { - cfg: { - viewBox: '0 0 24 24', - html: '' - }, close: { - viewBox: '0 0 24 24', - html: '' + viewBox: '0 0 384 512', + html: '' + }, + code: { + viewBox: '0 0 640 512', + html: '' }, - filter: { - viewBox: '0 0 24 24', - html: '' + collapse: { + viewBox: '0 0 448 512', + html: '' }, - fsClose: { - viewBox: '0 0 24 24', - html: ' ' + download: { + viewBox: '0 0 384 512', + html: '' }, - fsOpen: { - viewBox: '0 0 24 24', - html: ' ' + expand: { + viewBox: '0 0 448 512', + html: '' }, - fullscreen: { - viewBox: '0 0 96 96', - html: '' + gear: { + viewBox: '0 0 512 512', + html: '' }, - gf: { - viewBox: '0 0 510.4 510.4', - html: '' + github: { + viewBox: '0 0 496 512', + html: '' }, - gh: { - viewBox: '0 0 16 16', - html: '' + globe: { + viewBox: '0 0 512 512', + html: '' }, - hide: { - viewBox: '0 0 24 24', - html: ' ' + info: { + viewBox: '0 0 512 512', + html: '' }, install: { - viewBox: '0 0 16 16', - html: '' + viewBox: '0 0 512 512', + html: '' }, issue: { - viewBox: '0 0 24 24', - html: '' + viewBox: '0 0 512 512', + html: '' }, - nav: { - viewBox: '0 0 24 24', - html: ' ' + minus: { + viewBox: '0 0 448 512', + html: '' }, - plus: { - viewBox: '0 0 24 24', - html: '' + nav: { + viewBox: '0 0 448 512', + html: '' }, - search: { - viewBox: '0 0 24 24', - html: '' + pager: { + viewBox: '0 0 512 512', + html: '' }, verified: { viewBox: '0 0 56 56', @@ -2314,30 +2460,29 @@ const iconSVG = { html: '' }, refresh: { - viewBox: '0 0 1024 1024', + viewBox: '0 0 512 512', fill: 'currentColor', - html: '' + html: '' }, load(type, container) { - const { createElementNS } = safeSelf(); - const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg'); + const svgElem = _self.createElementNS('http://www.w3.org/2000/svg', 'svg'); for (const [k, v] of Object.entries(iconSVG[type])) { - if (k === 'html') { - continue; - } + if (k === 'html') continue; svgElem.setAttributeNS(null, k, v); } try { if (typeof iconSVG[type].html === 'string') { svgElem.innerHTML = iconSVG[type].html; + svgElem.setAttribute('id', `mujs_${type ?? 'Unknown'}`); } - // eslint-disable-next-line no-unused-vars - } catch (ex) { + } catch { /* empty */ } - if (container) { + if (isElem(container)) { container.appendChild(svgElem); return svgElem; + } else if (isObj(container)) { + formAttrs(svgElem, container); } return svgElem.outerHTML; } @@ -2348,11 +2493,12 @@ const iconSVG = { */ const StorageSystem = { prefix: 'MUJS', + events: new Set(), getItem(key) { return window.localStorage.getItem(key); }, has(key) { - return !isNull(this.getItem(key)); + return !this.getItem(key); }, setItem(key, value) { window.localStorage.setItem(key, value); @@ -2361,7 +2507,10 @@ const StorageSystem = { window.localStorage.removeItem(key); }, async setValue(key, v) { - v = typeof v === 'string' ? v : JSON.stringify(v ?? {}); + if (!v) { + return; + } + v = typeof v === 'string' ? v : JSON.stringify(v); if (isGM) { if (isFN(GM.setValue)) { await GM.setValue(key, v); @@ -2389,6 +2538,7 @@ const StorageSystem = { ? JSON.parse(this.getItem(`${this.prefix}-${key}`)) : def; } catch (ex) { + ex.cause = 'getValue'; err(ex); return def; } @@ -2425,14 +2575,11 @@ const Network = { } data = Object.assign({}, data); method = this.bscStr(method, false); - responseType = this.bscStr(responseType); + responseType = this.bscStr(responseType, true); const params = { method, ...data }; - if (params.hermes) { - delete params.hermes; - } if (isGM && !useFetch) { if (params.credentials) { Object.assign(params, { @@ -2483,8 +2630,8 @@ const Network = { } else if (responseType.match(/clone/)) { resolve(check('clone')); } else if (responseType.match(/document/)) { - const domParser = new DOMParser(); const respTxt = check('text'); + const domParser = new DOMParser(); if (respTxt instanceof Promise) { respTxt.then((txt) => { const doc = domParser.parseFromString(txt, 'text/html'); @@ -2519,8 +2666,7 @@ const Network = { } } return await new Promise((resolve, reject) => { - const { XMLHttpRequest } = safeSelf(); - const req = new XMLHttpRequest(); + const req = new _self.XMLHttpRequest(); let method = 'GET'; let url = BLANK_PAGE; let body; @@ -2580,141 +2726,127 @@ const Network = { return txt.replaceAll(/\W/g, ''); } }; -const sleazyRedirect = () => { - const locObj = window.top.location; - const { hostname } = locObj; - const gfSite = /greasyfork\.org/.test(hostname); - if (!gfSite && cfg.sleazyredirect) { - return; - } - const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; - if (!qs('span.sign-in-link')) { - return; - } - if (!/scripts\/\d+/.test(locObj.href)) { - return; - } - if ( - !qs('#script-info') && - (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) - ) { - const str = locObj.href.replace( - /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, - '//$1' + otherSite + '.org' - ); - info(`Redirecting to "${str}"`); - if (isFN(locObj.assign)) { - locObj.assign(str); - } else { - locObj.href = str; +const Counter = { + cnt: { + total: { + count: 0 + } + }, + set(engine) { + if (!this.cnt[engine.name]) { + const counter = make('count-frame', engine.enabled ? '' : 'hidden', { + dataset: { + counter: engine.name + }, + title: decode(engine.query ?? engine.url), + textContent: '0' + }); + this.cnt[engine.name] = { + root: counter, + count: 0 + }; + return counter; + } + return this.cnt[engine.name].root; + }, + update(count, engine) { + this.cnt[engine.name].count += count; + this.cnt.total.count += count; + this.updateAll(); + }, + updateAll() { + for (const v of Object.values(this.cnt)) dom.text(v.root, v.count); + }, + reset() { + for (const [k, v] of Object.entries(this.cnt)) { + dom.text(v.root, 0); + v.count = 0; + const engine = cfg.engines.find((engine) => k === engine.name); + if (engine) { + dom.cl[engine.enabled ? 'remove' : 'add'](v.root, 'hidden'); + } } } }; // #region Container /** - * @type { import("../typings/UserJS.d.ts").Container } + * @type { typeof import("../typings/UserJS.d.ts").Container } */ -class Container { +const Container = class { + static prompts = []; webpage; host; - domain; - ready; injected; shadowRoot; - supported; + shadowSupport; frame; - cache; userjsCache; root; unsaved; isBlacklisted; rebuild; - counters; opacityMin; opacityMax; - constructor(url) { + constructor() { this.remove = this.remove.bind(this); this.refresh = this.refresh.bind(this); - this.updateCounter = this.updateCounter.bind(this); - this.updateCounters = this.updateCounters.bind(this); this.showError = this.showError.bind(this); + this.toElem = this.toElem.bind(this); - this.webpage = strToURL(url); - this.host = this.getHost(this.webpage.host); - this.domain = this.getDomain(this.webpage.host); - this.ready = false; + this.webpage = url; + this.host = getHostname(url?.hostname ?? BLANK_PAGE); this.injected = false; this.shadowRoot = undefined; - this.supported = isFN(make('main-userjs').attachShadow); - this.frame = this.supported - ? make('main-userjs', '', { - dataset: { - insertedBy: $info.script.name, - role: 'primary-container' - } - }) - : make('iframe', 'mujs-iframe', { - dataset: { - insertedBy: $info.script.name, - role: 'primary-iframe' - }, - loading: 'lazy', - src: BLANK_PAGE, - style: - 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', - onload: (iFrame) => { - /** - * @type { HTMLIFrameElement } - */ - const target = iFrame.target; - if (!target.contentDocument) { - return; - } - this.shadowRoot = target.contentDocument.documentElement; - this.ready = true; - dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe'); - } - }); - if (this.supported) { - this.shadowRoot = this.frame.attachShadow({ - mode: 'open', - clonable: false, - delegatesfocus: false - }); - this.ready = true; - } - this.cache = memory.maps.container; - this.userjsCache = memory.maps.userjs; + this.shadowSupport = isFN(make('main-userjs').attachShadow); + + if (!this.shadowSupport) + throw new Error('Failed to initialize: "attachShadow not supported"', { cause: 'Container' }); + this.frame = make('main-userjs', { + style: 'visibility: visible;', + dataset: { + insertedBy: $info.script.name, + role: 'primary-container' + } + }); + this.shadowRoot = this.frame.attachShadow({ mode: 'closed' }); + + this.userjsCache = new Map(); this.root = make('mujs-root'); this.unsaved = false; this.isBlacklisted = false; this.rebuild = false; - this.counters = { - total: 0 - }; this.opacityMin = '0.15'; this.opacityMax = '1'; this.elementsReady = this.init(); const Timeout = class { constructor() { + /** + * @type {number[]} + */ this.ids = []; } - + /** + * @template R + * @param {number} delay + * @param {R} [reason] + */ set(delay, reason) { return new Promise((resolve, reject) => { - const id = setTimeout(() => { + const id = _self.setTimeout(() => { Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason); this.clear(id); }, delay); this.ids.push(id); }); } - + /** + * @param {...number} ids + */ clear(...ids) { this.ids = this.ids.filter((id) => { if (ids.includes(id)) { - clearTimeout(id); + _self.clearTimeout(id); return false; } return true; @@ -2726,30 +2858,17 @@ class Container { mouse: new Timeout() }; - this.injFN = () => {}; + this.injFN = BLANK_FN; window.addEventListener('beforeunload', this.remove); } - /** - * @param { function(): * } callback - * @param { Document } doc - */ - async inject(callback, doc) { + inject(callback, doc) { if (this.checkBlacklist(this.host)) { err(`Blacklisted "${this.host}"`); this.remove(); return; } - if (!this.shadowRoot) { - return; - } - if (doc === null) { - return; - } - - while (this.ready === false) { - await new Promise((resolve) => requestAnimationFrame(resolve)); - } + if (!this.shadowRoot || isNull(doc)) return; try { doc.documentElement.appendChild(this.frame); if (this.injected) { @@ -2759,57 +2878,38 @@ class Container { return; } this.shadowRoot.append(this.root); - if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) { + if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); - } this.injected = true; this.initFn(); - if (this.elementsReady && isFN(callback)) { - this.injFN = callback.call(this, this.shadowRoot); - } + if (isFN(callback) && this.elementsReady) this.injFN = callback.call(this, this.shadowRoot); } catch (ex) { err(ex); this.remove(); } } initFn() { - this.renderTheme(cfg.theme); - - for (const engine of cfg.engines) { - if (engine.enabled) { - const counter = make('count-frame', '', { - dataset: { - counter: engine.name - }, - title: engine.query ? decodeURIComponent(engine.query) : engine.url, - textContent: '0' - }); - this.countframe.append(counter); - } - this.counters[engine.name] = 0; - } + this.setTheme(); - const cfgpage = this.cfgpage; - const table = this.table; - const exBtn = this.exBtn; - const supported = this.supported; - const frame = this.frame; - const refresh = this.refresh; - const cache = this.cache; - const cHost = this.host; - const urlBar = this.urlBar; + Counter.cnt.total.root = this.mainbtn; + if (this.countframe) + for (const engine of cfg.engines) this.countframe.append(Counter.set(engine)); + const { refresh, urlBar, host, userjsCache, cfgpage, table } = this; class Tabs { /** * @param { HTMLElement } root */ constructor(root) { - this.Tab = new Map(); + /** + * @type { Set } + */ + this.pool = new Set(); this.blank = BLANK_PAGE; this.protocal = 'mujs:'; - this.protoReg = new RegExp(`${this.protocal}(.+)`); + this.protoReg = new RegExp(`${this.protocal}(.+)`, 'i'); this.el = { - add: make('mujs-addtab', '', { + add: make('mujs-addtab', { textContent: '+', dataset: { command: 'new-tab' @@ -2820,113 +2920,93 @@ class Container { }; this.el.head.append(this.el.add); this.el.root.append(this.el.head); - this.custom = () => {}; - } - hasTab(...params) { - for (const p of params) { - if (!this.Tab.has(p)) { - return false; - } - const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host); - if (isBlank(content)) { - return false; - } - } - return true; + this.custom = BLANK_FN; } - storeTab(host) { - const h = host ?? this.blank; - if (!this.Tab.has(h)) { - this.Tab.set(h, new Set()); - } - return this.Tab.get(h); + /** + * @param {string} hostname + */ + getTab(hostname) { + return [...this.pool].find(({ dataset }) => hostname === dataset.host); } - cache(host, ...tabs) { - const h = host ?? this.blank; - const tabCache = this.storeTab(h); - for (const t of normalizeTarget(tabs)) { - if (tabCache.has(t)) { - continue; - } - tabCache.add(t); - } - this.Tab.set(h, tabCache); - return tabCache; + getActive() { + return [...this.pool].find((tab) => tab.classList.contains('active')); } - intFN(host) { - if (!host.startsWith(this.protocal)) { - return; - } - const type = host.match(this.protoReg)[1]; - if (type === 'settings') { - dom.cl.remove([cfgpage, exBtn], 'hidden'); + /** + * @param {string} hostname + */ + intFN(hostname) { + const [, host] = this.protoReg.exec(hostname) ?? []; + if (host === 'settings') { + dom.cl.remove(cfgpage, 'hidden'); dom.cl.add(table, 'hidden'); - if (!supported) { - dom.attr(frame, 'style', 'height: 100%;'); - } + urlBar.placeholder = 'Search settings'; } } + /** + * @param {HTMLElement} tab + * @param {boolean} [build] + */ active(tab, build = true) { - for (const t of normalizeTarget(tab, false)) { - dom.cl.add([cfgpage, exBtn], 'hidden'); + if (!this.pool.has(tab)) this.pool.add(tab); + dom.cl.add([table, cfgpage], 'hidden'); + dom.cl.remove([...this.pool], 'active'); + dom.cl.add(tab, 'active'); + if (!build) { dom.cl.remove(table, 'hidden'); - dom.cl.remove(qsA('mujs-tab', this.el.head), 'active'); - dom.cl.add(t, 'active'); - if (!build) { - continue; - } - const host = t.dataset.host ?? this.blank; - if (host === this.blank) { - refresh(); - } else if (host.startsWith(this.protocal)) { - this.intFN(host); - } else { - this.custom(host); - } + return; } - } - /** @param { HTMLElement } tab */ - close(tab) { - for (const t of normalizeTarget(tab, false)) { - const host = t.dataset.host; - if (cache.has(host)) { - cache.delete(host); - } - if (dom.cl.has(t, 'active')) { - refresh(); - } - const sibling = t.previousElementSibling ?? t.nextElementSibling; - if (sibling) { - if (sibling.dataset.command !== 'new-tab') { - this.active(sibling); - } + const host = tab.dataset.host ?? this.blank; + if (host === this.blank) { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + refresh(); + } else if (host.startsWith(this.protocal)) { + this.intFN(host); + } else { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + this.custom(host); + } + } + /** @param { HTMLElement } tab */ + close(tab) { + if (this.pool.has(tab)) this.pool.delete(tab); + if (cfg.clearTabCache) { + const { host } = tab.dataset; + const arr = Array.from(userjsCache.values()).filter(({ _mujs }) => { + return !isEmpty(_mujs) && _mujs.info.host === host; + }); + for (const a of arr) { + arr.splice(arr.indexOf(a), 1); } - if (this.Tab.has(host)) { - this.Tab.delete(host); + } + if (tab.classList.contains('active')) refresh(); + const sibling = tab.nextElementSibling ?? tab.previousElementSibling; + if (sibling) { + if (sibling.dataset.command !== 'new-tab') { + this.active(sibling); } - t.remove(); } + tab.remove(); } - create(host = undefined) { - if (typeof host === 'string') { - if (host.startsWith(this.protocal) && this.hasTab(host)) { - this.active(this.Tab.get(host)); - return; - } - const content = normalizeTarget(this.storeTab(host)).filter( - (t) => host === t.dataset.host - ); - if (!isEmpty(content)) { + /** + * @param {string} [hostname] + */ + create(hostname = undefined) { + if (typeof hostname === 'string') { + const createdTab = this.getTab(hostname); + if (this.protoReg.test(hostname) && createdTab) { + this.active(createdTab); return; } } - const tab = make('mujs-tab', '', { + const tab = make('mujs-tab', { dataset: { command: 'switch-tab' }, style: `order: ${this.el.head.childElementCount};` }); - const tabClose = make('mu-js', '', { + const tabClose = make('mu-js', { dataset: { command: 'close-tab' }, @@ -2934,35 +3014,34 @@ class Container { textContent: 'X' }); const tabHost = make('mujs-host'); + const [, host] = this.protoReg.exec(hostname) ?? []; tab.append(tabHost, tabClose); this.el.head.append(tab); this.active(tab, false); - this.cache(host, tab); - if (isNull(host)) { + if (isNull(hostname)) { refresh(); - urlBar.placeholder = i18n$('newTab'); tab.dataset.host = this.blank; tabHost.title = i18n$('newTab'); tabHost.textContent = i18n$('newTab'); - } else if (host.startsWith(this.protocal)) { - const type = host.match(this.protoReg)[1]; - tab.dataset.host = host || cHost; - tabHost.title = type || tab.dataset.host; + } else if (host) { + tab.dataset.host = hostname || host; + tabHost.title = host || tab.dataset.host; tabHost.textContent = tabHost.title; - this.intFN(host); + this.intFN(hostname); } else { - tab.dataset.host = host || cHost; - tabHost.title = host || cHost; + tab.dataset.host = hostname || host; + tabHost.title = hostname || host; tabHost.textContent = tabHost.title; } return tab; } } - this.tab = new Tabs(this.toolbar); - this.tab.create(this.host); + this.Tabs = new Tabs(this.toolbar); + this.Tabs.create(host); const tabbody = this.tabbody; - const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; + const getCellValue = (tr, idx) => + tr.children[idx].dataset.value || tr.children[idx].textContent; const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) @@ -3007,51 +3086,30 @@ class Container { this.tabhead = make('thead'); this.header = make('mujs-header'); this.tbody = make('mujs-body'); - this.cfgpage = make('mujs-row', 'mujs-cfg hidden'); - this.btnframe = make('mujs-column', 'btn-frame'); - this.exBtn = make('mujs-column', 'mujs-sty-flex hidden'); - this.fsearch = make('mujs-btn', 'hidden'); - this.exportCFG = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_config'), - dataset: { - command: 'export-cfg' - } - }); - this.importCFG = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_config'), - dataset: { - command: 'import-cfg' - } - }); - this.exportTheme = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_theme'), - dataset: { - command: 'export-theme' - } - }); - this.importTheme = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_theme'), - dataset: { - command: 'import-theme' + this.cfgpage = make('mujs-row', 'mujs-cfg hidden', { + _mujs: { + base: [], + sections: new Set() } }); + this.main = make('mujs-main', 'hidden'); + this.urlContainer = make('mujs-url'); + this.btnframe = make('mujs-column', 'btn-frame'); this.btnHandles = make('mujs-column', 'btn-handles'); this.btnHide = make('mujs-btn', 'hide-list', { title: i18n$('min'), - innerHTML: iconSVG.load('hide'), + innerHTML: iconSVG.load('minus'), dataset: { command: 'hide-list' } }); this.btnfullscreen = make('mujs-btn', 'fullscreen', { title: i18n$('max'), - innerHTML: iconSVG.load('fullscreen'), + innerHTML: iconSVG.load('expand'), dataset: { command: 'fullscreen' } }); - this.main = make('mujs-main', 'hidden'); - this.urlContainer = make('mujs-url'); this.closebtn = make('mujs-btn', 'close', { title: i18n$('close'), innerHTML: iconSVG.load('close'), @@ -3061,18 +3119,18 @@ class Container { }); this.btncfg = make('mujs-btn', 'settings hidden', { title: 'Settings', - innerHTML: iconSVG.load('cfg'), + innerHTML: iconSVG.load('gear'), dataset: { command: 'settings' } }); this.btnhome = make('mujs-btn', 'github hidden', { title: `GitHub (v${ - $info.script.version.includes('.') || $info.script.version.includes('Book') + /\d+\.\d+\.\d+|Book/.test($info.script.version) ? $info.script.version : $info.script.version.slice(0, 5) })`, - innerHTML: iconSVG.load('gh'), + innerHTML: iconSVG.load('github'), dataset: { command: 'open-tab', webpage: $info.script.namespace @@ -3088,7 +3146,7 @@ class Container { }); this.btngreasy = make('mujs-btn', 'greasy hidden', { title: 'Greasy Fork', - innerHTML: iconSVG.load('gf'), + innerHTML: iconSVG.load('globe'), dataset: { command: 'open-tab', webpage: 'https://greasyfork.org/scripts/421603' @@ -3101,9 +3159,9 @@ class Container { command: 'navigation' } }); - const makeTHead = (rows) => { + const makeTHead = (rows = []) => { const tr = make('tr'); - for (const r of normalizeTarget(rows)) { + for (const r of rows) { const tparent = make('th', r.class ?? '', r); tr.append(tparent); } @@ -3129,17 +3187,28 @@ class Container { } ]); // #endregion - - this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); - this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + if (isMobile) { + dom.cl.add([this.btnHide, this.btnfullscreen, this.closebtn], 'hidden'); + this.btnframe.append( + this.btnHide, + this.btnfullscreen, + this.closebtn, + this.btnhome, + this.btngreasy, + this.btnissue, + this.btncfg, + this.btnnav + ); + } else { + this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); + this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + } this.toolbar.append(this.btnHandles); this.urlContainer.append(this.urlBar); this.header.append(this.urlContainer, this.rateContainer, this.countframe, this.btnframe); this.tbody.append(this.table, this.cfgpage); this.main.append(this.toolbar, this.header, this.tbody, this.footer, this.promptElem); this.mainframe.append(this.mainbtn); - this.exBtn.append(this.importCFG, this.importTheme, this.exportCFG, this.exportTheme); - this.header.append(this.exBtn); this.root.append(this.mainframe, this.main); return true; @@ -3149,55 +3218,43 @@ class Container { return false; } remove() { - memory.store.clear(); - if (this.frame) { - this.frame.remove(); - } + this.userjsCache.clear(); + dom.remove(this.frame); } async save() { this.unsaved = false; await StorageSystem.setValue('Config', cfg); info('Saved config:', cfg); + this.redirect(); return cfg; } /** * @param { string } css - CSS to inject * @param { string } name - Name of stylesheet - * @return { HTMLStyleElement } Style element + * @return { HTMLStyleElement | undefined } Style element */ - loadCSS(css, name = 'CSS') { + loadCSS(css, name = 'CSS', useGM = true) { try { - if (typeof name !== 'string') { + if (this.stylesheet) return this.stylesheet; + if (typeof name !== 'string') throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (qs(`style[data-role="${name}"]`, this.root)) { - return qs(`style[data-role="${name}"]`, this.root); - } - if (typeof css !== 'string') { + if (typeof css !== 'string') throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (isBlank(css)) { + if (isBlank(css)) throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' }); - } const parent = isEmpty(this.root.shadowRoot) ? this.root : this.root.shadowRoot; - if (isGM) { - let sty; - if (isFN(GM.addElement)) { - sty = GM.addElement(parent, 'style', { - textContent: css - }); - } else if (isFN(GM_addElement)) { - sty = GM_addElement(parent, 'style', { - textContent: css - }); - } + if (useGM && isGM) { + const fn = (isFN(GM.addElement) && GM.addElement) || (isFN(GM_addElement) && GM_addElement); + if (!isFN(fn)) return this.loadCSS(css, name, false); + const sty = fn(parent, 'style', { textContent: css }); if (isElem(sty)) { sty.dataset.insertedBy = $info.script.name; sty.dataset.role = name; - return sty; + this.stylesheet = sty; + return this.stylesheet; } } - const sty = make('style', '', { + const sty = make('style', { textContent: css, dataset: { insertedBy: $info.script.name, @@ -3205,102 +3262,64 @@ class Container { } }); parent.appendChild(sty); - return sty; + this.stylesheet = sty; + return this.stylesheet; } catch (ex) { err(ex); } } checkBlacklist(str) { str = str || this.host; - let blacklisted = false; if (/accounts*\.google\./.test(this.webpage.host)) { - blacklisted = true; + this.isBlacklisted = true; + return this.isBlacklisted; } + let blacklisted = false; for (const b of normalizeTarget(cfg.blacklist)) { if (typeof b === 'string') { if (b.startsWith('userjs-')) { - const r = /userjs-(\w+)/.exec(b)[1]; + const [, r] = /userjs-(\w+)/.exec(b) ?? []; const biList = builtinList[r]; if (isRegExp(biList)) { - if (!biList.test(str)) continue; - blacklisted = true; + if (biList.test(str)) blacklisted = true; } else if (isObj(biList) && biList.host === this.host) { blacklisted = true; } } } else if (isObj(b)) { - if (!b.enabled) { - continue; - } + if (!b.enabled) continue; if (b.regex === true) { const reg = new RegExp(b.url, b.flags); - if (!reg.test(str)) continue; - blacklisted = true; + if (reg.test(str)) blacklisted = true; } if (Array.isArray(b.url)) { for (const c of b.url) { - if (!str.includes(c)) continue; - blacklisted = true; + if (str.includes(c)) blacklisted = true; } } - if (!str.includes(b.url)) continue; - blacklisted = true; + if (str.includes(b.url)) blacklisted = true; } } this.isBlacklisted = blacklisted; return this.isBlacklisted; } - getInfo(url) { - const webpage = strToURL(url || this.webpage); - const host = this.getHost(webpage.host); - const domain = this.getDomain(webpage.host); - return { - domain, - host, - webpage - }; - } - /** - * @template { string } S - * @param { S } str - */ - getHost(str = '') { - return str.split('.').splice(-2).join('.'); - } - /** - * @template { string } S - * @param { S } str - */ - getDomain(str = '') { - return str.split('.').at(-2) ?? BLANK_PAGE; - } - renderTheme(theme) { - theme = theme || cfg.theme; + setTheme() { + const theme = cfg.theme ?? DEFAULT_CONFIG.theme; if (theme === DEFAULT_CONFIG.theme) { return; } - const sty = this.root.style; + const { style } = this.root; for (const [k, v] of Object.entries(theme)) { const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(v)) { - theme[k] = prop; - } - if (prop === v) { - continue; - } - sty.removeProperty(str); - sty.setProperty(str, v); + const prop = style.getPropertyValue(str); + if (isEmpty(v)) theme[k] = prop; + if (prop === v) continue; + style.removeProperty(str); + style.setProperty(str, v); } } makePrompt(txt, dataset = {}, usePrompt = true) { - if (qs('.prompt', this.promptElem)) { - for (const elem of qsA('.prompt', this.promptElem)) { - if (elem.dataset.prompt) { - elem.remove(); - } - } - } + dom.remove(Container.prompts); const el = make('mu-js', 'prompt', { dataset: { prompt: txt @@ -3326,114 +3345,157 @@ class Container { }); elPrompt.append(elYes, elNo); el.append(elPrompt); + } else { + const elPrompt = make('mu-js', 'prompt-body'); + const elNo = make('mujs-btn', 'prompt-deny', { + textContent: i18n$('close') + }); + ael(elNo, isMobile ? 'touchend' : 'click', () => { + el.remove(); + }); + elPrompt.append(elNo); + el.append(elPrompt); } + Container.prompts.push(el); this.promptElem.append(el); + return el; } + /** + * @template {string | Error} E + * @param {...E} ex + */ showError(...ex) { err(...ex); const error = make('mu-js', 'error'); let str = ''; for (const e of ex) { str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`; - if (isObj(e)) { - if (e.notify) { - dom.cl.add(this.mainframe, 'error'); - } - } } - const { createTextNode } = safeSelf(); - error.appendChild(createTextNode(str)); + error.appendChild(_self.createTextNode(str)); this.footer.append(error); } - updateCounter(count, engine) { - this.counters[engine.name] += count; - this.counters.total += count; - this.updateCounters(); + refresh() { + this.urlBar.placeholder = i18n$('newTab'); + Counter.reset(); + dom.cl.remove(this.toElem(), 'hidden'); + dom.cl.remove(this.cfgpage._mujs.sections, 'hidden'); + dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); } - updateCounters() { - for (const [k, v] of Object.entries(this.counters)) { - if (k === 'total') { - continue; - } - const c = qs(`count-frame[data-counter="${k}"]`, this.countframe); - if (c) { - dom.text(c, v); + /** + * Redirects sleazyfork userscripts from greasyfork.org to sleazyfork.org + * + * Taken from: https://greasyfork.org/scripts/23840 + */ + redirect() { + const locObj = window.top.location; + const { hostname } = locObj; + const gfSite = /greasyfork\.org/.test(hostname); + if (!gfSite && cfg.sleazyredirect) return; + const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; + if (!qs('span.sign-in-link')) return; + if (!/scripts\/\d+/.test(locObj.href)) return; + if ( + !qs('#script-info') && + (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) + ) { + const str = locObj.href.replace( + /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, + '//$1' + otherSite + '.org' + ); + info(`Redirecting to "${str}"`); + if (isFN(locObj.assign)) { + locObj.assign(str); + } else { + locObj.href = str; } } - dom.text(this.mainbtn, this.counters.total); } - refresh() { - this.urlBar.placeholder = i18n$('newTab'); - this.counters.total = 0; - for (const engine of cfg.engines) { - this.counters[engine.name] = 0; + /** + * @param {number} [time] + */ + async timeoutFrame(time) { + const frameTimeout = this.timeouts.frame; + frameTimeout.clear(...frameTimeout.ids); + if (dom.cl.has(this.mainframe, 'hidden')) { + return; } - this.updateCounters(); - dom.cl.remove(qsA('tr[data-engine]', this.tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden'); - dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); + time = time ?? cfg.time ?? DEFAULT_CONFIG.time; + let n = 10000; + if (typeof time === 'number' && !Number.isNaN(time)) { + n = this.isBlacklisted ? time / 2 : time; + } + await frameTimeout.set(n); + this.remove(); + frameTimeout.clear(...frameTimeout.ids); } -} + toElem() { + return Array.from(this).map(({ _mujs }) => { + return _mujs.root; + }); + } + *[Symbol.iterator]() { + const arr = Array.from(this.userjsCache.values()).filter(({ _mujs }) => { + return !isEmpty(_mujs) && _mujs.info.engine.enabled; + }); + for (const userjs of arr) { + yield userjs; + } + } +}; const container = new Container(); // #endregion // #region Primary Function function primaryFN() { const respHandles = { - build: async () => {} + build: BLANK_ASYNC_FN }; try { - const { scheduler } = safeSelf(); + const { scheduler } = _self; const { - mainframe, - urlBar, - rateContainer, - footer, - tabbody, - toolbar, - tabhead, - cfgpage, - fsearch, btnfullscreen, + mainframe, main, - btncfg, - btnhome, - btnissue, - btngreasy, - tab, - userjsCache, - updateCounter, - showError + Tabs, + showError, + cfgpage: { + _mujs: { sections: cfgSec, base: cfgBase } + } } = container; - const frameTimeout = container.timeouts.frame; - const cfgMap = memory.maps.cfg; - const rebuildCfg = () => { - for (const engine of cfg.engines) { - if (cfgMap.has(engine.name)) { - const inp = cfgMap.get(engine.name); - inp.checked = engine.enabled; - if (engine.name === 'github') { - const txt = cfgMap.get('github-token'); - dom.prop(txt, 'value', engine.token); - } + const reloadCfg = () => { + for (const base of cfgBase) { + const [, CONFIG, SUB_CONFIG] = /^(\w+)-(.+)/.exec(base.value) ?? []; + let d = DEFAULT_CONFIG[base.value]; + let v = cfg[base.value]; + if (base.tag === 'engine') { + const dEngine = DEFAULT_CONFIG.engines.find((engine) => engine.name === base.value); + const vEngine = cfg.engines.find((engine) => engine.name === base.value); + if (dEngine) d = dEngine; + if (vEngine) v = vEngine; + } else if (CONFIG) { + d = DEFAULT_CONFIG[CONFIG][SUB_CONFIG]; + v = cfg[CONFIG][SUB_CONFIG]; } - } - for (const [k, v] of Object.entries(cfg)) { - if (typeof v === 'boolean') { - if (cfgMap.has(k)) { - const inp = cfgMap.get(k); - if (inp.type === 'checkbox') { - inp.checked = v; + base.cache = v; + if (base.type === 'checkbox') { + if (CONFIG) { + if (CONFIG === 'filters') { + base.elem.checked = cfg[CONFIG][SUB_CONFIG].enabled; } else { - dom.prop(inp, 'value', v); + base.elem.checked = v; + } + } else if (base.tag === 'engine') { + base.elem.checked = v.enabled; + base.elemUrl.value = decode(v.query); + base.elemUrl.placeholder = decode(d.query); + if (base.elemToken) { + base.elemToken = v.token; } } + } else { + base.elem.value = v; } } - // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); - for (const [k, v] of Object.entries(cfg.theme)) { - dom.prop(cfgMap.get(k), 'value', v); - } - container.renderTheme(cfg.theme); + container.setTheme(); }; const doInstallProcess = async (installLink) => { const locObj = window.top.location; @@ -3445,41 +3507,158 @@ function primaryFN() { installLink.remove(); await init(); }; + const doDownloadProcess = async (details) => { + if (!details.url) { + return; + } + const a = make('a'); + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.setAttribute('type', 'text/plain'); + a.dispatchEvent(new MouseEvent('click')); + await init(); + }; + const applyTo = (ujs, name, elem, root) => { + const n = ujs._mujs.code[name] ?? ujs._mujs.code.data_meta[name]; + if (isEmpty(n)) { + const el = make('mujs-a', { + textContent: i18n$('listing_none') + }); + elem.append(el); + return; + } + dom.prop(elem, 'innerHTML', ''); + dom.cl.remove(root, 'hidden'); + if (isObj(n)) { + if (name === 'resource') { + for (const [k, v] of Object.entries(n)) { + const el = make('mujs-a', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = v; + } + elem.append(el); + } + } else { + const el = make('mujs-a', { + textContent: n.text + }); + if (n.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${n.text}`; + } + elem.append(el); + } + } else if (typeof n === 'string') { + const el = make('mujs-a', { + textContent: n + }); + elem.append(el); + } else { + for (const c of n) { + if (typeof c === 'string' && c.startsWith('http')) { + const el = make('mujs-a', { + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } + }); + elem.append(el); + } else if (isObj(c)) { + const el = make('mujs-a', { + textContent: c.text + }); + if (c.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${c.text}`; + } + elem.append(el); + } else { + const el = make('mujs-a', { + textContent: c + }); + elem.append(el); + } + } + } + }; // #region Main event handlers - ael(main, 'click', async (evt) => { + const frameTimeout = container.timeouts.frame; + ael(main, isMobile ? 'touchend' : 'click', async (evt) => { try { + if (isNull(evt.target)) return; /** @type { HTMLElement } */ - const target = evt.target.closest('[data-command]'); - if (!target) { - return; - } - const prmpt = /prompt-/.test(target.dataset.command); + const t = evt.target; + const target = t.closest('[data-command]'); + if (isNull(target)) return; let dataset = target.dataset; let cmd = dataset.command; - let prmptChoice = false; - if (prmpt) { + if (/^prompt-/.test(target.dataset.command)) { dataset = target.parentElement.dataset; cmd = dataset.command; - prmptChoice = /confirm/.test(target.dataset.command); - target.parentElement.parentElement.remove(); + let pElem = target.parentElement.parentElement; + if (/prompt-install/.test(target.dataset.command)) { + pElem = target.parentElement.parentElement.parentElement; + const a = make('a', { + onclick(evt) { + evt.preventDefault(); + doInstallProcess(evt.target); + } + }); + a.href = target.dataset.code_url; + a.click(); + } else if (/prompt-download/.test(target.dataset.command)) { + pElem = target.parentElement.parentElement.parentElement; + const dataUserJS = container.userjsCache.get(+target.dataset.userjs); + if (dataUserJS) { + const code_obj = await dataUserJS._mujs.code.request(false, target.dataset.code_url); + if (typeof code_obj.code === 'string') + doDownloadProcess({ + url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(code_obj.code), + filename: `${dataUserJS.name}${isUserCSS(target.dataset.code_url) ? '.user.css' : '.user.js'}` + }); + } + } + pElem.remove(); + return; } - if (cmd === 'install-script' && dataset.userjs) { - let installCode = dataset.userjs; - if (!prmpt && dataset.userjs.endsWith('.user.css')) { - container.makePrompt(i18n$('prmpt_css'), dataset); + if (cmd === 'install-script') { + const dataUserJS = container.userjsCache.get(+dataset.userjs); + if (isNull(dataUserJS)) { return; - } else if (prmpt !== prmptChoice) { - installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js'); } - const dlBtn = make('a', '', { - onclick(evt) { - evt.preventDefault(); - doInstallProcess(evt.target); + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-install', + code_url: ujs.code_url + } + }); + list.append(a); } - }); - dlBtn.href = installCode; - dlBtn.click(); - } else if (cmd === 'open-tab' && dataset.webpage) { + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + const a = make('a', { + onclick(evt) { + evt.preventDefault(); + doInstallProcess(evt.target); + } + }); + a.href = dataUserJS.code_url; + a.click(); + } + } else if (/open-tab|more-info/.test(cmd) && dataset.webpage) { + if (cmd === 'more-info') evt.preventDefault(); + con.log(cmd); if (isGM) { if (isFN(GM.openInTab)) { return GM.openInTab(dataset.webpage); @@ -3492,15 +3671,19 @@ function primaryFN() { } return window.open(dataset.webpage, '_blank'); } else if (cmd === 'navigation') { - if (dom.cl.has(btngreasy, 'hidden')) { - dom.cl.remove([btncfg, btngreasy, btnhome, btnissue], 'hidden'); - } else { - dom.cl.add([btncfg, btngreasy, btnhome, btnissue], 'hidden'); + for (const e of normalizeTarget(qsA('mujs-btn', target.parentElement)).filter( + (e) => !dom.cl.has(e, 'nav') + )) { + dom.cl.toggle(e, 'hidden'); } } else if (cmd === 'list-description') { const arr = []; const ignoreTags = new Set(['TD', 'MUJS-A', 'MU-JS']); - for (const node of target.parentElement.childNodes) { + /** + * @type { import("../typings/UserJS.d.ts").mujsName } + */ + const p = target.parentElement; + for (const node of Object.values(p._mujs)) { if (ignoreTags.has(node.tagName)) { continue; } @@ -3522,28 +3705,24 @@ function primaryFN() { } } else if (cmd === 'close') { container.remove(); - } else if (cmd === 'show-filter') { - dom.cl.toggle(fsearch, 'hidden'); } else if (cmd === 'fullscreen') { if (dom.cl.has(btnfullscreen, 'expanded')) { dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand')); } else { dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } } else if (cmd === 'hide-list') { dom.cl.add(main, 'hidden'); dom.cl.remove(mainframe, 'hidden'); - timeoutFrame(); + container.timeoutFrame(); } else if (cmd === 'save') { container.rebuild = true; - dom.prop(rateContainer, 'innerHTML', ''); + dom.prop(container.rateContainer, 'innerHTML', ''); if (!dom.prop(target, 'disabled')) { const config = await container.save(); - sleazyRedirect(); if (container.rebuild) { - container.cache.clear(); if (config.autofetch) { respHandles.build(); } @@ -3553,136 +3732,172 @@ function primaryFN() { } } else if (cmd === 'reset') { cfg = DEFAULT_CONFIG; - dom.cl.remove(mainframe, 'error'); - if (qs('.error', footer)) { - for (const elem of qsA('.error', footer)) { - elem.remove(); - } - } + dom.remove(qsA('.error', container.footer)); container.unsaved = true; container.rebuild = true; - rebuildCfg(); + reloadCfg(); } else if (cmd === 'settings') { if (container.unsaved) { showError('Unsaved changes'); } - tab.create('mujs:settings'); + Tabs.create('mujs:settings'); container.rebuild = false; } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } else if (cmd === 'switch-tab') { - tab.active(target); + Tabs.active(target); } else if (cmd === 'close-tab' && target.parentElement) { - tab.close(target.parentElement); + Tabs.close(target.parentElement); } else if (cmd === 'download-userjs') { - if (!container.userjsCache.has(+dataset.userjs)) { - return; - } const dataUserJS = container.userjsCache.get(+dataset.userjs); - let installCode = dataUserJS.code_url; - if (!prmpt && dataUserJS.code_url.endsWith('.user.css')) { - container.makePrompt('Download as UserStyle?', dataset); + if (isNull(dataUserJS)) { return; - } else if (prmpt !== prmptChoice) { - installCode = dataUserJS.code_url.replace(/\.user\.css$/, '.user.js'); } - const r = await dataUserJS._mujs.code.request(false, installCode); - const txt = r.data; - if (typeof txt !== 'string') { - return; + + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-download', + code_url: ujs.code_url, + userjs: dataset.userjs + } + }); + list.append(a); + } + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + const code_obj = await dataUserJS._mujs.code.request(false); + if (typeof code_obj.code === 'string') + doDownloadProcess({ + url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(code_obj.code), + filename: `${dataUserJS.name}${isUserCSS(dataUserJS.code_url) ? '.user.css' : '.user.js'}` + }); } - const userjsName = dataset.userjsName ?? dataset.userjs; - const userjsExt = prmpt !== prmptChoice ? '.user.js' : '.user.css'; - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = `${userjsName}${userjsExt}`; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } else if (cmd === 'load-userjs') { + } else if (cmd === 'load-userjs' || cmd === 'load-header') { if (!container.userjsCache.has(+dataset.userjs)) { return; } const codeArea = qs('textarea', target.parentElement.parentElement); - if (!isEmpty(codeArea.value)) { + if (!isEmpty(codeArea.value) && cmd === codeArea.dataset.load) { dom.cl.toggle(codeArea, 'hidden'); return; } + codeArea.dataset.load = cmd; const dataUserJS = container.userjsCache.get(+dataset.userjs); - const r = await dataUserJS._mujs.code.request(); - const txt = r.data; - if (typeof txt !== 'string') { + const code_obj = await dataUserJS._mujs.code.request(); + if (typeof code_obj.data_code_block !== 'string') { + codeArea.value = 'An error occured'; return; } - const code_obj = new ParseUserJS(txt); - codeArea.value = code_obj.get_code_block(); + codeArea.value = + cmd === 'load-userjs' ? code_obj.data_code_block : code_obj.data_meta_block; dom.cl.remove(codeArea, 'hidden'); - const apTo = (name, elem, root) => { - const n = dataUserJS._mujs.code[name]; - if (isEmpty(n)) { - const el = make('mujs-a', '', { - textContent: i18n$('listing_none') + for (const e of qsA( + 'mujs-column[data-el="matches"]', + target.parentElement.parentElement + )) { + applyTo(dataUserJS, e.dataset.type, qs('.mujs-grants', e), e); + } + } else if (cmd === 'load-page') { + if (!container.userjsCache.has(+dataset.userjs)) { + return; + } + let pageArea = qs('mujs-page', target.parentElement.parentElement); + if (!pageArea) { + pageArea = make('mujs-page'); + target.parentElement.parentElement.append(pageArea); + const dataUserJS = container.userjsCache.get(+dataset.userjs); + const engine = dataUserJS._mujs.info.engine; + let pageURL; + if (engine.name.includes('fork')) { + const { + navigator: { language } + } = _self; + const { current } = Language; + pageURL = dataUserJS.url.replace( + /\/scripts/, + `/${/^(zh|fr|es)/.test(current) ? language : current}/scripts` + ); + } else if (engine.name.includes('github')) { + const page_url = await Network.req(dataUserJS.page_url, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(() => { + return {}; }); - elem.append(el); + if (!page_url.download_url) { + return; + } + const page = await Network.req(page_url.download_url, 'GET', 'text'); + if (container.shadowSupport) { + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + innerHTML: page + }); + shadow.append(div); + } return; + } else { + pageURL = dataUserJS.url; } - dom.prop(elem, 'innerHTML', ''); - dom.cl.remove(root, 'hidden'); - for (const c of n) { - if (typeof c === 'string' && c.startsWith('http')) { - const el = make('mujs-a', '', { - textContent: c, - dataset: { - command: 'open-tab', - webpage: c - } - }); - elem.append(el); - } else if (isObj(c)) { - if (name === 'resource') { - for (const [k, v] of Object.entries(c)) { - const el = make('mujs-a', '', { - textContent: k ?? 'ERROR' - }); - if (v.startsWith('http')) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = v; - } - elem.append(el); - } - } else { - const el = make('mujs-a', '', { - textContent: c.text - }); - if (c.domain) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = `https://${c.text}`; - } - elem.append(el); + if (!pageURL) { + return; + } + const page = await Network.req(pageURL, 'GET', 'document'); + const getContent = () => { + let content = 'An error occured'; + const h = new URL(dataUserJS.url); + const root = qs('.user-content', page.documentElement); + for (const e of qsA('[href]', root)) { + e.target = '_blank'; + e.style = 'pointer-events: auto;'; + if (e.href.startsWith('/')) { + e.href = `${h.origin}${e.href}`; } + } + for (const e of qsA('img[src]', root)) { + e.style = + 'max-width: 25em; max-height: 25em; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;'; + } + if (root) { + content = root.innerHTML; } else { - const el = make('mujs-a', '', { - textContent: c - }); - elem.append(el); + content = 'No additional info available'; } + return content; + }; + if (container.shadowSupport) { + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + style: 'pointer-events: none;', + innerHTML: getContent() + }); + shadow.append(div); } - }; - const m = qsA('mujs-column[data-el="matches"]', target.parentElement.parentElement); - for (const e of normalizeTarget(m)) { - apTo(e.dataset.type, qs('.mujs-grants', e), e); + return; } + if (!dom.cl.has(pageArea, 'hidden')) { + dom.cl.add(pageArea, 'hidden'); + return; + } + dom.cl.remove(pageArea, 'hidden'); } else if (/export-/.test(cmd)) { - const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` + const toCfg = cmd === 'export-cfg'; + doDownloadProcess({ + url: + 'data:text/plain;charset=utf-8,' + + encodeURIComponent(JSON.stringify(toCfg ? cfg : cfg.theme, null, ' ')), + filename: `Magic_Userscript_${toCfg ? 'config' : 'theme'}.json` }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); } else if (/import-/.test(cmd)) { if (qs('input', target.parentElement)) { qs('input', target.parentElement).click(); @@ -3692,43 +3907,35 @@ function primaryFN() { type: 'file', accept: '.json', onchange(evt) { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - if (result.blacklist) { - log(`Imported config: { ${file.name} }`, result); - cfg = result; - container.unsaved = true; - container.rebuild = true; - rebuildCfg(); - container.save().then((config) => { - sleazyRedirect(); - container.cache.clear(); - if (config.autofetch) { - respHandles.build(); - } - container.unsaved = false; - container.rebuild = false; - }); - } else { - log(`Imported theme: { ${file.name} }`, result); - cfg.theme = result; - container.renderTheme(cfg.theme); + const file = evt.target.files[0]; + if (file === undefined || file.name === '') { + return; + } + const fr = new FileReader(); + fr.onload = function () { + if (typeof fr.result !== 'string') { + return; + } + const content = JSON.parse(fr.result); + if (content.blacklist) { + cfg = content; + container.unsaved = true; + container.rebuild = true; + reloadCfg(); + container.save().then((config) => { + if (config.autofetch) { + respHandles.build(); } - inpJSON.remove(); - }; - reader.onerror = () => { - showError(reader.error); - inpJSON.remove(); - }; - }); - } catch (ex) { - showError(ex); + container.unsaved = false; + container.rebuild = false; + }); + } else { + cfg.theme = content; + container.setTheme(); + } inpJSON.remove(); - } + }; + fr.readAsText(file); } }); target.parentElement.append(inpJSON); @@ -3750,9 +3957,9 @@ function primaryFN() { const dataset = target.dataset; const cmd = dataset.command; if (cmd === 'switch-tab' || cmd === 'close-tab') { - tab.close(target); + Tabs.close(target); } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } }); if (!isMobile) { @@ -3774,9 +3981,59 @@ function primaryFN() { }); } } + /** + * @param {CustomEvent} evt + */ + const updatedItem = (evt) => { + const ujs = evt.detail; + if (!ujs._mujs) return; + if (ujs.deleted === true) { + ujs._mujs.root.remove(); + container.userjsCache.delete(ujs.id); + Counter.reset(); + MUList.sortRecords(); + return; + } + if (!isEmpty(ujs.code_urls)) ujs.code_url = ujs.code_urls[0].code_url; + for (const elem of qsA('[data-name]', ujs._mujs.root)) { + const name = elem.dataset.name; + if (name === 'code') { + if (ujs._mujs.code.data_code_block) { + if (cfg.preview.code && !cfg.preview.metadata) { + elem.value = ujs._mujs.code.data_code_block; + } else if (cfg.preview.metadata && !cfg.preview.code) { + elem.value = ujs._mujs.code.data_meta_block; + } else { + elem.value = `${ujs._mujs.code.META_START_COMMENT}${ujs._mujs.code.data_meta_block}${ujs._mujs.code.META_END_COMMENT}${ujs._mujs.code.data_code_block}`; + } + } + continue; + } + if (!ujs[name]) continue; + if (name === 'license') { + dom.attr(elem, 'title', ujs.license ?? i18n$('no_license')); + dom.text(elem, `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`); + } else if (name === 'code_updated_at') { + dom.text(elem, Language.toDate(ujs.code_updated_at)); + elem.dataset.value = new Date(ujs.code_updated_at).toISOString(); + } else if (name === 'created_date') { + dom.text(elem, `${i18n$('created_date')}: ${Language.toDate(ujs.created_at)}`); + elem.dataset.value = new Date(ujs.created_at).toISOString(); + } else if (name === 'total_installs') { + dom.text(elem, `${i18n$('total_installs')}: ${Language.toNumber(ujs.total_installs)}`); + } else { + dom.text(elem, ujs[name]); + } + } + if (ujs._mujs.code.data_code_block) { + for (const e of qsA('mujs-column[data-el="matches"]', ujs._mujs.root)) { + applyTo(ujs, e.dataset.type, qs('.mujs-grants', e), e); + } + } + if (container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); + }; + ael(main, 'updateditem', updatedItem); // #endregion - const META_START_COMMENT = '// ==UserScript=='; - const META_END_COMMENT = '// ==/UserScript=='; const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; const APPLIES_TO_ALL_PATTERNS = [ 'http://*', @@ -3804,92 +4061,85 @@ function primaryFN() { */ data_code_block; /** - * @type { { [meta: string]: any } } + * @type { { [meta: string]: string | string[] | { [resource: string]: string } } } */ data_meta; /** - * @type { string[] } + * @type { {text: string;domain: boolean;tld_extra: boolean}[] } */ data_names; - constructor(code) { - this.code = code; - this.get_all(); - } - get_all() { - this.get_meta_block(); - this.get_code_block(); - this.parse_meta(); - this.calculate_applies_to_names(); + constructor(code, isCSS) { + this.isUserCSS = isCSS === true; + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + if (code) { + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + } } get_meta_block() { if (isEmpty(this.code)) { - return null; + return ''; } if (this.data_meta_block) { return this.data_meta_block; } - const start_block = this.code.indexOf(META_START_COMMENT); + const start_block = this.code.indexOf(this.META_START_COMMENT); if (isNull(start_block)) { - return null; + return ''; } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); if (isNull(end_block)) { - return null; + return ''; } - const meta_block = this.code.substring(start_block + META_START_COMMENT.length, end_block); + const meta_block = this.code.substring( + start_block + this.META_START_COMMENT.length, + end_block + ); this.data_meta_block = meta_block; - return meta_block; + return this.data_meta_block; } get_code_block() { if (isEmpty(this.code)) { - return null; + return ''; } if (this.data_code_block) { return this.data_code_block; } - const start_block = this.code.indexOf(META_START_COMMENT); + const start_block = this.code.indexOf(this.META_START_COMMENT); if (isNull(start_block)) { return null; } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); if (isNull(end_block)) { return null; } const code_block = this.code.substring( - end_block + META_END_COMMENT.length, + end_block + this.META_END_COMMENT.length, this.code.length ); - const lines = []; - for (const l of code_block.split('\n')) { - if (isEmpty(l)) { - continue; - } - lines.push(l); - } - const clean_code = lines.join('\n'); - this.data_code_block = clean_code; - return clean_code; + this.data_code_block = code_block.split('\n').filter(Boolean).join('\n'); + return this.data_code_block; } parse_meta() { if (isEmpty(this.code)) { - return null; + return {}; } if (this.data_meta) { return this.data_meta; } const meta = {}; - const meta_block = this.get_meta_block(); const meta_block_map = new Map(); - for (const meta_line of meta_block.split('\n')) { - const meta_match = meta_line.match(/\/\/\s+@([a-zA-Z:-]+)\s+(.*)/); - if (isNull(meta_match)) { - continue; - } - const key = meta_match[1].trim(); - const value = meta_match[2].trim(); - if (!meta_block_map.has(key)) { - meta_block_map.set(key, []); - } + const reg = (this.isUserCSS && /@([a-zA-Z:-]+)\s+(.*)/) || /\/\/\s+@([a-zA-Z:-]+)\s+(.*)/; + for (const meta_line of this.get_meta_block().split('\n').filter(Boolean)) { + let [, key, value] = reg.exec(meta_line) ?? []; + if (!key) continue; + key = key.trim(); + value = value.trim(); + if (!meta_block_map.has(key)) meta_block_map.set(key, []); const meta_map = meta_block_map.get(key); meta_map.push(value); meta_block_map.set(key, meta_map); @@ -3904,20 +4154,16 @@ function primaryFN() { this.data_meta = meta; return this.data_meta; } - /** - * @returns { string[] } - */ calculate_applies_to_names() { if (isEmpty(this.code)) { - return null; + return []; } if (this.data_names) { return this.data_names; } - const meta = this.parse_meta(); let patterns = []; - for (const [k, v] of Object.entries(meta)) { - if (/include|match/.test(k)) { + for (const [k, v] of Object.entries(this.parse_meta())) { + if (/include|match/i.test(k)) { if (Array.isArray(v)) { patterns = patterns.concat(v); } else { @@ -3929,156 +4175,192 @@ function primaryFN() { return []; } if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { - return []; + this.data_names = [ + { + domain: false, + text: 'All sites', + tld_extra: false + } + ]; + return this.data_names; } - const name_set = new Set(); + this.data_names = ParseUserJS.getNames(patterns); + return this.data_names; + } + intersect(a, ...arr) { + return !isBlank([...new Set(a)].filter((v) => arr.every((b) => b.includes(v)))); + } + static getNames(patterns = []) { + const name_map = new Map(); const addObj = (obj) => { - if (name_set.has(obj)) { + if (name_map.has(obj.text)) { return; } - name_set.add(obj); + name_map.set(obj.text, obj); }; for (let p of patterns) { - try { - const original_pattern = p; - let pre_wildcards = []; - if (p.match(/^\/(.*)\/$/)) { - pre_wildcards = [p]; - } else { - let m = p.match(/^\*(https?:.*)/i); - if (!isNull(m)) { - p = m[1]; - } - p = p - .replace(/^\*:/i, 'http:') - .replace(/^\*\/\//i, 'http://') - .replace(/^http\*:/i, 'http:') - .replace(/^(https?):([^/])/i, '$1://$2'); - m = p.match(/^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = m[1] + m[2]; - } - m = p.match(/^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/); - if (!isNull(m)) { - if (m[2].match(/A([0-9]+\.){2,}z/)) { - p = `${m[1]}tld${m[3]}`; - pre_wildcards = [p.split('*')[0]]; - } else { - pre_wildcards = [p]; - } + const original_pattern = p; + let pre_wildcards = []; + if (p.match(/^\/(.*)\/$/)) { + pre_wildcards = [p]; + } else { + let m = /^\*(https?:.*)/i.exec(p); + if (m) { + p = m[1]; + } + p = p + .replace(/^\*:/i, 'http:') + .replace(/^\*\/\//i, 'http://') + .replace(/^http\*:/i, 'http:') + .replace(/^(https?):([^/])/i, '$1://$2'); + m = /^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = m[1] + m[2]; + } + m = /^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/.exec(p); + if (m) { + if (m[2].match(/A([0-9]+\.){2,}z/)) { + p = `${m[1]}tld${m[3]}`; + pre_wildcards = [p.split('*')[0]]; } else { pre_wildcards = [p]; } + } else { + pre_wildcards = [p]; } - for (const pre_wildcard of pre_wildcards) { - try { - const urlObj = new URL(pre_wildcard); - const { host } = urlObj; - if (isNull(host)) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (!host.includes('.') && host.includes('*')) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (host.endsWith('.tld')) { - for (let i = 0; i < TLD_EXPANSION.length; i++) { - const tld = TLD_EXPANSION[i]; - addObj({ - text: host.replace(/tld$/i, tld), - domain: true, - tld_extra: i != 0 - }); - } - } else if (host.endsWith('.')) { - addObj({ - text: host.slice(0, -1), - domain: true, - tld_extra: false - }); - } else { + } + for (const pre_wildcard of pre_wildcards) { + try { + const urlObj = new URL(pre_wildcard); + const { host } = urlObj; + if (isNull(host)) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (!host.includes('.') && host.includes('*')) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.tld')) { + for (let i = 0; i < TLD_EXPANSION.length; i++) { + const tld = TLD_EXPANSION[i]; addObj({ - text: host, + text: host.replace(/tld$/i, tld), domain: true, - tld_extra: false + tld_extra: i != 0 }); } - // eslint-disable-next-line no-unused-vars - } catch (ex) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.')) { + addObj({ + text: host.slice(0, -1), + domain: true, + tld_extra: false + }); + } else { + addObj({ + text: host, + domain: true, + tld_extra: false + }); } + } catch { + addObj({ text: original_pattern, domain: false, tld_extra: false }); } - } catch (ex) { - err(ex); } } - const arr = [...name_set]; - this.data_names = arr; - return this.data_names; + return [...name_map.values()]; } - intersect(a = [], b = []) { - for (const v of a) { - if (b.includes(v)) { - return true; + /** + * @template {import("../typings/types.d.ts").GSForkQuery} O + * @param {boolean} translate + * @param {O["code_url"]} code_url + * @param {O} [obj] + */ + async request(translate = false, code_url, obj) { + if (this.data_code_block) { + return this; + } + /** @type { string } */ + const code = await Network.req(code_url, 'GET', 'text').catch(err); + if (typeof code !== 'string') { + return this; + } + this.isUserCSS = isUserCSS(code_url); + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + + const { data_meta } = this; + if (translate) { + if (data_meta[`name:${Language.current}`]) { + Object.assign(obj, { + name: data_meta[`name:${Language.current}`] + }); + this.translated = true; + } + if (data_meta[`description:${Language.current}`]) { + Object.assign(obj, { + description: data_meta[`description:${Language.current}`] + }); + this.translated = true; } } - for (const v of b) { - if (a.includes(v)) { - return true; + if (Array.isArray(data_meta.grant)) { + data_meta.grant = union(data_meta.grant); + } + if (data_meta.resource) { + const obj = {}; + if (typeof data_meta.resource === 'string') { + const [, key, value] = /(.+)\s+(.+)/.exec(data_meta.resource) ?? []; + if (key) { + obj[key.trim()] = value; + } + } else { + for (const r of data_meta.resource) { + const [, key, value] = /(.+)\s+(http.+)/.exec(r) ?? []; + if (key) { + obj[key.trim()] = value; + } + } } + data_meta.resource = obj; } - return false; + Object.assign(this, { + code_size: [Network.format(code.length)], + meta: data_meta + }); + + return this; } - // intersect(a, ...arr) { - // return [...new Set(a)].filter((v) => arr.every((b) => b.includes(v))); - // } } - const mergeTemplate = (obj = {}) => { - const template = { - id: 0, - bad_ratings: 0, - good_ratings: 0, - ok_ratings: 0, - daily_installs: 0, - total_installs: 0, - name: 'NOT FOUND', - description: 'NOT FOUND', - version: '0.0.0', - url: BLANK_PAGE, - code_url: BLANK_PAGE, - created_at: Date.now(), - code_updated_at: Date.now(), - locale: 'NOT FOUND', - deleted: false, - users: [ - { - id: 0, - name: 'NOT FOUND', - url: BLANK_PAGE - } - ] - }; - /** @type {ObjectConstructor["hasOwn"]} */ - const hasOwn = - Object.hasOwn || - function hasOwn(it, key) { - return Object.prototype.hasOwnProperty.call(it, key); - }; - for (const key in template) { - if (hasOwn(obj, key)) continue; - obj[key] = template[key]; - } - return obj; + const template = { + id: 0, + bad_ratings: 0, + good_ratings: 0, + ok_ratings: 0, + daily_installs: 0, + total_installs: 0, + name: 'NOT FOUND', + description: 'NOT FOUND', + version: '0.0.0', + url: BLANK_PAGE, + code_url: BLANK_PAGE, + created_at: Date.now(), + code_updated_at: Date.now(), + locale: 'NOT FOUND', + deleted: false, + users: [] }; const mkList = (txt = '', obj = {}) => { - if (!obj.root || !obj.type) { - return; - } + if (!obj.root || !obj.type) return; const { root, type } = obj; const appliesTo = make('mu-js', 'mujs-list', { textContent: `${txt}: ` @@ -4092,10 +4374,9 @@ function primaryFN() { }); ujsURLs.append(appliesTo, applyList); root.append(ujsURLs); - const list = obj.list ?? []; if (isEmpty(list)) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: i18n$('listing_none') }); applyList.append(elem); @@ -4104,7 +4385,7 @@ function primaryFN() { } for (const c of list) { if (typeof c === 'string' && c.startsWith('http')) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c, dataset: { command: 'open-tab', @@ -4115,7 +4396,7 @@ function primaryFN() { } else if (isObj(c)) { if (type === 'resource') { for (const [k, v] of Object.entries(c)) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: k ?? 'ERROR' }); if (v.startsWith('http')) { @@ -4125,7 +4406,7 @@ function primaryFN() { applyList.append(elem); } } else { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c.text }); if (c.domain) { @@ -4135,55 +4416,45 @@ function primaryFN() { applyList.append(elem); } } else { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c }); applyList.append(elem); } } }; - /** - * @param {number} [time] - */ - const timeoutFrame = async (time) => { - frameTimeout.clear(...frameTimeout.ids); - if (dom.cl.has(mainframe, 'hidden')) { - return; - } - time = time ?? cfg.time ?? DEFAULT_CONFIG.time; - let n = 10000; - if (typeof time === 'number' && !Number.isNaN(time)) { - n = container.isBlacklisted ? time / 2 : time; - } - await frameTimeout.set(n); - container.remove(); - return frameTimeout.clear(...frameTimeout.ids); - }; // #region Create UserJS /** * @param { import("../typings/types.d.ts").GSForkQuery } ujs * @param { string } engine */ const createjs = (ujs, engine) => { - ujs = mergeTemplate(ujs); - // Lets not add this UserJS to the list - if (ujs.id === 421603) { - return; - } - if (badUserJS.includes(ujs.id) || badUserJS.includes(ujs.url)) { - return; - } - if (!container.userjsCache.has(ujs.id)) { - container.userjsCache.set(ujs.id, ujs); - } + const a = [ + ujs.deleted === true, + ujs.id === 421603, // Lets not add this UserJS to the list + badUserJS.includes(ujs.id), + badUserJS.includes(ujs.url) + ].some((t) => t === true); + if (a) return; + if (!container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); const eframe = make('td', 'install-btn'); const uframe = make('td', 'mujs-uframe'); const fdaily = make('td', 'mujs-list', { - textContent: ujs.daily_installs + textContent: ujs.daily_installs, + dataset: { + name: 'daily_installs' + } }); const fupdated = make('td', 'mujs-list', { - textContent: language.toDate(ujs.code_updated_at) + textContent: Language.toDate(ujs.code_updated_at), + dataset: { + name: 'code_updated_at', + value: new Date(ujs.code_updated_at).toISOString() + } }); + /** + * @type { import("../typings/UserJS.d.ts").mujsName } + */ const fname = make('td', 'mujs-name'); const fmore = make('mujs-column', 'mujs-list hidden', { dataset: { @@ -4206,16 +4477,24 @@ function primaryFN() { textContent: `${i18n$('version_number')}: ${ujs.version}` }); const fcreated = make('mu-js', 'mujs-list', { - textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}` + textContent: `${i18n$('created_date')}: ${Language.toDate(ujs.created_at)}`, + dataset: { + name: 'created_at', + value: new Date(ujs.created_at).toISOString() + } }); const flicense = make('mu-js', 'mujs-list', { title: ujs.license ?? i18n$('no_license'), textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`, - style: - 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: fit-content; max-width: 20em;' + dataset: { + name: 'license' + } }); const ftotal = make('mu-js', 'mujs-list', { - textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}` + textContent: `${i18n$('total_installs')}: ${Language.toNumber(ujs.total_installs)}`, + dataset: { + name: 'total_installs' + } }); const fratings = make('mu-js', 'mujs-list', { title: i18n$('ratings'), @@ -4225,6 +4504,7 @@ function primaryFN() { title: i18n$('good'), textContent: ujs.good_ratings, dataset: { + name: 'good_ratings', el: 'good' } }); @@ -4232,6 +4512,7 @@ function primaryFN() { title: i18n$('ok'), textContent: ujs.ok_ratings, dataset: { + name: 'ok_ratings', el: 'ok' } }); @@ -4239,6 +4520,7 @@ function primaryFN() { title: i18n$('bad'), textContent: ujs.bad_ratings, dataset: { + name: 'bad_ratings', el: 'bad' } }); @@ -4254,11 +4536,11 @@ function primaryFN() { title: `${i18n$('install')} "${ujs.name}"`, dataset: { command: 'install-script', - userjs: ujs.code_url + userjs: ujs.id } }); - const scriptDownload = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('install')} ${i18n$('saveFile')}`, + const scriptDownload = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('download')} ${i18n$('saveFile')}`, dataset: { command: 'download-userjs', userjs: ujs.id, @@ -4267,6 +4549,7 @@ function primaryFN() { }); const tr = make('tr', 'frame', { dataset: { + engine, scriptId: ujs.id } }); @@ -4279,27 +4562,25 @@ function primaryFN() { spellcheck: false, wrap: 'soft' }); - const loadCode = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('search')} ${i18n$('preview_code')}`, + const loadCode = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} ${i18n$('code')}`, dataset: { command: 'load-userjs', userjs: ujs.id } }); - if (engine) { - tr.dataset.engine = engine; - // if (engine.includes('fork')) { - // fdaily.dataset.command = 'open-tab'; - // fdaily.dataset.webpage = `${ujs.url}/stats`; - // fupdated.dataset.command = 'open-tab'; - // fupdated.dataset.webpage = `${ujs.url}/versions`; - // } - if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { - tr.dataset.good = 'upsell'; + const loadMetadata = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} ${i18n$('metadata')}`, + dataset: { + command: 'load-header', + userjs: ujs.id } + }); + if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { + tr.dataset.good = 'upsell'; } for (const u of ujs.users) { - const user = make('mujs-a', '', { + const user = make('mujs-a', { innerHTML: u.name, title: u.url, dataset: { @@ -4307,13 +4588,16 @@ function primaryFN() { webpage: u.url } }); - if (cfg.recommend.author && u.id === authorID) { + if ( + cfg.recommend.author && + (u.id === authorID || u.url === 'https://github.com/magicoflolis') + ) { tr.dataset.author = 'upsell'; dom.prop(user, 'innerHTML', `${u.name} ${iconSVG.load('verified')}`); } uframe.append(user); } - if (engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.id)) { + if (cfg.recommend.others && goodUserJS.includes(ujs.id)) { tr.dataset.good = 'upsell'; } eframe.append(scriptInstall); @@ -4321,376 +4605,331 @@ function primaryFN() { jsInfo.append(ftotal, ratings, fver, fcreated); mkList(i18n$('code_size'), { list: ujs._mujs.code.code_size, - type: 'size', + type: 'code_size', root: jsInfo }); jsInfoB.append(flicense); + const data_meta = ujs._mujs.code?.data_meta ?? {}; mkList(i18n$('antifeatures'), { - list: ujs._mujs.code.antifeatures, + list: data_meta.antifeatures ?? [], type: 'antifeatures', root: jsInfoB }); mkList(i18n$('applies_to'), { - list: ujs._mujs.code.match, - type: 'match', + list: ujs._mujs.code?.data_names ?? [], + type: 'data_names', root: jsInfoB }); mkList('@grant', { - list: ujs._mujs.code.grant, + list: data_meta.grant ?? [], type: 'grant', root: jsInfoB }); mkList('@require', { - list: ujs._mujs.code.meta?.require, + list: data_meta.require, type: 'require', root: jsInfoB }); - const script_resources = ujs._mujs.code.meta?.resource; mkList('@resource', { - list: isNull(script_resources) ? [] : [script_resources], + list: isNull(data_meta.resource) ? [] : [data_meta.resource], type: 'resource', root: jsInfoB }); fmore.append(jsInfo, jsInfoB); - fBtns.append(scriptDownload, loadCode); + fBtns.append(scriptDownload, loadCode, loadMetadata); fname.append(ftitle, fdesc, fmore, fBtns, codeArea); + fname._mujs = { fmore, fBtns, codeArea }; - const code_data = ujs._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - codeArea.value = code_obj.get_code_block(); - } + const loadPage = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('pager')} Page`, + dataset: { + command: 'load-page', + userjs: ujs.id + } + }); + fBtns.append(loadPage); - if (ujs._mujs.code?.translated) { - tr.classList.add('translated'); - } + if (ujs._mujs.code?.translated) tr.classList.add('translated'); - for (const e of [fname, uframe, fdaily, fupdated, eframe]) { - tr.append(e); - } - tabbody.append(tr); + for (const e of [fname, uframe, fdaily, fupdated, eframe]) tr.append(e); ujs._mujs.root = tr; - - return tr; + return ujs._mujs.root; }; // #endregion const loadFilters = () => { - const resp = []; - for (const v of Object.values(cfg.filters)) { - if (!v.enabled) { - continue; - } - resp.push(new RegExp(v.regExp, v.flag)); - } - return { - regExp: resp, - get(name) { - for (const v of Object.values(cfg.filters)) { - if (v.name === name) { - return new RegExp(v.regExp, v.flag) - }; + /** @type {Map} */ + const pool = new Map(); + const handles = { + pool, + enabled() { + return [...pool.values()].filter((o) => o.enabled); + }, + refresh() { + if (!Object.is(pool.size, 0)) pool.clear(); + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); } - // return resp.find(r => r.name === name); + return this; + }, + get(str) { + return [...pool.values()].find((v) => v.keyReg.test(str) || v.valueReg.test(str)); }, + /** + * @param { import("../typings/types.d.ts").GSForkQuery } param0 + */ match({ name, users }) { - if (isBlank(resp)) { - return true; - } - const strArr = [{ name }, ...users]; - for (const r of resp) { - const v = strArr.find((o) => o.name.match(r)); - if (v) { - return false; - } + const p = handles.enabled(); + if (Object.is(p.length, 0)) return true; + for (const v of p) { + if ([{ name }, ...users].find((o) => o.name.match(v.reg))) return false; } return true; } }; + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); + } + return handles.refresh(); }; // #region List - class List { - engines; - cache; - intHost; - constructor(host = undefined) { + /** + * @type { typeof import("../typings/UserJS.d.ts").List } + */ + const List = class { + #intEngines; + #intHost; + constructor(hostname = undefined) { this.build = this.build.bind(this); - if (isEmpty(host)) host = container.host; - this.engines = cfg.engines; - this.cache = container.cache.get(host); - this.host = host; + this.groupBy = this.groupBy.bind(this); + this.dispatch = this.dispatch.bind(this); + this.sortRecords = this.sortRecords.bind(this); + this.#intEngines = cfg.engines ?? []; + this.setHost(hostname); } - set host(hostname) { - this.intHost = hostname; - - if (!container.cache.has(hostname)) { - const engineTemplate = {}; - for (const engine of cfg.engines) { - engineTemplate[engine.name] = []; + setEngines(engines = []) { + const { host } = this; + const e = engines.filter((e) => { + if (!e.enabled) { + return false; } - container.cache.set(hostname, engineTemplate); - } - if (container.checkBlacklist(hostname)) { + const v = engineUnsupported[e.name] ?? []; + if (v.includes(host)) { + showError(`Engine: "${e.name}" unsupported on "${host}"`); + container.timeoutFrame(); + return false; + } + return true; + }); + this.#intEngines = e; + return e; + } + + setHost(hostname) { + if (isEmpty(hostname)) hostname = container.host; + this.#intHost = hostname; + this.blacklisted = container.checkBlacklist(hostname); + this.#intEngines = this.setEngines(this.engines); + this.domain = this.getDomain(this.#intHost); + if (this.blacklisted) { showError(`Blacklisted "${hostname}"`); - timeoutFrame(); - this.blacklisted = true; - } else { - this.blacklisted = false; + container.timeoutFrame(); } + return hostname; + } - const isSupported = (name) => { - for (const [k, v] of Object.entries(engineUnsupported)) { - if (k !== name) { - continue; - } - if (v.includes(hostname)) { - return false; - } - } - return true; - }; - this.engines = cfg.engines.filter((e) => e.enabled && isSupported(e.name)); - this.cache = container.cache.get(hostname); + dispatch(ujs) { + const { CustomEvent } = _self; + const customEvent = new CustomEvent('updateditem', { detail: ujs }); + main.dispatchEvent(customEvent); + } + + get engines() { + return this.#intEngines; } get host() { - return this.intHost; + return this.#intHost; + } + + getDomain(str = '') { + if (str === '*') { + return 'all-sites'; + } + return str.split('.').at(-2) ?? BLANK_PAGE; } // #region Builder build() { try { container.refresh(); - if (this.blacklisted) { - return; - } - if (isEmpty(this.engines)) { + const { blacklisted, engines, host, domain, dispatch } = this; + if (blacklisted || isEmpty(engines)) { container.opacityMin = '0'; mainframe.style.opacity = container.opacityMin; return; } - info(' Building list', { cache: this.cache, engines: this.engines }); - const customRecords = []; - const host = this.host; - const arr = []; + const fetchRecords = []; const bsFilter = loadFilters(); + const hostCache = Array.from(this); - for (const engine of this.engines) { - const cEngine = this.cache[`${engine.name}`]; - if (!isEmpty(cEngine)) { - for (const ujs of cEngine) { - if (isElem(ujs._mujs.root)) { - tabbody.append(ujs._mujs.root); - } - } - updateCounter(cEngine.length, engine); - continue; - } - const respError = (error) => { - if (!error.cause) error.cause = engine.name; - if (error.message.startsWith('429')) { - showError(`Engine: "${engine.name}" Too many requests...`); - return; - } - showError(`Engine: "${engine.name}"`, error.message); - }; - const _mujs = (d) => { - const obj = { - ...d, - _mujs: { - root: {}, - info: { - engine, - host - }, - code: { - antifeatures: [], - grant: [], - match: [], - meta: {}, - request: async function (translate = false, code_url) { - if (this.data) { - return this; - } - this.data = ''; - /** @type { string } */ - const code = await Network.req(code_url ?? d.code_url, 'GET', 'text').catch( - showError - ); - if (typeof code !== 'string') { - return this; - } - Object.assign(this, { - data: code, - code_size: [Network.format(code.length)] - }); - const grantSet = new Set(); - const afSet = new Set(); - const code_obj = new ParseUserJS(code); - const meta = code_obj.parse_meta(); - const applies_to_names = code_obj.calculate_applies_to_names(); - - if (translate) { - for (const lng of language.cache) { - if (meta[`name:${lng}`]) { - Object.assign(obj, { - name: meta[`name:${lng}`] - }); - this.translated = true; - } - if (meta[`description:${lng}`]) { - Object.assign(obj, { - description: meta[`description:${lng}`] - }); - this.translated = true; - } - } - } - - for (const [key, value] of Object.entries(meta)) { - if (/grant/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (grantSet.has(v)) { - continue; - } - grantSet.add(v); - } - } else if (/antifeature/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (afSet.has(v)) { - continue; - } - afSet.add(v); - } - } - } - if (typeof meta.require === 'string') { - const s = meta.require; - meta.require = [s]; - } - if (meta.resource) { - const obj = {}; - if (typeof meta.resource === 'string') { - const reg = /(.+)\s+(.+)/.exec(meta.resource); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } - } else { - for (const r of meta.resource) { - const reg = /(.+)\s+(http.+)/.exec(r); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } - } - } - meta.resource = obj; - } - Object.assign(this, { - meta, - match: applies_to_names, - grant: [...grantSet], - antifeatures: [...afSet] - }); + info('Building list', { hostCache, engines, container, list: this }); - return this; + const g = this.groupBy(); + const toFetch = engines.filter((engine) => !g[engine.name]); + const isCached = toFetch.filter((engine) => + hostCache.find(({ _mujs }) => engine.name === _mujs.info.engine.name) + ); + if (!isBlank(toFetch) && isBlank(isCached)) { + for (const engine of engines) { + info(`Fetching from "${engine.name}" for "${host}"`); + const respError = (error) => { + if (!error.cause) error.cause = engine.name; + if (error.message.startsWith('429')) { + showError(`Engine: "${engine.name}" Too many requests...`); + return; + } + showError(`Engine: "${engine.name}"`, error.message); + }; + const _mujs = (d) => { + /** + * @type {import("../typings/types.d.ts").GSForkQuery} + */ + const ujs = { + ...template, + ...d, + code_urls: [], + _mujs: { + root: {}, + info: { + engine, + host + }, + code: { + meta: {} } } - } + }; + ujs._mujs.code.request = async (translate = false, code_url) => { + if (typeof ujs._mujs.code.data_code_block === 'string') { + return ujs._mujs.code; + } + const p = new ParseUserJS(); + await p.request(translate, code_url ?? ujs.code_url, ujs); + if (code_url) { + return p; + } + for (const [k, v] of Object.entries(p)) ujs._mujs.code[k] = v; + return ujs._mujs.code; + }; + return ujs; }; - return obj; - }; - const toQuery = (fallback) => { - if (engine.query) { - return decodeURIComponent(engine.query).replace(/\{host\}/g, host); - } - return fallback; - }; - /** - * @param { import("../typings/types.d.ts").GSFork } dataQ - */ - const forkFN = async (dataQ) => { - if (!dataQ) { - showError('Invalid data received from the server, check internet connection'); - return; - } - /** - * @type { import("../typings/types.d.ts").GSForkQuery[] } - */ - const dq = Array.isArray(dataQ) - ? dataQ - : Array.isArray(dataQ.query) - ? dataQ.query - : []; - const dataA = dq - .filter(Boolean) - .filter((d) => !d.deleted) - .filter(bsFilter.match); - if (isBlank(dataA)) { - return; - } - const data = dataA.map(_mujs); - const otherLng = []; /** - * @param {import("../typings/types.d.ts").GSForkQuery} d - * @returns {boolean} + * Prior to UserScript v7.0.0 + * @template {string} F + * @param {F} fallback + * @returns {F} */ - const inUserLanguage = (d) => { - const dlocal = d.locale.split('-')[0] ?? d.locale; - if (language.cache.includes(dlocal)) { - return true; + const toQuery = (fallback) => { + if (engine.query) { + return decode(engine.query) + .replace(/\{host\}/g, host) + .replace(/\{domain\}/g, domain); } - otherLng.push(d); - return false; + return fallback; }; - const filterLang = data.filter((d) => { - if (cfg.filterlang && !inUserLanguage(d)) { - return false; - } - return true; - }); - let finalList = filterLang; - const hds = []; - for (const ujs of otherLng) { - const c = await ujs._mujs.code.request(true); - if (c.translated) { - hds.push(ujs); - } - } - finalList = union(hds, filterLang); - - for (const ujs of finalList) { - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + /** + * @param { import("../typings/types.d.ts").GSFork } dataQ + */ + const forkFN = async (dataQ) => { + if (!dataQ) { + showError('Invalid data received from the server, check internet connection'); + return; } - createjs(ujs, engine.name); - } - this.cache[engine.name].push(...finalList); - updateCounter(finalList.length, engine); - }; - /** - * @param {Document} htmlDocument - */ - const openuserjs = async (htmlDocument) => { - try { - if (!htmlDocument) { - showError('Invalid data received from the server, TODO fix this'); + const dq = Array.isArray(dataQ) + ? dataQ + : Array.isArray(dataQ.query) + ? dataQ.query + : []; + const dataA = dq + .filter(Boolean) + .filter((d) => !d.deleted) + .filter(bsFilter.match); + if (isBlank(dataA)) { return; } - const selected = htmlDocument.documentElement; - if (/openuserjs/gi.test(engine.name)) { - for (const i of qsA('.col-sm-8 .tr-link', selected)) { - while (isNull(qs('.script-version', i))) { - await new Promise((resolve) => requestAnimationFrame(resolve)); + const { groupBy } = _self; + const g = groupBy(dataA.map(_mujs), ({ locale }) => { + const [current = locale] = locale.split('-'); + return current; + }); + for (const [k, list] of Object.entries(g)) { + if (!list) break; + for (const ujs of list) { + if (cfg.filterlang && k !== Language.current) { + const c = await ujs._mujs.code.request(true); + if (!c.translated) continue; + } + if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); } - const fixurl = dom - .prop(qs('.tr-link-a', i), 'href') - .replace( - new RegExp(document.location.origin, 'gi'), - 'https://openuserjs.org' + if (isUserCSS(ujs.code_url)) { + ujs.code_urls.push( + { + name: `${ujs.name} (.user.css)`, + code_url: ujs.code_url + }, + { + name: `${ujs.name} (.user.js)`, + code_url: ujs.code_url.replace(/\.user\.css$/, '.user.js') + } ); - const ujs = mergeTemplate( - _mujs({ + } + createjs(ujs, engine.name); + } + } + }; + /** + * @param {Document} htmlDocument + */ + const openuserjs = async (htmlDocument) => { + try { + if (!htmlDocument) { + showError('Invalid data received from the server, TODO fix this'); + return; + } + const d = htmlDocument.documentElement; + if (/openuserjs/gi.test(engine.name)) { + const col = qsA('.col-sm-8 .tr-link', d) ?? []; + for (const i of col) { + while (isNull(qs('.script-version', i))) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } + const fixurl = dom + .prop(qs('.tr-link-a', i), 'href') + .replace( + new RegExp(document.location.origin, 'gi'), + 'https://openuserjs.org' + ); + const ujs = _mujs({ name: dom.text(qs('.tr-link-a', i)), description: dom.text(qs('p', i)), version: dom.text(qs('.script-version', i)), @@ -4705,130 +4944,209 @@ function primaryFN() { url: dom.prop(qs('.inline-block a', i), 'href') } ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + }); + if (bsFilter.match(ujs)) { + continue; + } + if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); + } + createjs(ujs, engine.name); } - createjs(ujs, engine.name); - customRecords.push(ujs); } + } catch (ex) { + showError(ex); } - this.cache[engine.name].push(...customRecords); - updateCounter(customRecords.length, engine); - } catch (ex) { - showError(ex); - } - }; - const gitFN = async (data) => { - try { - if (isBlank(data.items)) { - showError('Invalid data received from the server, TODO fix this'); - return; - } - for (const r of data.items) { - const ujs = mergeTemplate( - _mujs({ + }; + const gitFN = (data) => { + try { + if (isBlank(data.items)) { + return; + } + for (const r of data.items) { + const ujs = _mujs({ + id: r.id ?? 0, name: r.name, - description: isEmpty(r.repository.description) - ? i18n$('no_license') - : r.repository.description, + description: isEmpty(r.description) ? i18n$('no_license') : r.description, url: r.html_url, - code_url: r.html_url.replace(/\/blob\//g, '/raw/'), - code_updated_at: r.commit || Date.now(), - total_installs: r.score, + code_url: r.html_url, + page_url: `${r.url}/contents/README.md`, + created_at: r.created_at, + code_updated_at: r.updated_at || Date.now(), + daily_installs: r.watchers_count ?? 0, + good_ratings: r.stargazers_count ?? 0, users: [ { - name: r.repository.owner.login, - url: r.repository.owner.html_url + name: r.owner.login, + url: r.owner.html_url } ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + }); + if (r.license?.name) ujs.license = r.license.name; + const rootPath = r.contents_url.replace(/\{\+path\}/, ''); + const fetchContent = async (dir) => { + const contents = await Network.req(dir, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(respError); + for (const content of contents) { + if (content.type === 'file') { + if (isUserJS(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } else if (isUserCSS(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } + } else if (content.type === 'dir') { + await fetchContent(`${rootPath}/${content.path}`); + } + } + }; + fetchContent(rootPath).then(() => { + if (isEmpty(ujs.code_urls)) { + ujs.deleted = true; + } else if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); + return; + } + dispatch(ujs); + }); + createjs(ujs, engine.name); } - createjs(ujs, engine.name); - customRecords.push(ujs); + } catch (ex) { + showError(ex); } - this.cache[engine.name].push(...customRecords); - updateCounter(data.items.length, engine); - } catch (ex) { - showError(ex); - } - }; - let netFN; - if (/github/gi.test(engine.name)) { - if (isEmpty(engine.token)) { - showError(`"${engine.name}" requires a token to use`); - continue; - } - netFN = Network.req( - toQuery( - `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30` - ), - 'GET', - 'json', - { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${engine.token}`, - 'X-GitHub-Api-Version': '2022-11-28' - } + }; + let netFN; + if (/github/gi.test(engine.name)) { + if (isEmpty(engine.token)) { + showError(`"${engine.name}" requires a token to use`); + continue; } - ) - .then(gitFN) - .then(() => { - Network.req('https://api.github.com/rate_limit', 'GET', 'json', { + Network.req( + `https://api.github.com/search/repositories?q=topic:${domain}+topic:userstyle`, + 'GET', + 'json', + { headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${engine.token}`, 'X-GitHub-Api-Version': '2022-11-28' } - }) - .then((data) => { - for (const [key, value] of Object.entries(data.resources.code_search)) { - const txt = make('mujs-row', 'rate-info', { - textContent: `${key.toUpperCase()}: ${value}` - }); - rateContainer.append(txt); + } + ) + .then(gitFN) + .catch(respError); + netFN = Network.req( + toQuery( + `https://api.github.com/search/repositories?q=topic:${domain}+topic:userscript` + ), + 'GET', + 'json', + { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ) + .then(gitFN) + .then(() => { + Network.req('https://api.github.com/rate_limit', 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' } }) - .catch(respError); - }); - } else if (/openuserjs/gi.test(engine.name)) { - netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( - openuserjs - ); - } else { - netFN = Network.req( - toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) - ).then(forkFN); - } - if (netFN) { - arr.push(netFN.catch(respError)); + .then((data) => { + for (const [key, value] of Object.entries(data.resources.code_search)) { + const txt = make('mujs-row', 'rate-info', { + textContent: `${key.toUpperCase()}: ${value}` + }); + container.rateContainer.append(txt); + } + }) + .catch(respError); + }); + } else if (/openuserjs/gi.test(engine.name)) { + netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( + openuserjs + ); + } else { + netFN = Network.req( + toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) + ).then(forkFN); + } + if (netFN) { + fetchRecords.push(netFN.catch(respError)); + } } + } else { + for (const ujs of hostCache) container.tabbody.append(ujs._mujs.root); } - urlBar.placeholder = i18n$('search_placeholder'); - urlBar.value = ''; + container.urlBar.placeholder = i18n$('search_placeholder'); + container.urlBar.value = ''; - Promise.allSettled(arr).then(() => { - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - }); + if (isBlank(fetchRecords)) { + this.sortRecords(); + return; + } + Promise.allSettled(fetchRecords).then(this.sortRecords).catch(showError); } catch (ex) { showError(ex); } } // #endregion - } + + sortRecords() { + const arr = Array.from(this); + for (const ujs of arr.flat().sort((a, b) => { + const sortType = cfg.autoSort ?? 'daily_installs'; + return b[sortType] - a[sortType]; + })) { + if (isElem(ujs._mujs.root)) container.tabbody.append(ujs._mujs.root); + } + for (const [name, value] of Object.entries(this.groupBy(arr))) + Counter.update(value.length, { name }); + } + + groupBy() { + return _self.groupBy(Array.from(this), ({ _mujs }) => _mujs.info.engine.name); + } + + *[Symbol.iterator]() { + const { host, engines } = this; + const arr = Array.from(container).filter( + ({ _mujs }) => + _mujs.info.host === host && + engines.find((engine) => engine.enabled && engine.name === _mujs.info.engine.name) + ); + for (const userjs of arr) { + yield userjs; + } + } + }; const MUList = new List(); // #endregion // #region Make Config @@ -4847,21 +5165,23 @@ function primaryFN() { command: 'reset' } }); + cbtn.append(resetbtn, savebtn); + const makesection = (name, tag) => { tag = tag ?? i18n$('no_license'); name = name ?? i18n$('no_license'); - const sec = make('mujs-section', '', { + const sec = make('mujs-section', { dataset: { name: tag } }); - const lb = make('label', '', { + const lb = make('label', { dataset: { command: tag } }); - const divDesc = make('mu-js', '', { - textContent: name + const divDesc = make('mu-js', { + innerHTML: name }); ael(sec, 'click', (evt) => { /** @type { HTMLElement } */ @@ -4871,7 +5191,7 @@ function primaryFN() { } const cmd = target.dataset.command; if (cmd === tag) { - const a = qsA(`label[data-${tag}]`, sec); + const a = qsA(`[data-${tag}]`, sec); if (dom.cl.has(a, 'hidden')) { dom.cl.remove(a, 'hidden'); } else { @@ -4879,65 +5199,142 @@ function primaryFN() { } } }); - lb.append(divDesc); sec.append(lb); - cfgpage.append(sec); + container.cfgpage.append(sec); + if(!cfgSec.has(sec)) cfgSec.add(sec); return sec; }; - const sections = { general: makesection('General', 'general'), load: makesection('Automation', 'load'), list: makesection('List', 'list'), - filters: makesection('List Filters', 'filters'), + filters: makesection( + `List Filters ${iconSVG.load('info', { dataset: { command: 'more-info', webpage: 'https://greasyfork.org/scripts/12179' } })}`, + 'filters' + ), blacklist: makesection('Blacklist (WIP)', 'blacklist'), - // engine: makesection('Search Engine', 'engine'), - theme: makesection('Theme Colors', 'theme') + engine: makesection('Search Engines', 'engine'), + theme: makesection('Theme Colors', 'theme'), + exp: makesection('Import / Export', 'exp') }; - - const makerow = (desc, type = null, nm, attrs = {}) => { - desc = desc ?? i18n$('no_license'); - nm = nm ?? i18n$('no_license'); - const sec = make('mujs-section', '', { + const makeRow = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { + const [name, CONFIG, SUB_CONFIG] = /^(\w+)-(.+)/.exec(value) ?? []; + const lb = make('label', 'sub-section hidden', { dataset: { - name: nm + [tag]: text } }); - const lb = make('label'); - const divDesc = make('mu-js', '', { - textContent: desc + const txt = make('mu-js', { + innerHTML: text }); - lb.append(divDesc); - sec.append(lb); - cfgpage.append(sec); - if (isNull(type)) { + lb.append(txt); + const getDefault = () => { + if (tag === 'engine') { + const engine = DEFAULT_CONFIG.engines.find((engine) => engine.name === value); + if (engine) { + return engine; + } + } + if (CONFIG) return DEFAULT_CONFIG[CONFIG][SUB_CONFIG]; + return DEFAULT_CONFIG[value]; + }; + const getValue = () => { + if (tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === value); + if (engine) { + return engine; + } + } + if (CONFIG) return cfg[CONFIG][SUB_CONFIG]; + return cfg[value]; + }; + const obj = { + text, + tag, + value, + type, + attrs, + default: getDefault(), + cache: getValue() + }; + if (type === 'select') { + const inp = make('select', { + dataset: { + [tag]: text + }, + ...attrs + }); + for (const selV of Object.keys(template)) { + if (selV === 'deleted' || selV === 'users') continue; + const o = make('option', { + value: selV, + textContent: selV + }); + inp.append(o); + } + inp.value = cfg[value]; + lb.append(inp); + if (sections[tag]) { + sections[tag].append(lb); + } + obj.elem = inp; + cfgBase.push(obj); return lb; } - const inp = make('input', '', { + const inp = make('input', { type, dataset: { - name: nm + [tag]: text }, ...attrs }); - if (!cfgMap.has(nm)) { - cfgMap.set(nm, inp); + + if (tag === 'engine') { + inp.dataset.name = value; } + + if (sections[tag]) { + sections[tag].append(lb); + } + if (type === 'checkbox') { const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { + const la = make('label', { onclick() { inp.dispatchEvent(new MouseEvent('click')); } }); inlab.append(inp, la); lb.append(inlab); - if (nm.includes('-')) { - return inp; + + if (CONFIG) { + if (CONFIG === 'filters') { + inp.checked = cfg[CONFIG][SUB_CONFIG].enabled; + } else { + inp.checked = cfg[CONFIG][SUB_CONFIG]; + } + } else { + inp.checked = cfg[value]; } - if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { - const engine = cfg.engines.find((engine) => engine.name === nm); + ael(inp, 'change', (evt) => { + container.unsaved = true; + if (/filterlang/i.test(value)) { + container.rebuild = true; + } + if (CONFIG) { + if (CONFIG === 'filters') { + cfg[CONFIG][SUB_CONFIG].enabled = evt.target.checked; + } else { + cfg[CONFIG][SUB_CONFIG] = evt.target.checked; + } + } else { + cfg[value] = evt.target.checked; + } + }); + + if (tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === value); if (engine) { inp.checked = engine.enabled; inp.dataset.engine = engine.name; @@ -4945,17 +5342,18 @@ function primaryFN() { container.unsaved = true; container.rebuild = true; engine.enabled = evt.target.checked; + MUList.setEngines(cfg.engines); }); if (engine.query) { const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name); - const urlInp = make('input', '', { + const urlInp = make('input', { type: 'text', defaultValue: '', - value: decodeURIComponent(engine.query) ?? '', - placeholder: decodeURIComponent(d.query) ?? '', + value: decode(engine.query), + placeholder: decode(d.query), dataset: { - name: nm, + name, engine: engine.name }, onchange(evt) { @@ -4963,15 +5361,17 @@ function primaryFN() { container.rebuild = true; try { engine.query = encodeURIComponent(new URL(evt.target.value).toString()); + MUList.setEngines(cfg.engines); } catch (ex) { err(ex); } } }); - sec.append(urlInp); + obj.elemUrl = urlInp; + lb.append(urlInp); } if (engine.name === 'github') { - const ghToken = make('input', '', { + const ghToken = make('input', { type: 'text', defaultValue: '', value: engine.token ?? '', @@ -4983,100 +5383,72 @@ function primaryFN() { container.unsaved = true; container.rebuild = true; engine.token = evt.target.value; + MUList.setEngines(cfg.engines); } }); - sec.append(ghToken); - cfgMap.set('github-token', ghToken); + obj.elemToken = ghToken; + lb.append(ghToken); } } - } else { - inp.checked = cfg[nm]; - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(nm)) { - container.rebuild = true; - } - cfg[nm] = evt.target.checked; - }); } - } else { - lb.append(inp); - } - return inp; - }; - const mkSection = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { - cfgMap.set(text, value); - const lb = make('label', 'sub-section hidden', { - textContent: text, - dataset: { - [tag]: text - } - }); - const inp = make('input', '', { - type, - dataset: { - [tag]: text - }, - ...attrs - }); - - if (sections[tag]) { - sections[tag].append(lb); - } - - if (type === 'checkbox') { - const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { - onclick() { - inp.dispatchEvent(new MouseEvent('click')); - } - }); - inlab.append(inp, la); - lb.append(inlab); - - const nm = /^(\w+)-(.+)/.exec(value); - if (nm) { - if (nm[1] === 'filters') { - inp.checked = cfg[nm[1]][nm[2]].enabled; - } else { - inp.checked = cfg[nm[1]][nm[2]]; - } - } else { - inp.checked = cfg[value]; - } - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(value)) { - container.rebuild = true; - } - if (nm) { - if (nm[1] === 'filters') { - cfg[nm[1]][nm[2]].enabled = evt.target.checked; - } else { - cfg[nm[1]][nm[2]] = evt.target.checked; - } - } else { - cfg[value] = evt.target.checked; - } - }); } else { if (type === 'text') { inp.defaultValue = ''; inp.value = value ?? ''; inp.placeholder = value ?? ''; + + if (tag === 'theme') { + inp.dataset[tag] = text; + ael(inp, 'change', (evt) => { + let isvalid = true; + try { + const val = evt.target.value; + const sty = container.root.style; + const str = `--mujs-${text}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(val)) { + cfg.theme[text] = DEFAULT_CONFIG.theme[text]; + sty.removeProperty(str); + return; + } + if (prop === val) { + return; + } + sty.removeProperty(str); + sty.setProperty(str, val); + cfg.theme[text] = val; + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } + } + }); + } } lb.append(inp); } + obj.elem = inp; + cfgBase.push(obj); return lb; }; if (isGM) { - mkSection(i18n$('userjs_sync'), 'cache'); - mkSection(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); + makeRow(i18n$('userjs_sync'), 'cache'); + makeRow(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); } - mkSection(i18n$('redirect'), 'sleazyredirect'); - mkSection(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { + makeRow( + `${i18n$('redirect')} ${iconSVG.load('info', { dataset: { command: 'more-info', webpage: 'https://greasyfork.org/scripts/23840' } })}`, + 'sleazyredirect' + ); + makeRow(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { defaultValue: 10000, value: cfg.time, min: 0, @@ -5104,85 +5476,101 @@ function primaryFN() { } }); - mkSection(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); - mkSection(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { + makeRow(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); + makeRow(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { onchange(e) { if (e.target.checked) { dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } else { dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand')); } } }); + makeRow('Clear on Tab close', 'clearTabCache', 'checkbox', 'load'); - mkSection(i18n$('filter'), 'filterlang', 'checkbox', 'list'); - mkSection(i18n$('preview_code'), 'codePreview', 'checkbox', 'list'); - mkSection('Recommend author', 'recommend-author', 'checkbox', 'list'); - mkSection('Recommend scripts', 'recommend-others', 'checkbox', 'list'); + makeRow(i18n$('default_sort'), 'autoSort', 'select', 'list'); + makeRow(i18n$('filter'), 'filterlang', 'checkbox', 'list'); + makeRow(i18n$('preview_code'), 'preview-code', 'checkbox', 'list'); + makeRow(i18n$('preview_metadata'), 'preview-metadata', 'checkbox', 'list'); + makeRow(i18n$('recommend_author'), 'recommend-author', 'checkbox', 'list'); + makeRow(i18n$('recommend_other'), 'recommend-others', 'checkbox', 'list'); - for (const [k, v] of Object.entries(cfg.filters)) { - mkSection(v.name, `filters-${k}`, 'checkbox', 'filters'); - } + for (const [k, v] of Object.entries(cfg.filters)) + makeRow(v.name, `filters-${k}`, 'checkbox', 'filters'); - makerow('Greasy Fork', 'checkbox', 'greasyfork'); - makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); - makerow('Open UserJS', 'checkbox', 'openuserjs'); - makerow('GitHub API', 'checkbox', 'github'); + makeRow('Greasy Fork', 'greasyfork', 'checkbox', 'engine'); + makeRow('Sleazy Fork', 'sleazyfork', 'checkbox', 'engine'); + makeRow('Open UserJS', 'openuserjs', 'checkbox', 'engine'); + makeRow( + `GitHub API ${iconSVG.load('info', { dataset: { command: 'more-info', webpage: 'https://docs.github.com/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens' } })}`, + 'github', + 'checkbox', + 'engine' + ); - for (const [k, v] of Object.entries(cfg.theme)) { - const lb = make('label', 'hidden', { - textContent: k, - dataset: { - theme: k - } - }); - const inp = make('input', '', { - type: 'text', - defaultValue: '', - value: v ?? '', - placeholder: v ?? '', - dataset: { - theme: k - }, - onchange(evt) { - let isvalid = true; - try { - const val = evt.target.value; - const sty = container.root.style; - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(val)) { - cfg.theme[k] = DEFAULT_CONFIG.theme[k]; - sty.removeProperty(str); - return; - } - if (prop === val) { - return; - } - sty.removeProperty(str); - sty.setProperty(str, val); - cfg.theme[k] = val; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } - } - } - }); - cfgMap.set(k, inp); - lb.append(inp); - sections.theme.append(lb); - } + for (const [k, v] of Object.entries(cfg.theme)) makeRow(k, v, 'text', 'theme'); + // const blacklist = make('textarea', { + // dataset: { + // name: 'blacklist' + // }, + // rows: '10', + // autocomplete: false, + // spellcheck: false, + // wrap: 'soft', + // value: JSON.stringify(cfg.blacklist, null, ' '), + // oninput(evt) { + // let isvalid = true; + // try { + // cfg.blacklist = JSON.parse(evt.target.value); + // isvalid = true; + // } catch (ex) { + // err(ex); + // isvalid = false; + // } finally { + // if (isvalid) { + // dom.cl.remove(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', false); + // } else { + // dom.cl.add(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', true); + // } + // } + // } + // }); + // const addList = make('mujs-add', { + // textContent: '+', + // dataset: { + // command: 'new-list' + // } + // }); + // const n = make('input', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Name', + // }); + // const inpValue = make('input', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Value', + // }); + // const label = make('label', 'new-list hidden', { + // dataset: { + // blacklist: 'new-list' + // } + // }); + // label.append(n, inpValue, addList); + // listSec.append(label); + // ael(addList, 'click', () => { + // if (isEmpty(n.value) || isEmpty(inpValue.value)) { + // return + // }; + // createList(n.value, n.value, inpValue.value); + // }); const createList = (key, v = '', disabled = false, type = 'String') => { let txt = key; if (typeof key === 'string') { @@ -5192,10 +5580,8 @@ function primaryFN() { txt = `Built-in "${s}"`; v = builtinList[s]; } - } else { - if (!key.enabled) { - return; - } + } else if (!key.enabled) { + return; } if (isRegExp(v)) { @@ -5212,7 +5598,7 @@ function primaryFN() { blacklist: key } }); - const inp = make('input', '', { + const inp = make('input', { type: 'text', defaultValue: '', value: v ?? '', @@ -5242,7 +5628,7 @@ function primaryFN() { } } }); - const selType = make('select', '', { + const selType = make('select', { disabled, dataset: { blacklist: key @@ -5250,14 +5636,14 @@ function primaryFN() { }); if (disabled) { inp.readOnly = true; - const o = make('option', '', { + const o = make('option', { value: type, textContent: type }); selType.append(o); } else { for (const selV of ['String', 'RegExp', 'Object']) { - const o = make('option', '', { + const o = make('option', { value: selV, textContent: selV }); @@ -5268,107 +5654,85 @@ function primaryFN() { lb.append(inp, selType); sections.blacklist.append(lb); }; - // const blacklist = make('textarea', '', { - // dataset: { - // name: 'blacklist' - // }, - // rows: '10', - // autocomplete: false, - // spellcheck: false, - // wrap: 'soft', - // value: JSON.stringify(cfg.blacklist, null, ' '), - // oninput(evt) { - // let isvalid = true; - // try { - // cfg.blacklist = JSON.parse(evt.target.value); - // isvalid = true; - // } catch (ex) { - // err(ex); - // isvalid = false; - // } finally { - // if (isvalid) { - // dom.cl.remove(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', false); - // } else { - // dom.cl.add(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', true); - // } - // } - // } - // }); - // cfgMap.set('blacklist', blacklist); - // const addList = make('mujs-add', '', { - // textContent: '+', - // dataset: { - // command: 'new-list' - // } - // }); - // const n = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Name', - // }); - // const inpValue = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Value', - // }); - // const label = make('label', 'new-list hidden', { - // dataset: { - // blacklist: 'new-list' - // } - // }); - // label.append(n, inpValue, addList); - // listSec.append(label); - // ael(addList, 'click', () => { - // if (isEmpty(n.value) || isEmpty(inpValue.value)) { - // return - // }; - // createList(n.value, n.value, inpValue.value); - // }); - - for (const key of cfg.blacklist) { - createList(key); + for (const key of cfg.blacklist) createList(key); + const transfers = { + export: { + cfg: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_config'), + dataset: { + command: 'export-cfg', + exp: 'export-cfg' + } + }), + theme: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_theme'), + dataset: { + command: 'export-theme', + exp: 'export-theme' + } + }) + }, + import: { + cfg: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_config'), + dataset: { + command: 'import-cfg', + exp: 'import-cfg' + } + }), + theme: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_theme'), + dataset: { + command: 'import-theme', + exp: 'import-theme' + } + }) + } + }; + for (const value of Object.values(transfers)) { + for (const v of Object.values(value)) { + sections.exp.append(v); + } } - - cbtn.append(resetbtn, savebtn); - cfgpage.append(cbtn); + container.cfgpage.append(cbtn); }; // #endregion - container.tab.custom = (host) => { - MUList.host = host; + container.Tabs.custom = (host) => { + MUList.setHost(host); respHandles.build(); }; ael(mainframe, 'mouseenter', (evt) => { evt.preventDefault(); evt.stopPropagation(); + if (isNull(evt.target)) return; evt.target.style.opacity = container.opacityMax; frameTimeout.clear(...frameTimeout.ids); }); ael(mainframe, 'mouseleave', (evt) => { evt.preventDefault(); evt.stopPropagation(); + if (isNull(evt.target)) return; evt.target.style.opacity = container.opacityMin; - timeoutFrame(); + container.timeoutFrame(); }); + let initClick = true; ael(mainframe, 'click', (evt) => { evt.preventDefault(); frameTimeout.clear(...frameTimeout.ids); + if (initClick && !cfg.autofetch) { + initClick = false; + respHandles.build(); + } dom.cl.remove(main, 'hidden'); dom.cl.add(mainframe, 'hidden'); if (cfg.autoexpand) { - dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); - } - if (dom.cl.has(mainframe, 'error')) { - tab.create('mujs:settings'); + dom.cl.add([container.btnfullscreen, main], 'expanded'); + dom.prop(container.btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } }); - ael(urlBar, 'input', (evt) => { + ael(container.urlBar, 'input', (evt) => { evt.preventDefault(); - if (urlBar.placeholder === i18n$('newTab')) { + if (container.urlBar.placeholder === i18n$('newTab')) { return; } /** @@ -5376,28 +5740,24 @@ function primaryFN() { */ const val = evt.target.value; if (isEmpty(val)) { - dom.cl.remove(qsA('tr[data-engine]', tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + dom.cl.remove([...container.toElem(), ...cfgSec], 'hidden'); return; } const finds = new Set(); - if (!dom.cl.has(cfgpage, 'hidden')) { + if (!dom.cl.has(container.cfgpage, 'hidden')) { const reg = new RegExp(val, 'gi'); - for (const elem of qsA('mujs-section[data-name]', cfgpage)) { - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (elem.dataset.name.match(reg)) { - finds.add(elem); - } + for (const elem of cfgSec) { + if (!isElem(elem)) continue; + if (finds.has(elem)) continue; + if (elem.textContent.match(reg)) finds.add(elem); } - dom.cl.add(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + dom.cl.add(cfgSec, 'hidden'); dom.cl.remove([...finds], 'hidden'); return; } + const cacheValues = Array.from(container).filter(({ _mujs }) => { + return !finds.has(_mujs.root); + }); /** * @param {RegExpMatchArray} regExp * @param {keyof import("../typings/types.d.ts").GSForkQuery} key @@ -5405,43 +5765,25 @@ function primaryFN() { const ezQuery = (regExp, key) => { const q_value = val.replace(regExp, ''); const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - let k = v[key]; + for (const v of cacheValues) { if (typeof k === 'number') { - k = `${v[key]}`; - } - if (k && k.match(reg)) { - finds.add(elem); + if (`${v[key]}`.match(reg)) finds.add(v._mujs.root); } } }; if (val.match(/^(code_url|url):/)) { ezQuery(/^(code_url|url):/, 'code_url'); } else if (val.match(/^(author|users?):/)) { - const q_value = val.replace(/^(author|users?):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users) { + const [, parts] = /^[\w_]+:(.+)/.exec(val) ?? []; + if (parts) { + const reg = new RegExp(parts, 'gi'); + for (const v of cacheValues.filter((v) => !isEmpty(v.users))) { for (const user of v.users) { for (const value of Object.values(user)) { if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); + finds.add(v._mujs.root); } else if (typeof value === 'number' && `${value}`.match(reg)) { - finds.add(elem); + finds.add(v._mujs.root); } } } @@ -5458,149 +5800,95 @@ function primaryFN() { } else if (val.match(/^description:/)) { ezQuery(/^description:/, 'description'); } else if (val.match(/^(search_engine|engine):/)) { - const q_value = val.replace(/^(search_engine|engine):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v._mujs.info.engine) { - for (const value of Object.values(v._mujs.info.engine)) { - if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); - } - } - } + const [, parts] = /^[\w_]+:(\w+)/.exec(val) ?? []; + if (parts) { + const reg = new RegExp(parts, 'gi'); + for (const { _mujs } of cacheValues) + if (_mujs.info.engine.name.match(reg)) finds.add(_mujs.root); } } else if (val.match(/^filter:/)) { - const parts = /^\w+:(.+)/.exec(val); + const [, parts] = /^\w+:(.+)/.exec(val) ?? []; if (parts) { const bsFilter = loadFilters(); - const p = parts[1].trim().toLocaleLowerCase(); - for (const [key, value] of Object.entries(cfg.filters)) { - const k = key.trim().toLocaleLowerCase(); - const v = value.name.trim().toLocaleLowerCase(); - if (p.includes(k) || p.includes(v)) { - const reg = bsFilter.get(value.name); - if (reg) { - [...userjsCache.values()].filter(({ name, users, _mujs }) => { - const elem = _mujs.root; - if (!isElem(elem)) { - return false; - } - if (finds.has(elem)) { - return false; - } - const strArr = [{ name }, ...users]; - const v = strArr.find((o) => o.name.match(reg)); - if (v) { - return false; - } - finds.add(elem); - return true; - }); - } + const filterType = bsFilter.get(parts.trim().toLocaleLowerCase()); + if (filterType) { + const { reg } = filterType; + for (const { name, users, _mujs } of cacheValues) { + if ([{ name }, ...users].find((o) => o.name.match(reg))) continue; + finds.add(_mujs.root); } } } } else if (val.match(/^recommend:/)) { - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users.find(u => u.id === authorID)) { - finds.add(elem); - } else if (goodUserJS.includes(v.url)) { - finds.add(elem); - } else if (goodUserJS.includes(v.id)) { - finds.add(elem); + for (const { url, id, users, _mujs } of cacheValues) { + if ( + users.find((u) => u.id === authorID) || + goodUserJS.includes(url) || + goodUserJS.includes(id) + ) { + finds.add(_mujs.root); } } } else { const reg = new RegExp(val, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.name && v.name.match(reg)) { - finds.add(elem); - } - if (v.description && v.description.match(reg)) { - finds.add(elem); - } - const code_data = v._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - const meta = code_obj.parse_meta(code_data); - for (const key of Object.keys(meta)) { - if (/name|desc/i.test(key) && key.match(reg)) { - finds.add(elem); - } - } - } + for (const v of cacheValues) { + if (v.name && v.name.match(reg)) finds.add(v._mujs.root); + if (v.description && v.description.match(reg)) finds.add(v._mujs.root); + if (v._mujs.code.data_meta) + for (const key of Object.keys(v._mujs.code.data_meta)) + if (/name|desc/i.test(key) && key.match(reg)) finds.add(v._mujs.root); } } - dom.cl.add(qsA('tr[data-engine]', tabbody), 'hidden'); + dom.cl.add(container.toElem(), 'hidden'); dom.cl.remove([...finds], 'hidden'); }); - ael(urlBar, 'change', (evt) => { + ael(container.urlBar, 'change', (evt) => { evt.preventDefault(); - const val = evt.target.value; - if (urlBar.placeholder === i18n$('newTab') && qs('mujs-tab.active', toolbar)) { - const tabElem = qs('mujs-tab.active', toolbar); - const tabHost = qs('mujs-host', tabElem); - if (val.startsWith(tab.protocal)) { - tab.close(tabElem); - if (tab.hasTab(val)) { - tab.active(tab.Tab.get(val)); + if (isNull(evt.target)) return; + const { target } = evt; + const tabElem = Tabs.getActive(); + if (container.urlBar.placeholder === i18n$('newTab') && tabElem) { + const tabHost = tabElem.firstElementChild; + const host = formatURL(normalizedHostname(target.value)); + if (Tabs.protoReg.test(target.value)) { + const createdTab = Tabs.getTab(target.value); + Tabs.close(tabElem); + if (createdTab) { + Tabs.active(createdTab); } else { - tab.create(val); + Tabs.create(target.value); } - return; - } else if (val === '*') { - tabElem.dataset.host = val; + target.placeholder = i18n$('search_placeholder'); + target.value = ''; + } else if (host === '*') { + tabElem.dataset.host = host; tabHost.title = ''; tabHost.textContent = ''; - MUList.host = val; + MUList.setHost(host); + respHandles.build(); + } else if (container.checkBlacklist(host)) { + showError(`Blacklisted "${host}"`); + } else { + tabElem.dataset.host = host; + tabHost.title = host; + tabHost.textContent = host; + MUList.setHost(host); respHandles.build(); - return; - } - const value = container.getHost(val); - if (container.checkBlacklist(value)) { - showError(`Blacklisted "${value}"`); - return; } - tabElem.dataset.host = value; - tabHost.title = value; - tabHost.textContent = value; - MUList.host = value; - respHandles.build(); - return; } }); scheduler.postTask(makecfg, { priority: 'background' }); respHandles.build = async () => { - const time = await scheduler.postTask(MUList.build, { priority: 'background' }); - return timeoutFrame(time); + await scheduler.postTask(MUList.build, { priority: 'background' }); + container.timeoutFrame(); }; if (cfg.autofetch) { respHandles.build(); + } else { + container.timeoutFrame(); } - dbg('Container', container); } catch (ex) { err(ex); container.remove(); @@ -5609,24 +5897,23 @@ function primaryFN() { } // #endregion /** - * @template { Function } F + * @template F * @param { (this: F, doc: Document) => * } onDomReady */ const loadDOM = (onDomReady) => { - if (!isFN(onDomReady)) { - return; - } - if (document.readyState === 'interactive' || document.readyState === 'complete') { - onDomReady(document); - return; + if (isFN(onDomReady)) { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + onDomReady(document); + } else { + document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { + once: true + }); + } } - document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { - once: true - }); }; -const init = async () => { - const stored = await StorageSystem.getValue('Config', DEFAULT_CONFIG); +const init = async (prefix = 'Config') => { + const stored = await StorageSystem.getValue(prefix, DEFAULT_CONFIG); cfg = { ...DEFAULT_CONFIG, ...stored @@ -5634,21 +5921,20 @@ const init = async () => { info('Config:', cfg); loadDOM((doc) => { try { - if (window.location === null) { + if (window.location === null) throw new Error('"window.location" is null, reload the webpage or use a different one', { cause: 'loadDOM' }); - } - if (doc === null) { + if (doc === null) throw new Error('"doc" is null, reload the webpage or use a different one', { cause: 'loadDOM' }); + container.redirect(); + if (cfg.autoinject) { + container.inject(primaryFN, doc); + } else { + container.timeoutFrame(); } - trustedTypes.init(); - sleazyRedirect(); - - if (cfg.autoinject) container.inject(primaryFN, doc); - Command.register(i18n$('userjs_inject'), () => { container.inject(primaryFN, doc); }); @@ -5662,4 +5948,4 @@ const init = async () => { }; init(); -})(); \ No newline at end of file +})(); diff --git a/dist/userscript_plus-5.0.0.xpi b/dist/userscript_plus-5.0.0.xpi deleted file mode 100644 index eec2c4a..0000000 Binary files a/dist/userscript_plus-5.0.0.xpi and /dev/null differ diff --git a/eslint.config.js b/eslint.config.js index 7c77b70..85de384 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,94 +1,69 @@ -import js from '@eslint/js'; -import globals from 'globals'; -import eslintConfigPrettier from 'eslint-config-prettier'; +import { defineConfig } from 'eslint/config'; -const userJSGlobals = { - code: 'readonly', - metadata: 'readonly', - main_css: 'readonly', - languageList: 'readonly', - translations: 'readonly', - userjs: 'writable', - ...globals.es2023, - ...globals.browser, - ...globals.greasemonkey -}; -const webextGlobals = { - MU: 'writable', - sleazyfork_redirect: 'readonly', - webext: 'readonly', - brws: 'readonly', - userjs: 'writable', - ...globals.es2023, - ...globals.browser, - ...globals.webextensions -}; -const parserOptions = { - allowImportExportEverywhere: false, - ecmaFeatures: { - globalReturn: true, - arrowFunctions: true, - modules: true - } -}; -const rules = { - 'keyword-spacing': ['error', { before: true }], - 'no-var': 'error', - 'prefer-const': ['error', { destructuring: 'all' }], - 'prefer-promise-reject-errors': 'error', - 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], - quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], - 'space-before-blocks': ['error', 'always'] -}; +import globals from 'globals'; +import pluginJs from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier/flat'; -export default [ - js.configs.recommended, +export default defineConfig([ + pluginJs.configs.recommended, eslintConfigPrettier, { - files: ['src/js/*.js'], + rules: { + 'no-var': 'error', + 'prefer-const': ['error', { destructuring: 'all' }], + 'prefer-promise-reject-errors': 'error', + 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], + quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], + 'space-before-blocks': ['error', 'always'] + }, languageOptions: { ecmaVersion: 'latest', sourceType: 'module', - globals: webextGlobals, - parserOptions - }, - rules + globals: globals.es2024 + } + }, + { + files: ['src/js/*.js'], + languageOptions: { + globals: { + ...globals.browser, + ...globals.webextensions + } + } }, { files: ['src/UserJS/main.js'], languageOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - globals: userJSGlobals, - parserOptions - }, - rules + sourceType: 'script', + globals: { + main_css: 'readonly', + translations: 'writable', + userjs: 'writable', + ...globals.browser, + ...globals.greasemonkey + } + } }, { files: ['src/UserJS/header.js'], languageOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - globals: userJSGlobals, - parserOptions + sourceType: 'script', + globals: { + code: 'readonly', + metadata: 'readonly', + languageList: 'readonly', + ...globals.browser + } }, rules: { - ...rules, quotes: 'off', 'no-unused-vars': 'off' } }, { - files: ['tools/*.js'], + files: ['tools/*.js', 'utils/**/*.js'], languageOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - globals: { - ...globals.es2021, - ...globals.node - }, - parserOptions - }, - rules + globals: globals.node + } } -]; +]); diff --git a/package.json b/package.json index f4f73ec..11fdcd9 100644 --- a/package.json +++ b/package.json @@ -2,140 +2,57 @@ "name": "userscript-plus", "description": "Finds available userscripts for the current webpage.", "author": "Magic ", - "version": "5.0.2", + "version": "6.0.0", "license": "MIT", "homepage": "https://github.com/magicoflolis/Userscript-Plus", "bugs": { "url": "https://github.com/magicoflolis/Userscript-Plus/issues" }, - "userJS": { - "version": "7.3.0", - "name": "Magic Userscript+ : Show Site All UserJS", - "description": "Finds available userscripts for the current webpage.", - "bugs": "https://github.com/magicoflolis/Userscript-Plus/issues", - "homepage": "https://github.com/magicoflolis/Userscript-Plus", - "icon": "./src/img/icon_64.png", - "downloadURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.user.js", - "updateURL": "https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.meta.js", - "build": { - "source": { - "head": "./src/UserJS/header.js", - "body": "./src/UserJS/main.js", - "extras": { - "mainCSS": "./build/css/magicuserjs.css" - } - }, - "watch": { - "files": [ - "main.js", - "header.js", - "magicuserjs.css" - ], - "dirs": [ - "src/UserJS", - "build/css" - ] - }, - "paths": { - "dev": { - "fileName": "magic-userjs.dev", - "env": "./src/UserJS/.env", - "dir": "./web-server" - }, - "public": { - "fileName": "magic-userjs", - "env": "./dist/.env", - "dir": "./dist" - } - } - }, - "metadata": { - "compatible": [ - "chrome", - "firefox", - "edge", - "opera", - "safari" - ], - "connect": [ - "greasyfork.org", - "sleazyfork.org", - "github.com", - "openuserjs.org" - ], - "grant": [ - "GM_addElement", - "GM_info", - "GM_getValue", - "GM_openInTab", - "GM_setValue", - "GM_registerMenuCommand", - "GM_xmlhttpRequest", - "GM.addElement", - "GM.info", - "GM.getValue", - "GM.openInTab", - "GM.setValue", - "GM.registerMenuCommand", - "GM.xmlHttpRequest" - ], - "exclude": [], - "include": [], - "exclude-match": [], - "match": [ - "https://*/*" - ], - "noframes": true, - "resource": {}, - "require": [], - "run-at": "document-start" - } - }, "type": "module", "scripts": { - "dev:UserJS": "concurrently \"node ./tools/userscript.js\" \"pnpm run dev:Webserver\" \"sass --no-source-map -s expanded -w ./src/sass:./build/css\" ", + "dev:UserJS": "concurrently \"dotenvx run -f .env --quiet -- node ./tools/userscript.js\" \"http-server ./web-server -p 9090 -s --no-dotfiles -c-1\"", "dev:FF": "concurrently \"sass --embed-sources -s expanded -w ./src/sass:./build/firefox/css\" \"pnpm run webpack:FF\"", "dev:Cr": "concurrently \"sass --embed-sources -s expanded -w ./src/sass:./build/chrome/css\" \"pnpm run webpack:Cr\"", - "dev:Webserver": "http-server ./web-server -p 9090 -s --no-dotfiles -c-1", - "pub:UserJS": "concurrently \"sass --no-source-map -s expanded ./src/sass:./build/css\" \"node -r dotenv/config ./tools/userscript.js dotenv_config_path=./dist/.env\"", - "pub:FF": "webpack --mode production --config=tools/webpack.config.js --env brws=firefox", - "pub:Cr": "webpack --mode production --config=tools/webpack.config.js --env brws=chrome", + "pub:UserJS": "dotenvx run --env JS_ENV=production -f .env --quiet -- node ./tools/userscript.js", "webpack:Cr": "webpack --progress --mode development --config=tools/webpack.config.js --env brws=chrome", "webpack:FF": "webpack --progress --mode development --config=tools/webpack.config.js --env brws=firefox", - "web-run:firefox-android": "web-ext run --config=./tools/web-ext.js -t firefox-android --firefox-apk org.mozilla.firefox" + "web-ext:run": "web-ext run --config=./tools/web-ext.mjs", + "web-ext:android": "web-ext run --config=./tools/web-ext.mjs -t firefox-android --firefox-apk org.mozilla.firefox" }, "devDependencies": { + "@dotenvx/dotenvx": "^1.39.0", + "@eslint/js": "^9.22.0", "@swc/cli": "^0.6.0", - "@swc/core": "^1.10.14", - "@types/chrome": "0.0.301", + "@swc/core": "^1.11.11", + "@types/chrome": "0.0.309", + "@types/eslint-config-prettier": "^6.11.3", "@types/firefox-webext-browser": "120.0.4", "@types/greasemonkey": "4.0.7", - "@types/node": "^22.13.1", + "@types/node": "^22.13.10", "@types/tampermonkey": "^5.0.4", "@types/trusted-types": "^2.0.7", "@types/webpack": "^5.28.5", "@violentmonkey/types": "0.2.0", "browserslist": "^4.24.4", "concurrently": "^9.1.2", - "copy-webpack-plugin": "^12.0.2", + "copy-webpack-plugin": "^13.0.0", "crx3": "^1.1.3", "css-loader": "^7.1.2", - "dotenv": "^16.4.7", - "eslint": "^9.19.0", - "eslint-config-prettier": "^10.0.1", - "globals": "^15.14.0", + "eslint": "^9.22.0", + "eslint-config-prettier": "^10.1.1", + "globals": "^16.0.0", "http-server": "^14.1.1", "mini-css-extract-plugin": "^2.9.2", "path-browserify": "^1.0.1", - "prettier": "^3.4.2", - "sass-embedded": "^1.83.4", - "sass-loader": "^16.0.4", + "prettier": "^3.5.3", + "sass-embedded": "^1.86.0", + "sass-loader": "^16.0.5", "swc-loader": "^0.2.6", - "terser-webpack-plugin": "^5.3.11", - "typescript": "^5.7.3", - "watchpack": "^2.4.2", - "web-ext": "^8.4.0", - "webpack": "^5.97.1", + "terser-webpack-plugin": "^5.3.14", + "typescript": "^5.8.2", + "user.js": "workspace:^", + "web-ext": "^8.5.0", + "webpack": "^5.98.0", "webpack-cli": "^6.0.1", "webpack-merge": "^6.0.1" }, @@ -154,11 +71,21 @@ "violentmonkey" ], "webExt": { - "sourceDir": "./build/firefox/", - "artifactsDir": "./build" + "artifactsDir": "./build", + "build": { + "overwriteDest": true + }, + "sourceDir": "./build/firefox" }, "engines": { - "node": ">=20" + "node": ">=20", + "pnpm": ">=10" }, - "packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4" + "packageManager": "pnpm@10.3.0+sha512.ee592eda8815a8a293c206bb0917c4bb0ff274c50def7cbc17be05ec641fc2d1b02490ce660061356bd0d126a4d7eb2ec8830e6959fb8a447571c631d5a2442d", + "pnpm": { + "onlyBuiltDependencies": [ + "@swc/core", + "spawn-sync" + ] + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8ed7a9..d17a679 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,15 +8,24 @@ importers: .: devDependencies: + '@dotenvx/dotenvx': + specifier: ^1.39.0 + version: 1.39.0 + '@eslint/js': + specifier: ^9.22.0 + version: 9.22.0 '@swc/cli': specifier: ^0.6.0 - version: 0.6.0(@swc/core@1.10.14) + version: 0.6.0(@swc/core@1.11.11) '@swc/core': - specifier: ^1.10.14 - version: 1.10.14 + specifier: ^1.11.11 + version: 1.11.11 '@types/chrome': - specifier: 0.0.301 - version: 0.0.301 + specifier: 0.0.309 + version: 0.0.309 + '@types/eslint-config-prettier': + specifier: ^6.11.3 + version: 6.11.3 '@types/firefox-webext-browser': specifier: 120.0.4 version: 120.0.4 @@ -24,8 +33,8 @@ importers: specifier: 4.0.7 version: 4.0.7 '@types/node': - specifier: ^22.13.1 - version: 22.13.1 + specifier: ^22.13.10 + version: 22.13.10 '@types/tampermonkey': specifier: ^5.0.4 version: 5.0.4 @@ -34,7 +43,7 @@ importers: version: 2.0.7 '@types/webpack': specifier: ^5.28.5 - version: 5.28.5(@swc/core@1.10.14)(webpack-cli@6.0.1) + version: 5.28.5(@swc/core@1.11.11)(webpack-cli@6.0.1) '@violentmonkey/types': specifier: 0.2.0 version: 0.2.0 @@ -45,69 +54,92 @@ importers: specifier: ^9.1.2 version: 9.1.2 copy-webpack-plugin: - specifier: ^12.0.2 - version: 12.0.2(webpack@5.97.1) + specifier: ^13.0.0 + version: 13.0.0(webpack@5.98.0) crx3: specifier: ^1.1.3 version: 1.1.3 css-loader: specifier: ^7.1.2 - version: 7.1.2(webpack@5.97.1) - dotenv: - specifier: ^16.4.7 - version: 16.4.7 + version: 7.1.2(webpack@5.98.0) eslint: - specifier: ^9.19.0 - version: 9.19.0 + specifier: ^9.22.0 + version: 9.22.0 eslint-config-prettier: - specifier: ^10.0.1 - version: 10.0.1(eslint@9.19.0) + specifier: ^10.1.1 + version: 10.1.1(eslint@9.22.0) globals: - specifier: ^15.14.0 - version: 15.14.0 + specifier: ^16.0.0 + version: 16.0.0 http-server: specifier: ^14.1.1 version: 14.1.1 mini-css-extract-plugin: specifier: ^2.9.2 - version: 2.9.2(webpack@5.97.1) + version: 2.9.2(webpack@5.98.0) path-browserify: specifier: ^1.0.1 version: 1.0.1 prettier: - specifier: ^3.4.2 - version: 3.4.2 + specifier: ^3.5.3 + version: 3.5.3 sass-embedded: - specifier: ^1.83.4 - version: 1.83.4 + specifier: ^1.86.0 + version: 1.86.0 sass-loader: - specifier: ^16.0.4 - version: 16.0.4(sass-embedded@1.83.4)(webpack@5.97.1) + specifier: ^16.0.5 + version: 16.0.5(sass-embedded@1.86.0)(webpack@5.98.0) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.14)(webpack@5.97.1) + version: 0.2.6(@swc/core@1.11.11)(webpack@5.98.0) terser-webpack-plugin: - specifier: ^5.3.11 - version: 5.3.11(@swc/core@1.10.14)(webpack@5.97.1) + specifier: ^5.3.14 + version: 5.3.14(@swc/core@1.11.11)(webpack@5.98.0) typescript: - specifier: ^5.7.3 - version: 5.7.3 - watchpack: - specifier: ^2.4.2 - version: 2.4.2 + specifier: ^5.8.2 + version: 5.8.2 + user.js: + specifier: workspace:^ + version: link:utils/user.js web-ext: - specifier: ^8.4.0 - version: 8.4.0 + specifier: ^8.5.0 + version: 8.5.0 webpack: - specifier: ^5.97.1 - version: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + specifier: ^5.98.0 + version: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) webpack-cli: specifier: ^6.0.1 - version: 6.0.1(webpack@5.97.1) + version: 6.0.1(webpack@5.98.0) webpack-merge: specifier: ^6.0.1 version: 6.0.1 + utils/builder: + dependencies: + '@userjs/i18n': + specifier: workspace:^ + version: link:../i18n + minimist: + specifier: ^1.2.8 + version: 1.2.8 + sass-embedded: + specifier: ^1.85.1 + version: 1.85.1 + watchpack: + specifier: ^2.4.2 + version: 2.4.2 + + utils/i18n: {} + + utils/user.js: + dependencies: + '@userjs/builder': + specifier: workspace:^ + version: link:../builder + '@userjs/i18n': + specifier: workspace:^ + version: link:../i18n + packages: '@aashutoshrathi/word-wrap@1.2.6': @@ -126,8 +158,8 @@ packages: resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.26.7': - resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} + '@babel/runtime@7.26.10': + resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} '@bufbuild/protobuf@2.2.0': @@ -150,6 +182,16 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} + '@dotenvx/dotenvx@1.39.0': + resolution: {integrity: sha512-qGfDpL/3S17MQYXpR3HkBS5xNQ7wiFlqLdpr+iIQzv17aMRcSlgL4EjMIsYFZ540Dq17J+y5FVElA1AkVoXiUA==} + hasBin: true + + '@ecies/ciphers@0.2.2': + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -160,36 +202,40 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} + '@eslint/config-array@0.19.2': + resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.10.0': - resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} + '@eslint/config-helpers@0.1.0': + resolution: {integrity: sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} + '@eslint/eslintrc@3.3.0': + resolution: {integrity: sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@8.57.1': resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/js@9.19.0': - resolution: {integrity: sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==} + '@eslint/js@9.22.0': + resolution: {integrity: sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.5': - resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} + '@eslint/plugin-kit@0.2.7': + resolution: {integrity: sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fluent/syntax@0.19.0': @@ -225,8 +271,8 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} '@jridgewell/gen-mapping@0.3.2': @@ -250,8 +296,20 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@mdn/browser-compat-data@5.6.31': - resolution: {integrity: sha512-E/X4YlErVjBCMvaJbqD2Jb0B/Z+TQJyw4vN6q98iit05RLW9VEcsedaTiL81BAbYF9GICn/KpFGE9dMJi66Ydw==} + '@mdn/browser-compat-data@5.7.3': + resolution: {integrity: sha512-ckygcngv0i7Qe0yOzzge/K7Gr5dnk2jNm/AYdqUd1ZTGa9pIEdDuVyWmL3bDU/NdJ8FtdSAjng98YfUuou9Csw==} + + '@noble/ciphers@1.2.1': + resolution: {integrity: sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==} + engines: {node: ^14.21.3 || >=16} + + '@noble/curves@1.8.1': + resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.7.1': + resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} + engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -284,10 +342,6 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - '@swc/cli@0.6.0': resolution: {integrity: sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==} engines: {node: '>= 16.14.0'} @@ -299,68 +353,68 @@ packages: chokidar: optional: true - '@swc/core-darwin-arm64@1.10.14': - resolution: {integrity: sha512-Dh4VyrhDDb05tdRmqJ/MucOPMTnrB4pRJol18HVyLlqu1HOT5EzonUniNTCdQbUXjgdv5UVJSTE1lYTzrp+myA==} + '@swc/core-darwin-arm64@1.11.11': + resolution: {integrity: sha512-vJcjGVDB8cZH7zyOkC0AfpFYI/7GHKG0NSsH3tpuKrmoAXJyCYspKPGid7FT53EAlWreN7+Pew+bukYf5j+Fmg==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.10.14': - resolution: {integrity: sha512-KpzotL/I0O12RE3tF8NmQErINv0cQe/0mnN/Q50ESFzB5kU6bLgp2HMnnwDTm/XEZZRJCNe0oc9WJ5rKbAJFRQ==} + '@swc/core-darwin-x64@1.11.11': + resolution: {integrity: sha512-/N4dGdqEYvD48mCF3QBSycAbbQd3yoZ2YHSzYesQf8usNc2YpIhYqEH3sql02UsxTjEFOJSf1bxZABDdhbSl6A==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.10.14': - resolution: {integrity: sha512-20yRXZjMJVz1wp1TcscKiGTVXistG+saIaxOmxSNQia1Qun3hSWLL+u6+5kXbfYGr7R2N6kqSwtZbIfJI25r9Q==} + '@swc/core-linux-arm-gnueabihf@1.11.11': + resolution: {integrity: sha512-hsBhKK+wVXdN3x9MrL5GW0yT8o9GxteE5zHAI2HJjRQel3HtW7m5Nvwaq+q8rwMf4YQRd8ydbvwl4iUOZx7i2Q==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.10.14': - resolution: {integrity: sha512-Gy7cGrNkiMfPxQyLGxdgXPwyWzNzbHuWycJFcoKBihxZKZIW8hkPBttkGivuLC+0qOgsV2/U+S7tlvAju7FtmQ==} + '@swc/core-linux-arm64-gnu@1.11.11': + resolution: {integrity: sha512-YOCdxsqbnn/HMPCNM6nrXUpSndLXMUssGTtzT7ffXqr7WuzRg2e170FVDVQFIkb08E7Ku5uOnnUVAChAJQbMOQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.10.14': - resolution: {integrity: sha512-+oYVqJvFw62InZ8PIy1rBACJPC2WTe4vbVb9kM1jJj2D7dKLm9acnnYIVIDsM5Wo7Uab8RvPHXVbs19IBurzuw==} + '@swc/core-linux-arm64-musl@1.11.11': + resolution: {integrity: sha512-nR2tfdQRRzwqR2XYw9NnBk9Fdvff/b8IiJzDL28gRR2QiJWLaE8LsRovtWrzCOYq6o5Uu9cJ3WbabWthLo4jLw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.10.14': - resolution: {integrity: sha512-OmEbVEKQFLQVHwo4EJl9osmlulURy46k232Opfpn/1ji0t2KcNCci3POsnfMuoZjLkGJv8vGNJdPQxX+CP+wSA==} + '@swc/core-linux-x64-gnu@1.11.11': + resolution: {integrity: sha512-b4gBp5HA9xNWNC5gsYbdzGBJWx4vKSGybGMGOVWWuF+ynx10+0sA/o4XJGuNHm8TEDuNh9YLKf6QkIO8+GPJ1g==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.10.14': - resolution: {integrity: sha512-OZW+Icm8DMPqHbhdxplkuG8qrNnPk5i7xJOZWYi1y5bTjgGFI4nEzrsmmeHKMdQTaWwsFrm3uK1rlyQ48MmXmg==} + '@swc/core-linux-x64-musl@1.11.11': + resolution: {integrity: sha512-dEvqmQVswjNvMBwXNb8q5uSvhWrJLdttBSef3s6UC5oDSwOr00t3RQPzyS3n5qmGJ8UMTdPRmsopxmqaODISdg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.10.14': - resolution: {integrity: sha512-sTvc+xrDQXy3HXZFtTEClY35Efvuc3D+busYm0+rb1+Thau4HLRY9WP+sOKeGwH9/16rzfzYEqD7Ds8A9ykrHw==} + '@swc/core-win32-arm64-msvc@1.11.11': + resolution: {integrity: sha512-aZNZznem9WRnw2FbTqVpnclvl8Q2apOBW2B316gZK+qxbe+ktjOUnYaMhdCG3+BYggyIBDOnaJeQrXbKIMmNdw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.10.14': - resolution: {integrity: sha512-j2iQ4y9GWTKtES5eMU0sDsFdYni7IxME7ejFej25Tv3Fq4B+U9tgtYWlJwh1858nIWDXelHiKcSh/UICAyVMdQ==} + '@swc/core-win32-ia32-msvc@1.11.11': + resolution: {integrity: sha512-DjeJn/IfjgOddmJ8IBbWuDK53Fqw7UvOz7kyI/728CSdDYC3LXigzj3ZYs4VvyeOt+ZcQZUB2HA27edOifomGw==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.10.14': - resolution: {integrity: sha512-TYtWkUSMkjs0jGPeWdtWbex4B+DlQZmN/ySVLiPI+EltYCLEXsFMkVFq6aWn48dqFHggFK0UYfvDrJUR2c3Qxg==} + '@swc/core-win32-x64-msvc@1.11.11': + resolution: {integrity: sha512-Gp/SLoeMtsU4n0uRoKDOlGrRC6wCfifq7bqLwSlAG8u8MyJYJCcwjg7ggm0rhLdC2vbiZ+lLVl3kkETp+JUvKg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.10.14': - resolution: {integrity: sha512-WSrnE6JRnH20ZYjOOgSS4aOaPv9gxlkI2KRkN24kagbZnPZMnN8bZZyzw1rrLvwgpuRGv17Uz+hflosbR+SP6w==} + '@swc/core@1.11.11': + resolution: {integrity: sha512-pCVY2Wn6dV/labNvssk9b3Owi4WOYsapcbWm90XkIj4xH/56Z6gzja9fsU+4MdPuEfC2Smw835nZHcdCFGyX6A==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -371,8 +425,8 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/types@0.1.17': - resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@swc/types@0.1.19': + resolution: {integrity: sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==} '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} @@ -381,8 +435,11 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@types/chrome@0.0.301': - resolution: {integrity: sha512-BljW7InXXOfDYr4nG79y5lnaklrZ0gmeTwCUkuCXK2+69dHnZIrg13KhXJE/HrcFUmy8NG/EK+RTGScsh/Hbiw==} + '@types/chrome@0.0.309': + resolution: {integrity: sha512-ZFADzcp8b+roUrux68U8pti4cmNOLJXWkShk8lfxj9SBcjYqpJt7NypBprSJUJDJVakGZgd2Tt00QePIGh7oPA==} + + '@types/eslint-config-prettier@6.11.3': + resolution: {integrity: sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==} '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -420,8 +477,8 @@ packages: '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - '@types/node@22.13.1': - resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} + '@types/node@22.13.10': + resolution: {integrity: sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==} '@types/tampermonkey@5.0.4': resolution: {integrity: sha512-HEJ0O5CkDZSwIgd9Zip4xM8o7gZC8zBmJuH1YEXNINIC+aCWEXD10pnpo3NQG5IWHx+Xr4fMSGq1jqeKXH2lpA==} @@ -566,18 +623,13 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.14.0: resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true - addons-linter@7.8.0: - resolution: {integrity: sha512-SnDJUAtrHu1RUMIiIZXNN8wF0ze7IbDokwUy5HZYlCkNTn8E4RcI74xhdHtCdtoQprAmyhxePP/hFf+9XPhe8w==} + addons-linter@7.9.0: + resolution: {integrity: sha512-ehoh43VG+DGOVxdT0XsO6camSMaHRCJf7shHX2qtKMRQo/29M1fGwZ+ssRTL0SucqLCvsmu3hmv13fEhC+79aA==} engines: {node: '>=18.0.0'} hasBin: true @@ -617,11 +669,6 @@ packages: ajv: optional: true - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 - ajv-keywords@5.1.0: resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} peerDependencies: @@ -851,6 +898,10 @@ packages: resolution: {integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==} engines: {node: '>=8.0.0'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -901,8 +952,8 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} - copy-webpack-plugin@12.0.2: - resolution: {integrity: sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==} + copy-webpack-plugin@13.0.0: + resolution: {integrity: sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==} engines: {node: '>= 18.12.0'} peerDependencies: webpack: ^5.1.0 @@ -1046,6 +1097,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + eciesjs@0.4.14: + resolution: {integrity: sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + electron-to-chromium@1.5.80: resolution: {integrity: sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw==} @@ -1093,8 +1148,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-prettier@10.0.1: - resolution: {integrity: sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==} + eslint-config-prettier@10.1.1: + resolution: {integrity: sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==} hasBin: true peerDependencies: eslint: '>=7.0.0' @@ -1112,8 +1167,8 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: @@ -1130,8 +1185,8 @@ packages: deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true - eslint@9.19.0: - resolution: {integrity: sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==} + eslint@9.22.0: + resolution: {integrity: sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1236,6 +1291,14 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.4.3: + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1369,12 +1432,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.14.0: - resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} - engines: {node: '>=18'} - - globby@14.0.1: - resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==} + globals@16.0.0: + resolution: {integrity: sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==} engines: {node: '>=18'} got@13.0.0: @@ -1464,12 +1523,12 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - image-size@1.2.0: - resolution: {integrity: sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==} + image-size@2.0.1: + resolution: {integrity: sha512-NI6NK/2zchlZopsQrcVIS7jxA0/rtIy74B+/rx5s7rKQyFebmQjZVhzxXgRZJROk+WhhOq+S6sUaODxp0L5hfg==} engines: {node: '>=16.x'} hasBin: true @@ -1619,6 +1678,10 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} @@ -1864,6 +1927,10 @@ packages: object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + on-exit-leak-free@2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} @@ -1961,10 +2028,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - pbf@3.2.1: resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==} hasBin: true @@ -1986,6 +2049,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pino-abstract-transport@1.2.0: resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} @@ -2053,8 +2120,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.4.2: - resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} hasBin: true @@ -2099,9 +2166,6 @@ packages: queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - queue@6.0.2: - resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -2211,133 +2275,258 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-embedded-android-arm64@1.83.4: - resolution: {integrity: sha512-tgX4FzmbVqnQmD67ZxQDvI+qFNABrboOQgwsG05E5bA/US42zGajW9AxpECJYiMXVOHmg+d81ICbjb0fsVHskw==} + sass-embedded-android-arm64@1.85.1: + resolution: {integrity: sha512-27oRheqNA3SJM2hAxpVbs7mCKUwKPWmEEhyiNFpBINb5ELVLg+Ck5RsGg+SJmo130ul5YX0vinmVB5uPWc8X5w==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] - sass-embedded-android-arm@1.83.4: - resolution: {integrity: sha512-9Z4pJAOgEkXa3VDY/o+U6l5XvV0mZTJcSl0l/mSPHihjAHSpLYnOW6+KOWeM8dxqrsqTYcd6COzhanI/a++5Gw==} + sass-embedded-android-arm64@1.86.0: + resolution: {integrity: sha512-r7MZtlAI2VFUnKE8B5UOrpoE6OGpdf1dIB6ndoxb3oiURgMyfTVU7yvJcL12GGvtVwQ2boCj6dq//Lqq9CXPlQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.85.1: + resolution: {integrity: sha512-GkcgUGMZtEF9gheuE1dxCU0ZSAifuaFXi/aX7ZXvjtdwmTl9Zc/OHR9oiUJkc8IW9UI7H8TuwlTAA8+SwgwIeQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-arm@1.86.0: + resolution: {integrity: sha512-NS8v6BCbzskXUMBtzfuB+j2yQMgiwg5edKHTYfQU7gAWai2hkRhS06YNEMff3aRxV0IFInxPRHOobd8xWPHqeA==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] - sass-embedded-android-ia32@1.83.4: - resolution: {integrity: sha512-RsFOziFqPcfZXdFRULC4Ayzy9aK6R6FwQ411broCjlOBX+b0gurjRadkue3cfUEUR5mmy0KeCbp7zVKPLTK+5Q==} + sass-embedded-android-ia32@1.85.1: + resolution: {integrity: sha512-f3x16NyRgtXFksIaO/xXKrUhttUBv8V0XsAR2Dhdb/yz4yrDrhzw9Wh8fmw7PlQqECcQvFaoDr3XIIM6lKzasw==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [android] - sass-embedded-android-riscv64@1.83.4: - resolution: {integrity: sha512-EHwh0nmQarBBrMRU928eTZkFGx19k/XW2YwbPR4gBVdWLkbTgCA5aGe8hTE6/1zStyx++3nDGvTZ78+b/VvvLg==} + sass-embedded-android-ia32@1.86.0: + resolution: {integrity: sha512-UjfElrGaOTNOnxLZLxf6MFndFIe7zyK+81f83BioZ7/jcoAd6iCHZT8yQMvu8wINyVodPcaXZl8KxlKcl62VAA==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [android] + + sass-embedded-android-riscv64@1.85.1: + resolution: {integrity: sha512-IP6OijpJ8Mqo7XqCe0LsuZVbAxEFVboa0kXqqR5K55LebEplsTIA2GnmRyMay3Yr/2FVGsZbCb6Wlgkw23eCiA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] - sass-embedded-android-x64@1.83.4: - resolution: {integrity: sha512-0PgQNuPWYy1jEOEPDVsV89KfqOsMLIp9CSbjBY7jRcwRhyVAcigqrUG6bDeNtojHUYKA1kU+Eh/85WxOHUOgBw==} + sass-embedded-android-riscv64@1.86.0: + resolution: {integrity: sha512-TsqCLxHWLFS2mbpUkL/nge3jSkaPK2VmLkkoi5iO/EQT4SFvm1lNUgPwlLXu9DplZ+aqGVzRS9Y6Psjv+qW7kw==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.85.1: + resolution: {integrity: sha512-Mh7CA53wR3ADvXAYipFc/R3vV4PVOzoKwWzPxmq+7i8UZrtsVjKONxGtqWe9JG1mna0C9CRZAx0sv/BzbOJxWg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-android-x64@1.86.0: + resolution: {integrity: sha512-8Q263GgwGjz7Jkf7Eghp7NrwqskDL95WO9sKrNm9iOd2re/M48W7RN/lpdcZwrUnEOhueks0RRyYyZYBNRz8Tg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] - sass-embedded-darwin-arm64@1.83.4: - resolution: {integrity: sha512-rp2ywymWc3nymnSnAFG5R/8hvxWCsuhK3wOnD10IDlmNB7o4rzKby1c+2ZfpQGowlYGWsWWTgz8FW2qzmZsQRw==} + sass-embedded-darwin-arm64@1.85.1: + resolution: {integrity: sha512-msWxzhvcP9hqGVegxVePVEfv9mVNTlUgGr6k7O7Ihji702mbtrH/lKwF4aRkkt4g1j7tv10+JtQXmTNi/pi9kA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] - sass-embedded-darwin-x64@1.83.4: - resolution: {integrity: sha512-kLkN2lXz9PCgGfDS8Ev5YVcl/V2173L6379en/CaFuJJi7WiyPgBymW7hOmfCt4uO4R1y7CP2Uc08DRtZsBlAA==} + sass-embedded-darwin-arm64@1.86.0: + resolution: {integrity: sha512-d8oMEaIweq1tjrb/BT43igDviOMS1TeDpc51QF7vAHkt9drSjPmqEmbqStdFYPAGZj1j0RA4WCRoVl6jVixi/w==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.85.1: + resolution: {integrity: sha512-J4UFHUiyI9Z+mwYMwz11Ky9TYr3hY1fCxeQddjNGL/+ovldtb0yAIHvoVM0BGprQDm5JqhtUk8KyJ3RMJqpaAA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] - sass-embedded-linux-arm64@1.83.4: - resolution: {integrity: sha512-E0zjsZX2HgESwyqw31EHtI39DKa7RgK7nvIhIRco1d0QEw227WnoR9pjH3M/ZQy4gQj3GKilOFHM5Krs/omeIA==} + sass-embedded-darwin-x64@1.86.0: + resolution: {integrity: sha512-5NLRtn0ZUDBkfpKOsgLGl9B34po4Qui8Nff/lXTO+YkxBQFX4GoMkYNk9EJqHwoLLzICsxIhNDMMDiPGz7Fdrw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.85.1: + resolution: {integrity: sha512-jGadetB03BMFG2rq3OXub/uvC/lGpbQOiLGEz3NLb2nRZWyauRhzDtvZqkr6BEhxgIWtMtz2020yD8ZJSw/r2w==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-arm64@1.86.0: + resolution: {integrity: sha512-50A+0rhahRDRkKkv+qS7GDAAkW1VPm2RCX4zY4JWydhV4NwMXr6HbkLnsJ2MGixCyibPh59iflMpNBhe7SEMNg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - sass-embedded-linux-arm@1.83.4: - resolution: {integrity: sha512-nL90ryxX2lNmFucr9jYUyHHx21AoAgdCL1O5Ltx2rKg2xTdytAGHYo2MT5S0LIeKLa/yKP/hjuSvrbICYNDvtA==} + sass-embedded-linux-arm@1.85.1: + resolution: {integrity: sha512-X0fDh95nNSw1wfRlnkE4oscoEA5Au4nnk785s9jghPFkTBg+A+5uB6trCjf0fM22+Iw6kiP4YYmDdw3BqxAKLQ==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-arm@1.86.0: + resolution: {integrity: sha512-b6wm0+Il+blJDleRXAqA6JISGMjRb0/thTEg4NWgmiJwUoZjDycj5FTbfYPnLXjCEIMGaYmW3patrJ3JMJcT3Q==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - sass-embedded-linux-ia32@1.83.4: - resolution: {integrity: sha512-ew5HpchSzgAYbQoriRh8QhlWn5Kw2nQ2jHoV9YLwGKe3fwwOWA0KDedssvDv7FWnY/FCqXyymhLd6Bxae4Xquw==} + sass-embedded-linux-ia32@1.85.1: + resolution: {integrity: sha512-7HlYY90d9mitDtNi5s+S+5wYZrTVbkBH2/kf7ixrzh2BFfT0YM81UHLJRnGX93y9aOMBL6DSZAIfkt1RsV9bkQ==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] - sass-embedded-linux-musl-arm64@1.83.4: - resolution: {integrity: sha512-IzMgalf6MZOxgp4AVCgsaWAFDP/IVWOrgVXxkyhw29fyAEoSWBJH4k87wyPhEtxSuzVHLxKNbc8k3UzdWmlBFg==} + sass-embedded-linux-ia32@1.86.0: + resolution: {integrity: sha512-h0mr9w71TV3BRPk9JHr0flnRCznhkraY14gaj5T+t78vUFByOUMxp4hTr+JpZAR5mv0mIeoMwrQYwWJoqKI0mw==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + + sass-embedded-linux-musl-arm64@1.85.1: + resolution: {integrity: sha512-FLkIT0p18XOkR6wryJ13LqGBDsrYev2dRk9dtiU18NCpNXruKsdBQ1ZnWHVKB3h1dA9lFyEEisC0sooKdNfeOQ==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] - sass-embedded-linux-musl-arm@1.83.4: - resolution: {integrity: sha512-0RrJRwMrmm+gG0VOB5b5Cjs7Sd+lhqpQJa6EJNEaZHljJokEfpE5GejZsGMRMIQLxEvVphZnnxl6sonCGFE/QQ==} + sass-embedded-linux-musl-arm64@1.86.0: + resolution: {integrity: sha512-5OZjiJIUyhvKJIGNDEjyRUWDe+W91hq4Bji27sy8gdEuDzPWLx4NzwpKwsBUALUfyW/J5dxgi0ZAQnI3HieyQg==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-musl-arm@1.85.1: + resolution: {integrity: sha512-5vcdEqE8QZnu6i6shZo7x2N36V7YUoFotWj2rGekII5ty7Nkaj+VtZhUEOp9tAzEOlaFuDp5CyO1kUCvweT64A==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-arm@1.86.0: + resolution: {integrity: sha512-KZU70jBMVykC9HzS+o2FhrJaprFLDk3LWXVPtBFxgLlkcQ/apCkUCh2WVNViLhI2U4NrMSnTvd4kDnC/0m8qIw==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] - sass-embedded-linux-musl-ia32@1.83.4: - resolution: {integrity: sha512-LLb4lYbcxPzX4UaJymYXC+WwokxUlfTJEFUv5VF0OTuSsHAGNRs/rslPtzVBTvMeG9TtlOQDhku1F7G6iaDotA==} + sass-embedded-linux-musl-ia32@1.85.1: + resolution: {integrity: sha512-N1093T84zQJor1yyIAdYScB5eAuQarGK1tKgZ4uTnxVlgA7Xi1lXV8Eh7ox9sDqKCaWkVQ3MjqU26vYRBeRWyw==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + + sass-embedded-linux-musl-ia32@1.86.0: + resolution: {integrity: sha512-vq9wJ7kaELrsNU6Ld6kvrIHxoIUWaD+5T6TQVj4SJP/iw1NjonyCDMQGGs6UgsIEzvaIwtlSlDbRewAq+4PchA==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [linux] - sass-embedded-linux-musl-riscv64@1.83.4: - resolution: {integrity: sha512-zoKlPzD5Z13HKin1UGR74QkEy+kZEk2AkGX5RelRG494mi+IWwRuWCppXIovor9+BQb9eDWPYPoMVahwN5F7VA==} + sass-embedded-linux-musl-riscv64@1.85.1: + resolution: {integrity: sha512-WRsZS/7qlfYXsa93FBpSruieuURIu7ySfFhzYfF1IbKrNAGwmbduutkHZh2ddm5/vQMvQ0Rdosgv+CslaQHMcw==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - sass-embedded-linux-musl-x64@1.83.4: - resolution: {integrity: sha512-hB8+/PYhfEf2zTIcidO5Bpof9trK6WJjZ4T8g2MrxQh8REVtdPcgIkoxczRynqybf9+fbqbUwzXtiUao2GV+vQ==} + sass-embedded-linux-musl-riscv64@1.86.0: + resolution: {integrity: sha512-UZJPu4zKe3phEzoSVRh5jcSicBBPe+jEbVNALHSSz881iOAYnDQXHITGeQ4mM1/7e/LTyryHk6EPBoaLOv6JrA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-musl-x64@1.85.1: + resolution: {integrity: sha512-+OlLIilA5TnP0YEqTQ8yZtkW+bJIQYvzoGoNLUEskeyeGuOiIyn2CwL6G4JQB4xZQFaxPHb7JD3EueFkQbH0Pw==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-musl-x64@1.86.0: + resolution: {integrity: sha512-8taAgbWMk4QHneJcouWmWZJlmKa2O03g4I/CFo4bfMPL87bibY90pAsSDd+C+t81g0+2aK0/lY/BoB0r3qXLiA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - sass-embedded-linux-riscv64@1.83.4: - resolution: {integrity: sha512-83fL4n+oeDJ0Y4KjASmZ9jHS1Vl9ESVQYHMhJE0i4xDi/P3BNarm2rsKljq/QtrwGpbqwn8ujzOu7DsNCMDSHA==} + sass-embedded-linux-riscv64@1.85.1: + resolution: {integrity: sha512-mKKlOwMGLN7yP1p0gB5yG/HX4fYLnpWaqstNuOOXH+fOzTaNg0+1hALg0H0CDIqypPO74M5MS9T6FAJZGdT6dQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] - sass-embedded-linux-x64@1.83.4: - resolution: {integrity: sha512-NlnGdvCmTD5PK+LKXlK3sAuxOgbRIEoZfnHvxd157imCm/s2SYF/R28D0DAAjEViyI8DovIWghgbcqwuertXsA==} + sass-embedded-linux-riscv64@1.86.0: + resolution: {integrity: sha512-yREY6o2sLwiiA03MWHVpnUliLscz0flEmFW/wzxYZJDqg9eZteB3hUWgZD63eLm2PTZsYxDQpjAHpa48nnIEmA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-x64@1.85.1: + resolution: {integrity: sha512-uKRTv0z8NgtHV7xSren78+yoWB79sNi7TMqI7Bxd8fcRNIgHQSA8QBdF8led2ETC004hr8h71BrY60RPO+SSvA==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-x64@1.86.0: + resolution: {integrity: sha512-sH0F8np9PTgTbFcJWxfr1NzPkL5ID2NcpMtZyKPTdnn9NkE/L2UwXSo6xOvY0Duc4Hg+58wSrDnj6KbvdeHCPg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] - sass-embedded-win32-arm64@1.83.4: - resolution: {integrity: sha512-J2BFKrEaeSrVazU2qTjyQdAk+MvbzJeTuCET0uAJEXSKtvQ3AzxvzndS7LqkDPbF32eXAHLw8GVpwcBwKbB3Uw==} + sass-embedded-win32-arm64@1.85.1: + resolution: {integrity: sha512-/GMiZXBOc6AEMBC3g25Rp+x8fq9Z6Ql7037l5rajBPhZ+DdFwtdHY0Ou3oIU6XuWUwD06U3ii4XufXVFhsP6PA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [win32] - sass-embedded-win32-ia32@1.83.4: - resolution: {integrity: sha512-uPAe9T/5sANFhJS5dcfAOhOJy8/l2TRYG4r+UO3Wp4yhqbN7bggPvY9c7zMYS0OC8tU/bCvfYUDFHYMCl91FgA==} + sass-embedded-win32-arm64@1.86.0: + resolution: {integrity: sha512-4O1XVUxLTIjMOvrziYwEZgvFqC5sF6t0hTAPJ+h2uiAUZg9Joo0PvuEedXurjISgDBsb5W5DTL9hH9q1BbP4cQ==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-ia32@1.85.1: + resolution: {integrity: sha512-L+4BWkKKBGFOKVQ2PQ5HwFfkM5FvTf1Xx2VSRvEWt9HxPXp6SPDho6zC8fqNQ3hSjoaoASEIJcSvgfdQYO0gdg==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [win32] + + sass-embedded-win32-ia32@1.86.0: + resolution: {integrity: sha512-zuSP2axkGm4VaJWt38P464H+4424Swr9bzFNfbbznxe3Ue4RuqSBqwiLiYdg9Q1cecTQ2WGH7G7WO56KK7WLwg==} engines: {node: '>=14.0.0'} cpu: [ia32] os: [win32] - sass-embedded-win32-x64@1.83.4: - resolution: {integrity: sha512-C9fkDY0jKITdJFij4UbfPFswxoXN9O/Dr79v17fJnstVwtUojzVJWKHUXvF0Zg2LIR7TCc4ju3adejKFxj7ueA==} + sass-embedded-win32-x64@1.85.1: + resolution: {integrity: sha512-/FO0AGKWxVfCk4GKsC0yXWBpUZdySe3YAAbQQL0lL6xUd1OiUY8Kow6g4Kc1TB/+z0iuQKKTqI/acJMEYl4iTQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded-win32-x64@1.86.0: + resolution: {integrity: sha512-GVX0CHtukr3kjqfqretSlPiJzV7V4JxUjpRZV+yC9gUMTiDErilJh2Chw1r0+MYiYvumCDUSDlticmvJs7v0tA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] - sass-embedded@1.83.4: - resolution: {integrity: sha512-Hf2burRA/y5PGxsg6jB9UpoK/xZ6g/pgrkOcdl6j+rRg1Zj8XhGKZ1MTysZGtTPUUmiiErqzkP5+Kzp95yv9GQ==} + sass-embedded@1.85.1: + resolution: {integrity: sha512-0i+3h2Df/c71afluxC1SXqyyMmJlnKWfu9ZGdzwuKRM1OftEa2XM2myt5tR36CF3PanYrMjFKtRIj8PfSf838w==} + engines: {node: '>=16.0.0'} + hasBin: true + + sass-embedded@1.86.0: + resolution: {integrity: sha512-Ibq5DzxjSf9f/IJmKeHVeXlVqiZWdRJF+RXy6v6UupvMYVMU5Ei+teSFBvvpPD5bB2QhhnU/OJlSM0EBCtfr9g==} engines: {node: '>=16.0.0'} hasBin: true - sass-loader@16.0.4: - resolution: {integrity: sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==} + sass-loader@16.0.5: + resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} engines: {node: '>= 18.12.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -2360,10 +2549,6 @@ packages: sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - schema-utils@4.2.0: resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} engines: {node: '>= 12.13.0'} @@ -2397,6 +2582,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -2434,10 +2624,6 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - sonic-boom@3.7.0: resolution: {integrity: sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==} @@ -2578,8 +2764,8 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - terser-webpack-plugin@5.3.11: - resolution: {integrity: sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==} + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -2614,6 +2800,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} @@ -2656,8 +2846,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript@5.7.3: - resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} hasBin: true @@ -2671,10 +2861,6 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -2724,8 +2910,8 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - web-ext@8.4.0: - resolution: {integrity: sha512-zO0GNacJeqw3fwbBvgHC2G8reieyN+oCKiMQepX3iv2fm9KEBqg1gdbcdEvi7edfj+gjgxyXhVlFcHIvcEM2Cg==} + web-ext@8.5.0: + resolution: {integrity: sha512-Cf7MLD4GDBLapBojGCywcDuScm7hi+M8n7JzqDYX1oNTrVvFirfWcu+A6RCqRyu1siMu3jY4ul/hAu9Chkf1Aw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2751,8 +2937,8 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - webpack@5.97.1: - resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} + webpack@5.98.0: + resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -2780,6 +2966,11 @@ packages: engines: {node: '>= 8'} hasBin: true + which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + widest-line@5.0.0: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} @@ -2801,8 +2992,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2875,7 +3066,7 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.0 - '@babel/runtime@7.26.7': + '@babel/runtime@7.26.10': dependencies: regenerator-runtime: 0.14.1 @@ -2899,27 +3090,45 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} + '@dotenvx/dotenvx@1.39.0': + dependencies: + commander: 11.1.0 + dotenv: 16.4.7 + eciesjs: 0.4.14 + execa: 5.1.1 + fdir: 6.4.3(picomatch@4.0.2) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.2 + which: 4.0.0 + + '@ecies/ciphers@0.2.2(@noble/ciphers@1.2.1)': + dependencies: + '@noble/ciphers': 1.2.1 + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.4.0(eslint@9.19.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.22.0)': dependencies: - eslint: 9.19.0 + eslint: 9.22.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.19.2': dependencies: - '@eslint/object-schema': 2.1.5 + '@eslint/object-schema': 2.1.6 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/core@0.10.0': + '@eslint/config-helpers@0.1.0': {} + + '@eslint/core@0.12.0': dependencies: '@types/json-schema': 7.0.15 @@ -2929,7 +3138,7 @@ snapshots: debug: 4.3.4 espree: 9.6.1 globals: 13.23.0 - ignore: 5.2.4 + ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -2937,13 +3146,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/eslintrc@3.2.0': + '@eslint/eslintrc@3.3.0': dependencies: ajv: 6.12.6 debug: 4.3.4 espree: 10.3.0 globals: 14.0.0 - ignore: 5.2.4 + ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -2953,13 +3162,13 @@ snapshots: '@eslint/js@8.57.1': {} - '@eslint/js@9.19.0': {} + '@eslint/js@9.22.0': {} - '@eslint/object-schema@2.1.5': {} + '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.5': + '@eslint/plugin-kit@0.2.7': dependencies: - '@eslint/core': 0.10.0 + '@eslint/core': 0.12.0 levn: 0.4.1 '@fluent/syntax@0.19.0': {} @@ -2987,7 +3196,7 @@ snapshots: '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.1': {} + '@humanwhocodes/retry@0.4.2': {} '@jridgewell/gen-mapping@0.3.2': dependencies: @@ -3011,7 +3220,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 - '@mdn/browser-compat-data@5.6.31': {} + '@mdn/browser-compat-data@5.7.3': {} + + '@noble/ciphers@1.2.1': {} + + '@noble/curves@1.8.1': + dependencies: + '@noble/hashes': 1.7.1 + + '@noble/hashes@1.7.1': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -3041,11 +3258,9 @@ snapshots: '@sindresorhus/is@5.6.0': {} - '@sindresorhus/merge-streams@2.3.0': {} - - '@swc/cli@0.6.0(@swc/core@1.10.14)': + '@swc/cli@0.6.0(@swc/core@1.11.11)': dependencies: - '@swc/core': 1.10.14 + '@swc/core': 1.11.11 '@swc/counter': 0.1.3 '@xhmikosr/bin-wrapper': 13.0.5 commander: 8.3.0 @@ -3056,55 +3271,55 @@ snapshots: slash: 3.0.0 source-map: 0.7.4 - '@swc/core-darwin-arm64@1.10.14': + '@swc/core-darwin-arm64@1.11.11': optional: true - '@swc/core-darwin-x64@1.10.14': + '@swc/core-darwin-x64@1.11.11': optional: true - '@swc/core-linux-arm-gnueabihf@1.10.14': + '@swc/core-linux-arm-gnueabihf@1.11.11': optional: true - '@swc/core-linux-arm64-gnu@1.10.14': + '@swc/core-linux-arm64-gnu@1.11.11': optional: true - '@swc/core-linux-arm64-musl@1.10.14': + '@swc/core-linux-arm64-musl@1.11.11': optional: true - '@swc/core-linux-x64-gnu@1.10.14': + '@swc/core-linux-x64-gnu@1.11.11': optional: true - '@swc/core-linux-x64-musl@1.10.14': + '@swc/core-linux-x64-musl@1.11.11': optional: true - '@swc/core-win32-arm64-msvc@1.10.14': + '@swc/core-win32-arm64-msvc@1.11.11': optional: true - '@swc/core-win32-ia32-msvc@1.10.14': + '@swc/core-win32-ia32-msvc@1.11.11': optional: true - '@swc/core-win32-x64-msvc@1.10.14': + '@swc/core-win32-x64-msvc@1.11.11': optional: true - '@swc/core@1.10.14': + '@swc/core@1.11.11': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.17 + '@swc/types': 0.1.19 optionalDependencies: - '@swc/core-darwin-arm64': 1.10.14 - '@swc/core-darwin-x64': 1.10.14 - '@swc/core-linux-arm-gnueabihf': 1.10.14 - '@swc/core-linux-arm64-gnu': 1.10.14 - '@swc/core-linux-arm64-musl': 1.10.14 - '@swc/core-linux-x64-gnu': 1.10.14 - '@swc/core-linux-x64-musl': 1.10.14 - '@swc/core-win32-arm64-msvc': 1.10.14 - '@swc/core-win32-ia32-msvc': 1.10.14 - '@swc/core-win32-x64-msvc': 1.10.14 + '@swc/core-darwin-arm64': 1.11.11 + '@swc/core-darwin-x64': 1.11.11 + '@swc/core-linux-arm-gnueabihf': 1.11.11 + '@swc/core-linux-arm64-gnu': 1.11.11 + '@swc/core-linux-arm64-musl': 1.11.11 + '@swc/core-linux-x64-gnu': 1.11.11 + '@swc/core-linux-x64-musl': 1.11.11 + '@swc/core-win32-arm64-msvc': 1.11.11 + '@swc/core-win32-ia32-msvc': 1.11.11 + '@swc/core-win32-x64-msvc': 1.11.11 '@swc/counter@0.1.3': {} - '@swc/types@0.1.17': + '@swc/types@0.1.19': dependencies: '@swc/counter': 0.1.3 @@ -3114,11 +3329,13 @@ snapshots: '@tokenizer/token@0.3.0': {} - '@types/chrome@0.0.301': + '@types/chrome@0.0.309': dependencies: '@types/filesystem': 0.0.32 '@types/har-format': 1.2.10 + '@types/eslint-config-prettier@6.11.3': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -3151,7 +3368,7 @@ snapshots: '@types/minimatch@3.0.5': {} - '@types/node@22.13.1': + '@types/node@22.13.10': dependencies: undici-types: 6.20.0 @@ -3159,11 +3376,11 @@ snapshots: '@types/trusted-types@2.0.7': {} - '@types/webpack@5.28.5(@swc/core@1.10.14)(webpack-cli@6.0.1)': + '@types/webpack@5.28.5(@swc/core@1.11.11)(webpack-cli@6.0.1)': dependencies: - '@types/node': 22.13.1 + '@types/node': 22.13.10 tapable: 2.2.1 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -3172,7 +3389,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.13.1 + '@types/node': 22.13.10 '@ungap/structured-clone@1.2.0': {} @@ -3254,20 +3471,20 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.97.1)': + '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)': dependencies: - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.97.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.98.0) - '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.97.1)': + '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)': dependencies: - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.97.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.98.0) - '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.97.1)': + '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.98.0)': dependencies: - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) - webpack-cli: 6.0.1(webpack@5.97.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) + webpack-cli: 6.0.1(webpack@5.98.0) '@xhmikosr/archive-type@7.0.0': dependencies: @@ -3349,15 +3566,13 @@ snapshots: dependencies: acorn: 8.14.0 - acorn@8.12.1: {} - acorn@8.14.0: {} - addons-linter@7.8.0: + addons-linter@7.9.0: dependencies: '@fluent/syntax': 0.19.0 '@fregante/relaxed-json': 2.0.0 - '@mdn/browser-compat-data': 5.6.31 + '@mdn/browser-compat-data': 5.7.3 addons-moz-compare: 1.3.0 addons-scanner-utils: 9.12.0 ajv: 8.17.1 @@ -3372,10 +3587,10 @@ snapshots: espree: 10.3.0 esprima: 4.0.1 fast-json-patch: 3.1.1 - image-size: 1.2.0 + image-size: 2.0.1 json-merge-patch: 1.0.2 pino: 8.20.0 - semver: 7.6.3 + semver: 7.7.1 source-map-support: 0.5.21 upath: 2.0.1 yargs: 17.7.2 @@ -3414,10 +3629,6 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-keywords@3.5.2(ajv@6.12.6): - dependencies: - ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.12.0): dependencies: ajv: 8.12.0 @@ -3630,7 +3841,7 @@ snapshots: chrome-launcher@1.1.2: dependencies: - '@types/node': 22.13.1 + '@types/node': 22.13.10 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.1 @@ -3676,6 +3887,8 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + commander@11.1.0: {} + commander@12.1.0: {} commander@2.20.3: {} @@ -3727,15 +3940,14 @@ snapshots: dependencies: safe-buffer: 5.2.1 - copy-webpack-plugin@12.0.2(webpack@5.97.1): + copy-webpack-plugin@13.0.0(webpack@5.98.0): dependencies: - fast-glob: 3.3.2 glob-parent: 6.0.2 - globby: 14.0.1 normalize-path: 3.0.0 - schema-utils: 4.2.0 + schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + tinyglobby: 0.2.12 + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) core-util-is@1.0.3: {} @@ -3759,7 +3971,7 @@ snapshots: pbf: 3.2.1 yazl: 2.5.1 - css-loader@7.1.2(webpack@5.97.1): + css-loader@7.1.2(webpack@5.98.0): dependencies: icss-utils: 5.1.0(postcss@8.4.35) postcss: 8.4.35 @@ -3770,7 +3982,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.5.4 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) css-select@5.1.0: dependencies: @@ -3860,6 +4072,13 @@ snapshots: dotenv@16.4.7: {} + eciesjs@0.4.14: + dependencies: + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.2.1) + '@noble/ciphers': 1.2.1 + '@noble/curves': 1.8.1 + '@noble/hashes': 1.7.1 + electron-to-chromium@1.5.80: {} emoji-regex@10.4.0: {} @@ -3891,9 +4110,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.0.1(eslint@9.19.0): + eslint-config-prettier@10.1.1(eslint@9.22.0): dependencies: - eslint: 9.19.0 + eslint: 9.22.0 eslint-plugin-no-unsanitized@4.1.2(eslint@8.57.1): dependencies: @@ -3909,7 +4128,7 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.2.0: + eslint-scope@8.3.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 @@ -3945,7 +4164,7 @@ snapshots: glob-parent: 6.0.2 globals: 13.23.0 graphemer: 1.4.0 - ignore: 5.2.4 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -3961,18 +4180,19 @@ snapshots: transitivePeerDependencies: - supports-color - eslint@9.19.0: + eslint@9.22.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.19.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.22.0) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.10.0 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.19.0 - '@eslint/plugin-kit': 0.2.5 + '@eslint/config-array': 0.19.2 + '@eslint/config-helpers': 0.1.0 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.0 + '@eslint/js': 9.22.0 + '@eslint/plugin-kit': 0.2.7 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 + '@humanwhocodes/retry': 0.4.2 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -3980,7 +4200,7 @@ snapshots: cross-spawn: 7.0.6 debug: 4.3.4 escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 + eslint-scope: 8.3.0 eslint-visitor-keys: 4.2.0 espree: 10.3.0 esquery: 1.5.0 @@ -3989,7 +4209,7 @@ snapshots: file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - ignore: 5.2.4 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 @@ -4099,6 +4319,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.4.3(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.0.4 @@ -4231,16 +4455,7 @@ snapshots: globals@14.0.0: {} - globals@15.14.0: {} - - globby@14.0.1: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.2 - ignore: 5.2.4 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 + globals@16.0.0: {} got@13.0.0: dependencies: @@ -4344,11 +4559,9 @@ snapshots: ieee754@1.2.1: {} - ignore@5.2.4: {} + ignore@5.3.2: {} - image-size@1.2.0: - dependencies: - queue: 6.0.2 + image-size@2.0.1: {} immediate@3.0.6: {} @@ -4452,11 +4665,13 @@ snapshots: isexe@2.0.0: {} + isexe@3.1.1: {} + isobject@3.0.1: {} jest-worker@27.5.1: dependencies: - '@types/node': 22.13.1 + '@types/node': 22.13.10 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -4580,11 +4795,11 @@ snapshots: mimic-response@4.0.0: {} - mini-css-extract-plugin@2.9.2(webpack@5.97.1): + mini-css-extract-plugin@2.9.2(webpack@5.98.0): dependencies: schema-utils: 4.2.0 tapable: 2.2.1 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) minimatch@3.1.2: dependencies: @@ -4600,7 +4815,7 @@ snapshots: mkdirp@0.5.6: dependencies: - minimist: 1.2.7 + minimist: 1.2.8 mri@1.2.0: {} @@ -4666,6 +4881,8 @@ snapshots: object-inspect@1.12.3: {} + object-treeify@1.1.33: {} + on-exit-leak-free@2.1.0: {} once@1.4.0: @@ -4762,8 +4979,6 @@ snapshots: path-parse@1.0.7: {} - path-type@5.0.0: {} - pbf@3.2.1: dependencies: ieee754: 1.2.1 @@ -4779,6 +4994,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.2: {} + pino-abstract-transport@1.2.0: dependencies: readable-stream: 4.3.0 @@ -4868,7 +5085,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.4.2: {} + prettier@3.5.3: {} process-nextick-args@2.0.1: {} @@ -4900,10 +5117,6 @@ snapshots: queue-tick@1.0.1: {} - queue@6.0.2: - dependencies: - inherits: 2.0.4 - quick-format-unescaped@4.0.4: {} quick-lru@5.1.1: {} @@ -5008,67 +5221,159 @@ snapshots: safer-buffer@2.1.2: {} - sass-embedded-android-arm64@1.83.4: + sass-embedded-android-arm64@1.85.1: optional: true - sass-embedded-android-arm@1.83.4: + sass-embedded-android-arm64@1.86.0: optional: true - sass-embedded-android-ia32@1.83.4: + sass-embedded-android-arm@1.85.1: optional: true - sass-embedded-android-riscv64@1.83.4: + sass-embedded-android-arm@1.86.0: optional: true - sass-embedded-android-x64@1.83.4: + sass-embedded-android-ia32@1.85.1: optional: true - sass-embedded-darwin-arm64@1.83.4: + sass-embedded-android-ia32@1.86.0: optional: true - sass-embedded-darwin-x64@1.83.4: + sass-embedded-android-riscv64@1.85.1: optional: true - sass-embedded-linux-arm64@1.83.4: + sass-embedded-android-riscv64@1.86.0: optional: true - sass-embedded-linux-arm@1.83.4: + sass-embedded-android-x64@1.85.1: optional: true - sass-embedded-linux-ia32@1.83.4: + sass-embedded-android-x64@1.86.0: optional: true - sass-embedded-linux-musl-arm64@1.83.4: + sass-embedded-darwin-arm64@1.85.1: optional: true - sass-embedded-linux-musl-arm@1.83.4: + sass-embedded-darwin-arm64@1.86.0: optional: true - sass-embedded-linux-musl-ia32@1.83.4: + sass-embedded-darwin-x64@1.85.1: optional: true - sass-embedded-linux-musl-riscv64@1.83.4: + sass-embedded-darwin-x64@1.86.0: optional: true - sass-embedded-linux-musl-x64@1.83.4: + sass-embedded-linux-arm64@1.85.1: optional: true - sass-embedded-linux-riscv64@1.83.4: + sass-embedded-linux-arm64@1.86.0: optional: true - sass-embedded-linux-x64@1.83.4: + sass-embedded-linux-arm@1.85.1: optional: true - sass-embedded-win32-arm64@1.83.4: + sass-embedded-linux-arm@1.86.0: optional: true - sass-embedded-win32-ia32@1.83.4: + sass-embedded-linux-ia32@1.85.1: optional: true - sass-embedded-win32-x64@1.83.4: + sass-embedded-linux-ia32@1.86.0: optional: true - sass-embedded@1.83.4: + sass-embedded-linux-musl-arm64@1.85.1: + optional: true + + sass-embedded-linux-musl-arm64@1.86.0: + optional: true + + sass-embedded-linux-musl-arm@1.85.1: + optional: true + + sass-embedded-linux-musl-arm@1.86.0: + optional: true + + sass-embedded-linux-musl-ia32@1.85.1: + optional: true + + sass-embedded-linux-musl-ia32@1.86.0: + optional: true + + sass-embedded-linux-musl-riscv64@1.85.1: + optional: true + + sass-embedded-linux-musl-riscv64@1.86.0: + optional: true + + sass-embedded-linux-musl-x64@1.85.1: + optional: true + + sass-embedded-linux-musl-x64@1.86.0: + optional: true + + sass-embedded-linux-riscv64@1.85.1: + optional: true + + sass-embedded-linux-riscv64@1.86.0: + optional: true + + sass-embedded-linux-x64@1.85.1: + optional: true + + sass-embedded-linux-x64@1.86.0: + optional: true + + sass-embedded-win32-arm64@1.85.1: + optional: true + + sass-embedded-win32-arm64@1.86.0: + optional: true + + sass-embedded-win32-ia32@1.85.1: + optional: true + + sass-embedded-win32-ia32@1.86.0: + optional: true + + sass-embedded-win32-x64@1.85.1: + optional: true + + sass-embedded-win32-x64@1.86.0: + optional: true + + sass-embedded@1.85.1: + dependencies: + '@bufbuild/protobuf': 2.2.0 + buffer-builder: 0.2.0 + colorjs.io: 0.5.2 + immutable: 5.0.3 + rxjs: 7.8.1 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-android-arm: 1.85.1 + sass-embedded-android-arm64: 1.85.1 + sass-embedded-android-ia32: 1.85.1 + sass-embedded-android-riscv64: 1.85.1 + sass-embedded-android-x64: 1.85.1 + sass-embedded-darwin-arm64: 1.85.1 + sass-embedded-darwin-x64: 1.85.1 + sass-embedded-linux-arm: 1.85.1 + sass-embedded-linux-arm64: 1.85.1 + sass-embedded-linux-ia32: 1.85.1 + sass-embedded-linux-musl-arm: 1.85.1 + sass-embedded-linux-musl-arm64: 1.85.1 + sass-embedded-linux-musl-ia32: 1.85.1 + sass-embedded-linux-musl-riscv64: 1.85.1 + sass-embedded-linux-musl-x64: 1.85.1 + sass-embedded-linux-riscv64: 1.85.1 + sass-embedded-linux-x64: 1.85.1 + sass-embedded-win32-arm64: 1.85.1 + sass-embedded-win32-ia32: 1.85.1 + sass-embedded-win32-x64: 1.85.1 + + sass-embedded@1.86.0: dependencies: '@bufbuild/protobuf': 2.2.0 buffer-builder: 0.2.0 @@ -5079,42 +5384,36 @@ snapshots: sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-android-arm: 1.83.4 - sass-embedded-android-arm64: 1.83.4 - sass-embedded-android-ia32: 1.83.4 - sass-embedded-android-riscv64: 1.83.4 - sass-embedded-android-x64: 1.83.4 - sass-embedded-darwin-arm64: 1.83.4 - sass-embedded-darwin-x64: 1.83.4 - sass-embedded-linux-arm: 1.83.4 - sass-embedded-linux-arm64: 1.83.4 - sass-embedded-linux-ia32: 1.83.4 - sass-embedded-linux-musl-arm: 1.83.4 - sass-embedded-linux-musl-arm64: 1.83.4 - sass-embedded-linux-musl-ia32: 1.83.4 - sass-embedded-linux-musl-riscv64: 1.83.4 - sass-embedded-linux-musl-x64: 1.83.4 - sass-embedded-linux-riscv64: 1.83.4 - sass-embedded-linux-x64: 1.83.4 - sass-embedded-win32-arm64: 1.83.4 - sass-embedded-win32-ia32: 1.83.4 - sass-embedded-win32-x64: 1.83.4 - - sass-loader@16.0.4(sass-embedded@1.83.4)(webpack@5.97.1): + sass-embedded-android-arm: 1.86.0 + sass-embedded-android-arm64: 1.86.0 + sass-embedded-android-ia32: 1.86.0 + sass-embedded-android-riscv64: 1.86.0 + sass-embedded-android-x64: 1.86.0 + sass-embedded-darwin-arm64: 1.86.0 + sass-embedded-darwin-x64: 1.86.0 + sass-embedded-linux-arm: 1.86.0 + sass-embedded-linux-arm64: 1.86.0 + sass-embedded-linux-ia32: 1.86.0 + sass-embedded-linux-musl-arm: 1.86.0 + sass-embedded-linux-musl-arm64: 1.86.0 + sass-embedded-linux-musl-ia32: 1.86.0 + sass-embedded-linux-musl-riscv64: 1.86.0 + sass-embedded-linux-musl-x64: 1.86.0 + sass-embedded-linux-riscv64: 1.86.0 + sass-embedded-linux-x64: 1.86.0 + sass-embedded-win32-arm64: 1.86.0 + sass-embedded-win32-ia32: 1.86.0 + sass-embedded-win32-x64: 1.86.0 + + sass-loader@16.0.5(sass-embedded@1.86.0)(webpack@5.98.0): dependencies: neo-async: 2.6.2 optionalDependencies: - sass-embedded: 1.83.4 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + sass-embedded: 1.86.0 + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) sax@1.2.4: {} - schema-utils@3.3.0: - dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) - schema-utils@4.2.0: dependencies: '@types/json-schema': 7.0.11 @@ -5147,6 +5446,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.1: {} + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -5179,8 +5480,6 @@ snapshots: slash@3.0.0: {} - slash@5.1.0: {} - sonic-boom@3.7.0: dependencies: atomic-sleep: 1.0.0 @@ -5298,11 +5597,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swc-loader@0.2.6(@swc/core@1.10.14)(webpack@5.97.1): + swc-loader@0.2.6(@swc/core@1.11.11)(webpack@5.98.0): dependencies: - '@swc/core': 1.10.14 + '@swc/core': 1.11.11 '@swc/counter': 0.1.3 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) sync-child-process@1.0.2: dependencies: @@ -5318,21 +5617,21 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.21.1 - terser-webpack-plugin@5.3.11(@swc/core@1.10.14)(webpack@5.97.1): + terser-webpack-plugin@5.3.14(@swc/core@1.11.11)(webpack@5.98.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) optionalDependencies: - '@swc/core': 1.10.14 + '@swc/core': 1.11.11 terser@5.37.0: dependencies: '@jridgewell/source-map': 0.3.5 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -5352,6 +5651,11 @@ snapshots: through@2.3.8: {} + tinyglobby@0.2.12: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + titleize@3.0.0: {} tmp@0.2.3: {} @@ -5381,7 +5685,7 @@ snapshots: typedarray@0.0.6: {} - typescript@5.7.3: {} + typescript@5.8.2: {} uint8array-extras@1.4.0: {} @@ -5392,8 +5696,6 @@ snapshots: undici-types@6.20.0: {} - unicorn-magic@0.1.0: {} - union@0.5.0: dependencies: qs: 6.11.0 @@ -5444,11 +5746,11 @@ snapshots: dependencies: defaults: 1.0.4 - web-ext@8.4.0: + web-ext@8.5.0: dependencies: - '@babel/runtime': 7.26.7 + '@babel/runtime': 7.26.10 '@devicefarmer/adbkit': 3.3.8 - addons-linter: 7.8.0 + addons-linter: 7.9.0 camelcase: 8.0.0 chrome-launcher: 1.1.2 debounce: 1.2.1 @@ -5471,7 +5773,7 @@ snapshots: tmp: 0.2.3 update-notifier: 7.3.1 watchpack: 2.4.2 - ws: 8.18.0 + ws: 8.18.1 yargs: 17.7.2 zip-dir: 2.0.0 transitivePeerDependencies: @@ -5483,12 +5785,12 @@ snapshots: - supports-color - utf-8-validate - webpack-cli@6.0.1(webpack@5.97.1): + webpack-cli@6.0.1(webpack@5.98.0): dependencies: '@discoveryjs/json-ext': 0.6.3 - '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.97.1) - '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.97.1) - '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.97.1) + '@webpack-cli/configtest': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0) + '@webpack-cli/info': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0) + '@webpack-cli/serve': 3.0.1(webpack-cli@6.0.1)(webpack@5.98.0) colorette: 2.0.19 commander: 12.1.0 cross-spawn: 7.0.3 @@ -5497,7 +5799,7 @@ snapshots: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1) + webpack: 5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1) webpack-merge: 6.0.1 webpack-merge@6.0.1: @@ -5508,7 +5810,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.97.1(@swc/core@1.10.14)(webpack-cli@6.0.1): + webpack@5.98.0(@swc/core@1.11.11)(webpack-cli@6.0.1): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -5528,13 +5830,13 @@ snapshots: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.10.14)(webpack@5.97.1) + terser-webpack-plugin: 5.3.14(@swc/core@1.11.11)(webpack@5.98.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: - webpack-cli: 6.0.1(webpack@5.97.1) + webpack-cli: 6.0.1(webpack@5.98.0) transitivePeerDependencies: - '@swc/core' - esbuild @@ -5557,6 +5859,10 @@ snapshots: dependencies: isexe: 2.0.0 + which@4.0.0: + dependencies: + isexe: 3.1.1 + widest-line@5.0.0: dependencies: string-width: 7.2.0 @@ -5579,7 +5885,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.0: {} + ws@8.18.1: {} xdg-basedir@5.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..9a82b7a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'utils/*' diff --git a/src/UserJS/.env.example b/src/UserJS/.env.example deleted file mode 100644 index 2d29ccc..0000000 --- a/src/UserJS/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Please do not change this .env variable -JS_ENV="development" diff --git a/src/UserJS/header.js b/src/UserJS/header.js index 1df7454..076c916 100644 --- a/src/UserJS/header.js +++ b/src/UserJS/header.js @@ -2,47 +2,43 @@ (() => { 'use strict'; /******************************************************************************/ -const inIframe = () => { +const inIframe = (() => { try { return window.self !== window.top; - } catch (e) { + } catch { return true; } -} -if (inIframe()) { - return; -} +})(); +if (inIframe) return; let userjs = self.userjs; /** * Skip text/plain documents, based on uBlock Origin `vapi.js` file * - * [Source Code](https://github.com/gorhill/uBlock/blob/master/platform/common/vapi.js) + * [source code](https://github.com/gorhill/uBlock/blob/68962453ff6eec7ff109615a738beb8699b9844a/platform/common/vapi.js#L35) */ if ( (document instanceof Document || (document instanceof XMLDocument && document.createElement('div') instanceof HTMLDivElement)) && - /^image\/|^text\/plain/.test(document.contentType || '') === false && + /^text\/html|^application\/(xhtml|xml)/.test(document.contentType || '') === true && (self.userjs instanceof Object === false || userjs.UserJS !== true) ) { userjs = self.userjs = { UserJS: true }; +} else { + console.error('[%cMagic Userscript+%c] %cERROR','color: rgb(29, 155, 240);','','color: rgb(249, 24, 128);', `MIME type is not a document, got "${document.contentType || ''}"`); } -if (!(typeof userjs === 'object' && userjs.UserJS)) { - return; +if (!(typeof userjs === 'object' && userjs.UserJS)) return; +{ + /** Native implementation exists */ + const excludePolicy = [ + 'outlook.office.com' + ]; + const hostname = location?.hostname || ''; + if (window.trustedTypes && window.trustedTypes.createPolicy && !hostname.includes(excludePolicy)) window.trustedTypes.createPolicy('default', { createHTML: (string) => string, createScript: (string) => string, createScriptURL: (string) => string }); } -/** - * Uncompressed locales + compiler - * - * [_locales](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) - * - * [languageLoader.js](https://github.com/magicoflolis/Userscript-Plus/blob/master/tools/languageLoader.js) - */ +/** [i18n directory](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) */ const translations = [[languageList]]; -/** - * To compile this CSS `pnpm run build:Sass` - * - * [Uncompiled Cascading Style Sheet](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/sass) - */ +/** [source code](https://github.com/magicoflolis/Userscript-Plus/blob/master/src/sass/_main.scss) */ const main_css = `[[mainCSS]]`; /******************************************************************************/ [[code]] -})(); \ No newline at end of file +})(); diff --git a/src/UserJS/main.js b/src/UserJS/main.js index ffb8ca4..c0e4661 100644 --- a/src/UserJS/main.js +++ b/src/UserJS/main.js @@ -1,55 +1,107 @@ // #region Console -const dbg = (...msg) => { - const dt = new Date(); - console.debug( - '[%cMagic Userscript+%c] %cDBG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(255, 212, 0);', - `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, - ...msg - ); -}; -const err = (...msg) => { - console.error( - '[%cMagic Userscript+%c] %cERROR', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(249, 24, 128);', - ...msg - ); - const a = typeof alert !== 'undefined' && alert; - for (const ex of msg) { - if (typeof ex === 'object' && 'cause' in ex && a) { - a(`[Magic Userscript+] (${ex.cause}) ${ex.message}`); +const con = { + title: '[%cMagic Userscript+%c]', + color: 'color: rgb(29, 155, 240);', + dbg(...msg) { + const dt = new Date(); + console.debug( + `${con.title} %cDBG`, + con.color, + '', + 'color: rgb(255, 212, 0);', + `[${dt.getHours()}:${('0' + dt.getMinutes()).slice(-2)}:${('0' + dt.getSeconds()).slice(-2)}]`, + ...msg + ); + }, + err(...msg) { + console.error(`${con.title} %cERROR`, con.color, '', 'color: rgb(249, 24, 128);', ...msg); + const a = typeof alert !== 'undefined' && alert; + const t = con.title.replace(/%c/g, ''); + for (const ex of msg) { + if (typeof ex === 'object' && 'cause' in ex && a) { + a(`${t} (${ex.cause}) ${ex.message}`); + } } + }, + info(...msg) { + console.info(`${con.title} %cINF`, con.color, '', 'color: rgb(0, 186, 124);', ...msg); + }, + log(...msg) { + console.log(`${con.title} %cLOG`, con.color, '', 'color: rgb(219, 160, 73);', ...msg); } }; -const info = (...msg) => { - console.info( - '[%cMagic Userscript+%c] %cINF', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(0, 186, 124);', - ...msg - ); -}; -const log = (...msg) => { - console.log( - '[%cMagic Userscript+%c] %cLOG', - 'color: rgb(29, 155, 240);', - '', - 'color: rgb(219, 160, 73);', - ...msg - ); -}; +const { err, info } = con; // #endregion /** * @type { import("../typings/types.d.ts").config } */ -let cfg = {}; +let cfg; + +const BLANK_FN = function () {}; +const BLANK_ASYNC_FN = async function () {}; +const BLANK_PAGE = 'about:blank'; +/** + * @param {string} hn + */ +const normalizedHostname = (hn) => hn.replace(/^www\./, ''); +/** + * @param {string} txt + */ +const formatURL = (txt) => + txt + .split('.') + .splice(-2) + .join('.') + .replace(/\/|https:/g, ''); +/** + * @param {string} str + */ +const getHostname = (str) => formatURL(normalizedHostname(str)); +/** + * @type {URL | undefined} + */ +let url; +try { + url = new URL(window.location.href ?? BLANK_PAGE); +} catch { + /* empty */ +} +// #region Validators +const objToStr = (obj) => Object.prototype.toString.call(obj); +const isRegExp = (obj) => /RegExp/.test(objToStr(obj)); +const isElem = (obj) => /Element/.test(objToStr(obj)); +const isHTML = (obj) => /object HTML/.test(objToStr(obj)); +const isObj = (obj) => /Object/.test(objToStr(obj)); +const isFN = (obj) => /Function/.test(objToStr(obj)); +const isUserCSS = (str) => /\.user\.css$/.test(str); +const isUserJS = (str) => /\.user\.js$/.test(str); +/** + * @type { import("../typings/types.d.ts").isNull } + */ +const isNull = (obj) => { + return Object.is(obj, null) || Object.is(obj, undefined); +}; +/** + * @type { import("../typings/types.d.ts").isBlank } + */ +const isBlank = (obj) => { + return ( + (typeof obj === 'string' && Object.is(obj.trim(), '')) || + ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || + (Array.isArray(obj) && Object.is(obj.length, 0)) || + (isObj(obj) && Object.is(Object.keys(obj).length, 0)) + ); +}; +/** + * @type { import("../typings/types.d.ts").isEmpty } + */ +const isEmpty = (obj) => { + return isNull(obj) || isBlank(obj); +}; +// #endregion +// #region Globals /** * https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/global-this.js * @returns {typeof globalThis} @@ -69,86 +121,97 @@ function globalWin() { Function('return this')() ); } - -/** @type { import("../typings/UserJS.d.ts").safeSelf } */ -function safeSelf() { - if (userjs.safeSelf) { - return userjs.safeSelf; - } - const g = globalWin(); +class Safe { /** @type { import("../typings/UserJS.d.ts").safeHandles } */ - const safe = { - XMLHttpRequest: g.XMLHttpRequest, - createElement: g.document.createElement.bind(g.document), - createElementNS: g.document.createElementNS.bind(g.document), - createTextNode: g.document.createTextNode.bind(g.document), - setTimeout: g.setTimeout, - clearTimeout: g.clearTimeout, - navigator: g.navigator, - scheduler: { - postTask(callback, options) { - if ('scheduler' in g && 'postTask' in g.scheduler) { - return g.scheduler.postTask(callback, options); - } - - options = Object.assign({}, options); - - if (options.delay === undefined) options.delay = 0; - options.delay = Number(options.delay); - if (options.delay < 0) { - return Promise.reject(new TypeError('"delay" must be a positive number.')); - } - return new Promise((resolve) => { - g.setTimeout(() => { - resolve(callback()); - }, options.delay); - }); - }, - yield() { - if ('scheduler' in g && 'yield' in g.scheduler) { - scheduler.yield(); - return g.scheduler.yield(); - } - return new Promise((resolve) => { - g.setTimeout(resolve, 0); - }); - } - } - }; - userjs.safeSelf = safe; - return userjs.safeSelf; -} - -const trustedTypes = { - policy: false, - init() { - if (this.policy) { - return this.policy; - } - this.policy = true; + _safe; + create() { try { - // Native implementation exists - if (window.trustedTypes && window.trustedTypes.createPolicy) { - window.trustedTypes.createPolicy('default', { - createHTML: (string) => string - }); + const g = globalWin(); + /** @type { import("../typings/UserJS.d.ts").safeHandles } */ + const safe = { + XMLHttpRequest: g.XMLHttpRequest, + CustomEvent: g.CustomEvent, + createElement: g.document.createElement.bind(g.document), + createElementNS: g.document.createElementNS.bind(g.document), + createTextNode: g.document.createTextNode.bind(g.document), + setTimeout: g.setTimeout, + clearTimeout: g.clearTimeout, + navigator: g.navigator, + scheduler: { + postTask(callback, options) { + if ('scheduler' in g && 'postTask' in g.scheduler) { + return g.scheduler.postTask(callback, options); + } + + options = Object.assign({}, options); + + if (options.delay === undefined) options.delay = 0; + options.delay = Number(options.delay); + if (options.delay < 0) { + return Promise.reject(new TypeError('"delay" must be a positive number.')); + } + return new Promise((resolve) => { + g.setTimeout(() => { + resolve(callback()); + }, options.delay); + }); + }, + yield() { + if ('scheduler' in g && 'yield' in g.scheduler) { + scheduler.yield(); + return g.scheduler.yield(); + } + return new Promise((resolve) => { + g.setTimeout(resolve, 0); + }); + } + }, + groupBy(arr, callback) { + if (isFN(Object.groupBy)) { + return Object.groupBy(arr, callback); + } + /** [Object.groupBy polyfill](https://gist.github.com/gtrabanco/7c97bd41aa74af974fa935bfb5044b6e) */ + return arr.reduce((acc = {}, ...args) => { + const key = callback(...args); + acc[key] ??= []; + acc[key].push(args[0]); + return acc; + }, {}); + } + }; + for (const [k, v] of Object.entries(safe)) { + if (/scheduler|navigator/.test(k) || isFN(v)) continue; + throw new Error(`Safe handles "${k}" returned "${v}"`, { cause: 'safeSelf' }); } - } catch (ex) { - ex.cause = 'trustedTypes.init()'; - err(ex); + this._safe = safe; + } catch (e) { + err(e); + this._safe = null; } - return this.policy; + return this._safe; } -}; -trustedTypes.init(); - -const BLANK_PAGE = 'about:blank'; -// Lets highlight me :) + get _self() { + return this._safe ?? this.create(); + } + set _self(obj) { + this._safe = obj; + } +} +const { _self } = new Safe(); +// #endregion +// #region Constants +/** + * @type { import("../typings/types.d.ts").cfgBase } + */ +const cfgBase = []; +const cfgSec = new Set(); +/** Lets highlight me :) */ const authorID = 166061; /** - * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{{id}}` + * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{id}` */ const goodUserJS = [ + 33005, 394820, 438684, 4870, @@ -180,23 +243,21 @@ const goodUserJS = [ 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' ]; -/** - * Remove UserJS from banned accounts - */ +/** Remove UserJS from banned accounts */ const badUserJS = [478597]; -// Unsupport webpages for each engine +/** Unsupport host for search engines */ const engineUnsupported = { greasyfork: ['pornhub.com'], sleazyfork: ['pornhub.com'], openuserjs: [], github: [] }; -const getUAData = () => { +const isMobile = (() => { if (userjs.isMobile !== undefined) { return userjs.isMobile; } try { - const { navigator } = safeSelf(); + const { navigator } = _self; if (navigator) { const { userAgent, userAgentData } = navigator; const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; @@ -213,8 +274,7 @@ const getUAData = () => { err(ex); } return userjs.isMobile; -}; -const isMobile = getUAData(); +})(); const isGM = typeof GM !== 'undefined'; const builtinList = { local: /localhost|router|gov|(\d+\.){3}\d+/, @@ -226,19 +286,26 @@ const builtinList = { pathname: '/hentai/.+/read/page/.+' } }; +// #endregion +// #region DEFAULT_CONFIG /** * @type { import("../typings/types.d.ts").config } */ const DEFAULT_CONFIG = { autofetch: true, autoinject: true, + autoSort: 'daily_installs', + clearTabCache: true, cache: true, - codePreview: false, autoexpand: false, filterlang: false, sleazyredirect: false, time: 10000, blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], + preview: { + code: false, + metadata: false + }, engines: [ { enabled: true, @@ -260,7 +327,7 @@ const DEFAULT_CONFIG = { name: 'github', token: '', query: encodeURIComponent( - 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' + 'https://api.github.com/search/repositories?q=topic:{domain}+topic:userscript' ) } ], @@ -326,140 +393,68 @@ const DEFAULT_CONFIG = { } } }; -//#region i18n +// #endregion +// #region i18n +/** @type {Map} */ +const i18nMap = new Map(); class i18nHandler { constructor() { - /** @type { string[] } */ - this.cache = []; - - const { navigator } = safeSelf(); - const { languages } = navigator; - for (const nlang of languages) { - const lg = nlang.split('-')[0]; - if (this.cache.indexOf(lg) === -1) { - this.cache.push(lg); - } - } - - const current = navigator.language.split('-')[0] ?? 'en'; - if (!this.cache.includes(current)) { - this.cache.push(current); + for (const [k, v] of Object.entries(translations)) { + if (!i18nMap.has(k)) i18nMap.set(k, v); } } + /** + * @param {string | Date | number} str + */ toDate(str = '') { - const { navigator } = safeSelf(); - return new Intl.DateTimeFormat(navigator.language).format(new Date(str)); + const { navigator } = _self; + return new Intl.DateTimeFormat(navigator.language).format( + typeof str === 'string' ? new Date(str) : str + ); } + /** + * @param {number | bigint} number + */ toNumber(number) { - const { navigator } = safeSelf(); + const { navigator } = _self; return new Intl.NumberFormat(navigator.language).format(number); } - i18n$(...keys) { - const { navigator } = safeSelf(); + /** + * @type { import("../typings/UserJS.d.ts").i18n$ } + */ + i18n$(key) { + const { navigator } = _self; const current = navigator.language.split('-')[0] ?? 'en'; - const list = translations[cfg.language] ?? translations[current]; - const arr = []; - for (const key of keys) { - arr.push(list[key]); + try { + return i18nMap.get(current)?.[key] ?? 'Invalid Key'; + } catch (e) { + err(e); + return 'error'; } - return arr.length !== 1 ? arr : arr[0]; + } + + get current() { + const { navigator } = _self; + return navigator.language.split('-')[0] ?? 'en'; } } const language = new i18nHandler(); const { i18n$ } = language; -//#endregion +// #endregion // #region Utilities const union = (...arr) => [...new Set(arr.flat())]; /** - * @type { import("../typings/types.d.ts").qs } - */ -const qs = (selector, root) => { - try { - return (root || document).querySelector(selector); - } catch (ex) { - err(ex); - } - return null; -}; -/** - * @type { import("../typings/types.d.ts").qsA } - */ -const qsA = (selectors, root) => { - try { - return (root || document).querySelectorAll(selectors); - } catch (ex) { - err(ex); - } - return []; -}; -/** - * @type { import("../typings/types.d.ts").objToStr } - */ -const objToStr = (obj) => Object.prototype.toString.call(obj); -/** - * @type { import("../typings/types.d.ts").strToURL } + * @param {string} str */ -const strToURL = (str) => { - const WIN_LOCATION = window.location ?? BLANK_PAGE; +const decode = (str) => { try { - str = str ?? WIN_LOCATION; - return objToStr(str).includes('URL') ? str : new URL(str); + if (decodeURI(str) !== decodeURIComponent(str)) { + return decode(decodeURIComponent(str)); + } } catch (ex) { - ex.cause = 'strToURL'; err(ex); } - return WIN_LOCATION; -}; -/** - * @type { import("../typings/types.d.ts").isRegExp } - */ -const isRegExp = (obj) => { - const s = objToStr(obj); - return s.includes('RegExp'); -}; -/** - * @type { import("../typings/types.d.ts").isElem } - */ -const isElem = (obj) => { - const s = objToStr(obj); - return s.includes('Element'); -}; -/** - * @type { import("../typings/types.d.ts").isObj } - */ -const isObj = (obj) => { - const s = objToStr(obj); - return s.includes('Object'); -}; -/** - * @type { import("../typings/types.d.ts").isFN } - */ -const isFN = (obj) => { - const s = objToStr(obj); - return s.includes('Function'); -}; -/** - * @type { import("../typings/types.d.ts").isNull } - */ -const isNull = (obj) => { - return Object.is(obj, null) || Object.is(obj, undefined); -}; -/** - * @type { import("../typings/types.d.ts").isBlank } - */ -const isBlank = (obj) => { - return ( - (typeof obj === 'string' && Object.is(obj.trim(), '')) || - ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || - (Array.isArray(obj) && Object.is(obj.length, 0)) || - (isObj(obj) && Object.is(Object.keys(obj).length, 0)) - ); -}; -/** - * @type { import("../typings/types.d.ts").isEmpty } - */ -const isEmpty = (obj) => { - return isNull(obj) || isBlank(obj); + return str; }; /** * @type { import("../typings/types.d.ts").normalizeTarget } @@ -472,31 +467,46 @@ const normalizeTarget = (target, toQuery = true, root) => { return target; } if (typeof target === 'string') { - return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; + return toQuery ? Array.from((root || document).querySelectorAll(target)) : Array.of(target); } - if (isElem(target)) { - return [target]; + if (/object HTML/.test(Object.prototype.toString.call(target))) { + return Array.of(target); } return Array.from(target); }; /** - * @type { import("../typings/types.d.ts").ael } + * @type { import("../typings/types.d.ts").qs } */ -const ael = (el, type, listener, options = {}) => { +const qs = (selector, root) => { try { - for (const elem of normalizeTarget(el)) { - if (!elem) { - continue; - } - if (isMobile && type === 'click') { - elem.addEventListener('touchstart', listener, options); - continue; - } - elem.addEventListener(type, listener, options); - } + return (root || document).querySelector(selector); + } catch (ex) { + err(ex); + } + return null; +}; +/** + * @type { import("../typings/types.d.ts").qsA } + */ +const qsA = (selectors, root) => { + try { + return (root || document).querySelectorAll(selectors); } catch (ex) { err(ex); } + return []; +}; +/** + * @type { import("../typings/types.d.ts").ael } + */ +const ael = (el, type, listener, options = {}) => { + for (const elem of normalizeTarget(el).filter(isHTML)) { + if (isMobile && type === 'click') { + elem.addEventListener('touchstart', listener, options); + continue; + } + elem.addEventListener(type, listener, options); + } }; /** * @type { import("../typings/types.d.ts").formAttrs } @@ -528,8 +538,8 @@ const formAttrs = (elem, attr = {}) => { const make = (tagName, cname, attrs) => { let el; try { - const { createElement } = safeSelf(); - el = createElement(tagName); + const { createElement } = _self; + el = createElement(tagName.toLowerCase()); if (!isEmpty(cname)) { if (typeof cname === 'string') { el.className = cname; @@ -545,16 +555,14 @@ const make = (tagName, cname, attrs) => { } } } catch (ex) { + if (ex instanceof DOMException) throw new Error(`${ex.name}: ${ex.message}`, { cause: 'make' }); ex.cause = 'make'; err(ex); } return el; }; -/** - * @type { import("../typings/UserJS.d.ts").getGMInfo } - */ -const getGMInfo = () => { +const $info = (() => { if (isGM) { if (isObj(GM.info)) { return GM.info; @@ -567,20 +575,19 @@ const getGMInfo = () => { icon: '', name: 'Magic Userscript+', namespace: 'https://github.com/magicoflolis/Userscript-Plus', - updateURL: 'https://github.com/magicoflolis/Userscript-Plus/releases', + updateURL: 'https://github.com/magicoflolis/Userscript-Plus/raw/master/dist/magic-userjs.js', version: 'Bookmarklet', bugs: 'https://github.com/magicoflolis/Userscript-Plus/issues' } }; -}; -const $info = getGMInfo(); +})(); // #endregion /** * @type { import("../typings/types.d.ts").dom } */ const dom = { attr(target, attr, value = undefined) { - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { if (value === undefined) { return elem.getAttribute(attr); } @@ -592,7 +599,7 @@ const dom = { } }, prop(target, prop, value = undefined) { - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { if (value === undefined) { return elem[prop]; } @@ -600,7 +607,7 @@ const dom = { } }, text(target, text) { - const targets = normalizeTarget(target); + const targets = normalizeTarget(target).filter(isHTML); if (text === undefined) { return targets.length !== 0 ? targets[0].textContent : undefined; } @@ -608,135 +615,91 @@ const dom = { elem.textContent = text; } }, + remove(target) { + normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.remove()); + }, cl: { add(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.add(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.add(token); - } - } + token = normalizeTarget(token, false); + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.add(...token)); }, remove(target, token) { - if (Array.isArray(token)) { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(...token); - } - } else { - for (const elem of normalizeTarget(target)) { - elem.classList.remove(token); - } - } + token = normalizeTarget(token, false); + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.remove(...token)); }, toggle(target, token, force) { let r; - for (const elem of normalizeTarget(target)) { + for (const elem of normalizeTarget(target).filter(isHTML)) { r = elem.classList.toggle(token, force); } return r; }, has(target, token) { - for (const elem of normalizeTarget(target)) { - if (elem.classList.contains(token)) { - return true; - } - } - return false; + return normalizeTarget(target) + .filter(isHTML) + .some((elem) => elem.classList.contains(token)); } } }; -class Memorize { - constructor() { - /** - * @type {Map>} - */ - this.store = new Map(); - /** - * @type { { [key: string]: Map; userjs: Map } } - */ - this.maps = {}; - this.create('cfg', 'container', 'userjs'); - } - /** - * @template { string } S - * @param { ...S } maps - * @returns { S | S[] } - */ - create(...maps) { - const resp = []; - for (const key of maps) { - if (this.store.has(key)) { - return this.store.get(key); - } - const m = new Map(); - this.store.set(key, m); - this.maps[key] = m; - resp.push(this.store.get(key)); - } - return resp.length >= 2 ? resp : resp[0]; - } -} -const memory = new Memorize(); //#region Icon SVGs const iconSVG = { - cfg: { - viewBox: '0 0 24 24', - html: '' - }, close: { - viewBox: '0 0 24 24', - html: '' + viewBox: '0 0 384 512', + html: '' }, - filter: { - viewBox: '0 0 24 24', - html: '' + code: { + viewBox: '0 0 640 512', + html: '' }, - fsClose: { - viewBox: '0 0 24 24', - html: ' ' + collapse: { + viewBox: '0 0 448 512', + html: '' }, - fsOpen: { - viewBox: '0 0 24 24', - html: ' ' + download: { + viewBox: '0 0 384 512', + html: '' }, - fullscreen: { - viewBox: '0 0 96 96', - html: '' + expand: { + viewBox: '0 0 448 512', + html: '' }, - gf: { - viewBox: '0 0 510.4 510.4', - html: '' + gear: { + viewBox: '0 0 512 512', + html: '' }, - gh: { - viewBox: '0 0 16 16', - html: '' + github: { + viewBox: '0 0 496 512', + html: '' }, - hide: { - viewBox: '0 0 24 24', - html: ' ' + globe: { + viewBox: '0 0 512 512', + html: '' }, install: { - viewBox: '0 0 16 16', - html: '' + viewBox: '0 0 512 512', + html: '' }, issue: { - viewBox: '0 0 24 24', - html: '' + viewBox: '0 0 512 512', + html: '' }, - nav: { - viewBox: '0 0 24 24', - html: ' ' + minus: { + viewBox: '0 0 448 512', + html: '' }, - plus: { - viewBox: '0 0 24 24', - html: '' + nav: { + viewBox: '0 0 448 512', + html: '' }, - search: { - viewBox: '0 0 24 24', - html: '' + pager: { + viewBox: '0 0 512 512', + html: '' }, verified: { viewBox: '0 0 56 56', @@ -745,12 +708,12 @@ const iconSVG = { html: '' }, refresh: { - viewBox: '0 0 1024 1024', + viewBox: '0 0 512 512', fill: 'currentColor', - html: '' + html: '' }, load(type, container) { - const { createElementNS } = safeSelf(); + const { createElementNS } = _self; const svgElem = createElementNS('http://www.w3.org/2000/svg', 'svg'); for (const [k, v] of Object.entries(iconSVG[type])) { if (k === 'html') { @@ -761,6 +724,7 @@ const iconSVG = { try { if (typeof iconSVG[type].html === 'string') { svgElem.innerHTML = iconSVG[type].html; + svgElem.setAttribute('id', `mujs_${type ?? 'Unknown'}`); } // eslint-disable-next-line no-unused-vars } catch (ex) { @@ -779,11 +743,12 @@ const iconSVG = { */ const StorageSystem = { prefix: 'MUJS', + events: new Set(), getItem(key) { return window.localStorage.getItem(key); }, has(key) { - return !isNull(this.getItem(key)); + return !this.getItem(key); }, setItem(key, value) { window.localStorage.setItem(key, value); @@ -792,7 +757,10 @@ const StorageSystem = { window.localStorage.removeItem(key); }, async setValue(key, v) { - v = typeof v === 'string' ? v : JSON.stringify(v ?? {}); + if (!v) { + return; + } + v = typeof v === 'string' ? v : JSON.stringify(v); if (isGM) { if (isFN(GM.setValue)) { await GM.setValue(key, v); @@ -820,6 +788,7 @@ const StorageSystem = { ? JSON.parse(this.getItem(`${this.prefix}-${key}`)) : def; } catch (ex) { + ex.cause = 'getValue'; err(ex); return def; } @@ -856,14 +825,11 @@ const Network = { } data = Object.assign({}, data); method = this.bscStr(method, false); - responseType = this.bscStr(responseType); + responseType = this.bscStr(responseType, true); const params = { method, ...data }; - if (params.hermes) { - delete params.hermes; - } if (isGM && !useFetch) { if (params.credentials) { Object.assign(params, { @@ -914,8 +880,8 @@ const Network = { } else if (responseType.match(/clone/)) { resolve(check('clone')); } else if (responseType.match(/document/)) { - const domParser = new DOMParser(); const respTxt = check('text'); + const domParser = new DOMParser(); if (respTxt instanceof Promise) { respTxt.then((txt) => { const doc = domParser.parseFromString(txt, 'text/html'); @@ -950,7 +916,7 @@ const Network = { } } return await new Promise((resolve, reject) => { - const { XMLHttpRequest } = safeSelf(); + const { XMLHttpRequest } = _self; const req = new XMLHttpRequest(); let method = 'GET'; let url = BLANK_PAGE; @@ -1011,33 +977,45 @@ const Network = { return txt.replaceAll(/\W/g, ''); } }; -const sleazyRedirect = () => { - const locObj = window.top.location; - const { hostname } = locObj; - const gfSite = /greasyfork\.org/.test(hostname); - if (!gfSite && cfg.sleazyredirect) { - return; - } - const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; - if (!qs('span.sign-in-link')) { - return; - } - if (!/scripts\/\d+/.test(locObj.href)) { - return; - } - if ( - !qs('#script-info') && - (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) - ) { - const str = locObj.href.replace( - /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, - '//$1' + otherSite + '.org' - ); - info(`Redirecting to "${str}"`); - if (isFN(locObj.assign)) { - locObj.assign(str); - } else { - locObj.href = str; +const Counter = { + cnt: { + total: { + count: 0 + } + }, + set(engine) { + if (!this.cnt[engine.name]) { + const counter = make('count-frame', engine.enabled ? '' : 'hidden', { + dataset: { + counter: engine.name + }, + title: decode(engine.query ?? engine.url), + textContent: '0' + }); + this.cnt[engine.name] = { + root: counter, + count: 0 + }; + return counter; + } + return this.cnt[engine.name].root; + }, + update(count, engine) { + this.cnt[engine.name].count += count; + this.cnt.total.count += count; + this.updateAll(); + }, + updateAll() { + for (const v of Object.values(this.cnt)) dom.text(v.root, v.count); + }, + reset() { + for (const [k, v] of Object.entries(this.cnt)) { + dom.text(v.root, 0); + v.count = 0; + const engine = cfg.engines.find((engine) => k === engine.name); + if (engine) { + dom.cl[engine.enabled ? 'remove' : 'add'](v.root, 'hidden'); + } } } }; @@ -1048,37 +1026,44 @@ const sleazyRedirect = () => { class Container { webpage; host; - domain; - ready; injected; shadowRoot; supported; frame; - cache; userjsCache; root; unsaved; isBlacklisted; rebuild; - counters; opacityMin; opacityMax; - constructor(url) { + constructor() { this.remove = this.remove.bind(this); this.refresh = this.refresh.bind(this); - this.updateCounter = this.updateCounter.bind(this); - this.updateCounters = this.updateCounters.bind(this); this.showError = this.showError.bind(this); + this.toElem = this.toElem.bind(this); - this.webpage = strToURL(url); - this.host = this.getHost(this.webpage.host); - this.domain = this.getDomain(this.webpage.host); - this.ready = false; + this.webpage = url; + this.host = getHostname(url?.hostname ?? BLANK_PAGE); this.injected = false; this.shadowRoot = undefined; this.supported = isFN(make('main-userjs').attachShadow); + + // /** furture update */ + // if (!this.supported) throw new Error('Failed to initialize: "attachShadow not supported"', { cause: 'Container' }); + // this.frame = make('main-userjs', { + // style: 'visibility: visible;', + // dataset: { + // insertedBy: $info.script.name, + // role: 'primary-container' + // } + // }); + // this.shadowRoot = this.frame.attachShadow({ mode: 'closed' }); + + this.ready = false; this.frame = this.supported - ? make('main-userjs', '', { + ? make('main-userjs', { + style: 'visibility: visible;', dataset: { insertedBy: $info.script.name, role: 'primary-container' @@ -1092,37 +1077,26 @@ class Container { loading: 'lazy', src: BLANK_PAGE, style: - 'position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', - onload: (iFrame) => { - /** - * @type { HTMLIFrameElement } - */ - const target = iFrame.target; - if (!target.contentDocument) { - return; + 'visibility: visible;position: fixed;bottom: 1rem;right: 1rem;height: 525px;width: 90%;margin: 0px 1rem;z-index: 100000000000000020 !important;', + onload: ({ target }) => { + if (target.contentDocument) { + this.shadowRoot = target.contentDocument.documentElement; + this.ready = true; + dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe'); } - this.shadowRoot = target.contentDocument.documentElement; - this.ready = true; - dom.cl.add([this.shadowRoot, target.contentDocument.body], 'mujs-iframe'); } }); if (this.supported) { - this.shadowRoot = this.frame.attachShadow({ - mode: 'open', - clonable: false, - delegatesfocus: false - }); + this.shadowRoot = this.frame.attachShadow({ mode: 'closed' }); this.ready = true; } - this.cache = memory.maps.container; - this.userjsCache = memory.maps.userjs; + + this.hostCache = new Map(); + this.userjsCache = new Map(); this.root = make('mujs-root'); this.unsaved = false; this.isBlacklisted = false; this.rebuild = false; - this.counters = { - total: 0 - }; this.opacityMin = '0.15'; this.opacityMax = '1'; this.elementsReady = this.init(); @@ -1133,6 +1107,7 @@ class Container { } set(delay, reason) { + const { setTimeout } = _self; return new Promise((resolve, reject) => { const id = setTimeout(() => { Object.is(reason, null) || Object.is(reason, undefined) ? resolve() : reject(reason); @@ -1143,6 +1118,7 @@ class Container { } clear(...ids) { + const { clearTimeout } = _self; this.ids = this.ids.filter((id) => { if (ids.includes(id)) { clearTimeout(id); @@ -1157,7 +1133,7 @@ class Container { mouse: new Timeout() }; - this.injFN = () => {}; + this.injFN = BLANK_FN; window.addEventListener('beforeunload', this.remove); } @@ -1171,13 +1147,7 @@ class Container { this.remove(); return; } - if (!this.shadowRoot) { - return; - } - if (doc === null) { - return; - } - + if (!this.shadowRoot || doc === null) return; while (this.ready === false) { await new Promise((resolve) => requestAnimationFrame(resolve)); } @@ -1190,57 +1160,38 @@ class Container { return; } this.shadowRoot.append(this.root); - if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) { + if (isNull(this.loadCSS(main_css, 'primary-stylesheet'))) throw new Error('Failed to initialize script!', { cause: 'loadCSS' }); - } this.injected = true; this.initFn(); - if (this.elementsReady && isFN(callback)) { - this.injFN = callback.call(this, this.shadowRoot); - } + if (isFN(callback) && this.elementsReady) this.injFN = callback.call(this, this.shadowRoot); } catch (ex) { err(ex); this.remove(); } } initFn() { - this.renderTheme(cfg.theme); - - for (const engine of cfg.engines) { - if (engine.enabled) { - const counter = make('count-frame', '', { - dataset: { - counter: engine.name - }, - title: engine.query ? decodeURIComponent(engine.query) : engine.url, - textContent: '0' - }); - this.countframe.append(counter); - } - this.counters[engine.name] = 0; - } + this.setTheme(); - const cfgpage = this.cfgpage; - const table = this.table; - const exBtn = this.exBtn; - const supported = this.supported; - const frame = this.frame; - const refresh = this.refresh; - const cache = this.cache; - const cHost = this.host; - const urlBar = this.urlBar; + Counter.cnt.total.root = this.mainbtn; + if (this.countframe) + for (const engine of cfg.engines) this.countframe.append(Counter.set(engine)); + const { cfgpage, table, supported, frame, refresh, hostCache, urlBar, host } = this; class Tabs { /** * @param { HTMLElement } root */ constructor(root) { - this.Tab = new Map(); + /** + * @type { Set } + */ + this.pool = new Set(); this.blank = BLANK_PAGE; this.protocal = 'mujs:'; - this.protoReg = new RegExp(`${this.protocal}(.+)`); + this.protoReg = new RegExp(`${this.protocal}(.+)`, 'i'); this.el = { - add: make('mujs-addtab', '', { + add: make('mujs-addtab', { textContent: '+', dataset: { command: 'new-tab' @@ -1251,113 +1202,92 @@ class Container { }; this.el.head.append(this.el.add); this.el.root.append(this.el.head); - this.custom = () => {}; - } - hasTab(...params) { - for (const p of params) { - if (!this.Tab.has(p)) { - return false; - } - const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host); - if (isBlank(content)) { - return false; - } - } - return true; + this.custom = BLANK_FN; } - storeTab(host) { - const h = host ?? this.blank; - if (!this.Tab.has(h)) { - this.Tab.set(h, new Set()); - } - return this.Tab.get(h); + /** + * @param {string} hostname + */ + getTab(hostname) { + return [...this.pool].find(({ dataset }) => hostname === dataset.host); } - cache(host, ...tabs) { - const h = host ?? this.blank; - const tabCache = this.storeTab(h); - for (const t of normalizeTarget(tabs)) { - if (tabCache.has(t)) { - continue; - } - tabCache.add(t); - } - this.Tab.set(h, tabCache); - return tabCache; + getActive() { + return [...this.pool].find((tab) => tab.classList.contains('active')); } - intFN(host) { - if (!host.startsWith(this.protocal)) { + /** + * @param {string} hostname + */ + intFN(hostname) { + const p = this.protoReg.exec(hostname); + if (!p) { return; } - const type = host.match(this.protoReg)[1]; - if (type === 'settings') { - dom.cl.remove([cfgpage, exBtn], 'hidden'); + if (p[1] === 'settings') { + dom.cl.remove(cfgpage, 'hidden'); dom.cl.add(table, 'hidden'); + urlBar.placeholder = 'Search settings'; if (!supported) { dom.attr(frame, 'style', 'height: 100%;'); } } } + /** + * @param {HTMLElement} tab + * @param {boolean} [build] + */ active(tab, build = true) { - for (const t of normalizeTarget(tab, false)) { - dom.cl.add([cfgpage, exBtn], 'hidden'); + if (!this.pool.has(tab)) this.pool.add(tab); + dom.cl.add([table, cfgpage], 'hidden'); + dom.cl.remove([...this.pool], 'active'); + dom.cl.add(tab, 'active'); + if (!build) { dom.cl.remove(table, 'hidden'); - dom.cl.remove(qsA('mujs-tab', this.el.head), 'active'); - dom.cl.add(t, 'active'); - if (!build) { - continue; - } - const host = t.dataset.host ?? this.blank; - if (host === this.blank) { - refresh(); - } else if (host.startsWith(this.protocal)) { - this.intFN(host); - } else { - this.custom(host); - } + return; + } + const host = tab.dataset.host ?? this.blank; + if (host === this.blank) { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + refresh(); + } else if (host.startsWith(this.protocal)) { + this.intFN(host); + } else { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + this.custom(host); } } /** @param { HTMLElement } tab */ close(tab) { - for (const t of normalizeTarget(tab, false)) { - const host = t.dataset.host; - if (cache.has(host)) { - cache.delete(host); - } - if (dom.cl.has(t, 'active')) { - refresh(); - } - const sibling = t.previousElementSibling ?? t.nextElementSibling; - if (sibling) { - if (sibling.dataset.command !== 'new-tab') { - this.active(sibling); - } - } - if (this.Tab.has(host)) { - this.Tab.delete(host); + if (this.pool.has(tab)) this.pool.delete(tab); + const host = tab.dataset.host; + if (cfg.clearTabCache && hostCache.has(host)) hostCache.delete(host); + if (tab.classList.contains('active')) refresh(); + const sibling = tab.nextElementSibling ?? tab.previousElementSibling; + if (sibling) { + if (sibling.dataset.command !== 'new-tab') { + this.active(sibling); } - t.remove(); } + tab.remove(); } - create(host = undefined) { - if (typeof host === 'string') { - if (host.startsWith(this.protocal) && this.hasTab(host)) { - this.active(this.Tab.get(host)); - return; - } - const content = normalizeTarget(this.storeTab(host)).filter( - (t) => host === t.dataset.host - ); - if (!isEmpty(content)) { + /** + * @param {string} [hostname] + */ + create(hostname = undefined) { + if (typeof hostname === 'string') { + const createdTab = this.getTab(hostname); + if (this.protoReg.test(hostname) && createdTab) { + this.active(createdTab); return; } } - const tab = make('mujs-tab', '', { + const tab = make('mujs-tab', { dataset: { command: 'switch-tab' }, style: `order: ${this.el.head.childElementCount};` }); - const tabClose = make('mu-js', '', { + const tabClose = make('mu-js', { dataset: { command: 'close-tab' }, @@ -1365,35 +1295,34 @@ class Container { textContent: 'X' }); const tabHost = make('mujs-host'); + const p = this.protoReg.exec(hostname); tab.append(tabHost, tabClose); this.el.head.append(tab); this.active(tab, false); - this.cache(host, tab); - if (isNull(host)) { + if (isNull(hostname)) { refresh(); - urlBar.placeholder = i18n$('newTab'); tab.dataset.host = this.blank; tabHost.title = i18n$('newTab'); tabHost.textContent = i18n$('newTab'); - } else if (host.startsWith(this.protocal)) { - const type = host.match(this.protoReg)[1]; - tab.dataset.host = host || cHost; - tabHost.title = type || tab.dataset.host; + } else if (p) { + tab.dataset.host = hostname || host; + tabHost.title = p[1] || tab.dataset.host; tabHost.textContent = tabHost.title; - this.intFN(host); + this.intFN(hostname); } else { - tab.dataset.host = host || cHost; - tabHost.title = host || cHost; + tab.dataset.host = hostname || host; + tabHost.title = hostname || host; tabHost.textContent = tabHost.title; } return tab; } } - this.tab = new Tabs(this.toolbar); - this.tab.create(this.host); + this.Tabs = new Tabs(this.toolbar); + this.Tabs.create(host); const tabbody = this.tabbody; - const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; + const getCellValue = (tr, idx) => + tr.children[idx].dataset.value || tr.children[idx].textContent; const comparer = (idx, asc) => (a, b) => ((v1, v2) => v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) @@ -1439,50 +1368,24 @@ class Container { this.header = make('mujs-header'); this.tbody = make('mujs-body'); this.cfgpage = make('mujs-row', 'mujs-cfg hidden'); + this.main = make('mujs-main', 'hidden'); + this.urlContainer = make('mujs-url'); this.btnframe = make('mujs-column', 'btn-frame'); - this.exBtn = make('mujs-column', 'mujs-sty-flex hidden'); - this.fsearch = make('mujs-btn', 'hidden'); - this.exportCFG = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_config'), - dataset: { - command: 'export-cfg' - } - }); - this.importCFG = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_config'), - dataset: { - command: 'import-cfg' - } - }); - this.exportTheme = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_theme'), - dataset: { - command: 'export-theme' - } - }); - this.importTheme = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_theme'), - dataset: { - command: 'import-theme' - } - }); this.btnHandles = make('mujs-column', 'btn-handles'); this.btnHide = make('mujs-btn', 'hide-list', { title: i18n$('min'), - innerHTML: iconSVG.load('hide'), + innerHTML: iconSVG.load('minus'), dataset: { command: 'hide-list' } }); this.btnfullscreen = make('mujs-btn', 'fullscreen', { title: i18n$('max'), - innerHTML: iconSVG.load('fullscreen'), + innerHTML: iconSVG.load('expand'), dataset: { command: 'fullscreen' } }); - this.main = make('mujs-main', 'hidden'); - this.urlContainer = make('mujs-url'); this.closebtn = make('mujs-btn', 'close', { title: i18n$('close'), innerHTML: iconSVG.load('close'), @@ -1492,18 +1395,18 @@ class Container { }); this.btncfg = make('mujs-btn', 'settings hidden', { title: 'Settings', - innerHTML: iconSVG.load('cfg'), + innerHTML: iconSVG.load('gear'), dataset: { command: 'settings' } }); this.btnhome = make('mujs-btn', 'github hidden', { title: `GitHub (v${ - $info.script.version.includes('.') || $info.script.version.includes('Book') + /\d+\.\d+\.\d+|Book/.test($info.script.version) ? $info.script.version : $info.script.version.slice(0, 5) })`, - innerHTML: iconSVG.load('gh'), + innerHTML: iconSVG.load('github'), dataset: { command: 'open-tab', webpage: $info.script.namespace @@ -1519,7 +1422,7 @@ class Container { }); this.btngreasy = make('mujs-btn', 'greasy hidden', { title: 'Greasy Fork', - innerHTML: iconSVG.load('gf'), + innerHTML: iconSVG.load('globe'), dataset: { command: 'open-tab', webpage: 'https://greasyfork.org/scripts/421603' @@ -1532,9 +1435,9 @@ class Container { command: 'navigation' } }); - const makeTHead = (rows) => { + const makeTHead = (rows = []) => { const tr = make('tr'); - for (const r of normalizeTarget(rows)) { + for (const r of rows) { const tparent = make('th', r.class ?? '', r); tr.append(tparent); } @@ -1560,17 +1463,28 @@ class Container { } ]); // #endregion - - this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); - this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + if (isMobile) { + dom.cl.add([this.btnHide, this.btnfullscreen, this.closebtn], 'hidden'); + this.btnframe.append( + this.btnHide, + this.btnfullscreen, + this.closebtn, + this.btnhome, + this.btngreasy, + this.btnissue, + this.btncfg, + this.btnnav + ); + } else { + this.btnHandles.append(this.btnHide, this.btnfullscreen, this.closebtn); + this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + } this.toolbar.append(this.btnHandles); this.urlContainer.append(this.urlBar); this.header.append(this.urlContainer, this.rateContainer, this.countframe, this.btnframe); this.tbody.append(this.table, this.cfgpage); this.main.append(this.toolbar, this.header, this.tbody, this.footer, this.promptElem); this.mainframe.append(this.mainbtn); - this.exBtn.append(this.importCFG, this.importTheme, this.exportCFG, this.exportTheme); - this.header.append(this.exBtn); this.root.append(this.mainframe, this.main); return true; @@ -1580,55 +1494,47 @@ class Container { return false; } remove() { - memory.store.clear(); - if (this.frame) { - this.frame.remove(); - } + this.hostCache.clear(); + this.userjsCache.clear(); + dom.remove(this.frame); } async save() { this.unsaved = false; await StorageSystem.setValue('Config', cfg); info('Saved config:', cfg); + this.redirect(); return cfg; } /** * @param { string } css - CSS to inject * @param { string } name - Name of stylesheet - * @return { HTMLStyleElement } Style element + * @return { HTMLStyleElement | undefined } Style element */ loadCSS(css, name = 'CSS') { try { - if (typeof name !== 'string') { + if (typeof name !== 'string') throw new Error('"name" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (qs(`style[data-role="${name}"]`, this.root)) { + if (qs(`style[data-role="${name}"]`, this.root)) return qs(`style[data-role="${name}"]`, this.root); - } - if (typeof css !== 'string') { + if (typeof css !== 'string') throw new Error('"css" must be a typeof "string"', { cause: 'loadCSS' }); - } - if (isBlank(css)) { + if (isBlank(css)) throw new Error(`"${name}" contains empty CSS string`, { cause: 'loadCSS' }); - } const parent = isEmpty(this.root.shadowRoot) ? this.root : this.root.shadowRoot; if (isGM) { - let sty; - if (isFN(GM.addElement)) { - sty = GM.addElement(parent, 'style', { - textContent: css - }); - } else if (isFN(GM_addElement)) { - sty = GM_addElement(parent, 'style', { - textContent: css - }); - } + const fn = isFN(GM.addElement) + ? GM.addElement + : isFN(GM_addElement) + ? GM_addElement + : BLANK_FN; + const sty = fn(parent, 'style', { textContent: css }); if (isElem(sty)) { sty.dataset.insertedBy = $info.script.name; sty.dataset.role = name; return sty; } } - const sty = make('style', '', { + const sty = make('style', { textContent: css, dataset: { insertedBy: $info.script.name, @@ -1643,70 +1549,41 @@ class Container { } checkBlacklist(str) { str = str || this.host; - let blacklisted = false; if (/accounts*\.google\./.test(this.webpage.host)) { - blacklisted = true; + this.isBlacklisted = true; + return this.isBlacklisted; } + let blacklisted = false; for (const b of normalizeTarget(cfg.blacklist)) { if (typeof b === 'string') { if (b.startsWith('userjs-')) { const r = /userjs-(\w+)/.exec(b)[1]; const biList = builtinList[r]; if (isRegExp(biList)) { - if (!biList.test(str)) continue; - blacklisted = true; + if (biList.test(str)) blacklisted = true; } else if (isObj(biList) && biList.host === this.host) { blacklisted = true; } } } else if (isObj(b)) { - if (!b.enabled) { - continue; - } + if (!b.enabled) continue; if (b.regex === true) { const reg = new RegExp(b.url, b.flags); - if (!reg.test(str)) continue; - blacklisted = true; + if (reg.test(str)) blacklisted = true; } if (Array.isArray(b.url)) { for (const c of b.url) { - if (!str.includes(c)) continue; - blacklisted = true; + if (str.includes(c)) blacklisted = true; } } - if (!str.includes(b.url)) continue; - blacklisted = true; + if (str.includes(b.url)) blacklisted = true; } } this.isBlacklisted = blacklisted; return this.isBlacklisted; } - getInfo(url) { - const webpage = strToURL(url || this.webpage); - const host = this.getHost(webpage.host); - const domain = this.getDomain(webpage.host); - return { - domain, - host, - webpage - }; - } - /** - * @template { string } S - * @param { S } str - */ - getHost(str = '') { - return str.split('.').splice(-2).join('.'); - } - /** - * @template { string } S - * @param { S } str - */ - getDomain(str = '') { - return str.split('.').at(-2) ?? BLANK_PAGE; - } - renderTheme(theme) { - theme = theme || cfg.theme; + setTheme() { + const theme = cfg.theme ?? DEFAULT_CONFIG.theme; if (theme === DEFAULT_CONFIG.theme) { return; } @@ -1714,23 +1591,15 @@ class Container { for (const [k, v] of Object.entries(theme)) { const str = `--mujs-${k}`; const prop = sty.getPropertyValue(str); - if (isEmpty(v)) { - theme[k] = prop; - } - if (prop === v) { - continue; - } + if (isEmpty(v)) theme[k] = prop; + if (prop === v) continue; sty.removeProperty(str); sty.setProperty(str, v); } } makePrompt(txt, dataset = {}, usePrompt = true) { - if (qs('.prompt', this.promptElem)) { - for (const elem of qsA('.prompt', this.promptElem)) { - if (elem.dataset.prompt) { - elem.remove(); - } - } + for (const elem of normalizeTarget(qsA('.prompt', this.promptElem))) { + if (elem.dataset.prompt) elem.remove(); } const el = make('mu-js', 'prompt', { dataset: { @@ -1757,52 +1626,101 @@ class Container { }); elPrompt.append(elYes, elNo); el.append(elPrompt); + } else { + const elPrompt = make('mu-js', 'prompt-body'); + const elNo = make('mujs-btn', 'prompt-deny', { + textContent: i18n$('close') + }); + ael(elNo, isMobile ? 'touchend' : 'click', () => { + el.remove(); + }); + elPrompt.append(elNo); + el.append(elPrompt); } this.promptElem.append(el); + return el; } + /** + * @template {string | Error} E + * @param {...E} ex + */ showError(...ex) { err(...ex); const error = make('mu-js', 'error'); let str = ''; for (const e of ex) { str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`; - if (isObj(e)) { - if (e.notify) { - dom.cl.add(this.mainframe, 'error'); - } - } } - const { createTextNode } = safeSelf(); + const { createTextNode } = _self; error.appendChild(createTextNode(str)); this.footer.append(error); } - updateCounter(count, engine) { - this.counters[engine.name] += count; - this.counters.total += count; - this.updateCounters(); + refresh() { + this.urlBar.placeholder = i18n$('newTab'); + Counter.reset(); + dom.cl.remove(this.toElem(), 'hidden'); + dom.cl.remove(cfgSec, 'hidden'); + dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); } - updateCounters() { - for (const [k, v] of Object.entries(this.counters)) { - if (k === 'total') { - continue; - } - const c = qs(`count-frame[data-counter="${k}"]`, this.countframe); - if (c) { - dom.text(c, v); + /** + * Redirects sleazyfork userscripts from greasyfork.org to sleazyfork.org + * + * Taken from: https://greasyfork.org/scripts/23840 + */ + redirect() { + const locObj = window.top.location; + const { hostname } = locObj; + const gfSite = /greasyfork\.org/.test(hostname); + if (!gfSite && cfg.sleazyredirect) return; + const otherSite = gfSite ? 'sleazyfork' : 'greasyfork'; + if (!qs('span.sign-in-link')) return; + if (!/scripts\/\d+/.test(locObj.href)) return; + if ( + !qs('#script-info') && + (otherSite == 'greasyfork' || qs('div.width-constraint>section>p>a')) + ) { + const str = locObj.href.replace( + /\/\/([^.]+\.)?(greasyfork|sleazyfork)\.org/, + '//$1' + otherSite + '.org' + ); + info(`Redirecting to "${str}"`); + if (isFN(locObj.assign)) { + locObj.assign(str); + } else { + locObj.href = str; } } - dom.text(this.mainbtn, this.counters.total); } - refresh() { - this.urlBar.placeholder = i18n$('newTab'); - this.counters.total = 0; - for (const engine of cfg.engines) { - this.counters[engine.name] = 0; + /** + * @param {number} [time] + */ + async timeoutFrame(time) { + const frameTimeout = this.timeouts.frame; + frameTimeout.clear(...frameTimeout.ids); + if (dom.cl.has(this.mainframe, 'hidden')) { + return; + } + time = time ?? cfg.time ?? DEFAULT_CONFIG.time; + let n = 10000; + if (typeof time === 'number' && !Number.isNaN(time)) { + n = this.isBlacklisted ? time / 2 : time; + } + await frameTimeout.set(n); + this.remove(); + frameTimeout.clear(...frameTimeout.ids); + } + toElem() { + return Array.from(this).map(({ _mujs }) => { + return _mujs.root; + }); + } + *[Symbol.iterator]() { + const arr = Array.from(this.userjsCache.values()).filter(({ _mujs }) => { + return !isEmpty(_mujs) && _mujs.info.engine.enabled; + }); + for (const userjs of arr) { + yield userjs; } - this.updateCounters(); - dom.cl.remove(qsA('tr[data-engine]', this.tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden'); - dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); } } const container = new Container(); @@ -1810,61 +1728,70 @@ const container = new Container(); // #region Primary Function function primaryFN() { const respHandles = { - build: async () => {} + build: BLANK_ASYNC_FN }; try { - const { scheduler } = safeSelf(); + const { scheduler } = _self; const { mainframe, urlBar, rateContainer, footer, tabbody, - toolbar, - tabhead, cfgpage, - fsearch, btnfullscreen, main, - btncfg, - btnhome, - btnissue, - btngreasy, - tab, - userjsCache, - updateCounter, + Tabs, showError } = container; - const frameTimeout = container.timeouts.frame; - const cfgMap = memory.maps.cfg; - const rebuildCfg = () => { - for (const engine of cfg.engines) { - if (cfgMap.has(engine.name)) { - const inp = cfgMap.get(engine.name); - inp.checked = engine.enabled; - if (engine.name === 'github') { - const txt = cfgMap.get('github-token'); - dom.prop(txt, 'value', engine.token); - } - } - } - for (const [k, v] of Object.entries(cfg)) { - if (typeof v === 'boolean') { - if (cfgMap.has(k)) { - const inp = cfgMap.get(k); - if (inp.type === 'checkbox') { - inp.checked = v; + const reloadCfg = () => { + for (const base of cfgBase) { + const nm = /^(\w+)-(.+)/.exec(base.value); + const d = (() => { + if (base.tag === 'engine') { + const engine = DEFAULT_CONFIG.engines.find((engine) => engine.name === base.value); + if (engine) { + return engine; + } + } + if (nm) { + return DEFAULT_CONFIG[nm[1]][nm[2]]; + } + return DEFAULT_CONFIG[base.value]; + })(); + const v = (() => { + if (base.tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === base.value); + if (engine) { + return engine; + } + } + if (nm) { + return cfg[nm[1]][nm[2]]; + } + return cfg[base.value]; + })(); + base.cache = v; + if (base.type === 'checkbox') { + if (nm) { + if (nm[1] === 'filters') { + base.elem.checked = cfg[nm[1]][nm[2]].enabled; } else { - dom.prop(inp, 'value', v); + base.elem.checked = v; + } + } else if (base.tag === 'engine') { + base.elem.checked = v.enabled; + base.elemUrl.value = decode(v.query); + base.elemUrl.placeholder = decode(d.query); + if (base.elemToken) { + base.elemToken = v.token; } } + } else { + base.elem.value = v; } } - // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); - for (const [k, v] of Object.entries(cfg.theme)) { - dom.prop(cfgMap.get(k), 'value', v); - } - container.renderTheme(cfg.theme); + container.setTheme(); }; const doInstallProcess = async (installLink) => { const locObj = window.top.location; @@ -1876,40 +1803,155 @@ function primaryFN() { installLink.remove(); await init(); }; + const doDownloadProcess = async (details) => { + if (!details.url) { + return; + } + const a = make('a'); + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.setAttribute('type', 'text/plain'); + a.dispatchEvent(new MouseEvent('click')); + await init(); + }; + const applyTo = (ujs, name, elem, root) => { + const n = ujs._mujs.code[name] ?? ujs._mujs.code.data_meta[name]; + if (isEmpty(n)) { + const el = make('mujs-a', { + textContent: i18n$('listing_none') + }); + elem.append(el); + return; + } + dom.prop(elem, 'innerHTML', ''); + dom.cl.remove(root, 'hidden'); + if (isObj(n)) { + if (name === 'resource') { + for (const [k, v] of Object.entries(n)) { + const el = make('mujs-a', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = v; + } + elem.append(el); + } + } else { + const el = make('mujs-a', { + textContent: n.text + }); + if (n.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${n.text}`; + } + elem.append(el); + } + } else if (typeof n === 'string') { + const el = make('mujs-a', { + textContent: n + }); + elem.append(el); + } else { + for (const c of n) { + if (typeof c === 'string' && c.startsWith('http')) { + const el = make('mujs-a', { + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } + }); + elem.append(el); + } else if (isObj(c)) { + const el = make('mujs-a', { + textContent: c.text + }); + if (c.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${c.text}`; + } + elem.append(el); + } else { + const el = make('mujs-a', { + textContent: c + }); + elem.append(el); + } + } + } + }; // #region Main event handlers - ael(main, 'click', async (evt) => { + const frameTimeout = container.timeouts.frame; + ael(main, isMobile ? 'touchend' : 'click', async (evt) => { try { /** @type { HTMLElement } */ const target = evt.target.closest('[data-command]'); if (!target) { return; } - const prmpt = /prompt-/.test(target.dataset.command); let dataset = target.dataset; let cmd = dataset.command; - let prmptChoice = false; - if (prmpt) { + if (/^prompt-/.test(target.dataset.command)) { dataset = target.parentElement.dataset; cmd = dataset.command; - prmptChoice = /confirm/.test(target.dataset.command); - target.parentElement.parentElement.remove(); + let pElem = target.parentElement.parentElement; + if (/prompt-install/.test(target.dataset.command)) { + pElem = target.parentElement.parentElement.parentElement; + const a = make('a', { + onclick(evt) { + evt.preventDefault(); + doInstallProcess(evt.target); + } + }); + a.href = target.dataset.code_url; + a.click(); + } else if (/prompt-download/.test(target.dataset.command)) { + pElem = target.parentElement.parentElement.parentElement; + const dataUserJS = container.userjsCache.get(+target.dataset.userjs); + if (dataUserJS) { + const code_obj = await dataUserJS._mujs.code.request(false, target.dataset.code_url); + if (typeof code_obj.code === 'string') + doDownloadProcess({ + url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(code_obj.code), + filename: `${dataUserJS.name}${isUserCSS(target.dataset.code_url) ? '.user.css' : '.user.js'}` + }); + } + } + pElem.remove(); + return; } - if (cmd === 'install-script' && dataset.userjs) { - let installCode = dataset.userjs; - if (!prmpt && dataset.userjs.endsWith('.user.css')) { - container.makePrompt(i18n$('prmpt_css'), dataset); + if (cmd === 'install-script') { + const dataUserJS = container.userjsCache.get(+dataset.userjs); + if (isNull(dataUserJS)) { return; - } else if (prmpt !== prmptChoice) { - installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js'); } - const dlBtn = make('a', '', { - onclick(evt) { - evt.preventDefault(); - doInstallProcess(evt.target); + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-install', + code_url: ujs.code_url + } + }); + list.append(a); } - }); - dlBtn.href = installCode; - dlBtn.click(); + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + const a = make('a', { + onclick(evt) { + evt.preventDefault(); + doInstallProcess(evt.target); + } + }); + a.href = dataUserJS.code_url; + a.click(); + } } else if (cmd === 'open-tab' && dataset.webpage) { if (isGM) { if (isFN(GM.openInTab)) { @@ -1923,15 +1965,15 @@ function primaryFN() { } return window.open(dataset.webpage, '_blank'); } else if (cmd === 'navigation') { - if (dom.cl.has(btngreasy, 'hidden')) { - dom.cl.remove([btncfg, btngreasy, btnhome, btnissue], 'hidden'); - } else { - dom.cl.add([btncfg, btngreasy, btnhome, btnissue], 'hidden'); + for (const e of normalizeTarget(qsA('mujs-btn', target.parentElement)).filter( + (e) => !dom.cl.has(e, 'nav') + )) { + dom.cl.toggle(e, 'hidden'); } } else if (cmd === 'list-description') { const arr = []; const ignoreTags = new Set(['TD', 'MUJS-A', 'MU-JS']); - for (const node of target.parentElement.childNodes) { + for (const node of Object.values(target.parentElement._mujs)) { if (ignoreTags.has(node.tagName)) { continue; } @@ -1953,28 +1995,24 @@ function primaryFN() { } } else if (cmd === 'close') { container.remove(); - } else if (cmd === 'show-filter') { - dom.cl.toggle(fsearch, 'hidden'); } else if (cmd === 'fullscreen') { if (dom.cl.has(btnfullscreen, 'expanded')) { dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand')); } else { dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } } else if (cmd === 'hide-list') { dom.cl.add(main, 'hidden'); dom.cl.remove(mainframe, 'hidden'); - timeoutFrame(); + container.timeoutFrame(); } else if (cmd === 'save') { container.rebuild = true; dom.prop(rateContainer, 'innerHTML', ''); if (!dom.prop(target, 'disabled')) { const config = await container.save(); - sleazyRedirect(); if (container.rebuild) { - container.cache.clear(); if (config.autofetch) { respHandles.build(); } @@ -1984,136 +2022,170 @@ function primaryFN() { } } else if (cmd === 'reset') { cfg = DEFAULT_CONFIG; - dom.cl.remove(mainframe, 'error'); - if (qs('.error', footer)) { - for (const elem of qsA('.error', footer)) { - elem.remove(); - } - } + dom.remove(qsA('.error', footer)); container.unsaved = true; container.rebuild = true; - rebuildCfg(); + reloadCfg(); } else if (cmd === 'settings') { if (container.unsaved) { showError('Unsaved changes'); } - tab.create('mujs:settings'); + Tabs.create('mujs:settings'); container.rebuild = false; } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } else if (cmd === 'switch-tab') { - tab.active(target); + Tabs.active(target); } else if (cmd === 'close-tab' && target.parentElement) { - tab.close(target.parentElement); + Tabs.close(target.parentElement); } else if (cmd === 'download-userjs') { - if (!container.userjsCache.has(+dataset.userjs)) { - return; - } const dataUserJS = container.userjsCache.get(+dataset.userjs); - let installCode = dataUserJS.code_url; - if (!prmpt && dataUserJS.code_url.endsWith('.user.css')) { - container.makePrompt('Download as UserStyle?', dataset); + if (isNull(dataUserJS)) { return; - } else if (prmpt !== prmptChoice) { - installCode = dataUserJS.code_url.replace(/\.user\.css$/, '.user.js'); } - const r = await dataUserJS._mujs.code.request(false, installCode); - const txt = r.data; - if (typeof txt !== 'string') { - return; + + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-download', + code_url: ujs.code_url, + userjs: dataset.userjs + } + }); + list.append(a); + } + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + const code_obj = await dataUserJS._mujs.code.request(false); + if (typeof code_obj.code === 'string') + doDownloadProcess({ + url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(code_obj.code), + filename: `${dataUserJS.name}${isUserCSS(dataUserJS.code_url) ? '.user.css' : '.user.js'}` + }); } - const userjsName = dataset.userjsName ?? dataset.userjs; - const userjsExt = prmpt !== prmptChoice ? '.user.js' : '.user.css'; - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = `${userjsName}${userjsExt}`; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } else if (cmd === 'load-userjs') { + } else if (cmd === 'load-userjs' || cmd === 'load-header') { if (!container.userjsCache.has(+dataset.userjs)) { return; } const codeArea = qs('textarea', target.parentElement.parentElement); - if (!isEmpty(codeArea.value)) { + if (!isEmpty(codeArea.value) && cmd === codeArea.dataset.load) { dom.cl.toggle(codeArea, 'hidden'); return; } + codeArea.dataset.load = cmd; const dataUserJS = container.userjsCache.get(+dataset.userjs); - const r = await dataUserJS._mujs.code.request(); - const txt = r.data; - if (typeof txt !== 'string') { + const code_obj = await dataUserJS._mujs.code.request(); + if (typeof code_obj.data_code_block !== 'string') { + codeArea.value = 'An error occured'; return; } - const code_obj = new ParseUserJS(txt); - codeArea.value = code_obj.get_code_block(); + codeArea.value = + cmd === 'load-userjs' ? code_obj.data_code_block : code_obj.data_meta_block; dom.cl.remove(codeArea, 'hidden'); - const apTo = (name, elem, root) => { - const n = dataUserJS._mujs.code[name]; - if (isEmpty(n)) { - const el = make('mujs-a', '', { - textContent: i18n$('listing_none') + for (const e of qsA( + 'mujs-column[data-el="matches"]', + target.parentElement.parentElement + )) { + applyTo(dataUserJS, e.dataset.type, qs('.mujs-grants', e), e); + } + } else if (cmd === 'load-page') { + if (!container.userjsCache.has(+dataset.userjs)) { + return; + } + let pageArea = qs('mujs-page', target.parentElement.parentElement); + if (!pageArea) { + pageArea = make('mujs-page'); + target.parentElement.parentElement.append(pageArea); + const dataUserJS = container.userjsCache.get(+dataset.userjs); + const engine = dataUserJS._mujs.info.engine; + let pageURL; + if (engine.name.includes('fork')) { + const { navigator } = _self; + const current = navigator.language.split('-')[0] ?? 'en'; + pageURL = dataUserJS.url.replace( + /\/scripts/, + `/${/^(zh|fr|es)/.test(current) ? navigator.language : current}/scripts` + ); + } else if (engine.name.includes('github')) { + const page_url = await Network.req(dataUserJS.page_url, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(() => { + return {}; }); - elem.append(el); + if (!page_url.download_url) { + return; + } + const page = await Network.req(page_url.download_url, 'GET', 'text'); + if (container.supported) { + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + innerHTML: page + }); + shadow.append(div); + } return; + } else { + pageURL = dataUserJS.url; } - dom.prop(elem, 'innerHTML', ''); - dom.cl.remove(root, 'hidden'); - for (const c of n) { - if (typeof c === 'string' && c.startsWith('http')) { - const el = make('mujs-a', '', { - textContent: c, - dataset: { - command: 'open-tab', - webpage: c - } - }); - elem.append(el); - } else if (isObj(c)) { - if (name === 'resource') { - for (const [k, v] of Object.entries(c)) { - const el = make('mujs-a', '', { - textContent: k ?? 'ERROR' - }); - if (v.startsWith('http')) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = v; - } - elem.append(el); - } - } else { - const el = make('mujs-a', '', { - textContent: c.text - }); - if (c.domain) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = `https://${c.text}`; - } - elem.append(el); + if (!pageURL) { + return; + } + const page = await Network.req(pageURL, 'GET', 'document'); + const getContent = () => { + let content = 'An error occured'; + const h = new URL(dataUserJS.url); + const root = qs('.user-content', page.documentElement); + for (const e of qsA('[href]', root)) { + e.target = '_blank'; + e.style = 'pointer-events: auto;'; + if (e.href.startsWith('/')) { + e.href = `${h.origin}${e.href}`; } + } + for (const e of qsA('img[src]', root)) { + e.style = + 'max-width: 25em; max-height: 25em; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;'; + } + if (root) { + content = root.innerHTML; } else { - const el = make('mujs-a', '', { - textContent: c - }); - elem.append(el); + content = 'No additional info available'; } + return content; + }; + if (container.supported) { + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + style: 'pointer-events: none;', + innerHTML: getContent() + }); + shadow.append(div); } - }; - const m = qsA('mujs-column[data-el="matches"]', target.parentElement.parentElement); - for (const e of normalizeTarget(m)) { - apTo(e.dataset.type, qs('.mujs-grants', e), e); + return; + } + if (!dom.cl.has(pageArea, 'hidden')) { + dom.cl.add(pageArea, 'hidden'); + return; } + dom.cl.remove(pageArea, 'hidden'); } else if (/export-/.test(cmd)) { - const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); - const bytes = new TextEncoder().encode(str); - const blob = new Blob([bytes], { type: 'application/json;charset=utf-8' }); - const dlBtn = make('a', 'mujs-exporter', { - href: URL.createObjectURL(blob), - download: `Magic_Userscript_${cmd === 'export-cfg' ? 'config' : 'theme'}.json` + const toCfg = cmd === 'export-cfg'; + doDownloadProcess({ + url: + 'data:text/plain;charset=utf-8,' + + encodeURIComponent(JSON.stringify(toCfg ? cfg : cfg.theme, null, ' ')), + filename: `Magic_Userscript_${toCfg ? 'config' : 'theme'}.json` }); - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); } else if (/import-/.test(cmd)) { if (qs('input', target.parentElement)) { qs('input', target.parentElement).click(); @@ -2123,43 +2195,35 @@ function primaryFN() { type: 'file', accept: '.json', onchange(evt) { - try { - [...evt.target.files].forEach((file) => { - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = () => { - const result = JSON.parse(reader.result); - if (result.blacklist) { - log(`Imported config: { ${file.name} }`, result); - cfg = result; - container.unsaved = true; - container.rebuild = true; - rebuildCfg(); - container.save().then((config) => { - sleazyRedirect(); - container.cache.clear(); - if (config.autofetch) { - respHandles.build(); - } - container.unsaved = false; - container.rebuild = false; - }); - } else { - log(`Imported theme: { ${file.name} }`, result); - cfg.theme = result; - container.renderTheme(cfg.theme); + const file = evt.target.files[0]; + if (file === undefined || file.name === '') { + return; + } + const fr = new FileReader(); + fr.onload = function () { + if (typeof fr.result !== 'string') { + return; + } + const content = JSON.parse(fr.result); + if (content.blacklist) { + cfg = content; + container.unsaved = true; + container.rebuild = true; + reloadCfg(); + container.save().then((config) => { + if (config.autofetch) { + respHandles.build(); } - inpJSON.remove(); - }; - reader.onerror = () => { - showError(reader.error); - inpJSON.remove(); - }; - }); - } catch (ex) { - showError(ex); + container.unsaved = false; + container.rebuild = false; + }); + } else { + cfg.theme = content; + container.setTheme(); + } inpJSON.remove(); - } + }; + fr.readAsText(file); } }); target.parentElement.append(inpJSON); @@ -2181,9 +2245,9 @@ function primaryFN() { const dataset = target.dataset; const cmd = dataset.command; if (cmd === 'switch-tab' || cmd === 'close-tab') { - tab.close(target); + Tabs.close(target); } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } }); if (!isMobile) { @@ -2205,20 +2269,70 @@ function primaryFN() { }); } } - // #endregion - const META_START_COMMENT = '// ==UserScript=='; - const META_END_COMMENT = '// ==/UserScript=='; - const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; - const APPLIES_TO_ALL_PATTERNS = [ - 'http://*', - 'https://*', - 'http://*/*', - 'https://*/*', - 'http*://*', - 'http*://*/*', - '*', - '*://*', - '*://*/*', + /** + * @param {CustomEvent} evt + */ + const updatedItem = (evt) => { + const ujs = evt.detail; + if (!ujs._mujs) return; + if (ujs.deleted === true) { + ujs._mujs.root.remove(); + container.userjsCache.delete(ujs.id); + Counter.reset(); + MUList.sortRecords(); + return; + } + if (!isEmpty(ujs.code_urls)) ujs.code_url = ujs.code_urls[0].code_url; + for (const elem of qsA('[data-name]', ujs._mujs.root)) { + const name = elem.dataset.name; + if (name === 'code') { + if (ujs._mujs.code.data_code_block) { + if (cfg.preview.code && !cfg.preview.metadata) { + elem.value = ujs._mujs.code.data_code_block; + } else if (cfg.preview.metadata && !cfg.preview.code) { + elem.value = ujs._mujs.code.data_meta_block; + } else { + elem.value = `${ujs._mujs.code.META_START_COMMENT}${ujs._mujs.code.data_meta_block}${ujs._mujs.code.META_END_COMMENT}${ujs._mujs.code.data_code_block}`; + } + } + continue; + } + if (!ujs[name]) continue; + if (name === 'license') { + dom.attr(elem, 'title', ujs.license ?? i18n$('no_license')); + dom.text(elem, `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`); + } else if (name === 'code_updated_at') { + dom.text(elem, language.toDate(ujs.code_updated_at)); + elem.dataset.value = new Date(ujs.code_updated_at).toISOString(); + } else if (name === 'created_date') { + dom.text(elem, `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`); + elem.dataset.value = new Date(ujs.created_at).toISOString(); + } else if (name === 'total_installs') { + dom.text(elem, `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`); + } else { + dom.text(elem, ujs[name]); + } + } + if (ujs._mujs.code.data_code_block) { + for (const e of qsA('mujs-column[data-el="matches"]', ujs._mujs.root)) { + applyTo(ujs, e.dataset.type, qs('.mujs-grants', e), e); + } + } + if (container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); + }; + ael(main, 'updateditem', updatedItem); + // #endregion + const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; + const APPLIES_TO_ALL_PATTERNS = [ + 'http://*', + 'https://*', + 'http://*/*', + 'https://*/*', + 'http*://*', + 'http*://*/*', + '*', + '*://*', + '*://*/*', 'http*' ]; class ParseUserJS { @@ -2235,92 +2349,84 @@ function primaryFN() { */ data_code_block; /** - * @type { { [meta: string]: any } } + * @type { { [meta: string]: string | string[] | { [resource: string]: string } } } */ data_meta; /** - * @type { string[] } + * @type { {text: string;domain: boolean;tld_extra: boolean}[] } */ data_names; - constructor(code) { - this.code = code; - this.get_all(); - } - get_all() { - this.get_meta_block(); - this.get_code_block(); - this.parse_meta(); - this.calculate_applies_to_names(); + constructor(code, isCSS) { + this.isUserCSS = isCSS === true; + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + if (code) { + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + } } get_meta_block() { if (isEmpty(this.code)) { - return null; + return ''; } if (this.data_meta_block) { return this.data_meta_block; } - const start_block = this.code.indexOf(META_START_COMMENT); + const start_block = this.code.indexOf(this.META_START_COMMENT); if (isNull(start_block)) { - return null; + return ''; } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); if (isNull(end_block)) { - return null; + return ''; } - const meta_block = this.code.substring(start_block + META_START_COMMENT.length, end_block); + const meta_block = this.code.substring( + start_block + this.META_START_COMMENT.length, + end_block + ); this.data_meta_block = meta_block; - return meta_block; + return this.data_meta_block; } get_code_block() { if (isEmpty(this.code)) { - return null; + return ''; } if (this.data_code_block) { return this.data_code_block; } - const start_block = this.code.indexOf(META_START_COMMENT); + const start_block = this.code.indexOf(this.META_START_COMMENT); if (isNull(start_block)) { return null; } - const end_block = this.code.indexOf(META_END_COMMENT, start_block); + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); if (isNull(end_block)) { return null; } const code_block = this.code.substring( - end_block + META_END_COMMENT.length, + end_block + this.META_END_COMMENT.length, this.code.length ); - const lines = []; - for (const l of code_block.split('\n')) { - if (isEmpty(l)) { - continue; - } - lines.push(l); - } - const clean_code = lines.join('\n'); - this.data_code_block = clean_code; - return clean_code; + this.data_code_block = code_block.split('\n').filter(Boolean).join('\n'); + return this.data_code_block; } parse_meta() { if (isEmpty(this.code)) { - return null; + return {}; } if (this.data_meta) { return this.data_meta; } const meta = {}; - const meta_block = this.get_meta_block(); const meta_block_map = new Map(); - for (const meta_line of meta_block.split('\n')) { - const meta_match = meta_line.match(/\/\/\s+@([a-zA-Z:-]+)\s+(.*)/); - if (isNull(meta_match)) { - continue; - } + for (const meta_line of this.get_meta_block().split('\n').filter(Boolean)) { + const meta_match = /\/\/\s+@([a-zA-Z:-]+)\s+(.*)/.exec(meta_line); + if (!meta_match) continue; const key = meta_match[1].trim(); const value = meta_match[2].trim(); - if (!meta_block_map.has(key)) { - meta_block_map.set(key, []); - } + if (!meta_block_map.has(key)) meta_block_map.set(key, []); const meta_map = meta_block_map.get(key); meta_map.push(value); meta_block_map.set(key, meta_map); @@ -2335,20 +2441,16 @@ function primaryFN() { this.data_meta = meta; return this.data_meta; } - /** - * @returns { string[] } - */ calculate_applies_to_names() { if (isEmpty(this.code)) { - return null; + return []; } if (this.data_names) { return this.data_names; } - const meta = this.parse_meta(); let patterns = []; - for (const [k, v] of Object.entries(meta)) { - if (/include|match/.test(k)) { + for (const [k, v] of Object.entries(this.parse_meta())) { + if (/include|match/i.test(k)) { if (Array.isArray(v)) { patterns = patterns.concat(v); } else { @@ -2360,156 +2462,192 @@ function primaryFN() { return []; } if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { - return []; + this.data_names = [ + { + domain: false, + text: 'All sites', + tld_extra: false + } + ]; + return this.data_names; } - const name_set = new Set(); + this.data_names = ParseUserJS.getNames(patterns); + return this.data_names; + } + intersect(a, ...arr) { + return !isBlank([...new Set(a)].filter((v) => arr.every((b) => b.includes(v)))); + } + static getNames(patterns = []) { + const name_map = new Map(); const addObj = (obj) => { - if (name_set.has(obj)) { + if (name_map.has(obj.text)) { return; } - name_set.add(obj); + name_map.set(obj.text, obj); }; for (let p of patterns) { - try { - const original_pattern = p; - let pre_wildcards = []; - if (p.match(/^\/(.*)\/$/)) { - pre_wildcards = [p]; - } else { - let m = p.match(/^\*(https?:.*)/i); - if (!isNull(m)) { - p = m[1]; - } - p = p - .replace(/^\*:/i, 'http:') - .replace(/^\*\/\//i, 'http://') - .replace(/^http\*:/i, 'http:') - .replace(/^(https?):([^/])/i, '$1://$2'); - m = p.match(/^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = m[1] + m[2]; - } - m = p.match(/^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; - } - m = p.match(/^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/); - if (!isNull(m)) { - if (m[2].match(/A([0-9]+\.){2,}z/)) { - p = `${m[1]}tld${m[3]}`; - pre_wildcards = [p.split('*')[0]]; - } else { - pre_wildcards = [p]; - } + const original_pattern = p; + let pre_wildcards = []; + if (p.match(/^\/(.*)\/$/)) { + pre_wildcards = [p]; + } else { + let m = /^\*(https?:.*)/i.exec(p); + if (m) { + p = m[1]; + } + p = p + .replace(/^\*:/i, 'http:') + .replace(/^\*\/\//i, 'http://') + .replace(/^http\*:/i, 'http:') + .replace(/^(https?):([^/])/i, '$1://$2'); + m = /^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = m[1] + m[2]; + } + m = /^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/.exec(p); + if (m) { + if (m[2].match(/A([0-9]+\.){2,}z/)) { + p = `${m[1]}tld${m[3]}`; + pre_wildcards = [p.split('*')[0]]; } else { pre_wildcards = [p]; } + } else { + pre_wildcards = [p]; } - for (const pre_wildcard of pre_wildcards) { - try { - const urlObj = new URL(pre_wildcard); - const { host } = urlObj; - if (isNull(host)) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (!host.includes('.') && host.includes('*')) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (host.endsWith('.tld')) { - for (let i = 0; i < TLD_EXPANSION.length; i++) { - const tld = TLD_EXPANSION[i]; - addObj({ - text: host.replace(/tld$/i, tld), - domain: true, - tld_extra: i != 0 - }); - } - } else if (host.endsWith('.')) { - addObj({ - text: host.slice(0, -1), - domain: true, - tld_extra: false - }); - } else { + } + for (const pre_wildcard of pre_wildcards) { + try { + const urlObj = new URL(pre_wildcard); + const { host } = urlObj; + if (isNull(host)) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (!host.includes('.') && host.includes('*')) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.tld')) { + for (let i = 0; i < TLD_EXPANSION.length; i++) { + const tld = TLD_EXPANSION[i]; addObj({ - text: host, + text: host.replace(/tld$/i, tld), domain: true, - tld_extra: false + tld_extra: i != 0 }); } - // eslint-disable-next-line no-unused-vars - } catch (ex) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.')) { + addObj({ + text: host.slice(0, -1), + domain: true, + tld_extra: false + }); + } else { + addObj({ + text: host, + domain: true, + tld_extra: false + }); } + } catch { + addObj({ text: original_pattern, domain: false, tld_extra: false }); } - } catch (ex) { - err(ex); } } - const arr = [...name_set]; - this.data_names = arr; - return this.data_names; + return [...name_map.values()]; } - intersect(a = [], b = []) { - for (const v of a) { - if (b.includes(v)) { - return true; + /** + * @template {import("../typings/types.d.ts").GSForkQuery} O + * @param {boolean} translate + * @param {O["code_url"]} code_url + * @param {O} [obj] + */ + async request(translate = false, code_url, obj) { + if (this.data_code_block) { + return this; + } + /** @type { string } */ + const code = await Network.req(code_url, 'GET', 'text').catch(err); + if (typeof code !== 'string') { + return this; + } + this.isUserCSS = isUserCSS(code_url); + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + + const { data_meta } = this; + if (translate) { + if (data_meta[`name:${language.current}`]) { + Object.assign(obj, { + name: data_meta[`name:${language.current}`] + }); + this.translated = true; + } + if (data_meta[`description:${language.current}`]) { + Object.assign(obj, { + description: data_meta[`description:${language.current}`] + }); + this.translated = true; } } - for (const v of b) { - if (a.includes(v)) { - return true; + if (Array.isArray(data_meta.grant)) { + data_meta.grant = union(data_meta.grant); + } + if (data_meta.resource) { + const obj = {}; + if (typeof data_meta.resource === 'string') { + const reg = /(.+)\s+(.+)/.exec(data_meta.resource); + if (reg) { + obj[reg[1].trim()] = reg[2]; + } + } else { + for (const r of data_meta.resource) { + const reg = /(.+)\s+(http.+)/.exec(r); + if (reg) { + obj[reg[1].trim()] = reg[2]; + } + } } + data_meta.resource = obj; } - return false; + Object.assign(this, { + code_size: [Network.format(code.length)], + meta: data_meta + }); + + return this; } - // intersect(a, ...arr) { - // return [...new Set(a)].filter((v) => arr.every((b) => b.includes(v))); - // } } - const mergeTemplate = (obj = {}) => { - const template = { - id: 0, - bad_ratings: 0, - good_ratings: 0, - ok_ratings: 0, - daily_installs: 0, - total_installs: 0, - name: 'NOT FOUND', - description: 'NOT FOUND', - version: '0.0.0', - url: BLANK_PAGE, - code_url: BLANK_PAGE, - created_at: Date.now(), - code_updated_at: Date.now(), - locale: 'NOT FOUND', - deleted: false, - users: [ - { - id: 0, - name: 'NOT FOUND', - url: BLANK_PAGE - } - ] - }; - /** @type {ObjectConstructor["hasOwn"]} */ - const hasOwn = - Object.hasOwn || - function hasOwn(it, key) { - return Object.prototype.hasOwnProperty.call(it, key); - }; - for (const key in template) { - if (hasOwn(obj, key)) continue; - obj[key] = template[key]; - } - return obj; + const template = { + id: 0, + bad_ratings: 0, + good_ratings: 0, + ok_ratings: 0, + daily_installs: 0, + total_installs: 0, + name: 'NOT FOUND', + description: 'NOT FOUND', + version: '0.0.0', + url: BLANK_PAGE, + code_url: BLANK_PAGE, + created_at: Date.now(), + code_updated_at: Date.now(), + locale: 'NOT FOUND', + deleted: false, + users: [] }; const mkList = (txt = '', obj = {}) => { - if (!obj.root || !obj.type) { - return; - } + if (!obj.root || !obj.type) return; const { root, type } = obj; const appliesTo = make('mu-js', 'mujs-list', { textContent: `${txt}: ` @@ -2526,7 +2664,7 @@ function primaryFN() { const list = obj.list ?? []; if (isEmpty(list)) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: i18n$('listing_none') }); applyList.append(elem); @@ -2535,7 +2673,7 @@ function primaryFN() { } for (const c of list) { if (typeof c === 'string' && c.startsWith('http')) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c, dataset: { command: 'open-tab', @@ -2546,7 +2684,7 @@ function primaryFN() { } else if (isObj(c)) { if (type === 'resource') { for (const [k, v] of Object.entries(c)) { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: k ?? 'ERROR' }); if (v.startsWith('http')) { @@ -2556,7 +2694,7 @@ function primaryFN() { applyList.append(elem); } } else { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c.text }); if (c.domain) { @@ -2566,54 +2704,43 @@ function primaryFN() { applyList.append(elem); } } else { - const elem = make('mujs-a', '', { + const elem = make('mujs-a', { textContent: c }); applyList.append(elem); } } }; - /** - * @param {number} [time] - */ - const timeoutFrame = async (time) => { - frameTimeout.clear(...frameTimeout.ids); - if (dom.cl.has(mainframe, 'hidden')) { - return; - } - time = time ?? cfg.time ?? DEFAULT_CONFIG.time; - let n = 10000; - if (typeof time === 'number' && !Number.isNaN(time)) { - n = container.isBlacklisted ? time / 2 : time; - } - await frameTimeout.set(n); - container.remove(); - return frameTimeout.clear(...frameTimeout.ids); - }; // #region Create UserJS /** * @param { import("../typings/types.d.ts").GSForkQuery } ujs * @param { string } engine */ const createjs = (ujs, engine) => { - ujs = mergeTemplate(ujs); - // Lets not add this UserJS to the list - if (ujs.id === 421603) { - return; - } - if (badUserJS.includes(ujs.id) || badUserJS.includes(ujs.url)) { + const a = [ + ujs.deleted === true, + ujs.id === 421603, // Lets not add this UserJS to the list + badUserJS.includes(ujs.id), + badUserJS.includes(ujs.url) + ].some((t) => t === true); + if (a) { return; } - if (!container.userjsCache.has(ujs.id)) { - container.userjsCache.set(ujs.id, ujs); - } + if (!container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); const eframe = make('td', 'install-btn'); const uframe = make('td', 'mujs-uframe'); const fdaily = make('td', 'mujs-list', { - textContent: ujs.daily_installs + textContent: ujs.daily_installs, + dataset: { + name: 'daily_installs' + } }); const fupdated = make('td', 'mujs-list', { - textContent: language.toDate(ujs.code_updated_at) + textContent: language.toDate(ujs.code_updated_at), + dataset: { + name: 'code_updated_at', + value: new Date(ujs.code_updated_at).toISOString() + } }); const fname = make('td', 'mujs-name'); const fmore = make('mujs-column', 'mujs-list hidden', { @@ -2637,16 +2764,24 @@ function primaryFN() { textContent: `${i18n$('version_number')}: ${ujs.version}` }); const fcreated = make('mu-js', 'mujs-list', { - textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}` + textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`, + dataset: { + name: 'created_at', + value: new Date(ujs.created_at).toISOString() + } }); const flicense = make('mu-js', 'mujs-list', { title: ujs.license ?? i18n$('no_license'), textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`, - style: - 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: fit-content; max-width: 20em;' + dataset: { + name: 'license' + } }); const ftotal = make('mu-js', 'mujs-list', { - textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}` + textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`, + dataset: { + name: 'total_installs' + } }); const fratings = make('mu-js', 'mujs-list', { title: i18n$('ratings'), @@ -2656,6 +2791,7 @@ function primaryFN() { title: i18n$('good'), textContent: ujs.good_ratings, dataset: { + name: 'good_ratings', el: 'good' } }); @@ -2663,6 +2799,7 @@ function primaryFN() { title: i18n$('ok'), textContent: ujs.ok_ratings, dataset: { + name: 'ok_ratings', el: 'ok' } }); @@ -2670,6 +2807,7 @@ function primaryFN() { title: i18n$('bad'), textContent: ujs.bad_ratings, dataset: { + name: 'bad_ratings', el: 'bad' } }); @@ -2685,11 +2823,11 @@ function primaryFN() { title: `${i18n$('install')} "${ujs.name}"`, dataset: { command: 'install-script', - userjs: ujs.code_url + userjs: ujs.id } }); - const scriptDownload = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('install')} ${i18n$('saveFile')}`, + const scriptDownload = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('download')} ${i18n$('saveFile')}`, dataset: { command: 'download-userjs', userjs: ujs.id, @@ -2710,27 +2848,26 @@ function primaryFN() { spellcheck: false, wrap: 'soft' }); - const loadCode = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('search')} ${i18n$('preview_code')}`, + const loadCode = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} ${i18n$('code')}`, dataset: { command: 'load-userjs', userjs: ujs.id } }); - if (engine) { - tr.dataset.engine = engine; - // if (engine.includes('fork')) { - // fdaily.dataset.command = 'open-tab'; - // fdaily.dataset.webpage = `${ujs.url}/stats`; - // fupdated.dataset.command = 'open-tab'; - // fupdated.dataset.webpage = `${ujs.url}/versions`; - // } - if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { - tr.dataset.good = 'upsell'; + const loadMetadata = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} ${i18n$('metadata')}`, + dataset: { + command: 'load-header', + userjs: ujs.id } + }); + tr.dataset.engine = engine; + if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { + tr.dataset.good = 'upsell'; } for (const u of ujs.users) { - const user = make('mujs-a', '', { + const user = make('mujs-a', { innerHTML: u.name, title: u.url, dataset: { @@ -2738,13 +2875,16 @@ function primaryFN() { webpage: u.url } }); - if (cfg.recommend.author && u.id === authorID) { + if ( + cfg.recommend.author && + (u.id === authorID || u.url === 'https://github.com/magicoflolis') + ) { tr.dataset.author = 'upsell'; dom.prop(user, 'innerHTML', `${u.name} ${iconSVG.load('verified')}`); } uframe.append(user); } - if (engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.id)) { + if (cfg.recommend.others && goodUserJS.includes(ujs.id)) { tr.dataset.good = 'upsell'; } eframe.append(scriptInstall); @@ -2752,381 +2892,340 @@ function primaryFN() { jsInfo.append(ftotal, ratings, fver, fcreated); mkList(i18n$('code_size'), { list: ujs._mujs.code.code_size, - type: 'size', + type: 'code_size', root: jsInfo }); jsInfoB.append(flicense); + const data_meta = ujs._mujs.code?.data_meta ?? {}; mkList(i18n$('antifeatures'), { - list: ujs._mujs.code.antifeatures, + list: data_meta.antifeatures ?? [], type: 'antifeatures', root: jsInfoB }); mkList(i18n$('applies_to'), { - list: ujs._mujs.code.match, - type: 'match', + list: ujs._mujs.code?.data_names ?? [], + type: 'data_names', root: jsInfoB }); mkList('@grant', { - list: ujs._mujs.code.grant, + list: data_meta.grant ?? [], type: 'grant', root: jsInfoB }); mkList('@require', { - list: ujs._mujs.code.meta?.require, + list: data_meta.require, type: 'require', root: jsInfoB }); - const script_resources = ujs._mujs.code.meta?.resource; mkList('@resource', { - list: isNull(script_resources) ? [] : [script_resources], + list: isNull(data_meta.resource) ? [] : [data_meta.resource], type: 'resource', root: jsInfoB }); fmore.append(jsInfo, jsInfoB); - fBtns.append(scriptDownload, loadCode); + fBtns.append(scriptDownload, loadCode, loadMetadata); fname.append(ftitle, fdesc, fmore, fBtns, codeArea); + fname._mujs = { fmore, fBtns, codeArea }; - const code_data = ujs._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - codeArea.value = code_obj.get_code_block(); - } + const loadPage = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('pager')} Page`, + dataset: { + command: 'load-page', + userjs: ujs.id + } + }); + fBtns.append(loadPage); - if (ujs._mujs.code?.translated) { - tr.classList.add('translated'); - } + if (ujs._mujs.code?.translated) tr.classList.add('translated'); - for (const e of [fname, uframe, fdaily, fupdated, eframe]) { - tr.append(e); - } - tabbody.append(tr); + for (const e of [fname, uframe, fdaily, fupdated, eframe]) tr.append(e); ujs._mujs.root = tr; - - return tr; + return ujs._mujs.root; }; // #endregion const loadFilters = () => { - const resp = []; - for (const v of Object.values(cfg.filters)) { - if (!v.enabled) { - continue; - } - resp.push(new RegExp(v.regExp, v.flag)); - } - return { - regExp: resp, - get(name) { - for (const v of Object.values(cfg.filters)) { - if (v.name === name) { - return new RegExp(v.regExp, v.flag) - }; + /** @type {Map} */ + const pool = new Map(); + const handles = { + pool, + enabled() { + return [...pool.values()].filter((o) => o.enabled); + }, + refresh() { + if (!Object.is(pool.size, 0)) pool.clear(); + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); } - // return resp.find(r => r.name === name); + return this; }, + get(str) { + return [...pool.values()].find((v) => v.keyReg.test(str) || v.valueReg.test(str)); + }, + /** + * @param { import("../typings/types.d.ts").GSForkQuery } param0 + */ match({ name, users }) { - if (isBlank(resp)) { - return true; - } - const strArr = [{ name }, ...users]; - for (const r of resp) { - const v = strArr.find((o) => o.name.match(r)); - if (v) { - return false; - } + const p = handles.enabled(); + if (Object.is(p.length, 0)) return true; + for (const v of p) { + if ([{ name }, ...users].find((o) => o.name.match(v.reg))) return false; } return true; } }; + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); + } + return handles.refresh(); }; // #region List class List { - engines; - cache; + intEngines; intHost; - constructor(host = undefined) { + constructor(hostname = undefined) { this.build = this.build.bind(this); - if (isEmpty(host)) host = container.host; - this.engines = cfg.engines; - this.cache = container.cache.get(host); - this.host = host; + this.groupBy = this.groupBy.bind(this); + this.dispatch = this.dispatch.bind(this); + this.sortRecords = this.sortRecords.bind(this); + this.container = container; + this.intEngines = cfg.engines ?? []; + this.host = hostname; + } + + setEngines(engines = []) { + const { host } = this; + return engines.filter((e) => { + if (!e.enabled) { + return false; + } + const v = engineUnsupported[e.name] ?? []; + if (v.includes(host)) { + showError(`Engine: "${e.name}" unsupported on "${host}"`); + container.timeoutFrame(); + return false; + } + return true; + }); + } + + dispatch(ujs) { + const { CustomEvent } = _self; + const customEvent = new CustomEvent('updateditem', { detail: ujs }); + main.dispatchEvent(customEvent); + } + + set engines(engines) { + this.intEngines = this.setEngines(engines); + } + + get engines() { + return this.intEngines; } set host(hostname) { + if (isEmpty(hostname)) hostname = this.container.host; this.intHost = hostname; - - if (!container.cache.has(hostname)) { - const engineTemplate = {}; - for (const engine of cfg.engines) { - engineTemplate[engine.name] = []; - } - container.cache.set(hostname, engineTemplate); - } - if (container.checkBlacklist(hostname)) { + this.blacklisted = this.container.checkBlacklist(hostname); + this.intEngines = this.setEngines(this.engines); + this.domain = this.getDomain(this.intHost); + if (this.blacklisted) { showError(`Blacklisted "${hostname}"`); - timeoutFrame(); - this.blacklisted = true; - } else { - this.blacklisted = false; + this.container.timeoutFrame(); } - - const isSupported = (name) => { - for (const [k, v] of Object.entries(engineUnsupported)) { - if (k !== name) { - continue; - } - if (v.includes(hostname)) { - return false; - } - } - return true; - }; - this.engines = cfg.engines.filter((e) => e.enabled && isSupported(e.name)); - this.cache = container.cache.get(hostname); } get host() { return this.intHost; } + /** + * @template { string } S + * @param { S } str + */ + getDomain(str = '') { + if (str === '*') { + return 'all-sites'; + } + return str.split('.').at(-2) ?? BLANK_PAGE; + } + // #region Builder build() { try { container.refresh(); - if (this.blacklisted) { - return; - } - if (isEmpty(this.engines)) { + const { blacklisted, engines, host, domain, dispatch } = this; + if (blacklisted || isEmpty(engines)) { container.opacityMin = '0'; mainframe.style.opacity = container.opacityMin; return; } - info(' Building list', { cache: this.cache, engines: this.engines }); - const customRecords = []; - const host = this.host; - const arr = []; + const fetchRecords = []; const bsFilter = loadFilters(); + const hostCache = Array.from(this); - for (const engine of this.engines) { - const cEngine = this.cache[`${engine.name}`]; - if (!isEmpty(cEngine)) { - for (const ujs of cEngine) { - if (isElem(ujs._mujs.root)) { - tabbody.append(ujs._mujs.root); - } - } - updateCounter(cEngine.length, engine); - continue; - } - const respError = (error) => { - if (!error.cause) error.cause = engine.name; - if (error.message.startsWith('429')) { - showError(`Engine: "${engine.name}" Too many requests...`); - return; - } - showError(`Engine: "${engine.name}"`, error.message); - }; - const _mujs = (d) => { - const obj = { - ...d, - _mujs: { - root: {}, - info: { - engine, - host - }, - code: { - antifeatures: [], - grant: [], - match: [], - meta: {}, - request: async function (translate = false, code_url) { - if (this.data) { - return this; - } - this.data = ''; - /** @type { string } */ - const code = await Network.req(code_url ?? d.code_url, 'GET', 'text').catch( - showError - ); - if (typeof code !== 'string') { - return this; - } - Object.assign(this, { - data: code, - code_size: [Network.format(code.length)] - }); - const grantSet = new Set(); - const afSet = new Set(); - const code_obj = new ParseUserJS(code); - const meta = code_obj.parse_meta(); - const applies_to_names = code_obj.calculate_applies_to_names(); - - if (translate) { - for (const lng of language.cache) { - if (meta[`name:${lng}`]) { - Object.assign(obj, { - name: meta[`name:${lng}`] - }); - this.translated = true; - } - if (meta[`description:${lng}`]) { - Object.assign(obj, { - description: meta[`description:${lng}`] - }); - this.translated = true; - } - } - } - - for (const [key, value] of Object.entries(meta)) { - if (/grant/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (grantSet.has(v)) { - continue; - } - grantSet.add(v); - } - } else if (/antifeature/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (afSet.has(v)) { - continue; - } - afSet.add(v); - } - } - } - if (typeof meta.require === 'string') { - const s = meta.require; - meta.require = [s]; - } - if (meta.resource) { - const obj = {}; - if (typeof meta.resource === 'string') { - const reg = /(.+)\s+(.+)/.exec(meta.resource); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } - } else { - for (const r of meta.resource) { - const reg = /(.+)\s+(http.+)/.exec(r); - if (reg) { - obj[reg[1].trim()] = reg[2]; - } - } - } - meta.resource = obj; - } - Object.assign(this, { - meta, - match: applies_to_names, - grant: [...grantSet], - antifeatures: [...afSet] - }); + info('Building list', { hostCache, engines, container, list: this }); - return this; + const g = this.groupBy(); + const toFetch = engines.filter((engine) => !g[engine.name]); + const isCached = toFetch.filter((engine) => + hostCache.find(({ _mujs }) => engine.name === _mujs.info.engine.name) + ); + if (!isBlank(toFetch) && isBlank(isCached)) { + for (const engine of engines) { + info(`Fetching from "${engine.name}" for "${host}"`); + const respError = (error) => { + if (!error.cause) error.cause = engine.name; + if (error.message.startsWith('429')) { + showError(`Engine: "${engine.name}" Too many requests...`); + return; + } + showError(`Engine: "${engine.name}"`, error.message); + }; + const _mujs = (d) => { + /** + * @type {import("../typings/types.d.ts").GSForkQuery} + */ + const ujs = { + ...template, + ...d, + code_urls: [], + _mujs: { + root: {}, + info: { + engine, + host + }, + code: { + meta: {} } } - } + }; + ujs._mujs.code.request = async (translate = false, code_url) => { + if (typeof ujs._mujs.code.data_code_block === 'string') { + return ujs._mujs.code; + } + const p = new ParseUserJS(); + await p.request(translate, code_url ?? ujs.code_url, ujs); + if (code_url) { + return p; + } + for (const [k, v] of Object.entries(p)) ujs._mujs.code[k] = v; + return ujs._mujs.code; + }; + return ujs; }; - return obj; - }; - const toQuery = (fallback) => { - if (engine.query) { - return decodeURIComponent(engine.query).replace(/\{host\}/g, host); - } - return fallback; - }; - /** - * @param { import("../typings/types.d.ts").GSFork } dataQ - */ - const forkFN = async (dataQ) => { - if (!dataQ) { - showError('Invalid data received from the server, check internet connection'); - return; - } - /** - * @type { import("../typings/types.d.ts").GSForkQuery[] } - */ - const dq = Array.isArray(dataQ) - ? dataQ - : Array.isArray(dataQ.query) - ? dataQ.query - : []; - const dataA = dq - .filter(Boolean) - .filter((d) => !d.deleted) - .filter(bsFilter.match); - if (isBlank(dataA)) { - return; - } - const data = dataA.map(_mujs); - const otherLng = []; /** - * @param {import("../typings/types.d.ts").GSForkQuery} d - * @returns {boolean} + * Prior to UserScript v7.0.0 + * @template {string} F + * @param {F} fallback + * @returns {F} */ - const inUserLanguage = (d) => { - const dlocal = d.locale.split('-')[0] ?? d.locale; - if (language.cache.includes(dlocal)) { - return true; + const toQuery = (fallback) => { + if (engine.query) { + return decode(engine.query) + .replace(/\{host\}/g, host) + .replace(/\{domain\}/g, domain); } - otherLng.push(d); - return false; + return fallback; }; - const filterLang = data.filter((d) => { - if (cfg.filterlang && !inUserLanguage(d)) { - return false; - } - return true; - }); - let finalList = filterLang; - const hds = []; - for (const ujs of otherLng) { - const c = await ujs._mujs.code.request(true); - if (c.translated) { - hds.push(ujs); - } - } - finalList = union(hds, filterLang); - - for (const ujs of finalList) { - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + /** + * @param { import("../typings/types.d.ts").GSFork } dataQ + */ + const forkFN = async (dataQ) => { + if (!dataQ) { + showError('Invalid data received from the server, check internet connection'); + return; } - createjs(ujs, engine.name); - } - this.cache[engine.name].push(...finalList); - updateCounter(finalList.length, engine); - }; - /** - * @param {Document} htmlDocument - */ - const openuserjs = async (htmlDocument) => { - try { - if (!htmlDocument) { - showError('Invalid data received from the server, TODO fix this'); + const dq = Array.isArray(dataQ) + ? dataQ + : Array.isArray(dataQ.query) + ? dataQ.query + : []; + const dataA = dq + .filter(Boolean) + .filter((d) => !d.deleted) + .filter(bsFilter.match); + if (isBlank(dataA)) { return; } - const selected = htmlDocument.documentElement; - if (/openuserjs/gi.test(engine.name)) { - for (const i of qsA('.col-sm-8 .tr-link', selected)) { - while (isNull(qs('.script-version', i))) { - await new Promise((resolve) => requestAnimationFrame(resolve)); + const { groupBy } = _self; + /** + * @type { {[key: string]: import("../typings/types.d.ts").GSForkQuery[]} } + */ + const g = groupBy(dataA.map(_mujs), ({ locale }) => { + return locale.split('-')[0] ?? locale; + }); + for (const [k, list] of Object.entries(g)) { + for (const ujs of list) { + if (cfg.filterlang && k !== language.current) { + const c = await ujs._mujs.code.request(true); + if (!c.translated) continue; + } + if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); } - const fixurl = dom - .prop(qs('.tr-link-a', i), 'href') - .replace( - new RegExp(document.location.origin, 'gi'), - 'https://openuserjs.org' + if (isUserCSS(ujs.code_url)) { + ujs.code_urls.push( + { + name: `${ujs.name} (.user.css)`, + code_url: ujs.code_url + }, + { + name: `${ujs.name} (.user.js)`, + code_url: ujs.code_url.replace(/\.user\.css$/, '.user.js') + } ); - const ujs = mergeTemplate( - _mujs({ - name: dom.text(qs('.tr-link-a', i)), - description: dom.text(qs('p', i)), - version: dom.text(qs('.script-version', i)), - url: fixurl, - code_url: `${fixurl.replace(/\/scripts/gi, '/install')}.user.js`, + } + createjs(ujs, engine.name); + } + } + }; + /** + * @param {Document} htmlDocument + */ + const openuserjs = async (htmlDocument) => { + try { + if (!htmlDocument) { + showError('Invalid data received from the server, TODO fix this'); + return; + } + const selected = htmlDocument.documentElement; + if (/openuserjs/gi.test(engine.name)) { + const col = qsA('.col-sm-8 .tr-link', selected) ?? []; + for (const i of col) { + while (isNull(qs('.script-version', i))) { + await new Promise((resolve) => requestAnimationFrame(resolve)); + } + const fixurl = dom + .prop(qs('.tr-link-a', i), 'href') + .replace( + new RegExp(document.location.origin, 'gi'), + 'https://openuserjs.org' + ); + const ujs = _mujs({ + name: dom.text(qs('.tr-link-a', i)), + description: dom.text(qs('p', i)), + version: dom.text(qs('.script-version', i)), + url: fixurl, + code_url: `${fixurl.replace(/\/scripts/gi, '/install')}.user.js`, total_installs: dom.text(qs('td:nth-child(2) p', i)), created_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), code_updated_at: dom.attr(qs('td:nth-child(4) time', i), 'datetime'), @@ -3136,129 +3235,209 @@ function primaryFN() { url: dom.prop(qs('.inline-block a', i), 'href') } ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + }); + if (bsFilter.match(ujs)) { + continue; + } + if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); + } + createjs(ujs, engine.name); } - createjs(ujs, engine.name); - customRecords.push(ujs); } + } catch (ex) { + showError(ex); } - this.cache[engine.name].push(...customRecords); - updateCounter(customRecords.length, engine); - } catch (ex) { - showError(ex); - } - }; - const gitFN = async (data) => { - try { - if (isBlank(data.items)) { - showError('Invalid data received from the server, TODO fix this'); - return; - } - for (const r of data.items) { - const ujs = mergeTemplate( - _mujs({ + }; + const gitFN = (data) => { + try { + if (isBlank(data.items)) { + return; + } + for (const r of data.items) { + const ujs = _mujs({ + id: r.id ?? 0, name: r.name, - description: isEmpty(r.repository.description) - ? i18n$('no_license') - : r.repository.description, + description: isEmpty(r.description) ? i18n$('no_license') : r.description, url: r.html_url, - code_url: r.html_url.replace(/\/blob\//g, '/raw/'), - code_updated_at: r.commit || Date.now(), - total_installs: r.score, + code_url: r.html_url, + page_url: `${r.url}/contents/README.md`, + created_at: r.created_at, + code_updated_at: r.updated_at || Date.now(), + daily_installs: r.watchers_count ?? 0, + good_ratings: r.stargazers_count ?? 0, users: [ { - name: r.repository.owner.login, - url: r.repository.owner.html_url + name: r.owner.login, + url: r.owner.html_url } ] - }) - ); - if (bsFilter.match(ujs)) { - continue; - } - if (cfg.codePreview && !ujs._mujs.code.data) { - await ujs._mujs.code.request(); + }); + if (r.license?.name) ujs.license = r.license.name; + const rootPath = r.contents_url.replace(/\{\+path\}/, ''); + const fetchContent = async (dir) => { + const contents = await Network.req(dir, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(respError); + for (const content of contents) { + if (content.type === 'file') { + if (isUserJS(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } else if (isUserCSS(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } + } else if (content.type === 'dir') { + await fetchContent(`${rootPath}/${content.path}`); + } + } + }; + fetchContent(rootPath).then(() => { + if (isEmpty(ujs.code_urls)) { + ujs.deleted = true; + } else if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + ujs._mujs.code.request().then(() => { + dispatch(ujs); + }); + return; + } + dispatch(ujs); + }); + createjs(ujs, engine.name); } - createjs(ujs, engine.name); - customRecords.push(ujs); + } catch (ex) { + showError(ex); } - this.cache[engine.name].push(...customRecords); - updateCounter(data.items.length, engine); - } catch (ex) { - showError(ex); - } - }; - let netFN; - if (/github/gi.test(engine.name)) { - if (isEmpty(engine.token)) { - showError(`"${engine.name}" requires a token to use`); - continue; - } - netFN = Network.req( - toQuery( - `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30` - ), - 'GET', - 'json', - { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${engine.token}`, - 'X-GitHub-Api-Version': '2022-11-28' - } + }; + let netFN; + if (/github/gi.test(engine.name)) { + if (isEmpty(engine.token)) { + showError(`"${engine.name}" requires a token to use`); + continue; } - ) - .then(gitFN) - .then(() => { - Network.req('https://api.github.com/rate_limit', 'GET', 'json', { + Network.req( + `https://api.github.com/search/repositories?q=topic:${domain}+topic:userstyle`, + 'GET', + 'json', + { headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${engine.token}`, 'X-GitHub-Api-Version': '2022-11-28' } - }) - .then((data) => { - for (const [key, value] of Object.entries(data.resources.code_search)) { - const txt = make('mujs-row', 'rate-info', { - textContent: `${key.toUpperCase()}: ${value}` - }); - rateContainer.append(txt); + } + ) + .then(gitFN) + .catch(respError); + netFN = Network.req( + toQuery( + `https://api.github.com/search/repositories?q=topic:${domain}+topic:userscript` + ), + 'GET', + 'json', + { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ) + .then(gitFN) + .then(() => { + Network.req('https://api.github.com/rate_limit', 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' } }) - .catch(respError); - }); - } else if (/openuserjs/gi.test(engine.name)) { - netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( - openuserjs - ); - } else { - netFN = Network.req( - toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) - ).then(forkFN); - } - if (netFN) { - arr.push(netFN.catch(respError)); + .then((data) => { + for (const [key, value] of Object.entries(data.resources.code_search)) { + const txt = make('mujs-row', 'rate-info', { + textContent: `${key.toUpperCase()}: ${value}` + }); + rateContainer.append(txt); + } + }) + .catch(respError); + }); + } else if (/openuserjs/gi.test(engine.name)) { + netFN = Network.req(toQuery(`${engine.url}${host}`), 'GET', 'document').then( + openuserjs + ); + } else { + netFN = Network.req( + toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) + ).then(forkFN); + } + if (netFN) { + fetchRecords.push(netFN.catch(respError)); + } } + } else { + for (const ujs of hostCache) tabbody.append(ujs._mujs.root); } urlBar.placeholder = i18n$('search_placeholder'); urlBar.value = ''; - Promise.allSettled(arr).then(() => { - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - tabhead.rows[0].cells[2].dispatchEvent(new MouseEvent('click')); - }); + if (isBlank(fetchRecords)) { + this.sortRecords(); + return; + } + Promise.allSettled(fetchRecords).then(this.sortRecords).catch(showError); } catch (ex) { showError(ex); } } // #endregion + + sortRecords() { + const arr = Array.from(this); + for (const ujs of arr.flat().sort((a, b) => { + const sortType = cfg.autoSort ?? 'daily_installs'; + return b[sortType] - a[sortType]; + })) { + if (isElem(ujs._mujs.root)) tabbody.append(ujs._mujs.root); + } + for (const [name, value] of Object.entries(this.groupBy(arr))) + Counter.update(value.length, { name }); + } + + groupBy() { + const { groupBy } = _self; + return groupBy(Array.from(this), ({ _mujs }) => _mujs.info.engine.name); + } + + *[Symbol.iterator]() { + const { intHost, engines } = this; + const arr = Array.from(this.container).filter( + ({ _mujs }) => + _mujs.info.host === intHost && + engines.find((engine) => engine.enabled && engine.name === _mujs.info.engine.name) + ); + for (const userjs of arr) { + yield userjs; + } + } } const MUList = new List(); // #endregion @@ -3278,20 +3457,22 @@ function primaryFN() { command: 'reset' } }); + cbtn.append(resetbtn, savebtn); + const makesection = (name, tag) => { tag = tag ?? i18n$('no_license'); name = name ?? i18n$('no_license'); - const sec = make('mujs-section', '', { + const sec = make('mujs-section', { dataset: { name: tag } }); - const lb = make('label', '', { + const lb = make('label', { dataset: { command: tag } }); - const divDesc = make('mu-js', '', { + const divDesc = make('mu-js', { textContent: name }); ael(sec, 'click', (evt) => { @@ -3302,7 +3483,7 @@ function primaryFN() { } const cmd = target.dataset.command; if (cmd === tag) { - const a = qsA(`label[data-${tag}]`, sec); + const a = qsA(`[data-${tag}]`, sec); if (dom.cl.has(a, 'hidden')) { dom.cl.remove(a, 'hidden'); } else { @@ -3310,65 +3491,142 @@ function primaryFN() { } } }); - lb.append(divDesc); sec.append(lb); cfgpage.append(sec); + !cfgSec.has(sec) && cfgSec.add(sec); return sec; }; - const sections = { general: makesection('General', 'general'), load: makesection('Automation', 'load'), list: makesection('List', 'list'), filters: makesection('List Filters', 'filters'), blacklist: makesection('Blacklist (WIP)', 'blacklist'), - // engine: makesection('Search Engine', 'engine'), - theme: makesection('Theme Colors', 'theme') + engine: makesection('Search Engines', 'engine'), + theme: makesection('Theme Colors', 'theme'), + exp: makesection('Import / Export', 'exp') }; - - const makerow = (desc, type = null, nm, attrs = {}) => { - desc = desc ?? i18n$('no_license'); - nm = nm ?? i18n$('no_license'); - const sec = make('mujs-section', '', { + const makeRow = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { + const lb = make('label', 'sub-section hidden', { + textContent: text, dataset: { - name: nm + [tag]: text } }); - const lb = make('label'); - const divDesc = make('mu-js', '', { - textContent: desc - }); - lb.append(divDesc); - sec.append(lb); - cfgpage.append(sec); - if (isNull(type)) { + const getDefault = () => { + if (tag === 'engine') { + const engine = DEFAULT_CONFIG.engines.find((engine) => engine.name === value); + if (engine) { + return engine; + } + } + const nm = /^(\w+)-(.+)/.exec(value); + if (nm) { + return DEFAULT_CONFIG[nm[1]][nm[2]]; + } + return DEFAULT_CONFIG[value]; + }; + const getValue = () => { + if (tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === value); + if (engine) { + return engine; + } + } + const nm = /^(\w+)-(.+)/.exec(value); + if (nm) { + return cfg[nm[1]][nm[2]]; + } + return cfg[value]; + }; + const obj = { + text, + tag, + value, + type, + attrs, + default: getDefault(), + cache: getValue() + }; + if (type === 'select') { + const inp = make('select', { + dataset: { + [tag]: text + }, + ...attrs + }); + for (const selV of Object.keys(template)) { + if (selV === 'deleted' || selV === 'users') continue; + const o = make('option', { + value: selV, + textContent: selV + }); + inp.append(o); + } + inp.value = cfg[value]; + lb.append(inp); + if (sections[tag]) { + sections[tag].append(lb); + } + obj.elem = inp; + cfgBase.push(obj); return lb; } - const inp = make('input', '', { + const inp = make('input', { type, dataset: { - name: nm + [tag]: text }, ...attrs }); - if (!cfgMap.has(nm)) { - cfgMap.set(nm, inp); + + if (tag === 'engine') { + inp.dataset.name = value; } + + if (sections[tag]) { + sections[tag].append(lb); + } + if (type === 'checkbox') { const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { + const la = make('label', { onclick() { inp.dispatchEvent(new MouseEvent('click')); } }); inlab.append(inp, la); lb.append(inlab); - if (nm.includes('-')) { - return inp; + + const nm = /^(\w+)-(.+)/.exec(value); + if (nm) { + if (nm[1] === 'filters') { + inp.checked = cfg[nm[1]][nm[2]].enabled; + } else { + inp.checked = cfg[nm[1]][nm[2]]; + } + } else { + inp.checked = cfg[value]; } - if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { - const engine = cfg.engines.find((engine) => engine.name === nm); + ael(inp, 'change', (evt) => { + container.unsaved = true; + if (/filterlang/i.test(value)) { + container.rebuild = true; + } + if (nm) { + if (nm[1] === 'filters') { + cfg[nm[1]][nm[2]].enabled = evt.target.checked; + } else { + cfg[nm[1]][nm[2]] = evt.target.checked; + } + } else { + cfg[value] = evt.target.checked; + } + }); + + if (tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === value); if (engine) { inp.checked = engine.enabled; inp.dataset.engine = engine.name; @@ -3376,15 +3634,16 @@ function primaryFN() { container.unsaved = true; container.rebuild = true; engine.enabled = evt.target.checked; + MUList.engines = cfg.engines; }); if (engine.query) { const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name); - const urlInp = make('input', '', { + const urlInp = make('input', { type: 'text', defaultValue: '', - value: decodeURIComponent(engine.query) ?? '', - placeholder: decodeURIComponent(d.query) ?? '', + value: decode(engine.query), + placeholder: decode(d.query), dataset: { name: nm, engine: engine.name @@ -3394,15 +3653,17 @@ function primaryFN() { container.rebuild = true; try { engine.query = encodeURIComponent(new URL(evt.target.value).toString()); + MUList.engines = cfg.engines; } catch (ex) { err(ex); } } }); - sec.append(urlInp); + obj.elemUrl = urlInp; + lb.append(urlInp); } if (engine.name === 'github') { - const ghToken = make('input', '', { + const ghToken = make('input', { type: 'text', defaultValue: '', value: engine.token ?? '', @@ -3414,100 +3675,69 @@ function primaryFN() { container.unsaved = true; container.rebuild = true; engine.token = evt.target.value; + MUList.engines = cfg.engines; } }); - sec.append(ghToken); - cfgMap.set('github-token', ghToken); + obj.elemToken = ghToken; + lb.append(ghToken); } } - } else { - inp.checked = cfg[nm]; - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(nm)) { - container.rebuild = true; - } - cfg[nm] = evt.target.checked; - }); } - } else { - lb.append(inp); - } - return inp; - }; - const mkSection = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { - cfgMap.set(text, value); - const lb = make('label', 'sub-section hidden', { - textContent: text, - dataset: { - [tag]: text - } - }); - const inp = make('input', '', { - type, - dataset: { - [tag]: text - }, - ...attrs - }); - - if (sections[tag]) { - sections[tag].append(lb); - } - - if (type === 'checkbox') { - const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { - onclick() { - inp.dispatchEvent(new MouseEvent('click')); - } - }); - inlab.append(inp, la); - lb.append(inlab); - - const nm = /^(\w+)-(.+)/.exec(value); - if (nm) { - if (nm[1] === 'filters') { - inp.checked = cfg[nm[1]][nm[2]].enabled; - } else { - inp.checked = cfg[nm[1]][nm[2]]; - } - } else { - inp.checked = cfg[value]; - } - ael(inp, 'change', (evt) => { - container.unsaved = true; - if (/filterlang/i.test(value)) { - container.rebuild = true; - } - if (nm) { - if (nm[1] === 'filters') { - cfg[nm[1]][nm[2]].enabled = evt.target.checked; - } else { - cfg[nm[1]][nm[2]] = evt.target.checked; - } - } else { - cfg[value] = evt.target.checked; - } - }); } else { if (type === 'text') { inp.defaultValue = ''; inp.value = value ?? ''; inp.placeholder = value ?? ''; + + if (tag === 'theme') { + inp.dataset[tag] = text; + ael(inp, 'change', (evt) => { + let isvalid = true; + try { + const val = evt.target.value; + const sty = container.root.style; + const str = `--mujs-${text}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(val)) { + cfg.theme[text] = DEFAULT_CONFIG.theme[text]; + sty.removeProperty(str); + return; + } + if (prop === val) { + return; + } + sty.removeProperty(str); + sty.setProperty(str, val); + cfg.theme[text] = val; + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } + } + }); + } } lb.append(inp); } + obj.elem = inp; + cfgBase.push(obj); return lb; }; if (isGM) { - mkSection(i18n$('userjs_sync'), 'cache'); - mkSection(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); + makeRow(i18n$('userjs_sync'), 'cache'); + makeRow(i18n$('userjs_autoinject'), 'autoinject', 'checkbox', 'load'); } - mkSection(i18n$('redirect'), 'sleazyredirect'); - mkSection(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { + makeRow(i18n$('redirect'), 'sleazyredirect'); + makeRow(`${i18n$('dtime')} (ms)`, 'time', 'number', 'general', { defaultValue: 10000, value: cfg.time, min: 0, @@ -3535,85 +3765,96 @@ function primaryFN() { } }); - mkSection(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); - mkSection(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { + makeRow(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); + makeRow(i18n$('userjs_fullscreen'), 'autoexpand', 'checkbox', 'load', { onchange(e) { if (e.target.checked) { dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } else { dom.cl.remove([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsOpen')); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('expand')); } } }); + makeRow('Clear on Tab close', 'clearTabCache', 'checkbox', 'load'); - mkSection(i18n$('filter'), 'filterlang', 'checkbox', 'list'); - mkSection(i18n$('preview_code'), 'codePreview', 'checkbox', 'list'); - mkSection('Recommend author', 'recommend-author', 'checkbox', 'list'); - mkSection('Recommend scripts', 'recommend-others', 'checkbox', 'list'); + makeRow(i18n$('default_sort'), 'autoSort', 'select', 'list'); + makeRow(i18n$('filter'), 'filterlang', 'checkbox', 'list'); + makeRow(i18n$('preview_code'), 'preview-code', 'checkbox', 'list'); + makeRow(i18n$('preview_metadata'), 'preview-metadata', 'checkbox', 'list'); + makeRow(i18n$('recommend_author'), 'recommend-author', 'checkbox', 'list'); + makeRow(i18n$('recommend_other'), 'recommend-others', 'checkbox', 'list'); - for (const [k, v] of Object.entries(cfg.filters)) { - mkSection(v.name, `filters-${k}`, 'checkbox', 'filters'); - } + for (const [k, v] of Object.entries(cfg.filters)) + makeRow(v.name, `filters-${k}`, 'checkbox', 'filters'); - makerow('Greasy Fork', 'checkbox', 'greasyfork'); - makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); - makerow('Open UserJS', 'checkbox', 'openuserjs'); - makerow('GitHub API', 'checkbox', 'github'); + makeRow('Greasy Fork', 'greasyfork', 'checkbox', 'engine'); + makeRow('Sleazy Fork', 'sleazyfork', 'checkbox', 'engine'); + makeRow('Open UserJS', 'openuserjs', 'checkbox', 'engine'); + makeRow('GitHub API', 'github', 'checkbox', 'engine'); - for (const [k, v] of Object.entries(cfg.theme)) { - const lb = make('label', 'hidden', { - textContent: k, - dataset: { - theme: k - } - }); - const inp = make('input', '', { - type: 'text', - defaultValue: '', - value: v ?? '', - placeholder: v ?? '', - dataset: { - theme: k - }, - onchange(evt) { - let isvalid = true; - try { - const val = evt.target.value; - const sty = container.root.style; - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(val)) { - cfg.theme[k] = DEFAULT_CONFIG.theme[k]; - sty.removeProperty(str); - return; - } - if (prop === val) { - return; - } - sty.removeProperty(str); - sty.setProperty(str, val); - cfg.theme[k] = val; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } - } - } - }); - cfgMap.set(k, inp); - lb.append(inp); - sections.theme.append(lb); - } + for (const [k, v] of Object.entries(cfg.theme)) makeRow(k, v, 'text', 'theme'); + // const blacklist = make('textarea', { + // dataset: { + // name: 'blacklist' + // }, + // rows: '10', + // autocomplete: false, + // spellcheck: false, + // wrap: 'soft', + // value: JSON.stringify(cfg.blacklist, null, ' '), + // oninput(evt) { + // let isvalid = true; + // try { + // cfg.blacklist = JSON.parse(evt.target.value); + // isvalid = true; + // } catch (ex) { + // err(ex); + // isvalid = false; + // } finally { + // if (isvalid) { + // dom.cl.remove(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', false); + // } else { + // dom.cl.add(evt.target, 'mujs-invalid'); + // dom.prop(savebtn, 'disabled', true); + // } + // } + // } + // }); + // const addList = make('mujs-add', { + // textContent: '+', + // dataset: { + // command: 'new-list' + // } + // }); + // const n = make('input', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Name', + // }); + // const inpValue = make('input', { + // type: 'text', + // defaultValue: '', + // value: '', + // placeholder: 'Value', + // }); + // const label = make('label', 'new-list hidden', { + // dataset: { + // blacklist: 'new-list' + // } + // }); + // label.append(n, inpValue, addList); + // listSec.append(label); + // ael(addList, 'click', () => { + // if (isEmpty(n.value) || isEmpty(inpValue.value)) { + // return + // }; + // createList(n.value, n.value, inpValue.value); + // }); const createList = (key, v = '', disabled = false, type = 'String') => { let txt = key; if (typeof key === 'string') { @@ -3643,7 +3884,7 @@ function primaryFN() { blacklist: key } }); - const inp = make('input', '', { + const inp = make('input', { type: 'text', defaultValue: '', value: v ?? '', @@ -3673,7 +3914,7 @@ function primaryFN() { } } }); - const selType = make('select', '', { + const selType = make('select', { disabled, dataset: { blacklist: key @@ -3681,14 +3922,14 @@ function primaryFN() { }); if (disabled) { inp.readOnly = true; - const o = make('option', '', { + const o = make('option', { value: type, textContent: type }); selType.append(o); } else { for (const selV of ['String', 'RegExp', 'Object']) { - const o = make('option', '', { + const o = make('option', { value: selV, textContent: selV }); @@ -3699,76 +3940,50 @@ function primaryFN() { lb.append(inp, selType); sections.blacklist.append(lb); }; - // const blacklist = make('textarea', '', { - // dataset: { - // name: 'blacklist' - // }, - // rows: '10', - // autocomplete: false, - // spellcheck: false, - // wrap: 'soft', - // value: JSON.stringify(cfg.blacklist, null, ' '), - // oninput(evt) { - // let isvalid = true; - // try { - // cfg.blacklist = JSON.parse(evt.target.value); - // isvalid = true; - // } catch (ex) { - // err(ex); - // isvalid = false; - // } finally { - // if (isvalid) { - // dom.cl.remove(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', false); - // } else { - // dom.cl.add(evt.target, 'mujs-invalid'); - // dom.prop(savebtn, 'disabled', true); - // } - // } - // } - // }); - // cfgMap.set('blacklist', blacklist); - // const addList = make('mujs-add', '', { - // textContent: '+', - // dataset: { - // command: 'new-list' - // } - // }); - // const n = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Name', - // }); - // const inpValue = make('input', '', { - // type: 'text', - // defaultValue: '', - // value: '', - // placeholder: 'Value', - // }); - // const label = make('label', 'new-list hidden', { - // dataset: { - // blacklist: 'new-list' - // } - // }); - // label.append(n, inpValue, addList); - // listSec.append(label); - // ael(addList, 'click', () => { - // if (isEmpty(n.value) || isEmpty(inpValue.value)) { - // return - // }; - // createList(n.value, n.value, inpValue.value); - // }); - - for (const key of cfg.blacklist) { - createList(key); + for (const key of cfg.blacklist) createList(key); + const transfers = { + export: { + cfg: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_config'), + dataset: { + command: 'export-cfg', + exp: 'export-cfg' + } + }), + theme: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_theme'), + dataset: { + command: 'export-theme', + exp: 'export-theme' + } + }) + }, + import: { + cfg: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_config'), + dataset: { + command: 'import-cfg', + exp: 'import-cfg' + } + }), + theme: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_theme'), + dataset: { + command: 'import-theme', + exp: 'import-theme' + } + }) + } + }; + for (const value of Object.values(transfers)) { + for (const v of Object.values(value)) { + sections.exp.append(v); + } } - - cbtn.append(resetbtn, savebtn); cfgpage.append(cbtn); }; // #endregion - container.tab.custom = (host) => { + container.Tabs.custom = (host) => { MUList.host = host; respHandles.build(); }; @@ -3782,7 +3997,7 @@ function primaryFN() { evt.preventDefault(); evt.stopPropagation(); evt.target.style.opacity = container.opacityMin; - timeoutFrame(); + container.timeoutFrame(); }); ael(mainframe, 'click', (evt) => { evt.preventDefault(); @@ -3791,10 +4006,7 @@ function primaryFN() { dom.cl.add(mainframe, 'hidden'); if (cfg.autoexpand) { dom.cl.add([btnfullscreen, main], 'expanded'); - dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('fsClose')); - } - if (dom.cl.has(mainframe, 'error')) { - tab.create('mujs:settings'); + dom.prop(btnfullscreen, 'innerHTML', iconSVG.load('collapse')); } }); ael(urlBar, 'input', (evt) => { @@ -3806,29 +4018,27 @@ function primaryFN() { * @type { string } */ const val = evt.target.value; + const section = qsA('mujs-section[data-name]', cfgpage); if (isEmpty(val)) { - dom.cl.remove(qsA('tr[data-engine]', tabbody), 'hidden'); - dom.cl.remove(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + dom.cl.remove(container.toElem(), 'hidden'); + dom.cl.remove(section, 'hidden'); return; } const finds = new Set(); if (!dom.cl.has(cfgpage, 'hidden')) { const reg = new RegExp(val, 'gi'); - for (const elem of qsA('mujs-section[data-name]', cfgpage)) { - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (elem.dataset.name.match(reg)) { - finds.add(elem); - } + for (const elem of section) { + if (!isElem(elem)) continue; + if (finds.has(elem)) continue; + if (elem.dataset.name.match(reg)) finds.add(elem); } - dom.cl.add(qsA('mujs-section[data-name]', cfgpage), 'hidden'); + dom.cl.add(section, 'hidden'); dom.cl.remove([...finds], 'hidden'); return; } + const cacheValues = Array.from(container).filter(({ _mujs }) => { + return !finds.has(_mujs.root); + }); /** * @param {RegExpMatchArray} regExp * @param {keyof import("../typings/types.d.ts").GSForkQuery} key @@ -3836,43 +4046,29 @@ function primaryFN() { const ezQuery = (regExp, key) => { const q_value = val.replace(regExp, ''); const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } + for (const v of cacheValues) { let k = v[key]; if (typeof k === 'number') { k = `${v[key]}`; } if (k && k.match(reg)) { - finds.add(elem); + finds.add(v._mujs.root); } } }; if (val.match(/^(code_url|url):/)) { ezQuery(/^(code_url|url):/, 'code_url'); } else if (val.match(/^(author|users?):/)) { - const q_value = val.replace(/^(author|users?):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users) { + const parts = /^[\w_]+:(.+)/.exec(val); + if (parts) { + const reg = new RegExp(parts[1], 'gi'); + for (const v of cacheValues.filter((v) => !isEmpty(v.users))) { for (const user of v.users) { for (const value of Object.values(user)) { if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); + finds.add(v._mujs.root); } else if (typeof value === 'number' && `${value}`.match(reg)) { - finds.add(elem); + finds.add(v._mujs.root); } } } @@ -3889,98 +4085,43 @@ function primaryFN() { } else if (val.match(/^description:/)) { ezQuery(/^description:/, 'description'); } else if (val.match(/^(search_engine|engine):/)) { - const q_value = val.replace(/^(search_engine|engine):/, ''); - const reg = new RegExp(q_value, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v._mujs.info.engine) { - for (const value of Object.values(v._mujs.info.engine)) { - if (typeof value === 'string' && value.match(reg)) { - finds.add(elem); - } - } - } + const parts = /^[\w_]+:(\w+)/.exec(val); + if (parts) { + const reg = new RegExp(parts[1], 'gi'); + for (const { _mujs } of cacheValues) + if (_mujs.info.engine.name.match(reg)) finds.add(_mujs.root); } } else if (val.match(/^filter:/)) { const parts = /^\w+:(.+)/.exec(val); if (parts) { const bsFilter = loadFilters(); - const p = parts[1].trim().toLocaleLowerCase(); - for (const [key, value] of Object.entries(cfg.filters)) { - const k = key.trim().toLocaleLowerCase(); - const v = value.name.trim().toLocaleLowerCase(); - if (p.includes(k) || p.includes(v)) { - const reg = bsFilter.get(value.name); - if (reg) { - [...userjsCache.values()].filter(({ name, users, _mujs }) => { - const elem = _mujs.root; - if (!isElem(elem)) { - return false; - } - if (finds.has(elem)) { - return false; - } - const strArr = [{ name }, ...users]; - const v = strArr.find((o) => o.name.match(reg)); - if (v) { - return false; - } - finds.add(elem); - return true; - }); - } + const filterType = bsFilter.get(parts[1].trim().toLocaleLowerCase()); + if (filterType) { + const { reg } = filterType; + for (const { name, users, _mujs } of cacheValues) { + if ([{ name }, ...users].find((o) => o.name.match(reg))) continue; + finds.add(_mujs.root); } } } } else if (val.match(/^recommend:/)) { - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.users.find(u => u.id === authorID)) { - finds.add(elem); - } else if (goodUserJS.includes(v.url)) { - finds.add(elem); - } else if (goodUserJS.includes(v.id)) { - finds.add(elem); + for (const { url, id, users, _mujs } of cacheValues) { + if ( + users.find((u) => u.id === authorID) || + goodUserJS.includes(url) || + goodUserJS.includes(id) + ) { + finds.add(_mujs.root); } } } else { const reg = new RegExp(val, 'gi'); - for (const v of userjsCache.values()) { - const elem = v._mujs.root; - if (!isElem(elem)) { - continue; - } - if (finds.has(elem)) { - continue; - } - if (v.name && v.name.match(reg)) { - finds.add(elem); - } - if (v.description && v.description.match(reg)) { - finds.add(elem); - } - const code_data = v._mujs.code.data; - if (code_data) { - const code_obj = new ParseUserJS(code_data); - const meta = code_obj.parse_meta(code_data); - for (const key of Object.keys(meta)) { - if (/name|desc/i.test(key) && key.match(reg)) { - finds.add(elem); - } - } - } + for (const v of cacheValues) { + if (v.name && v.name.match(reg)) finds.add(v._mujs.root); + if (v.description && v.description.match(reg)) finds.add(v._mujs.root); + if (v._mujs.code.data_meta) + for (const key of Object.keys(v._mujs.code.data_meta)) + if (/name|desc/i.test(key) && key.match(reg)) finds.add(v._mujs.root); } } dom.cl.add(qsA('tr[data-engine]', tabbody), 'hidden'); @@ -3989,49 +4130,49 @@ function primaryFN() { ael(urlBar, 'change', (evt) => { evt.preventDefault(); const val = evt.target.value; - if (urlBar.placeholder === i18n$('newTab') && qs('mujs-tab.active', toolbar)) { - const tabElem = qs('mujs-tab.active', toolbar); - const tabHost = qs('mujs-host', tabElem); - if (val.startsWith(tab.protocal)) { - tab.close(tabElem); - if (tab.hasTab(val)) { - tab.active(tab.Tab.get(val)); + const tabElem = Tabs.getActive(); + if (urlBar.placeholder === i18n$('newTab') && tabElem) { + const tabHost = tabElem.firstElementChild; + const host = formatURL(normalizedHostname(val)); + if (Tabs.protoReg.test(val)) { + const createdTab = Tabs.getTab(val); + Tabs.close(tabElem); + if (createdTab) { + Tabs.active(createdTab); } else { - tab.create(val); + Tabs.create(val); } - return; - } else if (val === '*') { - tabElem.dataset.host = val; + evt.target.placeholder = i18n$('search_placeholder'); + evt.target.value = ''; + } else if (host === '*') { + tabElem.dataset.host = host; tabHost.title = ''; tabHost.textContent = ''; - MUList.host = val; + MUList.host = host; + respHandles.build(); + } else if (container.checkBlacklist(host)) { + showError(`Blacklisted "${host}"`); + } else { + tabElem.dataset.host = host; + tabHost.title = host; + tabHost.textContent = host; + MUList.host = host; respHandles.build(); - return; - } - const value = container.getHost(val); - if (container.checkBlacklist(value)) { - showError(`Blacklisted "${value}"`); - return; } - tabElem.dataset.host = value; - tabHost.title = value; - tabHost.textContent = value; - MUList.host = value; - respHandles.build(); - return; } }); scheduler.postTask(makecfg, { priority: 'background' }); respHandles.build = async () => { - const time = await scheduler.postTask(MUList.build, { priority: 'background' }); - return timeoutFrame(time); + await scheduler.postTask(MUList.build, { priority: 'background' }); + container.timeoutFrame(); }; if (cfg.autofetch) { respHandles.build(); + } else { + container.timeoutFrame(); } - dbg('Container', container); } catch (ex) { err(ex); container.remove(); @@ -4044,20 +4185,19 @@ function primaryFN() { * @param { (this: F, doc: Document) => * } onDomReady */ const loadDOM = (onDomReady) => { - if (!isFN(onDomReady)) { - return; - } - if (document.readyState === 'interactive' || document.readyState === 'complete') { - onDomReady(document); - return; + if (isFN(onDomReady)) { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + onDomReady(document); + } else { + document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { + once: true + }); + } } - document.addEventListener('DOMContentLoaded', (evt) => onDomReady(evt.target), { - once: true - }); }; -const init = async () => { - const stored = await StorageSystem.getValue('Config', DEFAULT_CONFIG); +const init = async (prefix = 'Config') => { + const stored = await StorageSystem.getValue(prefix, DEFAULT_CONFIG); cfg = { ...DEFAULT_CONFIG, ...stored @@ -4065,21 +4205,20 @@ const init = async () => { info('Config:', cfg); loadDOM((doc) => { try { - if (window.location === null) { + if (window.location === null) throw new Error('"window.location" is null, reload the webpage or use a different one', { cause: 'loadDOM' }); - } - if (doc === null) { + if (doc === null) throw new Error('"doc" is null, reload the webpage or use a different one', { cause: 'loadDOM' }); + container.redirect(); + if (cfg.autoinject) { + container.inject(primaryFN, doc); + } else { + container.timeoutFrame(); } - trustedTypes.init(); - sleazyRedirect(); - - if (cfg.autoinject) container.inject(primaryFN, doc); - Command.register(i18n$('userjs_inject'), () => { container.inject(primaryFN, doc); }); diff --git a/src/UserJS/svg/bug-svgrepo-com.svg b/src/UserJS/svg/bug-svgrepo-com.svg deleted file mode 100644 index 32b7852..0000000 --- a/src/UserJS/svg/bug-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/UserJS/svg/close-svgrepo-com.svg b/src/UserJS/svg/close-svgrepo-com.svg deleted file mode 100644 index a4a1e54..0000000 --- a/src/UserJS/svg/close-svgrepo-com.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/src/UserJS/svg/download-svgrepo-com.svg b/src/UserJS/svg/download-svgrepo-com.svg deleted file mode 100644 index 9b8cf09..0000000 --- a/src/UserJS/svg/download-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UserJS/svg/expand-full-fullscreen-svgrepo-com.svg b/src/UserJS/svg/expand-full-fullscreen-svgrepo-com.svg deleted file mode 100644 index 53218ff..0000000 --- a/src/UserJS/svg/expand-full-fullscreen-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UserJS/svg/filter-svgrepo-com.svg b/src/UserJS/svg/filter-svgrepo-com.svg deleted file mode 100644 index 8a5d29e..0000000 --- a/src/UserJS/svg/filter-svgrepo-com.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/UserJS/svg/fork-svgrepo-com.svg b/src/UserJS/svg/fork-svgrepo-com.svg deleted file mode 100644 index 8087aa8..0000000 --- a/src/UserJS/svg/fork-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UserJS/svg/fullscreen-svgrepo-com.svg b/src/UserJS/svg/fullscreen-svgrepo-com.svg deleted file mode 100644 index 15379af..0000000 --- a/src/UserJS/svg/fullscreen-svgrepo-com.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/UserJS/svg/github-svgrepo-com.svg b/src/UserJS/svg/github-svgrepo-com.svg deleted file mode 100644 index eaac757..0000000 --- a/src/UserJS/svg/github-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UserJS/svg/minus-svgrepo-com.svg b/src/UserJS/svg/minus-svgrepo-com.svg deleted file mode 100644 index 4f655c0..0000000 --- a/src/UserJS/svg/minus-svgrepo-com.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/UserJS/svg/navigation-svgrepo-com.svg b/src/UserJS/svg/navigation-svgrepo-com.svg deleted file mode 100644 index 3b865b1..0000000 --- a/src/UserJS/svg/navigation-svgrepo-com.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - ic_fluent_navigation_24_filled - Created with Sketch. - - - - - - \ No newline at end of file diff --git a/src/UserJS/svg/plus-svgrepo-com.svg b/src/UserJS/svg/plus-svgrepo-com.svg deleted file mode 100644 index 66a5b01..0000000 --- a/src/UserJS/svg/plus-svgrepo-com.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/UserJS/svg/search-svgrepo-com.svg b/src/UserJS/svg/search-svgrepo-com.svg deleted file mode 100644 index eb7f58d..0000000 --- a/src/UserJS/svg/search-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UserJS/svg/settings-svgrepo-com.svg b/src/UserJS/svg/settings-svgrepo-com.svg deleted file mode 100644 index eaeef67..0000000 --- a/src/UserJS/svg/settings-svgrepo-com.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json index ff0a09d..9810e02 100644 --- a/src/_locales/ar/messages.json +++ b/src/_locales/ar/messages.json @@ -141,12 +141,30 @@ "message": "إغلاق Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Code" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index dc12aca..4666f53 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -141,12 +141,30 @@ "message": "Userscript+ schließen" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Quelltext" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 87d600a..7a9e9d8 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -93,7 +93,7 @@ "message": "Preview Code" }, "saveFile": { - "message": "Save File" + "message": "Download" }, "newTab": { "message": "New Tab" @@ -141,12 +141,30 @@ "message": "Close Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Code" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/en_GB/messages.json b/src/_locales/en_GB/messages.json index 9ac0ef7..7a9e9d8 100644 --- a/src/_locales/en_GB/messages.json +++ b/src/_locales/en_GB/messages.json @@ -42,7 +42,7 @@ "message": "Search" }, "search_placeholder": { - "message": "Search scripts" + "message": "Search for userscripts" }, "install": { "message": "Install" @@ -66,7 +66,7 @@ "message": "Good" }, "ok": { - "message": "Ok" + "message": "Okay" }, "bad": { "message": "Bad" @@ -93,7 +93,7 @@ "message": "Preview Code" }, "saveFile": { - "message": "Save File" + "message": "Download" }, "newTab": { "message": "New Tab" @@ -141,12 +141,30 @@ "message": "Close Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Code" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index 104dae3..3091ecb 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -141,12 +141,30 @@ "message": "Cerrar Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Código" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index 6ca589f..f4d7dc4 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -141,12 +141,30 @@ "message": "Fermer Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Code" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json index 9aa3c8f..105a874 100644 --- a/src/_locales/ja/messages.json +++ b/src/_locales/ja/messages.json @@ -141,12 +141,30 @@ "message": "Userscript+ を閉じる" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "コード" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index 05da423..6d3ee8f 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -141,12 +141,30 @@ "message": "Sluit Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Code" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index 2812a5a..9313aae 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -141,12 +141,30 @@ "message": "Zamknij Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Kod" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index 6c3dcbc..f30c5cc 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -141,12 +141,30 @@ "message": "Закрыть Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "Исходный код" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/zh/messages.json b/src/_locales/zh/messages.json index 6304bae..1c40f1c 100644 --- a/src/_locales/zh/messages.json +++ b/src/_locales/zh/messages.json @@ -4,7 +4,7 @@ "description": "Must not exceed 45 characters" }, "extDesc": { - "message": "The power of Greasy Fork on the go! Finds available UserJS for the current webpage.", + "message": "为当前网页查找可用的 UserJS,随时随地发挥 Greasy Fork 的威力!", "description": "Must not exceed 132 characters" }, "extShortDesc": { @@ -141,12 +141,30 @@ "message": "关闭 Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "代码" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 6304bae..5015338 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -141,12 +141,30 @@ "message": "关闭 Userscript+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "代码" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index cba9b6d..7059ad6 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -141,12 +141,30 @@ "message": "關閉用戶腳本+" }, "userjs_sync": { - "message": "Sync with UserScript Manager" + "message": "Sync" }, "userjs_autoinject": { "message": "Inject on load" }, "auto_fetch": { "message": "Fetch on load" + }, + "code": { + "message": "代碼" + }, + "metadata": { + "message": "Metadata" + }, + "preview_metadata": { + "message": "Preview Metadata" + }, + "recommend_author": { + "message": "Recommend Author" + }, + "recommend_other": { + "message": "Recommend Others" + }, + "default_sort": { + "message": "Default Sort" } } diff --git a/src/html/background.html b/src/html/background.html deleted file mode 100644 index b268206..0000000 --- a/src/html/background.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Magic UserJS+ Background Page - - - - - - - diff --git a/src/html/form.html b/src/html/form.html deleted file mode 100644 index 4d9e1b8..0000000 --- a/src/html/form.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - Reset - Save - diff --git a/src/html/popup.html b/src/html/popup.html index 111813b..2357f06 100644 --- a/src/html/popup.html +++ b/src/html/popup.html @@ -2,56 +2,17 @@ - - - + + - + - - - - + - - - - - + >
- +
- - - diff --git a/src/html/settings.html b/src/html/settings.html deleted file mode 100644 index 335a789..0000000 --- a/src/html/settings.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - UserJS - Settings - - - - -

Settings

- - -
- -
-
-
- - - - - - diff --git a/src/img/icon_16.png b/src/img/icon_16.png index 03a9c49..950710b 100644 Binary files a/src/img/icon_16.png and b/src/img/icon_16.png differ diff --git a/src/img/icon_32.png b/src/img/icon_32.png index c4c3b59..962d9eb 100644 Binary files a/src/img/icon_32.png and b/src/img/icon_32.png differ diff --git a/src/img/icon_48.png b/src/img/icon_48.png new file mode 100644 index 0000000..ebf1fe4 Binary files /dev/null and b/src/img/icon_48.png differ diff --git a/src/img/icon_64.png b/src/img/icon_64.png new file mode 100644 index 0000000..d445fdc Binary files /dev/null and b/src/img/icon_64.png differ diff --git a/src/img/verified-svgrepo-com.svg b/src/img/verified-svgrepo-com.svg new file mode 100644 index 0000000..b5d5c48 --- /dev/null +++ b/src/img/verified-svgrepo-com.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/js/background.js b/src/js/background.js index e8c2464..82394d6 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,133 +1,306 @@ 'use strict'; + +import { webext, runtime } from './ext.js'; +import { hermes } from './messager.js'; import { log, err, info } from './logger.js'; -import { i18n$ } from './i18n.js'; -// import Config from './config.js'; import Network from './network.js'; -import { reqCode } from './request-code.js'; -import { language } from './language.js'; -import { isBlank, isNull, isEmpty, normalizeTarget, template } from './util.js'; -import { XMap } from './XMap.js'; -import storage, { storageByPrefix } from './storage.js'; - -const MUJS = { - cache: new XMap() -}; - -window.MUJS = MUJS; -window.Config = storage; -window.storageByPrefix = storageByPrefix; +import { + isBlank, + isNull, + isEmpty, + loadFilters, + isRegExp, + isObj, + formatURL, + decode +} from './util.js'; +import { BLANK_PAGE, template, builtinList } from './constants.js'; +import storage, { DEFAULT_CONFIG } from './storage.js'; +import { BaseContainer, BaseList } from './container.js'; +import { i18n$ } from './i18n.js'; /** * @type { import("../typings/types").config } */ -const cfg = {}; -/** - * @type { import("../typings/types").config } - */ -const DEFAULT_CONFIG = { - // cache: true, - codePreview: false, - // autoexpand: false, - filterlang: false, - sleazyredirect: false, - // time: 10000, - blacklist: [ - { - enabled: true, - regex: true, - flags: '', - name: 'Blacklist 1', - url: '(gov|cart|checkout|login|join|signin|signup|sign-up|password|reset|password_reset)' - }, - { - enabled: true, - regex: true, - flags: '', - name: 'Blacklist 2', - url: '(pay|bank|money|localhost|authorize|checkout|bill|wallet|router)' - }, - { - enabled: true, - regex: false, - flags: '', - name: 'Blacklist 3', - url: 'https://home.bluesnap.com' - }, - { - enabled: true, - regex: false, - flags: '', - name: 'Blacklist 4', - url: ['zalo.me', 'skrill.com'] - } - ], - engines: [ - { - enabled: true, - name: 'greasyfork', - url: 'https://greasyfork.org' - }, - { - enabled: false, - name: 'sleazyfork', - url: 'https://sleazyfork.org' - }, - { - enabled: false, - name: 'openuserjs', - url: 'https://openuserjs.org/?q=' - }, - { - enabled: false, - name: 'github', - url: 'https://api.github.com/search/code?q=', - token: '' - } - ], - theme: { - 'even-row': '', - 'odd-row': '', - 'even-err': '', - 'odd-err': '', - 'background-color': '', - 'gf-color': '', - 'sf-color': '', - 'border-b-color': '', - 'gf-btn-color': '', - 'sf-btn-color': '', - 'sf-txt-color': '', - 'txt-color': '', - 'chck-color': '', - 'chck-gf': '', - 'chck-git': '', - 'chck-open': '', - placeholder: '', - 'position-top': '', - 'position-bottom': '', - 'position-left': '', - 'position-right': '', - 'font-family': '' - }, - recommend: { - author: true, - others: true - } -}; - +let cfg = {}; const initCfg = async () => { const c = await storage.config.getMulti(); if (!c) { await storage.config.set(DEFAULT_CONFIG); } - Object.assign(cfg, c); + cfg = { + ...DEFAULT_CONFIG, + ...c + }; return cfg; }; initCfg(); -const { hermes } = userjs; +class Container extends BaseContainer { + constructor(url) { + super(url); + } + + checkBlacklist(str) { + str = str || this.host; + let blacklisted = false; + if (/accounts*\.google\./.test(str)) { + blacklisted = true; + } + const blacklist = cfg.blacklist ?? []; + for (const b of blacklist) { + if (typeof b === 'string') { + if (b.startsWith('userjs-')) { + const r = /userjs-(\w+)/.exec(b)[1]; + const biList = builtinList[r]; + if (isRegExp(biList)) { + if (!biList.test(str)) continue; + blacklisted = true; + } else if (isObj(biList) && biList.host === this.host) { + blacklisted = true; + } + } + } else if (isObj(b)) { + if (!b.enabled) { + continue; + } + if (b.regex === true) { + const reg = new RegExp(b.url, b.flags); + if (!reg.test(str)) continue; + blacklisted = true; + } + if (Array.isArray(b.url)) { + for (const c of b.url) { + if (!str.includes(c)) continue; + blacklisted = true; + } + } + if (!str.includes(b.url)) continue; + blacklisted = true; + } + } + this.isBlacklisted = blacklisted; + return this.isBlacklisted; + } +} +const container = new Container(); +// #region List +class List extends BaseList { + constructor(hostname = undefined, tabId = -2) { + super(hostname, container, cfg); + this.build = this.build.bind(this); + if (isEmpty(hostname)) hostname = BLANK_PAGE; + this.engines = cfg.engines; + this.host = hostname; + this.tabId = tabId; + } + + // #region Builder + async build() { + await initCfg(); + this.engines = cfg.engines; + const { container, blacklisted, engines, host, domain, tabId } = this; + if (blacklisted || isEmpty(engines) || host === BLANK_PAGE) { + return []; + } + const records = []; + const fetchRecords = []; + const bsFilter = loadFilters(cfg); + const hostCache = Array.from(this); + info('Building list', { hostCache, engines, container, list: this }); + try { + const g = this.groupBy(); + if (isEmpty(g)) { + const toFetch = engines.filter((engine) => !g[engine.name]); + for (const engine of toFetch) { + info(`Fetching from "${engine.name}" for "${host}"`); + const respError = (error) => { + if (!error.cause) error.cause = engine.name; + if (error.message.startsWith('429')) { + err(`Engine: "${engine.name}" Too many requests...`); + return; + } + err(`Engine: "${engine.name}"`, error.message); + }; + const _mujs = (d) => { + const obj = { + ...template, + ...d, + _mujs: { + root: {}, + info: { + tabId, + engine, + host + }, + code: { + meta: {} + } + } + }; + return obj; + }; + /** + * Prior to UserScript v7.0.0 + * @template {string} F + * @param {F} fallback + * @returns {F} + */ + const toQuery = (fallback) => { + if (engine.query) { + return decode(engine.query) + .replace(/\{host\}/g, host) + .replace(/\{domain\}/g, domain); + } + return fallback; + }; + /** + * @param { import("../typings/types.d.ts").GSFork } dataQ + */ + const forkFN = async (dataQ) => { + if (!dataQ) { + err('Invalid data received from the server, check internet connection'); + return; + } + /** + * @type { import("../typings/types.d.ts").GSForkQuery[] } + */ + const dq = Array.isArray(dataQ) ? dataQ : Array.isArray(dataQ.query) ? dataQ.query : []; + const dataA = dq + .filter(Boolean) + .filter((d) => !d.deleted) + .filter(bsFilter.match); + if (isBlank(dataA)) { + return; + } + const data = dataA.map(_mujs); + + for (const ujs of data) { + if (!ujs._mujs.code.data_code_block && (cfg.preview.code || cfg.preview.metadata)) { + await ujs._mujs.code.request(); + } + if (!container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); + } + records.push(...data); + }; + const gitFN = async (data) => { + try { + if (isBlank(data.items)) { + return; + } + for (const r of data.items) { + const ujs = _mujs({ + id: r.id ?? 0, + name: r.name, + description: isEmpty(r.description) ? i18n$('no_license') : r.description, + url: r.html_url, + code_url: r.html_url, + page_url: `${r.url}/contents/README.md`, + created_at: r.created_at, + code_updated_at: r.updated_at || Date.now(), + daily_installs: r.watchers_count ?? 0, + good_ratings: r.stargazers_count ?? 0, + users: [ + { + name: r.owner.login, + url: r.owner.html_url + } + ] + }); + if (r.license?.name) ujs.license = r.license.name; + const rootPath = r.contents_url.replace(/\{\+path\}/, ''); + const fetchContent = async (dir) => { + const contents = await Network.req(dir, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(respError); + for (const content of contents) { + if (content.type === 'file') { + if (/\.user\.js$/.test(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } else if (/\.user\.css$/.test(content.name)) { + ujs.code_urls.push({ + name: content.name, + code_url: content.download_url + }); + } + } else if (content.type === 'dir') { + await fetchContent(`${rootPath}/${content.path}`); + } + } + }; + await fetchContent(rootPath); + if (isEmpty(ujs.code_urls)) { + ujs.deleted = true; + } else if ( + !ujs._mujs.code.data_code_block && + (cfg.preview.code || cfg.preview.metadata) + ) { + await ujs._mujs.code.request(); + } + records.push(ujs); + } + } catch (ex) { + err(ex); + } + }; + let netFN; + if (/github/gi.test(engine.name)) { + if (isEmpty(engine.token)) { + err(`"${engine.name}" requires a token to use`); + continue; + } + const data = { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }; + Network.req( + `https://api.github.com/search/repositories?q=topic:userstyle+topic:${domain}`, + 'GET', + 'json', + data + ) + .then(gitFN) + .catch(respError); + netFN = Network.req( + toQuery( + `https://api.github.com/search/repositories?q=topic:userscript+topic:${domain}` + ), + 'GET', + 'json', + data + ).then(gitFN); + } else { + netFN = Network.req( + toQuery(`${engine.url}/scripts/by-site/${host}.json?language=all`) + ).then(forkFN); + } + if (netFN) fetchRecords.push(netFN.catch(respError)); + } + } + if (!isBlank(fetchRecords)) { + await Promise.allSettled(fetchRecords); + } + } catch (ex) { + err(ex); + } + return Array.from(this); + } + // #endregion +} +// #endregion const msgCache = {}; -webext.runtime.onConnect.addListener((p) => { +runtime.onConnect.addListener((p) => { hermes.port = p; /** * Default post message to send to all connected scripts @@ -138,532 +311,148 @@ webext.runtime.onConnect.addListener((p) => { log('Background Script: received message from content script', root); const r = root.msg; if (root.channel === 'Save') { - const v = isNull(r.value) ? cfg[r.prop] : r.value; - storage.config.setOne(r.prop, v); - } - if (root.channel === 'Reset') { + if (r.cfg) { + storage.config.set(r.cfg).then(initCfg); + } else { + const v = isNull(r.value) ? cfg[r.prop] : r.value; + storage.config.setOne(r.prop, v).then(initCfg); + } + } else if (root.channel === 'Reset') { storage.config.set(DEFAULT_CONFIG); + } else if (root.channel === 'Clear') { + const cache = Array.from(container).filter(({ _mujs }) => _mujs.info.host === r.host); + for (const ujs of cache) container.userjsCache.delete(ujs.id); + } else if (root.channel === 'Engine' && cfg.engines) { + const engine = cfg.engines.find((engine) => engine.name === r.engine.name); + for (const [k, v] of Object.entries(r.engine)) { + engine[k] = v; + } + storage.config.set(cfg).then(initCfg); } }); }); -// Unsupport webpages for each engine -const unsupported = { - greasyfork: ['pornhub.com'], - sleazyfork: ['pornhub.com'], - openuserjs: [], - github: [] -}; - -const formatURL = (txt) => - txt - .split('.') - .splice(-2) - .join('.') - .replace(/\/|https:/g, ''); - +const MUList = new List(); /** * @param {chrome.tabs.Tab} tab - * @returns {string} */ -const getTabUrl = (tab) => tab.pendingUrl || tab.url || ''; -// const queryOptions = { active: true, lastFocusedWindow: true }; -const queryOptions = { active: true, currentWindow: true }; - -// class Website { -// constructor(e) { -// this.engine = e.engine ?? {}; -// this.data = e.data ?? []; -// this.host = e.host ?? 'about:blank'; -// this.link = e.link ?? 'about:blank'; -// } - -// setData(v) { -// this.data = v ?? []; -// } -// } - -// class Engine { -// constructor(host, engine) { -// this.host = host ?? 'about:blank'; -// this.engine = engine ?? {}; -// this.data = []; -// } - -// setEngine(engine) { -// this.engine = engine; -// return this; -// } - -// setHost(host) { -// if (host !== this.host) { -// this.host = host; -// } -// return this; -// } - -// async getData() { -// if (isEmpty(this.host) || this.host === 'about:blank') { -// return [] -// } -// const engine = this.engine; -// /** -// * @param { import("../typings/types").GSFork } dataQ -// */ -// const forkFN = async (dataQ) => { -// if (!dataQ) { -// err('Invalid data received from the server, check internet connection'); -// return []; -// } -// /** -// * @type { import("../typings/types").GSForkQuery[] } -// */ -// const dq = Array.isArray(dataQ) ? dataQ : Array.isArray(dataQ.query) ? dataQ.query : []; -// const data = dq.filter((d) => !d.deleted); -// if (isBlank(data)) { -// return []; -// } -// const hideData = []; -// const inUserLanguage = (d) => { -// const dlocal = d.locale.split('-')[0] ?? d.locale; -// if (language.cache.includes(dlocal)) { -// return true; -// } -// hideData.push(d); -// return false; -// }; -// const filterLang = data.filter((d) => { -// if (cfg.filterlang && !inUserLanguage(d)) { -// return false; -// } -// return true; -// }); - -// let finalList = filterLang; -// const hds = []; -// for (const ujs of hideData) { -// await reqCode(ujs, true); -// if (ujs.translated) { -// hds.push(ujs); -// } -// } -// finalList = [...new Set([...hds, ...filterLang])]; - -// this.data.push(...finalList); -// return finalList; -// }; -// const gitFN = async (data) => { -// const records = []; -// try { -// if (isBlank(data.items)) { -// err('Invalid data received from the server, TODO fix this'); -// return; -// } -// for (const r of data.items) { -// const ujs = template.merge({ -// name: r.name, -// description: isEmpty(r.repository.description) -// ? i18n$('no_license') -// : r.repository.description, -// url: r.html_url, -// code_url: r.html_url.replace(/\/blob\//g, '/raw/'), -// code_updated_at: r.commit || Date.now(), -// total_installs: r.score, -// users: [ -// { -// name: r.repository.owner.login, -// url: r.repository.owner.html_url -// } -// ] -// }); -// if (cfg.codePreview && !ujs.code_data) { -// await reqCode(ujs); -// } -// records.push(ujs); -// } -// this.data.push(...records); -// } catch (ex) { -// err(ex); -// } -// return records; -// }; -// if (engine.name.includes('fork')) { -// await Network.req(`${engine.url}/scripts/by-site/${this.host}.json?language=all`) -// .then(forkFN) -// .catch(err); -// } else if (/github/gi.test(engine.name)) { -// if (isEmpty(engine.token)) { -// err(`"${engine.name}" requires a token to use`); -// return; -// } -// await Network.req( -// `${engine.url}"// ==UserScript=="+${this.host}+ "// ==/UserScript=="+in:file+language:js&per_page=30`, -// 'GET', -// 'json', -// { -// headers: { -// Accept: 'application/vnd.github+json', -// Authorization: `Bearer ${engine.token}`, -// 'X-GitHub-Api-Version': '2022-11-28' -// } -// } -// ) -// .then(gitFN) -// .catch(err); -// } -// return this.data; -// } -// } -// const eng = new Engine(); - -// const cac = new Map(); - -const webFetcher = async (loc, tabId = -2, link) => { - if (isBlank(loc)) { - return []; +const setCount = (tab, tabId) => { + let url; + try { + url = new URL(tab.url); + } catch (ex) { + err(ex); } - const host = formatURL(loc); - - let isBlacklisted = false; - for (const b of cfg.blacklist.filter((b) => b.enabled)) { - if (b.regex === true) { - const reg = new RegExp(b.url, b.flags); - if (!reg.test(host)) continue; - isBlacklisted = true; - } - if (Array.isArray(b.url)) { - for (const c of b.url) { - if (!host.includes(c)) continue; - isBlacklisted = true; - } - } - if (!host.includes(b.url)) continue; - isBlacklisted = true; - } - - // log('Blacklisted:', isBlacklisted, host); - if (isBlacklisted) { - return []; - } - - if (!MUJS.cache.has(host)) { - const engineTemplate = {}; - for (const engine of cfg.engines) { - engineTemplate[engine.name] = []; - } - MUJS.cache.set(host, engineTemplate); - } - - const isSupported = (name) => { - for (const [k, v] of Object.entries(unsupported)) { - if (k !== name) { - continue; - } - if (v.includes(host)) { - return false; - } - } - return true; - }; - const engines = cfg.engines.filter((e) => e.enabled && isSupported(e.name)); - if (isEmpty(engines)) { - return []; - } - const cache = MUJS.cache.get(host); - const sites = []; - - info('Building list', { cfg, cache, engines, allCache: MUJS.cache, sites }); - - - // for (const engine of engines) { - // if (!isEmpty(cache[`${engine.name}`])) { - // sites.push({ - // engine, - // data: normalizeTarget(cache[`${engine.name}`]), - // host, - // link - // // tab - // }); - // continue; - // } - // eng.setHost(host).setEngine(engine); - // await eng.getData(); - // } - // log(eng.data); - - for (const engine of engines) { - if (!isEmpty(cache[`${engine.name}`])) { - sites.push({ - engine, - data: normalizeTarget(cache[`${engine.name}`]), - host, - link - // tab + if (url !== undefined) { + if (!Object.is(url.origin, 'null')) { + MUList.host = formatURL(url.host); + MUList.tabId = tabId ?? tab.id ?? -2; + webext.action.setBadgeText({ + text: `${Array.from(MUList).length ?? 0}` }); - continue; + return; } - const engineArr = cache[engine.name]; - /** - * @param { import("../typings/types").GSFork } dataQ - */ - const forkFN = async (dataQ) => { - if (!dataQ) { - err('Invalid data received from the server, check internet connection'); - return []; - } - /** - * @type { import("../typings/types").GSForkQuery[] } - */ - const dq = Array.isArray(dataQ) ? dataQ : Array.isArray(dataQ.query) ? dataQ.query : []; - const data = dq.filter((d) => !d.deleted); - if (isBlank(data)) { - return []; - } - const hideData = []; - const inUserLanguage = (d) => { - const dlocal = d.locale.split('-')[0] ?? d.locale; - if (language.cache.includes(dlocal)) { - return true; - } - hideData.push(d); - return false; - }; - const filterLang = data.filter((d) => { - if (cfg.filterlang && !inUserLanguage(d)) { - return false; - } - return true; - }); - - let finalList = filterLang; - const hds = []; - for (const ujs of hideData) { - await reqCode(ujs, true); - if (ujs.translated) { - hds.push(ujs); - } - } - finalList = [...new Set([...hds, ...filterLang])]; - - // for (const ujs of finalList) { - // if (!cac.has(ujs.id)) { - // cac.set(ujs.id, { - // ...ujs, - // mujs: { - // engine, - // host, - // timestamp: Date.now(), - // } - // }) - // } - // } - - engineArr.push(...finalList); - return finalList; - }; - const gitFN = async (data) => { - const records = []; - try { - if (isBlank(data.items)) { - err('Invalid data received from the server, TODO fix this'); - return; - } - for (const r of data.items) { - const ujs = template.merge({ - name: r.name, - description: isEmpty(r.repository.description) - ? i18n$('no_license') - : r.repository.description, - url: r.html_url, - code_url: r.html_url.replace(/\/blob\//g, '/raw/'), - code_updated_at: r.commit || Date.now(), - total_installs: r.score, - users: [ - { - name: r.repository.owner.login, - url: r.repository.owner.html_url - } - ] - }); - if (cfg.codePreview && !ujs.code_data) { - await reqCode(ujs); - } - // createjs(ujs, engine.name); - records.push(ujs); - } - engineArr.push(...records); - } catch (ex) { - err(ex); - } - return records; - }; - if (engine.name.includes('fork')) { - const data = await Network.req(`${engine.url}/scripts/by-site/${host}.json?language=all`) - .then(forkFN) - .catch(err); - sites.push({ - engine, - data, - host, - link - // tab - }); - } else if (/github/gi.test(engine.name)) { - if (isEmpty(engine.token)) { - err(`"${engine.name}" requires a token to use`); - continue; - } - const data = await Network.req( - `${engine.url}"// ==UserScript=="+${host}+ "// ==/UserScript=="+in:file+language:js&per_page=30`, - 'GET', - 'json', - { - headers: { - Accept: 'application/vnd.github+json', - Authorization: `Bearer ${engine.token}`, - 'X-GitHub-Api-Version': '2022-11-28' - } - } - ) - .then(gitFN) - .catch(err); - sites.push({ - engine, - data, - host, - link - // tab - }); - } - } - if (tabId > -1) { - webext.tabs.get(tabId, updateCount); } - // log(cac); - return sites; + webext.action.setBadgeText({ + text: '0' + }); }; let requestId = -2; -// let isRedirect = false; webext.webRequest.onHeadersReceived.addListener( (e) => { if (Object.is(e.type, 'main_frame')) { - // log(e, isRedirect); - if (requestId !== e.requestId) { + if (cfg.autofetch && requestId !== e.requestId) { requestId = e.requestId; const loc = new URL(e.url); - webFetcher(loc.host, e.tabId, e.url); + MUList.host = formatURL(loc.host); + MUList.tabId = e.tabId ?? -2; + MUList.build().then(() => { + setCount(e, e.tabId); + }); } } }, { - urls: [ - // 'http://*/*', - 'https://*/*' - ] + urls: ['https://*/*'] } ); -// webext.webRequest.onBeforeRedirect.addListener( -// (e) => { -// if (Object.is(e.type, 'main_frame')) { -// log(e); -// if (requestId === e.requestId) { -// isRedirect = true; -// requestId = -2; -// } -// // if (requestId !== e.requestId) { -// // log(e); -// // requestId = e.requestId; -// // const loc = new URL(e.url); -// // webFetcher(loc.host, e.tabId); -// // } -// } -// }, -// { -// urls: [ -// // 'http://*/*', -// 'https://*/*' -// ] -// } -// ); /** * [onMessage description] - * @param {*} message - The message itself. This is a JSON-ifiable object. + * @param {object} message - The message itself. This is a JSON-ifiable object. * @param {chrome.runtime.MessageSender} sender * @param {(response: any) => void} sendResponse - A function to call, at most once, to send a response to the message. The function takes a single argument, which may be any JSON-ifiable object. This argument is passed back to the message sender. */ function onMessage(message, sender, sendResponse) { if (sender.url.includes('popup.html')) { - if (message.location) { - webFetcher(message.location).then((data) => sendResponse(data)); - } else { - webext.tabs.query(queryOptions, (tabs) => { - const tab = tabs[0]; - const loc = new URL(getTabUrl(tab)); - webFetcher(loc.host, tab.id, getTabUrl(tab)).then((data) => sendResponse(data)); - }); - } - } - - if (message.name) { - if (sender.url.includes('settings.html')) { - storage.config.setOne(message.name, message.value); - sendResponse({ - name: message.name, - value: message.value + if (message.type === 'getData') { + if (MUList.host !== message.hostname) { + MUList.host = message.hostname ?? BLANK_PAGE; + MUList.tabId = message.currentTab?.id ?? -2; + } + MUList.build().then((data) => { + if (message.init) { + sendResponse({ cfg, data }); + } else { + sendResponse({ data }); + } }); + } else if (message.type === 'save') { + if (message.cfg) { + storage.config.set(message.cfg).then(initCfg); + } else { + const v = isNull(message.value) ? cfg[message.prop] : message.value; + storage.config.setOne(message.prop, v).then(initCfg); + } + } else if (message.type === 'reset') { + storage.config.set(DEFAULT_CONFIG); + } else if (message.type === 'clear') { + const cache = Array.from(container).filter(({ _mujs }) => _mujs.info.host === message.host); + for (const ujs of cache) container.userjsCache.delete(ujs.id); + } else if (message.type === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === message.engine.name); + for (const [k, v] of Object.entries(message.engine)) { + engine[k] = v; + } + storage.config.set(cfg).then(initCfg); + } else if (message.location) { + MUList.host = formatURL(message.location); + MUList.build().then(sendResponse); } else { - sendResponse({ value: cfg[message.name] }); + sendResponse({ cfg }); } } return true; } -webext.runtime.onMessage.addListener(onMessage); +function start() { + runtime.onMessage.addListener(onMessage); -const tc = (tab) => { - const loc = new URL(tab.url); - const host = formatURL(loc.host); - return { - loc, - host, - cache: MUJS.cache.get(host) - }; -}; -const updateCount = (tab) => { - const { cache } = tc(tab); - let cnt = 0; - if (cache) { - for (const v of Object.values(cache)) { - cnt += v.length; - } - } - webext.browserAction.setBadgeText({ - text: `${cnt}` + webext.tabs.onRemoved.addListener((tabId) => { + const cache = Array.from(container.userjsCache.values()).filter(({ _mujs }) => { + return !isEmpty(_mujs) && _mujs.info.tabId === tabId; + }); + for (const ujs of cache) container.userjsCache.delete(ujs.id); }); -}; -// webext.tabs.onCreated.addListener((tab) => { -// updateCount(tab); -// }); + webext.tabs.onCreated.addListener(setCount); -webext.tabs.onActivated.addListener((activeInfo) => { - const { tabId } = activeInfo; - webext.tabs.get(tabId, updateCount); - // if (currentTab.tabId !== tabId) { - // currentTab.tabId = tabId; - // } - // log('onActivated', currentTab); -}); + webext.tabs.onActivated.addListener((activeInfo) => { + const { tabId } = activeInfo; + webext.tabs.get(tabId, setCount); + }); -webext.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { - updateCount(tab); - // if (changeInfo.status === 'complete') { - // if (currentTab.tabId !== tabId) { - // currentTab.tabId = tabId; - // currentTab.url = getTabUrl(tab); - // } - // } -}); + webext.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (tab.status === 'complete') { + setCount(tab, tabId); + } + }); + + webext.action.setBadgeText({ + text: '0' + }); +} + +start(); diff --git a/src/js/config.js b/src/js/config.js deleted file mode 100644 index 65599d1..0000000 --- a/src/js/config.js +++ /dev/null @@ -1,210 +0,0 @@ -'use strict'; - -const storage = webext.storage; -const Config = { - configLocalListeners: [], - configSyncListeners: [], - syncDefaults: { - // cache: true, - codePreview: false, - // autoexpand: false, - filterlang: false, - sleazyredirect: false, - // time: 10000, - blacklist: [ - { - enabled: true, - regex: true, - flags: '', - name: 'Blacklist 1', - url: '(gov|cart|checkout|login|join|signin|signup|sign-up|password|reset|password_reset)' - }, - { - enabled: true, - regex: true, - flags: '', - name: 'Blacklist 2', - url: '(pay|bank|money|localhost|authorize|checkout|bill|wallet|router)' - }, - { - enabled: true, - regex: false, - flags: '', - name: 'Blacklist 3', - url: 'https://home.bluesnap.com' - }, - { - enabled: true, - regex: false, - flags: '', - name: 'Blacklist 4', - url: ['zalo.me', 'skrill.com'] - } - ], - engines: [ - { - enabled: true, - name: 'greasyfork', - url: 'https://greasyfork.org' - }, - { - enabled: false, - name: 'sleazyfork', - url: 'https://sleazyfork.org' - }, - { - enabled: false, - name: 'openuserjs', - url: 'https://openuserjs.org/?q=' - }, - { - enabled: false, - name: 'github', - url: 'https://api.github.com/search/code?q=', - token: '' - } - ], - theme: { - 'even-row': '', - 'odd-row': '', - 'even-err': '', - 'odd-err': '', - 'background-color': '', - 'gf-color': '', - 'sf-color': '', - 'border-b-color': '', - 'gf-btn-color': '', - 'sf-btn-color': '', - 'sf-txt-color': '', - 'txt-color': '', - 'chck-color': '', - 'chck-gf': '', - 'chck-git': '', - 'chck-open': '', - placeholder: '', - 'position-top': '', - 'position-bottom': '', - 'position-left': '', - 'position-right': '', - 'font-family': '' - }, - recommend: { - author: true, - others: true - } - }, - cachedSyncConfig: {}, - /** - * @type { import("../typings/types").config } - */ - cachedLocalStorage: {}, - resetToDefault -}; - -function configProxy() { - storage.onChanged.addListener((changes = storage.StorageChange, areaName) => { - if (areaName === 'sync') { - for (const key in changes) { - Config.cachedSyncConfig[key] = changes[key].newValue; - } - for (const callback of Config.configSyncListeners) { - callback(changes); - } - } else if (areaName === 'local') { - for (const key in changes) { - Config.cachedLocalStorage[key] = changes[key].newValue; - } - for (const callback of Config.configLocalListeners) { - callback(changes); - } - } - }); - - const syncHandler = { - set(prop, value) { - Config.cachedSyncConfig[prop] = value; - storage.sync.set({ - [prop]: value - }); - return true; - }, - get(obj, prop) { - const data = Config.cachedSyncConfig[prop]; - return obj[prop] || data; - }, - deleteProperty(prop) { - storage.sync.remove(prop); - return true; - } - }; - - const localHandler = { - set(prop, value) { - Config.cachedLocalStorage[prop] = value; - storage.local.set({ - [prop]: value - }); - return true; - }, - get(obj, prop) { - const data = Config.cachedLocalStorage[prop]; - return obj[prop] || data; - }, - deleteProperty(prop) { - storage.local.remove(prop); - return true; - } - }; - return { - sync: new Proxy({ handler: syncHandler }, syncHandler), - local: new Proxy({ handler: localHandler }, localHandler) - }; -} - -async function fetchConfig() { - await Promise.all([ - new Promise((resolve) => { - storage.sync.get(null, function (items) { - Config.cachedSyncConfig = items; - resolve(); - }); - }), - new Promise((resolve) => { - storage.local.get(null, function (items) { - Config.cachedLocalStorage = items; - resolve(); - }); - }) - ]); -} - -async function setupConfig() { - await fetchConfig(); - for (const key in Config.syncDefaults) { - if (!Object.hasOwn(Config.cachedSyncConfig, key)) { - Config.cachedSyncConfig[key] = Config.syncDefaults[key]; - } - if (!Object.hasOwn(Config.cachedLocalStorage, key)) { - Config.cachedLocalStorage[key] = Config.syncDefaults[key]; - } - } - const config = configProxy(); - Config.config = config.sync; - Config.local = config.local; -} - -function resetToDefault() { - Config.cachedLocalStorage = Config.syncDefaults; - Config.cachedSyncConfig = Config.syncDefaults; - storage.local.set({ - ...Config.syncDefaults - }); - storage.sync.set({ - ...Config.syncDefaults - }); - return Config; -} - -setupConfig(); - -export default Config; diff --git a/src/js/constants.js b/src/js/constants.js new file mode 100644 index 0000000..a479978 --- /dev/null +++ b/src/js/constants.js @@ -0,0 +1,88 @@ +'use strict'; + +const template = { + id: 0, + bad_ratings: 0, + good_ratings: 0, + ok_ratings: 0, + daily_installs: 0, + total_installs: 0, + name: 'NOT FOUND', + description: 'NOT FOUND', + version: '0.0.0', + url: 'about:blank', + code_url: 'about:blank', + created_at: Date.now(), + code_updated_at: Date.now(), + locale: 'NOT FOUND', + deleted: false, + users: [] +}; +/** Unsupport host for search engines */ +const engineUnsupported = { + greasyfork: ['pornhub.com'], + sleazyfork: ['pornhub.com'], + openuserjs: [], + github: [] +}; +const builtinList = { + local: /localhost|router|gov|(\d+\.){3}\d+/, + finance: + /school|pay|bank|money|cart|checkout|authorize|bill|wallet|venmo|zalo|skrill|bluesnap|coin|crypto|currancy|insurance|finance/, + social: /login|join|signin|signup|sign-up|password|reset|password_reset/, + unsupported: { + host: 'fakku.net', + pathname: '/hentai/.+/read/page/.+' + } +}; +const BLANK_PAGE = 'about:blank'; +/** Lets highlight me :) */ +const authorID = 166061; +/** + * Some UserJS I personally enjoy - `https://greasyfork.org/scripts/{id}` + */ +const goodUserJS = [ + 33005, + 394820, + 438684, + 4870, + 394420, + 25068, + 483444, + 1682, + 22587, + 789, + 28497, + 386908, + 24204, + 404443, + 4336, + 368183, + 393396, + 473830, + 12179, + 423001, + 376510, + 23840, + 40525, + 6456, + 'https://openuserjs.org/install/Patabugen/Always_Remember_Me.user.js', + 'https://openuserjs.org/install/nokeya/Direct_links_out.user.js', + 'https://github.com/jijirae/y2monkey/raw/main/y2monkey.user.js', + 'https://github.com/jijirae/r2monkey/raw/main/r2monkey.user.js', + 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js', + 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', + 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' +]; +/** Remove UserJS from banned accounts */ +const badUserJS = [478597]; + +export { + authorID, + template, + engineUnsupported, + builtinList, + BLANK_PAGE, + goodUserJS, + badUserJS +} diff --git a/src/js/container.js b/src/js/container.js new file mode 100644 index 0000000..bf89591 --- /dev/null +++ b/src/js/container.js @@ -0,0 +1,140 @@ +'use strict'; + +import { isEmpty, isFN } from './util.js'; +import { BLANK_PAGE, engineUnsupported } from './constants.js'; +import { webext } from './ext.js'; + +const MUJS_ORIGIN = webext.runtime.getURL('').replace(/\/$/, ''); + +/** + * @type { import("../typings/UserJS.d.ts").Container } + */ +export class BaseContainer { + constructor() { + this.toElem = this.toElem.bind(this); + this.cache = new Map(); + this.userjsCache = new Map(); + } + + checkBlacklist() { + return true; + } + + toElem() { + return Array.from(this).map(({ _mujs }) => { + return _mujs.root; + }); + } + + *[Symbol.iterator]() { + const arr = Array.from(this.userjsCache.values()).filter(({ _mujs }) => { + return !isEmpty(_mujs) && _mujs.info.engine.enabled; + }); + for (const userjs of arr) { + yield userjs; + } + } +} + +export class BaseList { + intEngines; + /** + * @type { string | undefined } + */ + intHost; + /** + * @param {string} [hostname] + * @param {BaseContainer} [container] + * @param {import("../typings/types").config} [cfg] + */ + constructor(hostname = undefined, container = new BaseContainer(), cfg = {}) { + this.groupBy = this.groupBy.bind(this); + this.container = container; + this.intEngines = cfg.engines ?? []; + this.host = hostname; + } + + setEngines(engines = []) { + const { host } = this; + return engines.filter((e) => { + if (!e.enabled) { + return false; + } + const v = engineUnsupported[e.name] ?? []; + if (v.includes(host)) { + return false; + } + return true; + }); + } + + set engines(engines) { + this.intEngines = this.setEngines(engines); + } + + get engines() { + return this.intEngines; + } + + set host(hostname) { + if (!MUJS_ORIGIN.includes(hostname)) { + this.intHost = hostname ?? BLANK_PAGE; + } else if (isEmpty(this.intHost)) { + this.intHost = BLANK_PAGE; + } + + if (!this.container.cache.has(hostname)) { + const engineTemplate = {}; + for (const engine of this.engines) { + engineTemplate[engine.name] = []; + } + this.container.cache.set(hostname, engineTemplate); + } + this.blacklisted = this.container.checkBlacklist(hostname); + this.intEngines = this.setEngines(this.engines); + this.domain = this.getDomain(this.intHost); + } + + get host() { + return this.intHost; + } + + /** + * @template { string } S + * @param { S } str + * @returns { S | 'all-sites' } + */ + getDomain(str) { + if (str === '*') { + return 'all-sites'; + } + return str.split('.').at(-2) ?? BLANK_PAGE; + } + + groupBy() { + const arr = Array.from(this); + const callback = ({ _mujs }) => _mujs.info.engine.name; + if (isFN(Object.groupBy)) { + return Object.groupBy(arr, callback); + } + /** [Object.groupBy polyfill](https://gist.github.com/gtrabanco/7c97bd41aa74af974fa935bfb5044b6e) */ + return arr.reduce((acc = {}, ...args) => { + const key = callback(...args); + acc[key] ??= []; + acc[key].push(args[0]); + return acc; + }, {}); + } + + *[Symbol.iterator]() { + const { intHost, engines } = this; + const arr = Array.from(this.container).filter( + ({ _mujs }) => + _mujs.info.host === intHost && + engines.find((engine) => engine.enabled && engine.name === _mujs.info.engine.name) + ); + for (const userjs of arr) { + yield userjs; + } + } +} diff --git a/src/js/ext.js b/src/js/ext.js new file mode 100644 index 0000000..ebd6ec7 --- /dev/null +++ b/src/js/ext.js @@ -0,0 +1,34 @@ +export const webext = + self.browser instanceof Object && self.browser instanceof Element === false + ? self.browser + : self.chrome; +export const runtime = webext.runtime; + +/******************************************************************************/ + +// The extension's service worker can be evicted at any time, so when we +// send a message, we try a few more times when the message fails to be sent. + +export function sendMessage(msg) { + return new Promise((resolve, reject) => { + let i = 5; + const send = () => { + runtime + .sendMessage(msg) + .then((response) => { + resolve(response); + }) + .catch((reason) => { + i -= 1; + if (i <= 0) { + reject(reason); + } else { + setTimeout(send, 200); + } + }); + }; + send(); + }); +} + +/******************************************************************************/ diff --git a/src/js/i18n.js b/src/js/i18n.js index 959a1f3..c8a39a0 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -1,17 +1,37 @@ 'use strict'; -/******************************************************************************/ - const i18n = self.browser instanceof Object && self.browser instanceof Element === false ? self.browser.i18n : self.chrome.i18n; -const i18n$ = (...args) => i18n.getMessage(...args); +class i18nHandler { + /** + * @param {string | Date | number} str + */ + static toDate(str = '') { + return new Intl.DateTimeFormat(navigator.language).format( + typeof str === 'string' ? new Date(str) : str + ); + } + /** + * @param {number | bigint} number + */ + static toNumber(number) { + return new Intl.NumberFormat(navigator.language).format(number); + } + /** + * @param {...string} args + */ + static i18n$(...args) { + return i18n.getMessage(...args); + } +} -/******************************************************************************/ +const { i18n$, toDate, toNumber } = i18nHandler; +const language = { i18n$, toDate, toNumber }; -const isBackgroundProcess = document.title === 'Magic UserJS+ Background Page'; +const isBackgroundProcess = document && document.title === 'Magic UserJS+ Background Page'; if (isBackgroundProcess !== true) { // Helper to deal with the i18n'ing of HTML files. @@ -36,4 +56,4 @@ if (isBackgroundProcess !== true) { i18n.render(); } -export { i18n, i18n$ }; +export { i18n, i18n$, language }; diff --git a/src/js/language.js b/src/js/language.js deleted file mode 100644 index f1f817e..0000000 --- a/src/js/language.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -class LanguageHandler { - constructor() { - this.current = (navigator.language ?? 'en').split('-')[0] ?? 'en'; - this.cache = []; - - const languages = navigator.languages ?? []; - for (const nlang of languages) { - const lg = nlang.split('-')[0]; - if (this.cache.indexOf(lg) === -1) { - this.cache.push(lg); - } - } - - if (!this.cache.includes(this.current)) { - this.cache.push(this.current); - } - } -} - -const language = new LanguageHandler(); - -export { - language -}; \ No newline at end of file diff --git a/src/js/logger.js b/src/js/logger.js index 40c1863..cbf0e2f 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -20,11 +20,6 @@ const err = (...msg) => { 'color: rgb(249, 24, 128);', ...msg ); - for (const ex of msg) { - if (typeof ex === 'object' && 'cause' in ex && typeof alert !== 'undefined') { - alert(`[Magic Userscript+] (${ex.cause}) ${ex.message}`); - } - } }; const info = (...msg) => { console.info( diff --git a/src/js/messager.js b/src/js/messager.js new file mode 100644 index 0000000..934ab67 --- /dev/null +++ b/src/js/messager.js @@ -0,0 +1,124 @@ +'use strict'; + +import { webext } from './ext.js'; + +export const hermes = { + port: null, + portTimer: null, + portTimerDelay: 10000, + msgIdGenerator: 1, + pending: new Map(), + waitStartTime: 0, + shuttingDown: false, + shutdown: function () { + this.shuttingDown = true; + this.destroyPort(); + }, + disconnectListener: function () { + void webext.runtime.lastError; + this.port = null; + this.destroyPort(); + }, + disconnectListenerBound: null, + messageListener: function (details) { + if (typeof details !== 'object' || details === null) { + return; + } + // Response to specific message previously sent + if (details.msgId !== undefined) { + const resolver = this.pending.get(details.msgId); + if (resolver !== undefined) { + this.pending.delete(details.msgId); + resolver(details.msg); + return; + } + } + }, + messageListenerBound: null, + canDestroyPort: function () { + return this.pending.size === 0; + }, + portPoller: function () { + this.portTimer = null; + if (this.port !== null && this.canDestroyPort()) { + return this.destroyPort(); + } + this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); + this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000); + }, + portPollerBound: null, + destroyPort: function () { + if (this.portTimer !== null) { + clearTimeout(this.portTimer); + this.portTimer = null; + } + const port = this.port; + if (port !== null) { + port.disconnect(); + port.onMessage.removeListener(this.messageListenerBound); + port.onDisconnect.removeListener(this.disconnectListenerBound); + this.port = null; + } + // service pending callbacks + if (this.pending.size !== 0) { + const pending = this.pending; + this.pending = new Map(); + for (const resolver of pending.values()) { + resolver(); + } + } + }, + createPort: function () { + if (this.shuttingDown) { + return null; + } + if (this.messageListenerBound === null) { + this.messageListenerBound = this.messageListener.bind(this); + this.disconnectListenerBound = this.disconnectListener.bind(this); + this.portPollerBound = this.portPoller.bind(this); + } + try { + this.port = webext.runtime.connect({ name: 'hermes' }) || null; + // eslint-disable-next-line no-unused-vars + } catch (ex) { + this.port = null; + } + if (this.port === null) { + return null; + } + this.port.onMessage.addListener(this.messageListenerBound); + this.port.onDisconnect.addListener(this.disconnectListenerBound); + this.portTimerDelay = 10000; + if (this.portTimer === null) { + this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); + } + return this.port; + }, + getPort: function () { + return this.port ?? this.createPort(); + }, + send: function (channel, msg) { + // Too large a gap between the last request and the last response means + // the main process is no longer reachable: memory leaks and bad + // performance become a risk -- especially for long-lived, dynamic + // pages. Guard against this. + if (this.pending.size > 64) { + if (Date.now() - this.waitStartTime > 60000) { + this.shutdown(); + } + } + const port = this.getPort(); + if (port === null) { + return Promise.resolve(); + } + if (this.pending.size === 0) { + this.waitStartTime = Date.now(); + } + const msgId = this.msgIdGenerator++; + const promise = new Promise((resolve) => { + this.pending.set(msgId, resolve); + }); + port.postMessage({ channel, msgId, msg }); + return promise; + } +}; diff --git a/src/js/mu-common.js b/src/js/mu-common.js deleted file mode 100644 index a7d6863..0000000 --- a/src/js/mu-common.js +++ /dev/null @@ -1,385 +0,0 @@ -'use strict'; - -/** - * @typedef { object } userjs - */ - -// if (typeof userjs === 'undefined') { -// let userjs = (self.userjs = {}); -// } - -if (typeof userjs === 'object' && userjs.isNull instanceof Function === false) { - const win = self ?? window; - const doc = win.document; - const isMobile = /Mobile|Tablet/.test(navigator.userAgent); - /** - * Object is typeof `Element` - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isElem = (obj) => { - /** @type { string } */ - const s = Object.prototype.toString.call(obj); - return s.includes('Element'); - }; - /** - * Object is typeof `Function` - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isFN = (obj) => { - /** @type { string } */ - const s = Object.prototype.toString.call(obj); - return s.includes('Function'); - }; - /** - * Object is typeof `object` / JSON Object - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isObj = (obj) => { - /** @type { string } */ - const s = Object.prototype.toString.call(obj); - return s.includes('Object'); - }; - /** - * Object is `null` or `undefined` - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isNull = (obj) => { - return Object.is(obj, null) || Object.is(obj, undefined); - }; - /** - * Object is Blank - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isBlank = (obj) => { - return ( - (typeof obj === 'string' && Object.is(obj.trim(), '')) || - ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || - (Array.isArray(obj) && Object.is(obj.length, 0)) || - (isObj(obj) && Object.is(Object.keys(obj).length, 0)) - ); - }; - /** - * Object is Empty - * @template O - * @param { O } obj - * @returns { boolean } - */ - const isEmpty = (obj) => { - return isNull(obj) || isBlank(obj); - }; - /** - * @template T - * @param { T } target - * @param { boolean } toQuery - * @param { Element | Document | undefined } root - * @returns { T[] } - */ - const normalizeTarget = (target, toQuery = true, root) => { - if (Object.is(target, null) || Object.is(target, undefined)) { - return []; - } - if (Array.isArray(target)) { - return target; - } - if (typeof target === 'string') { - return toQuery ? Array.from((root || document).querySelectorAll(target)) : [target]; - } - if (isElem(target)) { - return [target]; - } - return Array.from(target); - }; - /** - * Add Event Listener - * @template { HTMLElement } E - * @template { keyof HTMLElementEventMap } K - * @param { E } el - * @param { K } event - * @param { (this: E, ev: HTMLElementEventMap[K]) => any } callback - * @param { boolean | AddEventListenerOptions } options - */ - const ael = (el, event, callback, options = {}) => { - try { - for (const elem of normalizeTarget(el)) { - if (!elem) { - continue; - } - if (isMobile && event === 'click') { - elem.addEventListener('touchstart', callback); - return; - } - if (event === 'fclick') { - event = 'click'; - } - elem.addEventListener(event, callback, options); - } - } catch (ex) { - console.error(ex); - } - }; - /** - * Form Attributes of Element - * @template { keyof HTMLElementTagNameMap } K - * @param { K } elem - * @param { keyof HTMLElement } attr - */ - const formAttrs = (elem, attr = {}) => { - for (const key in attr) { - if (typeof attr[key] === 'object') { - formAttrs(elem[key], attr[key]); - } else if (isFN(attr[key])) { - if (key === 'container') { - key(); - continue; - } - if (/^on/.test(key)) { - elem[key] = attr[key]; - continue; - } - ael(elem, key, attr[key]); - } else if (key === 'class') { - elem.className = attr[key]; - } else { - elem[key] = attr[key]; - } - } - }; - /** - * Make Element - * @template { keyof HTMLElementTagNameMap } K - * @param { K } tagName - * @param { string } cname - * @param { keyof HTMLElement } attrs - * @returns { HTMLElementTagNameMap[K] } - */ - const make = (tagName, cname, attrs = {}) => { - let el = null; - try { - el = document.createElement(tagName); - if (typeof cname === 'string' && !isEmpty(cname)) { - el.className = cname; - } - if (!isEmpty(attrs)) { - formAttrs(el, attrs); - } - } catch (ex) { - console.error(ex); - } - return el; - }; - /** - * setTimeout w/ Promise - * @param { number } timeout - Timeout in milliseconds (ms) - * @returns { Promise } Promise object - */ - const delay = (timeout = 5000) => new Promise((resolve) => setTimeout(resolve, timeout)); - const Network = { - /** - * Fetch a URL with fetch API as fallback - * - * When GM is supported, makes a request like XMLHttpRequest, with some special capabilities, not restricted by same-origin policy - * @link https://violentmonkey.github.io/api/gm/#gm_xmlhttprequest - * @link https://developer.mozilla.org/docs/Web/API/Fetch_API - * @param { RequestInfo | URL } url - The URL to fetch - * @param { Request['method'] } method - Fetch method - * @param { 'buffer' | 'json' | 'text' | 'blob' | 'document' } responseType - Response type - * @param { RequestInit } data - Fetch parameters - * @returns { Promise } Fetch results - */ - async req(url, method = 'GET', responseType = 'json', data = {}) { - if (isEmpty(url)) { - throw new Error('"url" parameter is empty'); - } - method = Network.bscStr(method, false); - responseType = Network.bscStr(responseType); - const params = { - method, - ...data - }; - return await new Promise((resolve, reject) => { - /** - * @param { Response } response - * @returns { Response | Document } - */ - const fetchResp = (response_1) => { - if (!response_1.ok) reject(response_1); - const check = (str_2 = 'text') => { - return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; - }; - if (responseType.match(/buffer/i)) { - resolve(check('arrayBuffer')); - } else if (responseType.match(/json/i)) { - resolve(check('json')); - } else if (responseType.match(/text/i)) { - resolve(check('text')); - } else if (responseType.match(/blob/i)) { - resolve(check('blob')); - } else if (responseType.match(/formdata/i)) { - resolve(check('formData')); - } else if (responseType.match(/clone/i)) { - resolve(check('clone')); - } else if (responseType.match(/document/i) && isFN(response_1.text)) { - const domParser = new DOMParser(); - const respTxt = response_1.text(); - if (respTxt instanceof Promise) { - respTxt.then((txt) => { - const doc = domParser.parseFromString(txt, 'text/html'); - resolve(doc); - }); - } else { - const doc = domParser.parseFromString(respTxt, 'text/html'); - resolve(doc); - } - } else { - resolve(response_1); - } - }; - fetch(url, params).then(fetchResp).catch(reject); - }); - }, - format(bytes, decimals = 2) { - if (Number.isNaN(bytes)) return '0 Bytes'; - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${Network.sizes[i]}`; - }, - sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], - /** - * @template { string } S - * @param { S } str - * @param { boolean } lowerCase - * @returns { S } - */ - bscStr(str = '', lowerCase = true) { - const txt = str[lowerCase ? 'toLowerCase' : 'toUpperCase'](); - return txt.replaceAll(/\W/g, ''); - } - }; - // const i18n$ = (...args) => { - // const arr = []; - // for (const arg of args) { - // arr.push(webext.i18n.getMessage(arg)); - // } - // return arr.length !== 1 ? arr : arr[0]; - // }; - - class Timeout { - constructor() { - this.ids = []; - } - - set(delay, reason) { - return new Promise((resolve, reject) => { - const id = setTimeout(() => { - isNull(reason) ? resolve() : reject(reason); - this.clear(id); - }, delay); - this.ids.push(id); - }); - } - - clear(...ids) { - this.ids = this.ids.filter((id) => { - if (ids.includes(id)) { - clearTimeout(id); - return false; - } - return true; - }); - } - } - class MUError extends Error { - /** - * @param {string} fnName - (Optional) Function name - * @param {...string} params - Extra error parameters - */ - constructor(fnName = 'muError', ...params) { - super(...params); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, MUError); - } else { - this.stack = new Error().stack; - } - this.fn = `[${fnName}]`; - this.name = this.constructor.name; - } - } - - userjs.error = MUError; - userjs.isMobile = isMobile; - userjs.Timeout = Timeout; - userjs.isElem = isElem; - userjs.isFN = isFN; - userjs.isObj = isObj; - userjs.isNull = isNull; - userjs.isBlank = isBlank; - userjs.isEmpty = isEmpty; - userjs.ael = ael; - userjs.formAttrs = formAttrs; - userjs.make = make; - userjs.delay = delay; - userjs.normalizeTarget = normalizeTarget; - - Object.assign(userjs, { - makeImage(imgSrc = '', attrs = {}, cname) { - const img = new Image(); - img.alt = ''; - img.referrerPolicy = 'no-referrer'; - img.src = imgSrc; - if (!isEmpty(cname)) { - img.className = cname; - } - if (!isEmpty(attrs)) { - for (const key in attrs) { - img[key] = attrs[key]; - } - } - return img; - }, - halt(e) { - e.preventDefault(); - e.stopPropagation(); - }, - injScript(text, remove = true) { - const inj = make('script', 'mu-injected', { - type: 'text/javascript', - innerHTML: text - }); - (doc.head || doc.documentElement || doc).appendChild(inj); - if (!remove) { - return inj; - } - delay(1000).then(() => inj.remove()); - }, - /** - * @param {Node} element - * @param {MutationCallback} callback - * @param {MutationObserverInit} options - */ - observe(element, callback, options = { subtree: true, childList: true }) { - const observer = new MutationObserver(callback); - callback([], observer); - observer.observe(element, options); - return observer; - }, - /** - * @param {string} url - URL of webpage to open - * @param {object} params - GM parameters - */ - openInTab(url, params = {}) { - params = Object.is(params, {}) ? '_blank' : params; - return win.open(url, params); - } - }); -} diff --git a/src/js/mu-hermes.js b/src/js/mu-hermes.js deleted file mode 100644 index 9772b05..0000000 --- a/src/js/mu-hermes.js +++ /dev/null @@ -1,126 +0,0 @@ -'use strict'; - -if (typeof userjs === 'object') { - userjs.hermes = { - port: null, - portTimer: null, - portTimerDelay: 10000, - msgIdGenerator: 1, - pending: new Map(), - waitStartTime: 0, - shuttingDown: false, - shutdown: function () { - this.shuttingDown = true; - this.destroyPort(); - }, - disconnectListener: function () { - void webext.runtime.lastError; - this.port = null; - this.destroyPort(); - }, - disconnectListenerBound: null, - messageListener: function (details) { - if (typeof details !== 'object' || details === null) { - return; - } - // Response to specific message previously sent - if (details.msgId !== undefined) { - const resolver = this.pending.get(details.msgId); - if (resolver !== undefined) { - this.pending.delete(details.msgId); - resolver(details.msg); - return; - } - } - }, - messageListenerBound: null, - canDestroyPort: function () { - return this.pending.size === 0; - }, - portPoller: function () { - this.portTimer = null; - if (this.port !== null && this.canDestroyPort()) { - return this.destroyPort(); - } - this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); - this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000); - }, - portPollerBound: null, - destroyPort: function () { - if (this.portTimer !== null) { - clearTimeout(this.portTimer); - this.portTimer = null; - } - const port = this.port; - if (port !== null) { - port.disconnect(); - port.onMessage.removeListener(this.messageListenerBound); - port.onDisconnect.removeListener(this.disconnectListenerBound); - this.port = null; - } - // service pending callbacks - if (this.pending.size !== 0) { - const pending = this.pending; - this.pending = new Map(); - for (const resolver of pending.values()) { - resolver(); - } - } - }, - createPort: function () { - if (this.shuttingDown) { - return null; - } - if (this.messageListenerBound === null) { - this.messageListenerBound = this.messageListener.bind(this); - this.disconnectListenerBound = this.disconnectListener.bind(this); - this.portPollerBound = this.portPoller.bind(this); - } - try { - this.port = webext.runtime.connect({ name: 'hermes' }) || null; - // eslint-disable-next-line no-unused-vars - } catch (ex) { - this.port = null; - } - if (this.port === null) { - return null; - } - this.port.onMessage.addListener(this.messageListenerBound); - this.port.onDisconnect.addListener(this.disconnectListenerBound); - this.portTimerDelay = 10000; - if (this.portTimer === null) { - this.portTimer = setTimeout(this.portPollerBound, this.portTimerDelay); - } - return this.port; - }, - getPort: function () { - return this.port !== null ? this.port : this.createPort(); - }, - send: function (channel, msg) { - // Too large a gap between the last request and the last response means - // the main process is no longer reachable: memory leaks and bad - // performance become a risk -- especially for long-lived, dynamic - // pages. Guard against this. - if (this.pending.size > 64) { - if (Date.now() - this.waitStartTime > 60000) { - this.shutdown(); - } - } - const port = this.getPort(); - if (port === null) { - return Promise.resolve(); - } - if (this.pending.size === 0) { - this.waitStartTime = Date.now(); - } - const msgId = this.msgIdGenerator++; - const promise = new Promise((resolve) => { - this.pending.set(msgId, resolve); - }); - port.postMessage({ channel, msgId, msg }); - return promise; - } - }; -} - -void 0; \ No newline at end of file diff --git a/src/js/network.js b/src/js/network.js index 50fc613..7a8440d 100644 --- a/src/js/network.js +++ b/src/js/network.js @@ -9,59 +9,57 @@ const Network = { if (isEmpty(url)) { throw new Error('"url" parameter is empty'); } - method = Network.bscStr(method, false); - responseType = Network.bscStr(responseType); + data = Object.assign({}, data); + method = this.bscStr(method, false); + responseType = this.bscStr(responseType); const params = { method, ...data }; - return await new Promise((resolve, reject) => { - /** - * @param { Response } response - * @returns { Response | Document } - */ - const fetchResp = (response_1) => { - if (!response_1.ok) reject(response_1); - const check = (str_2 = 'text') => { - return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; - }; - if (responseType.match(/buffer/i)) { - resolve(check('arrayBuffer')); - } else if (responseType.match(/json/i)) { - resolve(check('json')); - } else if (responseType.match(/text/i)) { - resolve(check('text')); - } else if (responseType.match(/blob/i)) { - resolve(check('blob')); - } else if (responseType.match(/formdata/i)) { - resolve(check('formData')); - } else if (responseType.match(/clone/i)) { - resolve(check('clone')); - } else if (responseType.match(/document/i) && isFN(response_1.text)) { - const domParser = new DOMParser(); - const respTxt = response_1.text(); - if (respTxt instanceof Promise) { - respTxt.then((txt) => { - const doc = domParser.parseFromString(txt, 'text/html'); + return new Promise((resolve, reject) => { + fetch(url, params) + .then((response_1) => { + if (!response_1.ok) reject(response_1); + const check = (str_2 = 'text') => { + return isFN(response_1[str_2]) ? response_1[str_2]() : response_1; + }; + if (responseType.match(/buffer/)) { + resolve(check('arrayBuffer')); + } else if (responseType.match(/json/)) { + resolve(check('json')); + } else if (responseType.match(/text/)) { + resolve(check('text')); + } else if (responseType.match(/blob/)) { + resolve(check('blob')); + } else if (responseType.match(/formdata/)) { + resolve(check('formData')); + } else if (responseType.match(/clone/)) { + resolve(check('clone')); + } else if (responseType.match(/document/)) { + const respTxt = check('text'); + const domParser = new DOMParser(); + if (respTxt instanceof Promise) { + respTxt.then((txt) => { + const doc = domParser.parseFromString(txt, 'text/html'); + resolve(doc); + }); + } else { + const doc = domParser.parseFromString(respTxt, 'text/html'); resolve(doc); - }); + } } else { - const doc = domParser.parseFromString(respTxt, 'text/html'); - resolve(doc); + resolve(response_1); } - } else { - resolve(response_1); - } - }; - fetch(url, params).then(fetchResp).catch(reject); + }) + .catch(reject); }); }, format(bytes, decimals = 2) { - if (Number.isNaN(bytes)) return '0 Bytes'; + if (Number.isNaN(bytes)) return `0 ${this.sizes[0]}`; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${Network.sizes[i]}`; + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${this.sizes[i]}`; }, sizes: ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], bscStr(str = '', lowerCase = true) { @@ -70,4 +68,4 @@ const Network = { } }; -export default Network; \ No newline at end of file +export default Network; diff --git a/src/js/popup.js b/src/js/popup.js index cde4b29..4f6febe 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1,74 +1,90 @@ 'use strict'; + +import { webext, runtime, sendMessage } from './ext.js'; import { err, log, info } from './logger.js'; -import { i18n$ } from './i18n.js'; -import { dom, normalizeTarget, qs, qsA } from './querySelector.js'; -import { reqCode, parse_meta } from './request-code.js'; -import { ael, make, isBlank, isNull, isEmpty, isObj, isString, hasOwn, openInTab } from './util.js'; -import { XMap } from './XMap.js'; +import { i18n$, language } from './i18n.js'; +import { dom, qs, qsA } from './querySelector.js'; +import { ParseUserJS } from './request-code.js'; +import Network from './network.js'; +import { + ael, + make, + isNull, + isEmpty, + isElem, + isObj, + openInTab, + strToURL, + isRegExp, + loadFilters, + formatURL, + normalizedHostname +} from './util.js'; +import { builtinList, template, BLANK_PAGE, badUserJS, authorID, goodUserJS } from './constants.js'; +import { DEFAULT_CONFIG } from './storage.js'; +import { BaseContainer, BaseList } from './container.js'; +/******************************************************************************/ /** * @type { import("../typings/types").config } */ let cfg = {}; +/******************************************************************************/ + +/** + * @type { chrome.tabs.Tab | browser.tabs.Tab } + */ +const currentTab = {}; +const tabURL = new URL(runtime.getURL('/')); -const { hermes } = userjs; +/******************************************************************************/ -// Lets highlight me :) -const authorID = 166061; -// Some UserJS I personally enjoy -const goodUserJS = [ - 423001, - 376510, - 23840, - 40525, - 6456, - 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/Manga_OnlineViewer.user.js', - 'https://github.com/jesus2099/konami-command/raw/master/INSTALL-USER-SCRIPT.user.js', - 'https://github.com/TagoDR/MangaOnlineViewer/raw/master/dist/Manga_OnlineViewer_Adult.user.js' -]; +const params = new URLSearchParams(location.search); +//#region Icon SVGs const iconSVG = { - install: { - viewBox: '0 0 16 16', - html: '' - }, - search: { - viewBox: '0 0 24 24', - html: '' - }, + close: 'fa-solid fa-xmark', + code: 'fa-solid fa-code', + collapse: 'fa-solid fa-compress', + download: 'fa-solid fa-file-arrow-down', + expand: 'fa-solid fa-arrow-up-right-from-square', // fa-expand + gear: 'fa-solid fa-gear', + github: 'fa-brands fa-github', + globe: 'fa-solid fa-globe', + install: 'fa-solid fa-download', + issue: 'fa-solid fa-bug', + minus: 'fa-solid fa-minus', + nav: 'fa-solid fa-bars', + pager: 'fa-solid fa-pager', verified: { viewBox: '0 0 56 56', fill: 'currentColor', stroke: 'currentColor', html: '' }, - refresh: { - viewBox: '0 0 1024 1024', - fill: 'currentColor', - html: '' - }, - // get(type, container) { - // return new Promise((resolve) => { - // const img = new Image(); - // img.src = webext.runtime.getURL(`/img/${type}.svg`); - // img.onload = () => { - // if (container) { - // container.appendChild(img); - // } - // resolve(img); - // } - // }) - // }, + refresh: 'fa-solid fa-rotate-right', load(type, container) { - const xmlns = 'http://www.w3.org/2000/svg'; - const svgElem = document.createElementNS(xmlns, 'svg'); - for (const [k, v] of Object.entries(iconSVG[type])) { - if (k === 'html') { - continue; + let svgElem; + if (typeof iconSVG[type] === 'string') { + svgElem = make('i', iconSVG[type], { + id: `mujs_${type ?? 'Unknown'}` + }); + } else { + svgElem = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + for (const [k, v] of Object.entries(iconSVG[type])) { + if (k === 'html') { + continue; + } + svgElem.setAttributeNS(null, k, v); + } + try { + if (typeof iconSVG[type].html === 'string') { + svgElem.innerHTML = iconSVG[type].html; + dom.attr(svgElem, 'id', `mujs_${type ?? 'Unknown'}`); + } + // eslint-disable-next-line no-unused-vars + } catch (ex) { + /* empty */ } - svgElem.setAttributeNS(null, k, v); - } - if (isString(iconSVG[type].html)) { - svgElem.innerHTML = iconSVG[type].html; } if (container) { container.appendChild(svgElem); @@ -77,78 +93,8 @@ const iconSVG = { return svgElem.outerHTML; } }; - -const main = qs('mujs-main'); -const cfgpage = qs('form'); -const footer = qs('.mujs-footer'); -const rateContainer = qs('.rate-container'); - -const table = qs('table'); -const tabbody = qs('tbody'); -const tabhead = qs('thead'); - -const urlBar = qs('.mujs-url-bar'); -const btncfg = qs('mujs-btn.settings'); -const btngreasy = qs('mujs-btn.greasy'); -const btnissue = qs('mujs-btn.issue'); -const btnhome = qs('mujs-btn.github'); - -const toolbar = qs('mujs-toolbar'); - -const promptElem = make('mujs-row', 'mujs-prompt'); -main.append(promptElem); - -const makeTHead = (rows) => { - const tr = make('tr'); - for (const r of normalizeTarget(rows)) { - const tparent = make('th', r.class ?? '', r); - tr.append(tparent); - } - tabhead.append(tr); - table.append(tabhead, tabbody); -}; -makeTHead([ - { - class: 'mujs-header-name', - textContent: i18n$('name') - }, - { - textContent: i18n$('createdby') - }, - { - textContent: i18n$('daily_installs') - }, - { - textContent: i18n$('updated') - }, - { - textContent: i18n$('install') - } -]); - -const renderTheme = (theme) => { - theme = theme || cfg.theme; - if (isEmpty(theme)) { - return; - } - // if (theme === defcfg.theme) { - // return; - // } - const sty = qs('mujs-root').style; - for (const [k, v] of Object.entries(theme)) { - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(v)) { - theme[k] = prop; - } - if (prop === v) { - continue; - } - sty.removeProperty(str); - sty.setProperty(str, v); - } -}; -const cfgMap = new XMap(); +//#endregion +const cfgMap = new Map(); const rebuildCfg = () => { for (const engine of cfg.engines) { if (cfgMap.has(engine.name)) { @@ -172,88 +118,452 @@ const rebuildCfg = () => { } } } - dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); - dom.prop(cfgMap.get('theme'), 'value', JSON.stringify(cfg.theme, null, ' ')); - renderTheme(cfg.theme); + // dom.prop(cfgMap.get('blacklist'), 'value', JSON.stringify(cfg.blacklist, null, ' ')); + for (const [k, v] of Object.entries(cfg.theme)) { + dom.prop(cfgMap.get(k), 'value', v); + } + container.renderTheme(cfg.theme); }; -const getHost = (str = '') => { - return str.split('.').splice(-2).join('.'); +const Counter = { + cnt: { + total: { + count: 0 + } + }, + set(engine) { + if (!this.cnt[engine.name]) { + const counter = make('count-frame', engine.enabled ? '' : 'hidden', { + dataset: { + counter: engine.name + }, + title: engine.query ? decodeURIComponent(engine.query) : engine.url, + textContent: '0' + }); + this.cnt[engine.name] = { + root: counter, + count: 0 + }; + return counter; + } + return this.cnt[engine.name].root; + }, + update(count, engine) { + this.cnt[engine.name].count += count; + this.cnt.total.count += count; + this.updateAll(); + }, + updateAll() { + for (const v of Object.values(this.cnt)) dom.text(v.root, v.count); + webext.action.setBadgeText({ + text: `${this.cnt.total.count}` + }); + document.title = `Magic UserJS+ (${this.cnt.total.count})`; + }, + reset() { + for (const [k, v] of Object.entries(this.cnt)) { + dom.text(v.root, 0); + v.count = 0; + const engine = cfg.engines.find((engine) => k === engine.name); + if (engine) { + dom.cl[engine.enabled ? 'remove' : 'add'](v.root, 'hidden'); + } + } + webext.action.setBadgeText({ + text: '0' + }); + document.title = 'Magic UserJS+ (0)'; + } }; -const ContainerHandler = class { - constructor() { +// #region Container +class Container extends BaseContainer { + constructor(url) { + super(url); + this.refresh = this.refresh.bind(this); this.showError = this.showError.bind(this); - // this.cleanup = this.cleanup.bind(this); - this.setHost(window.top.document.location.href); - this.cache = new XMap(); - this.userjsCache = new XMap(); + + this.webpage = strToURL(url); + this.host = this.getHost(this.webpage.host); + this.domain = this.getDomain(this.webpage.host); + this.unsaved = false; this.isBlacklisted = false; this.rebuild = false; - this.counters = { - total: 0 - }; - } + this.root = qs('mujs-root'); - setHost(link) { + this.init(); + } + // #region init + init() { try { - this.webpage = new URL(link); + this.mainbtn = make('count-frame', 'mainbtn', { + textContent: '0' + }); + this.promptElem = make('mujs-row', 'mujs-prompt'); + + this.urlBar = qs('.mujs-url-bar'); + this.rateContainer = qs('.rate-container'); + this.footer = qs('.mujs-footer'); + this.countframe = qs('.counter-container'); + this.toolbar = qs('mujs-toolbar'); + this.table = qs('table'); + this.tabhead = qs('thead'); + this.cfgpage = qs('.mujs-cfg'); + this.main = qs('mujs-main'); + this.tabbody = qs('tbody'); + this.btnframe = qs('.btn-frame'); + + this.btnfullscreen = make('mujs-btn', 'fullscreen', { + title: i18n$('max'), + innerHTML: iconSVG.load('expand'), + dataset: { + command: 'fullscreen' + } + }); + this.btnHandles = make('mujs-column', 'btn-handles'); + this.btncfg = make('mujs-btn', 'settings hidden', { + title: 'Settings', + innerHTML: iconSVG.load('gear'), + dataset: { + command: 'settings' + } + }); + this.btnhome = make('mujs-btn', 'github hidden', { + title: 'GitHub', + innerHTML: iconSVG.load('github'), + dataset: { + command: 'open-tab', + webpage: 'https://github.com/magicoflolis/Userscript-Plus' + } + }); + this.btnissue = make('mujs-btn', 'issue hidden', { + innerHTML: iconSVG.load('issue'), + title: i18n$('issue'), + dataset: { + command: 'open-tab', + webpage: 'https://github.com/magicoflolis/Userscript-Plus/issues' + } + }); + this.btngreasy = make('mujs-btn', 'greasy hidden', { + title: 'Greasy Fork', + innerHTML: iconSVG.load('globe'), + dataset: { + command: 'open-tab', + webpage: 'https://greasyfork.org/scripts/421603' + } + }); + this.btnnav = make('mujs-btn', 'nav', { + title: 'Navigation', + innerHTML: iconSVG.load('nav'), + dataset: { + command: 'navigation' + } + }); + if (params.size === 0) { + this.btnHandles.append(this.btnfullscreen); + } + + this.toolbar.append(this.btnHandles); + this.btnframe.append(this.btnhome, this.btngreasy, this.btnissue, this.btncfg, this.btnnav); + this.main.append(this.promptElem); + + const { host, refresh, cache, table } = this; + class Tabs { + /** + * @param { HTMLElement } root + */ + constructor(root) { + /** + * @type { Set } + */ + this.pool = new Set(); + this.blank = BLANK_PAGE; + this.protocal = 'mujs:'; + this.protoReg = new RegExp(`${this.protocal}(.+)`, 'i'); + this.el = { + add: make('mujs-addtab', { + textContent: '+', + dataset: { + command: 'new-tab' + } + }), + head: make('mujs-tabs'), + root + }; + this.el.head.append(this.el.add); + this.el.root.append(this.el.head); + this.custom = () => {}; + } + /** + * @param {string} hostname + */ + getTab(hostname) { + return [...this.pool].find(({ dataset }) => hostname === dataset.host); + } + getActive() { + return [...this.pool].find((tab) => tab.classList.contains('active')); + } + /** + * @param {string} hostname + */ + intFN(hostname) { + const p = this.protoReg.exec(hostname); + if (!p) { + return; + } + if (p[1] === 'settings') { + dom.cl.remove(cfgpage, 'hidden'); + dom.cl.add(table, 'hidden'); + urlBar.placeholder = 'Search settings'; + } + } + /** + * @param {HTMLElement} tab + * @param {boolean} [build] + */ + active(tab, build = true) { + if (!this.pool.has(tab)) this.pool.add(tab); + dom.cl.add([table, cfgpage], 'hidden'); + dom.cl.remove([...this.pool], 'active'); + dom.cl.add(tab, 'active'); + if (!build) { + dom.cl.remove(table, 'hidden'); + return; + } + const host = tab.dataset.host ?? this.blank; + if (host === this.blank) { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + refresh(); + } else if (host.startsWith(this.protocal)) { + this.intFN(host); + } else { + dom.cl.add(cfgpage, 'hidden'); + dom.cl.remove(table, 'hidden'); + this.custom(host); + } + } + /** @param { HTMLElement } tab */ + close(tab) { + if (this.pool.has(tab)) this.pool.delete(tab); + const host = tab.dataset.host; + if (cfg.clearTabCache && cache.has(host)) { + cache.delete(host); + sendMessage({ + type: 'clear', + host + }); + } + if (tab.classList.contains('active')) refresh(); + const sibling = tab.nextElementSibling ?? tab.previousElementSibling; + if (sibling) { + if (sibling.dataset.command !== 'new-tab') { + this.active(sibling); + } + } + tab.remove(); + } + /** + * @param {string} [hostname] + */ + create(hostname = undefined) { + if (typeof hostname === 'string') { + const createdTab = this.getTab(hostname); + if (this.protoReg.test(hostname) && createdTab) { + this.active(createdTab); + return; + } + } + const tab = make('mujs-tab', { + dataset: { + command: 'switch-tab' + }, + style: `order: ${this.el.head.childElementCount};` + }); + const tabClose = make('mu-js', { + dataset: { + command: 'close-tab' + }, + title: i18n$('close'), + textContent: 'X' + }); + const tabHost = make('mujs-host'); + const p = this.protoReg.exec(hostname); + tab.append(tabHost, tabClose); + this.el.head.append(tab); + this.active(tab, false); + if (isNull(hostname)) { + refresh(); + tab.dataset.host = this.blank; + tabHost.title = i18n$('newTab'); + tabHost.textContent = i18n$('newTab'); + } else if (p) { + tab.dataset.host = hostname || host; + tabHost.title = p[1] || tab.dataset.host; + tabHost.textContent = tabHost.title; + this.intFN(hostname); + } else { + tab.dataset.host = hostname || host; + tabHost.title = hostname || host; + tabHost.textContent = tabHost.title; + } + return tab; + } + } + this.Tabs = new Tabs(this.toolbar); + + const makeTHead = (rows = []) => { + const tr = make('tr'); + for (const r of rows) { + const tparent = make('th', r.class ?? '', r); + tr.append(tparent); + } + this.tabhead.append(tr); + this.table.append(this.tabhead, this.tabbody); + }; + makeTHead([ + { + class: 'mujs-header-name', + textContent: i18n$('name') + }, + { + textContent: i18n$('createdby') + }, + { + textContent: i18n$('daily_installs') + }, + { + textContent: i18n$('updated') + }, + { + textContent: i18n$('install') + } + ]); + + const tabbody = this.tabbody; + const getCellValue = (tr, idx) => + tr.children[idx].dataset.value || tr.children[idx].textContent; + const comparer = (idx, asc) => (a, b) => + ((v1, v2) => + v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) + ? v1 - v2 + : v1.toString().localeCompare(v2))( + getCellValue(asc ? a : b, idx), + getCellValue(asc ? b : a, idx) + ); + for (const th of this.tabhead.rows[0].cells) { + if (dom.text(th) === i18n$('install')) continue; + dom.cl.add(th, 'mujs-pointer'); + ael(th, 'click', () => { + /** [Stack Overflow Reference](https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/53880407#53880407) */ + Array.from(tabbody.querySelectorAll('tr')) + .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc))) + .forEach((tr) => tabbody.appendChild(tr)); + }); + } + return true; } catch (ex) { - err(ex, { cause: 'ContainerHandler' }); - this.webpage = window.location; + err(ex); } - this.host = getHost(this.webpage.hostname); + return false; + } + initFn() { + Counter.cnt.total.root = this.mainbtn; + for (const engine of cfg.engines) this.countframe.append(Counter.set(engine)); + } + // #endregion + setHost(link) { + this.webpage = strToURL(link); + this.host = this.getHost(this.webpage.host); + this.domain = this.getDomain(this.webpage.host); } - checkBlacklist(str) { str = str || this.host; let blacklisted = false; - for (const b of cfg.blacklist.filter((b) => b.enabled)) { - if (b.regex === true) { - const reg = new RegExp(b.url, b.flags); - if (!reg.test(str)) continue; - blacklisted = true; - } - if (Array.isArray(b.url)) { - for (const c of b.url) { - if (!str.includes(c)) continue; + if (/accounts*\.google\./.test(this.webpage.host)) { + blacklisted = true; + } + const blacklist = cfg.blacklist ?? []; + for (const b of blacklist) { + if (typeof b === 'string') { + if (b.startsWith('userjs-')) { + const r = /userjs-(\w+)/.exec(b)[1]; + const biList = builtinList[r]; + if (isRegExp(biList)) { + if (!biList.test(str)) continue; + blacklisted = true; + } else if (isObj(biList) && biList.host === this.host) { + blacklisted = true; + } + } + } else if (isObj(b)) { + if (!b.enabled) { + continue; + } + if (b.regex === true) { + const reg = new RegExp(b.url, b.flags); + if (!reg.test(str)) continue; blacklisted = true; } + if (Array.isArray(b.url)) { + for (const c of b.url) { + if (!str.includes(c)) continue; + blacklisted = true; + } + } + if (!str.includes(b.url)) continue; + blacklisted = true; } - if (!str.includes(b.url)) continue; - blacklisted = true; - } - if (blacklisted) { - this.showError('Blacklisted'); } this.isBlacklisted = blacklisted; - return blacklisted; + return this.isBlacklisted; } - - updateCounter(count, engine) { - this.counters[engine.name] += count; - this.counters.total += count; - this.updateCounters(); + getInfo(url) { + const webpage = strToURL(url || this.webpage); + const host = this.getHost(webpage.host); + const domain = this.getDomain(webpage.host); + return { + domain, + host, + webpage + }; } - - updateCounters() { - for (const [k, v] of Object.entries(this.counters)) { - if (k === 'total') { - continue; + /** + * @template { string } S + * @param { S } str + */ + getHost(str = '') { + return str.split('.').splice(-2).join('.'); + } + /** + * @template { string } S + * @param { S } str + */ + getDomain(str = '') { + return str.split('.').at(-2) ?? BLANK_PAGE; + } + renderTheme(theme) { + theme = theme || cfg.theme || DEFAULT_CONFIG.theme; + if (theme === DEFAULT_CONFIG.theme) { + return; + } + const sty = this.root.style; + for (const [k, v] of Object.entries(theme)) { + const str = `--mujs-${k}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(v)) { + theme[k] = prop; } - if (qs(`count-frame[data-counter="${k}"]`)) { - dom.text(qs(`count-frame[data-counter="${k}"]`), v); + if (prop === v) { + continue; } + sty.removeProperty(str); + sty.setProperty(str, v); } - webext.browserAction.setBadgeText({ - text: `${this.counters.total}` - }); - document.title = `Magic UserJS+ (${this.counters.total})`; } - - makePrompt(txt, dataset = {}, usePrompt = true) { - if (qs('.prompt', promptElem)) { - for (const elem of qsA('.prompt', promptElem)) { + makePrompt(txt) { + if (qs('.prompt', this.promptElem)) { + for (const elem of qsA('.prompt', this.promptElem)) { if (elem.dataset.prompt) { elem.remove(); } @@ -267,235 +577,67 @@ const ContainerHandler = class { const elHead = make('mu-js', 'prompt-head', { innerHTML: `${iconSVG.load('refresh')} ${txt}` }); - el.append(elHead); - if (usePrompt) { - const elPrompt = make('mu-js', 'prompt-body', { dataset }); - const elYes = make('mujs-btn', 'prompt-confirm', { - innerHTML: 'Confirm', - dataset: { - command: 'prompt-confirm' - } - }); - const elNo = make('mujs-btn', 'prompt-deny', { - innerHTML: 'Deny', - dataset: { - command: 'prompt-deny' - } - }); - elPrompt.append(elYes, elNo); - el.append(elPrompt); - } - promptElem.append(el); - } + const elPrompt = make('mu-js', 'prompt-body'); + const elClose = make('mujs-btn', 'prompt-deny', { + textContent: i18n$('close') + }); + ael(elClose, 'click', () => { + el.remove(); + }); + elPrompt.append(elClose); + el.append(elHead, elPrompt); + this.promptElem.append(el); + return el; + } save() { this.unsaved = false; - // MU.storage.setValue('Config', cfg); + sendMessage({ + type: 'save', + cfg + }); info('Saved:', cfg); } - + /** + * @template {string | Error} E + * @param {...E} ex + */ showError(...ex) { err(...ex); const error = make('mu-js', 'error'); let str = ''; for (const e of ex) { - str += `${isString(e) ? e : `${e.message} ${e.stack}`}\n`; - } - error.appendChild(document.createTextNode(str)); - footer.append(error); - } - - refresh() { - urlBar.placeholder = i18n$('newTab'); - this.counters.total = 0; - for (const engine of cfg.engines) { - this.counters[engine.name] = 0; - } - this.updateCounters(); - dom.prop([tabbody, rateContainer, footer], 'innerHTML', ''); - } -}; -const MUJS = new ContainerHandler(); - -class Tabs { - constructor() { - this.Tab = new XMap(); - this.blank = 'about:blank'; - this.protocal = 'mujs:'; - this.protoReg = new RegExp(`${this.protocal}(.+)`); - this.el = { - add: make('mujs-addtab', '', { - textContent: '+', - dataset: { - command: 'new-tab' - } - }), - head: make('mujs-tabs') - }; - this.el.head.append(this.el.add); - toolbar.append(this.el.head); - } - hasTab(...params) { - for (const p of params) { - if (!this.Tab.has(p)) { - return false; - } - const content = normalizeTarget(this.Tab.get(p)).filter((t) => p === t.dataset.host); - if (isBlank(content)) { - return false; - } - } - return true; - } - storeTab(host) { - const h = host ?? this.blank; - if (!this.Tab.has(h)) { - this.Tab.set(h, new Set()); - } - return this.Tab.get(h); - } - cache(host, ...tabs) { - const h = host ?? this.blank; - const tabCache = this.storeTab(h); - for (const t of normalizeTarget(tabs)) { - if (tabCache.has(t)) { - continue; - } - tabCache.add(t); - } - this.Tab.set(h, tabCache); - return tabCache; - } - mujs(host) { - if (!host.startsWith(this.protocal)) { - return; - } - const type = host.match(this.protoReg)[1]; - if (type === 'settings') { - dom.cl.remove([cfgpage], 'hidden'); - dom.cl.add(table, 'hidden'); - urlBar.placeholder = i18n$('search_placeholder'); - } - } - active(tab, build = true) { - for (const t of normalizeTarget(tab, false)) { - dom.cl.add([cfgpage], 'hidden'); - dom.cl.remove(table, 'hidden'); - dom.cl.remove(qsA('mujs-tab', this.el.head), 'active'); - dom.cl.add(t, 'active'); - if (!build) { - continue; - } - const host = t.dataset.host ?? this.blank; - if (host === this.blank) { - MUJS.refresh(); - } else if (host.startsWith(this.protocal)) { - this.mujs(host); - } else { - buildlist(host); - } - } - } - /** @param { HTMLElement } tab */ - close(tab) { - for (const t of normalizeTarget(tab, false)) { - const host = t.dataset.host; - if (MUJS.cache.has(host)) { - MUJS.cache.delete(host); - } - if (dom.cl.has(t, 'active')) { - MUJS.refresh(); - } - const sibling = t.previousElementSibling ?? t.nextElementSibling; - if (sibling) { - if (sibling.dataset.command !== 'new-tab') { - this.active(sibling); + str += `${typeof e === 'string' ? e : `${e.cause ? `[${e.cause}] ` : ''}${e.message}${e.stack ? ` ${e.stack}` : ''}`}\n`; + if (isObj(e)) { + if (e.notify) { + dom.cl.add(this.mainframe, 'error'); } } - if (this.Tab.has(host)) { - this.Tab.delete(host); - } - t.remove(); } + error.appendChild(document.createTextNode(str)); + this.footer.append(error); } - create(host = undefined) { - if (isString(host)) { - if (host.startsWith(this.protocal) && this.hasTab(host)) { - this.active(this.Tab.get(host)); - return; - } - const content = normalizeTarget(this.storeTab(host)).filter((t) => host === t.dataset.host); - if (!isEmpty(content)) { - return; - } - } - const tab = make('mujs-tab', '', { - dataset: { - command: 'switch-tab' - }, - style: `order: ${this.el.head.childElementCount};` - }); - const tabClose = make('mu-js', '', { - dataset: { - command: 'close-tab' - }, - title: i18n$('close'), - textContent: 'X' - }); - const tabHost = make('mujs-host'); - tab.append(tabHost, tabClose); - this.el.head.append(tab); - this.active(tab, false); - this.cache(host, tab); - if (isNull(host)) { - MUJS.refresh(); - // urlBar.placeholder = i18n$('newTab'); - tab.dataset.host = this.blank; - tabHost.title = i18n$('newTab'); - tabHost.textContent = i18n$('newTab'); - } else if (host.startsWith(this.protocal)) { - const type = host.match(this.protoReg)[1]; - tab.dataset.host = host || MUJS.host; - tabHost.title = type || tab.dataset.host; - tabHost.textContent = tabHost.title; - this.mujs(host); - } else { - tab.dataset.host = host || MUJS.host; - tabHost.title = host || MUJS.host; - tabHost.textContent = tabHost.title; - } - return tab; + refresh() { + this.urlBar.placeholder = i18n$('newTab'); + Counter.reset(); + dom.cl.remove(this.toElem(), 'hidden'); + dom.cl.remove(qsA('mujs-section[data-name]', this.cfgpage), 'hidden'); + dom.prop([this.tabbody, this.rateContainer, this.footer], 'innerHTML', ''); } } -const tab = new Tabs(); - -const template = { - id: 0, - bad_ratings: 0, - good_ratings: 0, - ok_ratings: 0, - daily_installs: 0, - total_installs: 0, - name: 'NOT FOUND', - description: 'NOT FOUND', - version: '0.0.0', - url: 'about:blank', - code_url: 'about:blank', - created_at: Date.now(), - code_updated_at: Date.now(), - users: [ - { - name: '', - url: '' - } - ] +const container = new Container(window.top.document.location.href); +// #endregion +const { urlBar, tabbody, cfgpage, main, Tabs, showError } = container; +container.Tabs.custom = (host) => { + MUList.host = host; + MUList.build(); }; + const mkList = (txt = '', obj = {}) => { if (!obj.root || !obj.type) { return; } const { root, type } = obj; - const list = obj.list ?? []; const appliesTo = make('mu-js', 'mujs-list', { textContent: `${txt}: ` }); @@ -508,26 +650,48 @@ const mkList = (txt = '', obj = {}) => { }); ujsURLs.append(appliesTo, applyList); root.append(ujsURLs); + + const list = obj.list ?? []; if (isEmpty(list)) { const elem = make('mujs-a', '', { textContent: i18n$('listing_none') }); applyList.append(elem); - if (type === 'antifeatures') { - dom.cl.add(ujsURLs, 'hidden'); - } + dom.cl.add(ujsURLs, 'hidden'); return; } for (const c of list) { - if (isObj(c)) { + if (typeof c === 'string' && c.startsWith('http')) { const elem = make('mujs-a', '', { - textContent: c.text + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } }); - if (c.domain) { - elem.dataset.command = 'open-tab'; - elem.dataset.webpage = `https://${c.text}`; - } applyList.append(elem); + } else if (isObj(c)) { + if (type === 'resource') { + for (const [k, v] of Object.entries(c)) { + const elem = make('mujs-a', '', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + elem.dataset.command = 'open-tab'; + elem.dataset.webpage = v; + } + applyList.append(elem); + } + } else { + const elem = make('mujs-a', '', { + textContent: c.text + }); + if (c.domain) { + elem.dataset.command = 'open-tab'; + elem.dataset.webpage = `https://${c.text}`; + } + applyList.append(elem); + } } else { const elem = make('mujs-a', '', { textContent: c @@ -535,35 +699,37 @@ const mkList = (txt = '', obj = {}) => { applyList.append(elem); } } - if (type === 'antifeatures') { - dom.cl.remove(ujsURLs, 'hidden'); - } -}; -const toLocaleDate = (str = '') => { - return new Intl.DateTimeFormat(navigator.language).format(new Date(str)); }; // #region Create UserJS +/** + * @param { import("../typings/types.d.ts").GSForkQuery } ujs + * @param { string } engine + */ const createjs = (ujs, engine) => { - for (const key in template) { - if (hasOwn(ujs, key)) { - continue; - } - ujs[key] = template[key]; - } - // Lets not add this UserJS to the list - if (ujs.id === 421603) { + const a = [ + ujs.deleted === true, + ujs.id === 421603, // Lets not add this UserJS to the list + badUserJS.includes(ujs.id), + badUserJS.includes(ujs.url) + ].find((t) => t === true); + if (a) { return; } - if (!MUJS.userjsCache.has(ujs.id)) { - MUJS.userjsCache.set(ujs.id, ujs); - } + if (!container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); const eframe = make('td', 'install-btn'); const uframe = make('td', 'mujs-uframe'); const fdaily = make('td', 'mujs-list', { - textContent: ujs.daily_installs + textContent: ujs.daily_installs, + dataset: { + name: 'daily_installs' + } }); const fupdated = make('td', 'mujs-list', { - textContent: toLocaleDate(ujs.code_updated_at) + textContent: language.toDate(ujs.code_updated_at), + dataset: { + name: 'code_updated_at', + value: new Date(ujs.code_updated_at).toISOString() + } }); const fname = make('td', 'mujs-name'); const fmore = make('mujs-column', 'mujs-list hidden', { @@ -587,16 +753,24 @@ const createjs = (ujs, engine) => { textContent: `${i18n$('version_number')}: ${ujs.version}` }); const fcreated = make('mu-js', 'mujs-list', { - textContent: `${i18n$('created_date')}: ${toLocaleDate(ujs.created_at)}` + textContent: `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`, + dataset: { + name: 'created_at', + value: new Date(ujs.created_at).toISOString() + } }); const flicense = make('mu-js', 'mujs-list', { title: ujs.license ?? i18n$('no_license'), textContent: `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`, - style: - 'text-overflow: ellipsis; overflow: hidden; white-space: nowrap; width: fit-content; max-width: 20em;' + dataset: { + name: 'license' + } }); const ftotal = make('mu-js', 'mujs-list', { - textContent: `${i18n$('total_installs')}: ${ujs.total_installs}` + textContent: `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`, + dataset: { + name: 'total_installs' + } }); const fratings = make('mu-js', 'mujs-list', { title: i18n$('ratings'), @@ -606,6 +780,7 @@ const createjs = (ujs, engine) => { title: i18n$('good'), textContent: ujs.good_ratings, dataset: { + name: 'good_ratings', el: 'good' } }); @@ -613,6 +788,7 @@ const createjs = (ujs, engine) => { title: i18n$('ok'), textContent: ujs.ok_ratings, dataset: { + name: 'ok_ratings', el: 'ok' } }); @@ -620,6 +796,7 @@ const createjs = (ujs, engine) => { title: i18n$('bad'), textContent: ujs.bad_ratings, dataset: { + name: 'bad_ratings', el: 'bad' } }); @@ -630,18 +807,16 @@ const createjs = (ujs, engine) => { command: 'list-description' } }); - const sIcon = ujs.code_url.endsWith('.user.css') ? '🧾' : ujs.code_url.endsWith('.user.js') ? '🐵' : '💾'; const scriptInstall = make('mu-jsbtn', 'install', { - // innerHTML: `${iconSVG.load('install')} ${i18n$('install')}`, - innerHTML: `${sIcon} ${i18n$('install')}`, + innerHTML: `${iconSVG.load('install')} ${i18n$('install')}`, title: `${i18n$('install')} "${ujs.name}"`, dataset: { command: 'install-script', userjs: ujs.code_url } }); - const scriptDownload = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('install')} ${i18n$('saveFile')}`, + const scriptDownload = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('download')} ${i18n$('saveFile')}`, dataset: { command: 'download-userjs', userjs: ujs.id, @@ -662,21 +837,26 @@ const createjs = (ujs, engine) => { spellcheck: false, wrap: 'soft' }); - const loadCode = make('mu-jsbtn', '', { - innerHTML: `${iconSVG.load('search')} ${i18n$('preview_code')}`, + const loadCode = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} ${i18n$('preview_code')}`, dataset: { command: 'load-userjs', userjs: ujs.id } }); - if (engine) { - tr.dataset.engine = engine; - if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { - tr.dataset.good = 'upsell'; + const loadMetadata = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('code')} Metadata`, + dataset: { + command: 'load-header', + userjs: ujs.id } + }); + tr.dataset.engine = engine; + if (!engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.url)) { + tr.dataset.good = 'upsell'; } for (const u of ujs.users) { - const user = make('mujs-a', '', { + const user = make('mujs-a', { innerHTML: u.name, title: u.url, dataset: { @@ -690,54 +870,223 @@ const createjs = (ujs, engine) => { } uframe.append(user); } - if (engine.includes('fork') && cfg.recommend.others && goodUserJS.includes(ujs.id)) { + if (cfg.recommend.others && goodUserJS.includes(ujs.id)) { tr.dataset.good = 'upsell'; } eframe.append(scriptInstall); ratings.append(fratings, fgood, fok, fbad); jsInfo.append(ftotal, ratings, fver, fcreated); - mkList('Size', { - list: ujs.code_size, - type: 'size', + mkList(i18n$('code_size'), { + list: ujs._mujs.code.code_size, + type: 'code_size', root: jsInfo }); jsInfoB.append(flicense); + const data_meta = ujs._mujs.code?.data_meta ?? {}; mkList(i18n$('antifeatures'), { - list: ujs.antifeatures, + list: data_meta.antifeatures ?? [], type: 'antifeatures', root: jsInfoB }); mkList(i18n$('applies_to'), { - list: ujs.code_match, - type: 'match-urls', + list: ujs._mujs.code?.data_names ?? [], + type: 'data_names', root: jsInfoB }); mkList('@grant', { - list: ujs.code_grant, - type: 'grants', + list: data_meta.grant ?? [], + type: 'grant', root: jsInfoB }); - fmore.append(jsInfo, jsInfoB); - fBtns.append(scriptDownload, loadCode); - fname.append(ftitle, fdesc, fmore, fBtns, codeArea); - - if (ujs.code_data) { - codeArea.value = ujs.code_data; - } + mkList('@require', { + list: data_meta.require, + type: 'require', + root: jsInfoB + }); + mkList('@resource', { + list: isNull(data_meta.resource) ? [] : [data_meta.resource], + type: 'resource', + root: jsInfoB + }); + fmore.append(jsInfo, jsInfoB); + fBtns.append(scriptDownload, loadCode, loadMetadata); + fname.append(ftitle, fdesc, fmore, fBtns, codeArea); - for (const e of [fname, uframe, fdaily, fupdated, eframe]) { - tr.append(e); - } - tabbody.append(tr); + const loadPage = make('mu-jsbtn', { + innerHTML: `${iconSVG.load('pager')} Page`, + dataset: { + command: 'load-page', + userjs: ujs.id + } + }); + fBtns.append(loadPage); + + if (ujs._mujs.code?.translated) tr.classList.add('translated'); + + for (const e of [fname, uframe, fdaily, fupdated, eframe]) tr.append(e); + ujs._mujs.root = tr; + return ujs._mujs.root; }; // #endregion +const applyTo = (ujs, name, elem, root) => { + const n = ujs._mujs.code[name] ?? ujs._mujs.code.data_meta[name]; + if (isEmpty(n)) { + const el = make('mujs-a', { + textContent: i18n$('listing_none') + }); + elem.append(el); + return; + } + dom.prop(elem, 'innerHTML', ''); + dom.cl.remove(root, 'hidden'); + if (isObj(n)) { + if (name === 'resource') { + for (const [k, v] of Object.entries(n)) { + const el = make('mujs-a', { + textContent: k ?? 'ERROR' + }); + if (v.startsWith('http')) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = v; + } + elem.append(el); + } + } else { + const el = make('mujs-a', { + textContent: n.text + }); + if (n.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${n.text}`; + } + elem.append(el); + } + } else if (typeof n === 'string') { + const el = make('mujs-a', { + textContent: n + }); + elem.append(el); + } else { + for (const c of n) { + if (typeof c === 'string' && c.startsWith('http')) { + const el = make('mujs-a', { + textContent: c, + dataset: { + command: 'open-tab', + webpage: c + } + }); + elem.append(el); + } else if (isObj(c)) { + const el = make('mujs-a', { + textContent: c.text + }); + if (c.domain) { + el.dataset.command = 'open-tab'; + el.dataset.webpage = `https://${c.text}`; + } + elem.append(el); + } else { + const el = make('mujs-a', { + textContent: c + }); + elem.append(el); + } + } + } +}; const doInstallProcess = (installLink) => { - const queryOptions = { active: true, currentWindow: true }; - webext.tabs.query(queryOptions, (tabs) => { - webext.tabs.update(tabs[0].id, { url: installLink }); - }); + if (typeof currentTab.id === 'number') { + webext.tabs.update(currentTab.id, { url: installLink }); + } +}; +const doDownloadProcess = (details) => { + if (!details.url) { + return; + } + const a = make('a'); + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.setAttribute('type', 'text/plain'); + a.dispatchEvent(new MouseEvent('click')); }; + +// #region List +class List extends BaseList { + constructor(hostname = undefined) { + super(hostname, container, cfg); + this.build = this.build.bind(this); + this.sortRecords = this.sortRecords.bind(this); + } + + // #region Builder + async build(results) { + try { + const { engines, container, host } = this; + if (isEmpty(engines)) { + for (const e of cfg.engines.filter((engine) => engine.enabled)) { + showError(`Engine: "${e.name}" unsupported on "${host}"`); + } + return; + } + if (container.checkBlacklist(host)) { + showError(`Blacklisted "${host}"`); + return; + } + container.refresh(); + if (!results) { + const { data } = await sendMessage({ + type: 'getData', + currentTab, + hostname: host + }); + results = data; + } + for (const ujs of results ?? []) { + // translate = false, code_url + ujs._mujs.code.request = async (code_url) => { + if (ujs._mujs.code.data_code_block) { + return ujs._mujs.code; + } + const p = new ParseUserJS(); + await p.request(code_url ?? ujs.code_url); + // await p.request(translate, code_url ?? ujs.code_url, ujs); + for (const [k, v] of Object.entries(p)) { + ujs._mujs.code[k] = v; + } + return ujs._mujs.code; + }; + createjs(ujs, ujs._mujs.info.engine.name); + } + urlBar.placeholder = i18n$('search_placeholder'); + urlBar.value = ''; + this.sortRecords(); + } catch (ex) { + showError(ex); + } + } + + sortRecords() { + const arr = Array.from(this); + for (const ujs of arr.flat().sort((a, b) => { + const sortType = cfg.autoSort ?? 'daily_installs'; + return b[sortType] - a[sortType]; + })) { + if (isElem(ujs._mujs.root)) tabbody.append(ujs._mujs.root); + } + for (const [name, value] of Object.entries(this.groupBy(arr))) { + Counter.update(value.length, { name }); + } + } + // #endregion +} +const MUList = new List(); +// #endregion + +container.renderTheme(); + +// #region Main event handlers ael(main, 'click', async (evt) => { try { /** @type { HTMLElement } */ @@ -748,29 +1097,56 @@ ael(main, 'click', async (evt) => { const prmpt = /prompt-/.test(target.dataset.command); let dataset = target.dataset; let cmd = dataset.command; - let prmptChoice = false; if (prmpt) { dataset = target.parentElement.dataset; cmd = dataset.command; - prmptChoice = /confirm/.test(target.dataset.command); - target.parentElement.parentElement.remove(); + if (/prompt-install/.test(target.dataset.command)) { + doInstallProcess(target.dataset.code_url); + target.parentElement.parentElement.parentElement.remove(); + } else if (/prompt-download/.test(target.dataset.command)) { + target.parentElement.parentElement.parentElement.remove(); + } else { + target.parentElement.parentElement.remove(); + } + return; } - if (cmd === 'install-script' && dataset.userjs) { - let installCode = dataset.userjs; - if (!prmpt && dataset.userjs.endsWith('.user.css')) { - MUJS.makePrompt(i18n$('prmpt_css'), dataset); + if (cmd === 'install-script') { + if (!container.userjsCache.has(+dataset.userjs)) { return; - } else if (prmpt !== prmptChoice) { - installCode = dataset.userjs.replace(/\.user\.css$/, '.user.js'); } - doInstallProcess(installCode); + const dataUserJS = container.userjsCache.get(+dataset.userjs); + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-install', + code_url: ujs.code_url + } + }); + list.append(a); + } + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + doInstallProcess(dataUserJS.code_url); + } } else if (cmd === 'open-tab' && dataset.webpage) { openInTab(dataset.webpage); + } else if (cmd === 'fullscreen') { + const tab = container.Tabs.getActive(); + openInTab(`${location.href}?host=${tab.dataset.host}`); } else if (cmd === 'navigation') { - if (dom.cl.has(btngreasy, 'hidden')) { - dom.cl.remove([btncfg, btngreasy, btnhome, btnissue], 'hidden'); - } else { - dom.cl.add([btncfg, btngreasy, btnhome, btnissue], 'hidden'); + for (const e of qsA('mujs-btn', target.parentElement)) { + if (dom.cl.has(e, 'nav')) continue; + if (dom.cl.has(e, 'hidden')) { + dom.cl.remove(e, 'hidden'); + } else { + dom.cl.add(e, 'hidden'); + } } } else if (cmd === 'list-description') { const arr = []; @@ -795,119 +1171,164 @@ ael(main, 'click', async (evt) => { } else { dom.cl.add(arr, 'hidden'); } - } else if (cmd === 'show-filter') { - dom.cl.toggle(qs('.mujs-fltlist').parentElement, 'hidden'); } else if (cmd === 'save') { if (!dom.prop(target, 'disabled')) { - MUJS.save(); - // sleazyRedirect(); - if (MUJS.rebuild) { - MUJS.cache.clear(); - buildlist(); + container.save(); + if (container.rebuild) { + container.cache.clear(); + MUList.build(); } - MUJS.unsaved = false; - MUJS.rebuild = false; + container.unsaved = false; + container.rebuild = false; } } else if (cmd === 'reset') { - // cfg = defcfg; - MUJS.unsaved = true; - MUJS.rebuild = true; + sendMessage({ type: 'reset' }); + cfg = DEFAULT_CONFIG; + container.unsaved = true; + container.rebuild = true; rebuildCfg(); } else if (cmd === 'settings') { - if (MUJS.unsaved) { - MUJS.showError('Unsaved changes'); + if (container.unsaved) { + showError('Unsaved changes'); } - tab.create('mujs:settings'); - MUJS.rebuild = false; + Tabs.create('mujs:settings'); + container.rebuild = false; } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } else if (cmd === 'switch-tab') { - tab.active(target); + Tabs.active(target); } else if (cmd === 'close-tab' && target.parentElement) { - tab.close(target.parentElement); + Tabs.close(target.parentElement); } else if (cmd === 'download-userjs') { - if (!MUJS.userjsCache.has(+dataset.userjs)) { + if (!container.userjsCache.has(+dataset.userjs)) { return; } - const dataUserJS = MUJS.userjsCache.get(+dataset.userjs); - const txt = await reqCode(dataUserJS); - if (typeof txt !== 'string') { - return; + const dataUserJS = container.userjsCache.get(+dataset.userjs); + + if (dataUserJS.code_urls.length > 1) { + const list = make('mujs-list', { + style: 'display: flex; flex-direction: column;' + }); + for (const ujs of dataUserJS.code_urls) { + const a = make('mujs-a', { + title: ujs.code_url, + textContent: ujs.name, + dataset: { + command: 'prompt-download', + code_url: ujs.code_url + } + }); + list.append(a); + } + container.makePrompt(`Multiple detected: ${list.outerHTML}`, dataset, false); + } else { + const code_obj = await dataUserJS._mujs.code.request(); + if (typeof code_obj.code === 'string') + doDownloadProcess({ + url: 'data:text/plain;charset=utf-8,' + encodeURIComponent(code_obj.code), + filename: `${dataUserJS.name}${/\.user\.css$/.test(dataUserJS.code_url) ? '.user.css' : '.user.js'}` + }); } - const makeUserJS = new Blob([txt], { type: 'text/plain' }); - const dlBtn = make('a', 'mujs_Downloader'); - dlBtn.href = URL.createObjectURL(makeUserJS); - dlBtn.download = `${dataset.userjsName ?? dataset.userjs}.user.js`; - dlBtn.click(); - URL.revokeObjectURL(dlBtn.href); - dlBtn.remove(); - } else if (cmd === 'load-userjs') { - if (!MUJS.userjsCache.has(+dataset.userjs)) { + } else if (cmd === 'load-userjs' || cmd === 'load-header') { + if (!container.userjsCache.has(+dataset.userjs)) { return; } const codeArea = qs('textarea', target.parentElement.parentElement); - if (!isEmpty(codeArea.value)) { + if (!isEmpty(codeArea.value) && cmd === codeArea.dataset.load) { dom.cl.toggle(codeArea, 'hidden'); return; } - const dataUserJS = MUJS.userjsCache.get(+dataset.userjs); - const txt = await reqCode(dataUserJS); - if (typeof txt !== 'string') { + codeArea.dataset.load = cmd; + const dataUserJS = container.userjsCache.get(+dataset.userjs); + const code_obj = await dataUserJS._mujs.code.request(); + if (typeof code_obj.data_code_block !== 'string') { + codeArea.value = 'An error occured'; return; } - codeArea.value = txt; + codeArea.value = cmd === 'load-userjs' ? code_obj.data_code_block : code_obj.data_meta_block; dom.cl.remove(codeArea, 'hidden'); - - const apTo = (name, elem) => { - if (isEmpty(dataUserJS[name])) { - const el = make('mujs-a', '', { - textContent: i18n$('listing_none') + for (const e of qsA('mujs-column[data-el="matches"]', target.parentElement.parentElement)) { + applyTo(dataUserJS, e.dataset.type, qs('.mujs-grants', e), e); + } + } else if (cmd === 'load-page') { + if (!container.userjsCache.has(+dataset.userjs)) { + return; + } + let pageArea = qs('mujs-page', target.parentElement.parentElement); + if (!pageArea) { + pageArea = make('mujs-page'); + target.parentElement.parentElement.append(pageArea); + const dataUserJS = container.userjsCache.get(+dataset.userjs); + const engine = dataUserJS._mujs.info.engine; + let pageURL; + if (engine.name.includes('fork')) { + const current = navigator.language.split('-')[0] ?? 'en'; + pageURL = dataUserJS.url.replace( + /\/scripts/, + `/${/^(zh|fr|es)/.test(current) ? navigator.language : current}/scripts` + ); + } else if (engine.name.includes('github')) { + const page_url = await Network.req(dataUserJS.page_url, 'GET', 'json', { + headers: { + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${engine.token}`, + 'X-GitHub-Api-Version': '2022-11-28' + } + }).catch(() => { + return {}; }); - elem.append(el); - if (name === 'antifeatures') { - dom.cl.add(elem, 'hidden'); + if (!page_url.download_url) { + return; } + const page = await Network.req(page_url.download_url, 'GET', 'text'); + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + innerHTML: page + }); + shadow.append(div); + return; } else { - for (const c of dataUserJS[name]) { - if (isObj(c)) { - const el = make('mujs-a', '', { - textContent: c.text - }); - if (c.domain) { - el.dataset.command = 'open-tab'; - el.dataset.webpage = `https://${c.text}`; - } - elem.append(el); - } else { - const el = make('mujs-a', '', { - textContent: c - }); - elem.append(el); + pageURL = dataUserJS.url; + } + if (!pageURL) { + return; + } + const page = await Network.req(pageURL, 'GET', 'document'); + const getContent = () => { + let content = 'An error occured'; + const h = new URL(dataUserJS.url); + const root = qs('.user-content', page.documentElement); + for (const e of qsA('[href]', root)) { + e.target = '_blank'; + e.style = 'pointer-events: auto;'; + if (e.href.startsWith('/')) { + e.href = `${h.origin}${e.href}`; } } - if (name === 'antifeatures') { - dom.cl.remove(elem, 'hidden'); + for (const e of qsA('img[src]', root)) { + e.style = + 'max-width: 25em; max-height: 25em; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;'; } - } - }; - const matchElem = qs( - '[data-type="match-urls"] > .mujs-grants', - target.parentElement.parentElement - ); - const grantElem = qs( - '[data-type="grants"] > .mujs-grants', - target.parentElement.parentElement - ); - const afElem = qs( - '[data-type="antifeatures"] > .mujs-grants', - target.parentElement.parentElement - ); - const sizeElem = qs('[data-type="size"] > .mujs-grants', target.parentElement.parentElement); - dom.prop([matchElem, grantElem, afElem, sizeElem], 'innerHTML', ''); - apTo('code_match', matchElem); - apTo('code_grant', grantElem); - apTo('antifeatures', afElem); - apTo('code_size', sizeElem); + if (root) { + content = root.innerHTML; + } else { + content = 'No additional info available'; + } + return content; + }; + const shadow = pageArea.attachShadow({ mode: 'closed' }); + const div = make('div', { + style: 'pointer-events: none;', + innerHTML: getContent() + }); + shadow.append(div); + return; + } + if (!dom.cl.has(pageArea, 'hidden')) { + dom.cl.add(pageArea, 'hidden'); + return; + } + dom.cl.remove(pageArea, 'hidden'); } else if (/export-/.test(cmd)) { const str = JSON.stringify(cmd === 'export-cfg' ? cfg : cfg.theme, null, ' '); const bytes = new TextEncoder().encode(str); @@ -936,29 +1357,28 @@ ael(main, 'click', async (evt) => { if (result.blacklist) { log(`Imported config: { ${file.name} }`, result); cfg = result; - MUJS.unsaved = true; - MUJS.rebuild = true; + container.unsaved = true; + container.rebuild = true; rebuildCfg(); - MUJS.save(); - // sleazyRedirect(); - MUJS.cache.clear(); - buildlist(); - MUJS.unsaved = false; - MUJS.rebuild = false; + container.save(); + container.cache.clear(); + MUList.build(); + container.unsaved = false; + container.rebuild = false; } else { log(`Imported theme: { ${file.name} }`, result); cfg.theme = result; - renderTheme(cfg.theme); + container.renderTheme(cfg.theme); } inpJSON.remove(); }; reader.onerror = () => { - MUJS.showError(reader.error); + showError(reader.error); inpJSON.remove(); }; }); } catch (ex) { - MUJS.showError(ex); + showError(ex); inpJSON.remove(); } } @@ -967,7 +1387,7 @@ ael(main, 'click', async (evt) => { inpJSON.click(); } } catch (ex) { - MUJS.showError(ex); + showError(ex); } }); ael(main, 'auxclick', (evt) => { @@ -982,61 +1402,179 @@ ael(main, 'auxclick', (evt) => { const dataset = target.dataset; const cmd = dataset.command; if (cmd === 'switch-tab' || cmd === 'close-tab') { - tab.close(target); + Tabs.close(target); } else if (cmd === 'new-tab') { - tab.create(); + Tabs.create(); } }); -renderTheme(cfg.theme); -const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent; -const comparer = (idx, asc) => (a, b) => - ((v1, v2) => - v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2))( - getCellValue(asc ? a : b, idx), - getCellValue(asc ? b : a, idx) - ); -for (const th of tabhead.rows[0].cells) { - if (dom.text(th) === i18n$('install')) continue; - dom.cl.add(th, 'mujs-pointer'); - ael(th, 'click', () => { - /** [Stack Overflow Reference](https://stackoverflow.com/questions/14267781/sorting-html-table-with-javascript/53880407#53880407) */ - Array.from(tabbody.querySelectorAll('tr')) - .sort(comparer(Array.from(th.parentNode.children).indexOf(th), (this.asc = !this.asc))) - .forEach((tr) => tabbody.appendChild(tr)); - }); -} +ael(main, 'updateditem', (evt) => { + /** + * @type {import("../typings/types.d.ts").GSForkQuery} + */ + const ujs = evt.detail; + if (!ujs._mujs) return; + for (const elem of qsA('[data-name]', ujs._mujs.root)) { + const name = elem.dataset.name; + if (name === 'code') { + if (ujs._mujs.code.data_code_block) { + if (cfg.preview.code && !cfg.preview.metadata) { + elem.value = ujs._mujs.code.data_code_block; + } else if (cfg.preview.metadata && !cfg.preview.code) { + elem.value = ujs._mujs.code.data_meta_block; + } else { + elem.value = `${ujs._mujs.code.META_START_COMMENT}${ujs._mujs.code.data_meta_block}${ujs._mujs.code.META_END_COMMENT}${ujs._mujs.code.data_code_block}`; + } + } + continue; + } + if (!ujs[name]) continue; + if (name === 'license') { + dom.attr(elem, 'title', ujs.license ?? i18n$('no_license')); + dom.text(elem, `${i18n$('license')}: ${ujs.license ?? i18n$('no_license')}`); + } else if (name === 'code_updated_at') { + dom.text(elem, language.toDate(ujs.code_updated_at)); + elem.dataset.value = new Date(ujs.code_updated_at).toISOString(); + } else if (name === 'created_date') { + dom.text(elem, `${i18n$('created_date')}: ${language.toDate(ujs.created_at)}`); + elem.dataset.value = new Date(ujs.created_at).toISOString(); + } else if (name === 'total_installs') { + dom.text(elem, `${i18n$('total_installs')}: ${language.toNumber(ujs.total_installs)}`); + } else { + dom.text(elem, ujs[name]); + } + } + if (ujs._mujs.code.data_code_block) { + for (const e of qsA('mujs-column[data-el="matches"]', ujs._mujs.root)) { + applyTo(ujs, e.dataset.type, qs('.mujs-grants', e), e); + } + } + if (container.userjsCache.has(ujs.id)) container.userjsCache.set(ujs.id, ujs); +}); ael(urlBar, 'input', (evt) => { evt.preventDefault(); if (urlBar.placeholder === i18n$('newTab')) { return; } + /** + * @type { string } + */ const val = evt.target.value; + const section = qsA('mujs-section[data-name]', cfgpage); if (isEmpty(val)) { - dom.cl.remove(qsA('tr[data-engine]', tabbody), 'hidden'); + dom.cl.remove(container.toElem(), 'hidden'); + dom.cl.remove(section, 'hidden'); return; } - const reg = new RegExp(val, 'gi'); const finds = new Set(); - const userjsCache = MUJS.userjsCache; - for (const [k, v] of userjsCache) { - const elem = qs(`tr[data-script-id="${k}"]`, tabbody); - if (!elem) { - continue; + if (!dom.cl.has(cfgpage, 'hidden')) { + const reg = new RegExp(val, 'gi'); + for (const elem of section) { + if (!isElem(elem)) { + continue; + } + if (finds.has(elem)) { + continue; + } + if (dom.text(elem).match(reg)) { + finds.add(elem); + } } - if (finds.has(elem)) { - continue; + dom.cl.add(section, 'hidden'); + dom.cl.remove([...finds], 'hidden'); + return; + } + const cacheValues = Array.from(container).filter(({ _mujs }) => { + return !finds.has(_mujs.root); + }); + /** + * @param {RegExpMatchArray} regExp + * @param {keyof import("../typings/types.d.ts").GSForkQuery} key + */ + const ezQuery = (regExp, key) => { + const q_value = val.replace(regExp, ''); + const reg = new RegExp(q_value, 'gi'); + for (const v of cacheValues) { + let k = v[key]; + if (typeof k === 'number') { + k = `${v[key]}`; + } + if (k && k.match(reg)) { + finds.add(v._mujs.root); + } + } + }; + if (val.match(/^(code_url|url):/)) { + ezQuery(/^(code_url|url):/, 'code_url'); + } else if (val.match(/^(author|users?):/)) { + const parts = /^[\w_]+:(.+)/.exec(val); + if (parts) { + const reg = new RegExp(parts[1], 'gi'); + for (const v of cacheValues.filter((v) => !isEmpty(v.users))) { + for (const user of v.users) { + for (const value of Object.values(user)) { + if (typeof value === 'string' && value.match(reg)) { + finds.add(v._mujs.root); + } else if (typeof value === 'number' && `${value}`.match(reg)) { + finds.add(v._mujs.root); + } + } + } + } + } + } else if (val.match(/^(locale|i18n):/)) { + ezQuery(/^(locale|i18n):/, 'locale'); + } else if (val.match(/^id:/)) { + ezQuery(/^id:/, 'id'); + } else if (val.match(/^license:/)) { + ezQuery(/^license:/, 'license'); + } else if (val.match(/^name:/)) { + ezQuery(/^name:/, 'name'); + } else if (val.match(/^description:/)) { + ezQuery(/^description:/, 'description'); + } else if (val.match(/^(search_engine|engine):/)) { + const parts = /^[\w_]+:(\w+)/.exec(val); + if (parts) { + const reg = new RegExp(parts[1], 'gi'); + for (const { _mujs } of cacheValues) { + if (!_mujs.info.engine.name.match(reg)) { + continue; + } + finds.add(_mujs.root); + } } - if (v.name && v.name.match(reg)) { - finds.add(elem); + } else if (val.match(/^filter:/)) { + const parts = /^\w+:(.+)/.exec(val); + if (parts) { + const bsFilter = loadFilters(cfg); + const filterType = bsFilter.get(parts[1].trim().toLocaleLowerCase()); + if (filterType) { + const { reg } = filterType; + for (const { name, users, _mujs } of cacheValues) { + if ([{ name }, ...users].find((o) => o.name.match(reg))) { + continue; + } + finds.add(_mujs.root); + } + } } - if (v.description && v.description.match(reg)) { - finds.add(elem); + } else if (val.match(/^recommend:/)) { + for (const { url, id, users, _mujs } of cacheValues) { + if ( + users.find((u) => u.id === authorID) || + goodUserJS.includes(url) || + goodUserJS.includes(id) + ) { + finds.add(_mujs.root); + } } - if (v.code_data) { - const meta = parse_meta(v.code_data); - for (const key of Object.keys(meta)) { - if (/name|desc/i.test(key) && key.match(reg)) { - finds.add(elem); + } else { + const reg = new RegExp(val, 'gi'); + for (const v of cacheValues) { + if (v.name && v.name.match(reg)) finds.add(v._mujs.root); + if (v.description && v.description.match(reg)) finds.add(v._mujs.root); + if (v._mujs.code.data_meta) { + for (const key of Object.keys(v._mujs.code.data_meta)) { + if (/name|desc/i.test(key) && key.match(reg)) finds.add(v._mujs.root); } } } @@ -1047,356 +1585,543 @@ ael(urlBar, 'input', (evt) => { ael(urlBar, 'change', (evt) => { evt.preventDefault(); const val = evt.target.value; - if (urlBar.placeholder === i18n$('newTab') && qs('mujs-tab.active', toolbar)) { - const tabElem = qs('mujs-tab.active', toolbar); - const tabHost = qs('mujs-host', tabElem); - if (val.startsWith('mujs:')) { - tab.close(tabElem); - if (tab.hasTab(val)) { - tab.active(tab.Tab.get(val)); + const tabElem = Tabs.getActive(); + if (urlBar.placeholder === i18n$('newTab') && tabElem) { + const tabHost = tabElem.firstElementChild; + const host = formatURL(normalizedHostname(val)); + if (Tabs.protoReg.test(val)) { + const createdTab = Tabs.getTab(val); + Tabs.close(tabElem); + if (createdTab) { + Tabs.active(createdTab); } else { - tab.create(val); + Tabs.create(val); } - return; - } else if (val === '*') { - tabElem.dataset.host = val; + evt.target.placeholder = i18n$('search_placeholder'); + evt.target.value = ''; + } else if (host === '*') { + tabElem.dataset.host = host; tabHost.title = ''; tabHost.textContent = ''; - buildlist(val); - return; - } - const value = getHost(val); - if (MUJS.checkBlacklist(value)) { - MUJS.showError(`Host blacklisted "${value}"`); - return; + MUList.host = host; + MUList.build(); + } else if (container.checkBlacklist(host)) { + showError(`Blacklisted "${host}"`); + } else { + tabElem.dataset.host = host; + tabHost.title = host; + tabHost.textContent = host; + MUList.host = host; + MUList.build(); } - tabElem.dataset.host = value; - tabHost.title = value; - tabHost.textContent = value; - buildlist(value); - return; } }); +// #endregion -async function buildlist(host = undefined) { - try { - if (isEmpty(host)) { - host = MUJS.host; - } - if (MUJS.checkBlacklist(host)) { - return; - } - if (!qs(`mujs-tab[data-host="${host}"]`)) { - tab.create(host); - } - MUJS.refresh(); - webext.runtime.sendMessage( - { - location: host - }, - (data) => { - if (isEmpty(data)) { - return; - } - for (const tabData of data) { - const { engine, host } = tabData; - if (!qs(`mujs-tab[data-host="${host}"]`)) { - tab.create(host); - } - for (const ujs of tabData.data) { - createjs(ujs, engine.name); - } - MUJS.updateCounter(tabData.data.length, engine); - } - urlBar.placeholder = i18n$('search_placeholder'); - urlBar.value = ''; - } - ); - } catch (ex) { - err(ex); - } -} +Tabs.custom = (host) => { + MUList.host = host; + MUList.build(); +}; -//#region Make Config +// #region Make Config const makecfg = () => { - const exBtn = make('mu-js', 'mujs-sty-flex'); - const exportCFG = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_config'), - dataset: { - command: 'export-cfg' - } - }); - const importCFG = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_config'), - dataset: { - command: 'import-cfg' - } - }); - const exportTheme = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_theme'), + const cbtn = make('mu-js', 'mujs-sty-flex'); + const savebtn = make('mujs-btn', 'save', { + textContent: i18n$('save'), dataset: { - command: 'export-theme' - } + command: 'save' + }, + disabled: false }); - const importTheme = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_theme'), + const resetbtn = make('mujs-btn', 'reset', { + textContent: i18n$('reset'), dataset: { - command: 'import-theme' + command: 'reset' } }); - exBtn.append(importCFG, importTheme, exportCFG, exportTheme); - cfgpage.append(exBtn); + cbtn.append(resetbtn, savebtn); - const makerow = (desc, type = null, nm, attrs = {}) => { - desc = desc ?? i18n$('no_license'); - nm = nm ?? i18n$('no_license'); - const sec = make('mujs-section', 'mujs-cfg-section', { - style: nm === 'cache' ? 'display: none;' : '' + const makesection = (name, tag) => { + tag = tag ?? i18n$('no_license'); + name = name ?? i18n$('no_license'); + const sec = make('mujs-section', { + dataset: { + name: tag + } + }); + const lb = make('label', { + dataset: { + command: tag + } }); - const lb = make('label'); - const divDesc = make('mu-js', 'mujs-cfg-desc', { - textContent: desc + const divDesc = make('mu-js', { + textContent: name }); + ael(sec, 'click', (evt) => { + /** @type { HTMLElement } */ + const target = evt.target.closest('[data-command]'); + if (!target) { + return; + } + const cmd = target.dataset.command; + if (cmd === tag) { + const a = qsA(`[data-${tag}]`, sec); + if (dom.cl.has(a, 'hidden')) { + dom.cl.remove(a, 'hidden'); + } else { + dom.cl.add(a, 'hidden'); + } + } + }); + lb.append(divDesc); sec.append(lb); cfgpage.append(sec); - if (isNull(type)) { + return sec; + }; + const sections = { + general: makesection('General', 'general'), + load: makesection('Automation', 'load'), + list: makesection('List', 'list'), + filters: makesection('List Filters', 'filters'), + blacklist: makesection('Blacklist (WIP)', 'blacklist'), + engine: makesection('Search Engines', 'engine'), + theme: makesection('Theme Colors', 'theme'), + exp: makesection('Import / Export', 'exp') + }; + const makeRow = (text, value, type = 'checkbox', tag = 'general', attrs = {}) => { + const lb = make('label', 'sub-section hidden', { + textContent: text, + dataset: { + [tag]: text + } + }); + cfgMap.set(text, value); + if (type === 'select') { + const inp = make('select', { + dataset: { + [tag]: text + }, + ...attrs + }); + for (const selV of Object.keys(template)) { + if (selV === 'deleted' || selV === 'users') continue; + const o = make('option', { + value: selV, + textContent: selV + }); + inp.append(o); + } + inp.value = cfg[value]; + lb.append(inp); + if (sections[tag]) { + sections[tag].append(lb); + } return lb; } - const inp = make('input', 'mujs-cfg-input', { + const inp = make('input', { type, dataset: { - name: nm + [tag]: text }, ...attrs }); - if (!cfgMap.has(nm)) { - cfgMap.set(nm, inp); + + if (tag === 'engine') { + inp.dataset.name = value; } + + if (sections[tag]) { + sections[tag].append(lb); + } + if (type === 'checkbox') { const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { + const la = make('label', { onclick() { inp.dispatchEvent(new MouseEvent('click')); } }); inlab.append(inp, la); lb.append(inlab); - if (nm.includes('-')) { - return inp; - } - if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { - for (const i of cfg.engines) { - if (i.name !== nm) continue; - inp.checked = i.enabled; - inp.dataset.engine = i.name; + + const nm = /^(\w+)-(.+)/.exec(value); + if (nm) { + if (nm[1] === 'filters') { + inp.checked = cfg[nm[1]][nm[2]].enabled; + } else { + inp.checked = cfg[nm[1]][nm[2]]; + } + } else { + inp.checked = cfg[value]; + } + ael(inp, 'change', (evt) => { + container.unsaved = true; + if (/filterlang/i.test(value)) { + container.rebuild = true; + } + if (nm) { + if (nm[1] === 'filters') { + cfg[nm[1]][nm[2]].enabled = evt.target.checked; + } else { + cfg[nm[1]][nm[2]] = evt.target.checked; + } + } else { + cfg[value] = evt.target.checked; + } + sendMessage({ + type: 'save', + cfg + }); + }); + + if (tag === 'engine') { + const engine = cfg.engines.find((engine) => engine.name === value); + if (engine) { + inp.checked = engine.enabled; + inp.dataset.engine = engine.name; ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - // MUJS.rebuild = true; - i.enabled = evt.target.checked; - hermes.send('Save', { - prop: 'engines', - value: cfg.engines + container.unsaved = true; + container.rebuild = true; + engine.enabled = evt.target.checked; + sendMessage({ + type: 'engine', + engine }); }); + + if (engine.query) { + const d = DEFAULT_CONFIG.engines.find((e) => e.name === engine.name); + const urlInp = make('input', { + type: 'text', + defaultValue: '', + value: decodeURIComponent(engine.query) ?? '', + placeholder: decodeURIComponent(d.query) ?? '', + dataset: { + name: nm, + engine: engine.name + }, + onchange(evt) { + container.unsaved = true; + container.rebuild = true; + try { + engine.query = encodeURIComponent(new URL(evt.target.value).toString()); + sendMessage({ + type: 'engine', + engine + }); + } catch (ex) { + err(ex); + } + } + }); + lb.append(urlInp); + } + if (engine.name === 'github') { + const ghToken = make('input', { + type: 'text', + defaultValue: '', + value: engine.token ?? '', + placeholder: 'Paste Access Token', + dataset: { + engine: 'github-token' + }, + onchange(evt) { + container.unsaved = true; + container.rebuild = true; + engine.token = evt.target.value; + sendMessage({ + type: 'engine', + engine + }); + } + }); + lb.append(ghToken); + cfgMap.set('github-token', ghToken); + } } - } else { - inp.checked = cfg[nm]; - ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - // if (/filterlang/i.test(nm)) { - // MUJS.rebuild = true; - // } - cfg[nm] = evt.target.checked; - hermes.send('Save', { - prop: nm, - value: cfg[nm] - }); - }); } } else { + if (type === 'text') { + inp.defaultValue = ''; + inp.value = value ?? ''; + inp.placeholder = value ?? ''; + } + lb.append(inp); } - return inp; + + return lb; }; - makerow(i18n$('redirect'), 'checkbox', 'sleazyredirect'); - makerow(i18n$('filter'), 'checkbox', 'filterlang'); - makerow(i18n$('preview_code'), 'checkbox', 'codePreview'); - for (const inp of [ - makerow('Recommend author', 'checkbox', 'recommend-author'), - makerow('Recommend scripts', 'checkbox', 'recommend-others') - ]) { - const nm = inp.dataset.name === 'recommend-author' ? 'author' : 'others'; - inp.checked = cfg.recommend[nm]; - ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - cfg.recommend[nm] = evt.target.checked; - hermes.send('Save', { - prop: 'recommend', - value: cfg.recommend - }); + makeRow(i18n$('redirect'), 'sleazyredirect'); + + makeRow(i18n$('auto_fetch'), 'autofetch', 'checkbox', 'load'); + makeRow('Clear on Tab close', 'clearTabCache', 'checkbox', 'load'); + + makeRow(i18n$('default_sort'), 'autoSort', 'select', 'list'); + makeRow(i18n$('filter'), 'filterlang', 'checkbox', 'list'); + makeRow(i18n$('preview_code'), 'preview-code', 'checkbox', 'list'); + makeRow(i18n$('preview_metadata'), 'preview-metadata', 'checkbox', 'list'); + makeRow(i18n$('recommend_author'), 'recommend-author', 'checkbox', 'list'); + makeRow(i18n$('recommend_others'), 'recommend-others', 'checkbox', 'list'); + + for (const [k, v] of Object.entries(cfg.filters)) { + makeRow(v.name, `filters-${k}`, 'checkbox', 'filters'); + } + + makeRow('Greasy Fork', 'greasyfork', 'checkbox', 'engine'); + makeRow('Sleazy Fork', 'sleazyfork', 'checkbox', 'engine'); + // makeRow('Open UserJS', 'openuserjs', 'checkbox', 'engine'); + makeRow('GitHub API', 'github', 'checkbox', 'engine'); + + for (const [k, v] of Object.entries(cfg.theme)) { + const lb = make('label', 'hidden', { + textContent: k, + dataset: { + theme: k + } }); + const inp = make('input', { + type: 'text', + defaultValue: '', + value: v ?? '', + placeholder: v ?? '', + dataset: { + theme: k + }, + onchange(evt) { + let isvalid = true; + try { + const val = evt.target.value; + const sty = container.root.style; + const str = `--mujs-${k}`; + const prop = sty.getPropertyValue(str); + if (isEmpty(val)) { + cfg.theme[k] = DEFAULT_CONFIG.theme[k]; + sty.removeProperty(str); + return; + } + if (prop === val) { + return; + } + sty.removeProperty(str); + sty.setProperty(str, val); + cfg.theme[k] = val; + sendMessage({ + type: 'save', + cfg + }); + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } + } + } + }); + cfgMap.set(k, inp); + lb.append(inp); + sections.theme.append(lb); } - makerow('Greasy Fork', 'checkbox', 'greasyfork'); - makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); - makerow('Open UserJS', 'checkbox', 'openuserjs'); - makerow('GitHub API', 'checkbox', 'github'); - const ghAPI = cfg.engines.find((c) => c.name === 'github'); - const ghToken = makerow('GitHub API (Token)', 'text', 'github', { - defaultValue: '', - value: ghAPI.token ?? '', - placeholder: 'Paste Access Token', - onchange(evt) { - MUJS.unsaved = true; - MUJS.rebuild = true; - ghAPI.token = evt.target.value; - hermes.send('Save', { - prop: 'engines', - value: cfg.engines - }); + const createList = (key, v = '', disabled = false, type = 'String') => { + let txt = key; + if (typeof key === 'string') { + if (key.startsWith('userjs-')) { + disabled = true; + const s = key.substring(7); + txt = `Built-in "${s}"`; + v = builtinList[s]; + } + } else { + if (!key.enabled) { + return; + } } - }); - ghToken.dataset.engine = 'github-token'; - cfgMap.set('github-token', ghToken); - const cbtn = make('mu-js', 'mujs-sty-flex'); - const savebtn = make('mujs-btn', 'save', { - textContent: i18n$('save'), - dataset: { - command: 'save' - }, - disabled: false - }); - const resetbtn = make('mujs-btn', 'reset', { - textContent: i18n$('reset'), - dataset: { - command: 'reset' + if (isRegExp(v)) { + v = v.toString(); + type = 'RegExp'; + } else { + v = JSON.stringify(v); + type = 'Object'; } - }); - const blacklist = make('textarea', '', { - dataset: { - name: 'blacklist' - }, - rows: '10', - autocomplete: false, - spellcheck: false, - wrap: 'soft', - value: JSON.stringify(cfg.blacklist, null, ' '), - oninput(evt) { - let isvalid = true; - try { - cfg.blacklist = JSON.parse(evt.target.value); - isvalid = true; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } + + const lb = make('label', 'hidden', { + textContent: txt, + dataset: { + blacklist: key } - } - }); - const theme = make('textarea', '', { - dataset: { - name: 'theme' - }, - rows: '10', - autocomplete: false, - spellcheck: false, - wrap: 'soft', - value: JSON.stringify(cfg.theme, null, ' '), - oninput(evt) { - let isvalid = true; - try { - cfg.theme = JSON.parse(evt.target.value); - isvalid = true; - renderTheme(JSON.parse(evt.target.value)); - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); + }); + const inp = make('input', { + type: 'text', + defaultValue: '', + value: v ?? '', + placeholder: v ?? '', + dataset: { + blacklist: key + }, + onchange(evt) { + let isvalid = true; + try { + const val = evt.target.value; + if (isEmpty(val)) { + return; + } + isvalid = true; + } catch (ex) { + err(ex); + isvalid = false; + } finally { + if (isvalid) { + dom.cl.remove(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', false); + } else { + dom.cl.add(evt.target, 'mujs-invalid'); + dom.prop(savebtn, 'disabled', true); + } } } + }); + const selType = make('select', { + disabled, + dataset: { + blacklist: key + } + }); + if (disabled) { + inp.readOnly = true; + const o = make('option', { + value: type, + textContent: type + }); + selType.append(o); + } else { + for (const selV of ['String', 'RegExp', 'Object']) { + const o = make('option', { + value: selV, + textContent: selV + }); + selType.append(o); + } } - }); - cfgMap.set('blacklist', blacklist); - cfgMap.set('theme', theme); - cbtn.append(resetbtn, savebtn); - cfgpage.append(blacklist, theme, cbtn); -}; -//#endregion - -const locSearch = location.search; + selType.value = type; + lb.append(inp, selType); + sections.blacklist.append(lb); + }; + for (const key of cfg.blacklist) { + createList(key); + } -const p = hermes.getPort(); -p.onMessage.addListener((root = {}) => { - const m = root.msg; - if (root.channel === 'Config' && isEmpty(cfg)) { - cfg = m.cfg || cfg; - for (const engine of cfg.engines) { - MUJS.counters[engine.name] = 0; - if (!engine.enabled) { - continue; - } - const counter = make('count-frame', '', { + const transfers = { + export: { + cfg: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_config'), dataset: { - counter: engine.name - }, - title: engine.url, - textContent: '0' - }); - qs('.counter-container').append(counter); + command: 'export-cfg', + exp: 'export-cfg' + } + }), + theme: make('mujs-btn', 'mujs-export sub-section hidden', { + textContent: i18n$('export_theme'), + dataset: { + command: 'export-theme', + exp: 'export-theme' + } + }) + }, + import: { + cfg: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_config'), + dataset: { + command: 'import-cfg', + exp: 'import-cfg' + } + }), + theme: make('mujs-btn', 'mujs-import sub-section hidden', { + textContent: i18n$('import_theme'), + dataset: { + command: 'import-theme', + exp: 'import-theme' + } + }) } - makecfg(); - if (/mujs=/.test(locSearch)) { - if (/settings/.test(locSearch)) { - tab.create('mujs:settings'); - } + }; + for (const value of Object.values(transfers)) { + for (const v of Object.values(value)) { + sections.exp.append(v); } } -}); -function primaryFN(data) { + cfgpage.append(cbtn); +}; +// #endregion + +async function init() { + const [tab] = await webext.tabs.query({ + active: true, + currentWindow: true + }); + const data = []; + if (tab instanceof Object === false) { + return true; + } + Object.assign(currentTab, tab); + let url; try { - if (isEmpty(data)) { - tab.create(); - return; - } - for (const tabData of data) { - const { engine, host } = tabData; - if (MUJS.checkBlacklist(host)) { - continue; - } - if (!qs(`mujs-tab[data-host="${host}"]`)) { - tab.create(host); - if (tabData.link) { - MUJS.setHost(tabData.link); - } + url = new URL(currentTab.url); + tabURL.href = url.href || ''; + } catch (ex) { + err(ex); + } + + if (url !== undefined) { + const hostname = formatURL(normalizedHostname(tabURL.hostname)); + const response = await sendMessage({ + init: true, + type: 'getData', + currentTab, + hostname + }); + if (response instanceof Object) { + Object.assign(cfg, response.cfg ?? DEFAULT_CONFIG); + if (Array.isArray(response.data)) { + data.push(...response.data); } - if (tabData.data) { - for (const ujs of tabData.data) { - createjs(ujs, engine.name); + container.initFn(); + makecfg(); + MUList.cfg = cfg; + MUList.engines = cfg.engines; + if (params.has('mujs', 'settings')) { + Tabs.create('mujs:settings'); + } else if (params.has('host')) { + const host = params.get('host'); + Tabs.create(host); + if (!host.startsWith(Tabs.protocal)) { + MUList.host = host; + MUList.build(data); } - MUJS.updateCounter(tabData.data.length, engine); + } else if (!url.origin.startsWith('https://')) { + Tabs.create(); + } else { + Tabs.create(hostname); + MUList.host = hostname; + MUList.build(data); } } - urlBar.placeholder = i18n$('search_placeholder'); - urlBar.value = ''; - } catch (ex) { - err(ex); } } -if (!/mujs=/.test(locSearch)) { - webext.runtime.sendMessage({}, primaryFN); +async function tryInit() { + try { + await init(); + } catch { + setTimeout(tryInit, 100); + } } + +tryInit(); diff --git a/src/js/punycode.js b/src/js/punycode.js new file mode 100644 index 0000000..a7f990d --- /dev/null +++ b/src/js/punycode.js @@ -0,0 +1,493 @@ +/*! https://mths.be/punycode v1.3.2 by @mathias */ +export default (function() { + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * http://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.3.2', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + return punycode; +})(); diff --git a/src/js/querySelector.js b/src/js/querySelector.js index a2e5376..3a0dc90 100644 --- a/src/js/querySelector.js +++ b/src/js/querySelector.js @@ -57,7 +57,7 @@ const SafeAnimationFrame = class { } }; /** - * @type { import("../typings/types").isElem } + * @type { import("../typings/types.d.ts").isElem } */ const isElem = (obj) => { /** @type { string } */ @@ -65,7 +65,7 @@ const isElem = (obj) => { return s.includes('Element'); }; /** - * @type { import("../typings/types").normalizeTarget } + * @type { import("../typings/types.d.ts").normalizeTarget } */ const normalizeTarget = (target, toQuery = true, root) => { if (Object.is(target, null) || Object.is(target, undefined)) { @@ -320,7 +320,7 @@ dom.cl = class { /******************************************************************************/ /** - * @type { import("../typings/types").qsA } + * @type { import("../typings/types.d.ts").qsA } */ const qsA = (selectors, root) => { try { @@ -331,7 +331,7 @@ const qsA = (selectors, root) => { return []; }; /** - * @type { import("../typings/types").qs } + * @type { import("../typings/types.d.ts").qs } */ const qs = (selector, root) => { try { @@ -343,7 +343,7 @@ const qs = (selector, root) => { }; /** - * @type { import("../typings/WebExt").query } + * @type { import("../typings/WebExt.d.ts").query } */ const query = async (selector, root) => { let el = null; diff --git a/src/js/request-code.js b/src/js/request-code.js index e4fac3e..e0bdf5a 100644 --- a/src/js/request-code.js +++ b/src/js/request-code.js @@ -1,11 +1,9 @@ 'use strict'; +import { err } from './logger.js'; import Network from './network.js'; -import { intersect, isNull, isEmpty, normalizeTarget } from './util.js'; -import { language } from './language.js'; +import { isNull, isBlank, isEmpty, union } from './util.js'; -const META_START_COMMENT = '// ==UserScript=='; -const META_END_COMMENT = '// ==/UserScript=='; const TLD_EXPANSION = ['com', 'net', 'org', 'de', 'co.uk']; const APPLIES_TO_ALL_PATTERNS = [ 'http://*', @@ -19,237 +17,288 @@ const APPLIES_TO_ALL_PATTERNS = [ '*://*/*', 'http*' ]; -/** - * @param { string } code - */ -const get_meta_block = (code) => { - if (isEmpty(code)) { - return null; - } - const start_block = code.indexOf(META_START_COMMENT); - if (isNull(start_block)) { - return null; - } - const end_block = code.indexOf(META_END_COMMENT, start_block); - if (isNull(end_block)) { - return null; - } - return code.substring(start_block + META_START_COMMENT.length, end_block); -}; -/** - * @param { string } code - */ -const parse_meta = (code) => { - if (isEmpty(code)) { - return null; - } - const meta = {}; - const meta_block = get_meta_block(code); - const meta_block_map = new Map(); - for (const meta_line of meta_block.split('\n')) { - const meta_match = meta_line.match(/\/\/\s+@([a-zA-Z:-]+)\s+(.*)/); - if (isNull(meta_match)) { - continue; - } - const key = meta_match[1].trim(); - const value = meta_match[2].trim(); - if (!meta_block_map.has(key)) { - meta_block_map.set(key, []); - } - const meta_map = meta_block_map.get(key); - meta_map.push(value); - meta_block_map.set(key, meta_map); +class ParseUserJS { + /** + * @type { string } + */ + code; + /** + * @type { string } + */ + data_meta_block; + /** + * @type { string } + */ + data_code_block; + /** + * @type { { [meta: string]: string | string[] | { [resource: string]: string } } } + */ + data_meta; + /** + * @type { {text: string;domain: boolean;tld_extra: boolean}[] } + */ + data_names; + constructor(code, isUserCSS) { + this.isUserCSS = isUserCSS ?? false; + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + if (code) { + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); + } } - for (const [key, value] of meta_block_map) { - if (value.length > 1) { - meta[key] = value; - } else { - meta[key] = value[0]; + get_meta_block() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_meta_block) { + return this.data_meta_block; + } + const start_block = this.code.indexOf(this.META_START_COMMENT); + if (isNull(start_block)) { + return null; + } + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); + if (isNull(end_block)) { + return null; } + const meta_block = this.code.substring(start_block + this.META_START_COMMENT.length, end_block); + this.data_meta_block = meta_block; + return this.data_meta_block; } - return meta; -}; -/** - * @template { string } S - * @param { S } code - * @returns { S[] } - */ -const calculate_applies_to_names = (code) => { - if (isEmpty(code)) { - return null; + get_code_block() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_code_block) { + return this.data_code_block; + } + const start_block = this.code.indexOf(this.META_START_COMMENT); + if (isNull(start_block)) { + return null; + } + const end_block = this.code.indexOf(this.META_END_COMMENT, start_block); + if (isNull(end_block)) { + return null; + } + const code_block = this.code.substring( + end_block + this.META_END_COMMENT.length, + this.code.length + ); + this.data_code_block = code_block + .split('\n') + .filter((l) => !isEmpty(l)) + .join('\n'); + return this.data_code_block; } - const meta = parse_meta(code); - let patterns = []; - for (const [k, v] of Object.entries(meta)) { - if (/include|match/.test(k)) { - if (Array.isArray(v)) { - patterns = patterns.concat(v); + parse_meta() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_meta) { + return this.data_meta; + } + const meta = {}; + const meta_block_map = new Map(); + for (const meta_line of this.get_meta_block().split('\n')) { + const meta_match = /\/\/\s+@([a-zA-Z:-]+)\s+(.*)/.exec(meta_line); + if (!meta_match) { + continue; + } + const key = meta_match[1].trim(); + const value = meta_match[2].trim(); + if (!meta_block_map.has(key)) { + meta_block_map.set(key, []); + } + const meta_map = meta_block_map.get(key); + meta_map.push(value); + meta_block_map.set(key, meta_map); + } + for (const [key, value] of meta_block_map) { + if (value.length > 1) { + meta[key] = value; } else { - patterns = patterns.concat([v]); + meta[key] = value[0]; } } + this.data_meta = meta; + return this.data_meta; } - if (isEmpty(patterns)) { - return []; - } - if (intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { - return []; - } - const name_set = new Set(); - const addObj = (obj) => { - if (name_set.has(obj)) { - return; - } - name_set.add(obj); - }; - for (let p of patterns) { - try { - const original_pattern = p; - let pre_wildcards = []; - if (p.match(/^\/(.*)\/$/)) { - pre_wildcards = [p]; - } else { - let m = p.match(/^\*(https?:.*)/i); - if (!isNull(m)) { - p = m[1]; - } - p = p - .replace(/^\*:/i, 'http:') - .replace(/^\*\/\//i, 'http://') - .replace(/^http\*:/i, 'http:') - .replace(/^(https?):([^/])/i, '$1://$2'); - m = p.match(/^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = m[1] + m[2]; - } - m = p.match(/^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; + calculate_applies_to_names() { + if (isEmpty(this.code)) { + return null; + } + if (this.data_names) { + return this.data_names; + } + let patterns = []; + for (const [k, v] of Object.entries(this.parse_meta())) { + if (/include|match/i.test(k)) { + if (Array.isArray(v)) { + patterns = patterns.concat(v); + } else { + patterns = patterns.concat([v]); } - m = p.match(/^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i); - if (!isNull(m)) { - p = `http://${m[1]}`; + } + } + if (isEmpty(patterns)) { + return []; + } + if (this.intersect(patterns, APPLIES_TO_ALL_PATTERNS)) { + this.data_names = [ + { + domain: false, + text: 'All sites', + tld_extra: false } - m = p.match(/^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/); - if (!isNull(m)) { - if (m[2].match(/A([0-9]+\.){2,}z/)) { - p = `${m[1]}tld${m[3]}`; - pre_wildcards = [p.split('*')[0]]; + ]; + return this.data_names; + } + this.data_names = ParseUserJS.getNames(patterns); + return this.data_names; + } + intersect(a, ...arr) { + return !isBlank([...new Set(a)].filter((v) => arr.every((b) => b.includes(v)))); + } + static getNames(patterns = []) { + const name_map = new Map(); + const addObj = (obj) => { + if (name_map.has(obj.text)) { + return; + } + name_map.set(obj.text, obj); + }; + for (let p of patterns) { + try { + const original_pattern = p; + let pre_wildcards = []; + if (p.match(/^\/(.*)\/$/)) { + pre_wildcards = [p]; + } else { + let m = /^\*(https?:.*)/i.exec(p); + if (m) { + p = m[1]; + } + p = p + .replace(/^\*:/i, 'http:') + .replace(/^\*\/\//i, 'http://') + .replace(/^http\*:/i, 'http:') + .replace(/^(https?):([^/])/i, '$1://$2'); + m = /^([a-z]+:\/\/)\*\.?([a-z0-9-]+(?:.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = m[1] + m[2]; + } + m = /^\*\.?([a-z0-9-]+\.[a-z0-9-]+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^http\*(?:\/\/)?\.?((?:[a-z0-9-]+)(?:\.[a-z0-9-]+)+.*)/i.exec(p); + if (m) { + p = `http://${m[1]}`; + } + m = /^([a-z]+:\/\/([a-z0-9-]+(?:\.[a-z0-9-]+)*\.))\*(.*)/.exec(p); + if (m) { + if (m[2].match(/A([0-9]+\.){2,}z/)) { + p = `${m[1]}tld${m[3]}`; + pre_wildcards = [p.split('*')[0]]; + } else { + pre_wildcards = [p]; + } } else { pre_wildcards = [p]; } - } else { - pre_wildcards = [p]; } - } - for (const pre_wildcard of pre_wildcards) { - try { - const uri = new URL(pre_wildcard); - if (isNull(uri.host)) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (!uri.host.includes('.') && uri.host.includes('*')) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); - } else if (uri.host.endsWith('.tld')) { - for (let i = 0; i < TLD_EXPANSION.length; i++) { - const tld = TLD_EXPANSION[i]; + for (const pre_wildcard of pre_wildcards) { + try { + const urlObj = new URL(pre_wildcard); + const { host } = urlObj; + if (isNull(host)) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (!host.includes('.') && host.includes('*')) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); + } else if (host.endsWith('.tld')) { + for (let i = 0; i < TLD_EXPANSION.length; i++) { + const tld = TLD_EXPANSION[i]; + addObj({ + text: host.replace(/tld$/i, tld), + domain: true, + tld_extra: i != 0 + }); + } + } else if (host.endsWith('.')) { + addObj({ + text: host.slice(0, -1), + domain: true, + tld_extra: false + }); + } else { addObj({ - text: uri.host.replace(/tld$/i, tld), + text: host, domain: true, - tld_extra: i != 0 + tld_extra: false }); } - } else if (uri.host.endsWith('.')) { - addObj({ - text: uri.host.slice(0, -1), - domain: true, - tld_extra: false - }); - } else { - addObj({ - text: uri.host, - domain: true, - tld_extra: false - }); + // eslint-disable-next-line no-unused-vars + } catch (ex) { + addObj({ text: original_pattern, domain: false, tld_extra: false }); } - // eslint-disable-next-line no-unused-vars - } catch (error) { - addObj({ text: original_pattern, domain: false, tld_extra: false }); } + } catch (ex) { + err(ex); } - } catch (ex) { - console.error(ex); } + return [...name_map.values()]; } - return [...name_set]; -}; -const reqCode = async (obj = {}, translate = false) => { - if (obj.code_data) { - return obj.code_data; - } - /** @type { string } */ - const code = await Network.req(obj.code_url, 'GET', 'text'); - if (typeof code !== 'string') { - return; - } - Object.assign(obj, { - code_data: code, - code_meta: {}, - code_size: [Network.format(code.length)], - code_match: [], - code_grant: [], - antifeatures: [] - }); - const grantSet = new Set(); - const afSet = new Set(); - const meta = parse_meta(code); - const applies_to_names = calculate_applies_to_names(code); - - if (translate) { - for (const lng of language.cache) { - if (meta[`name:${lng}`]) { - Object.assign(obj, { - name: meta[`name:${lng}`], - translated: true - }); - } - if (meta[`description:${lng}`]) { - Object.assign(obj, { - description: meta[`description:${lng}`], - translated: true - }); - } + async request(code_url) { + if (this.data_code_block) { + return this; } - } + /** @type { string } */ + const code = await Network.req(code_url, 'GET', 'text').catch(err); + if (typeof code !== 'string') { + return this; + } + this.isUserCSS = /\.user\.css$/.test(code_url); + this.META_START_COMMENT = this.isUserCSS ? '/* ==UserStyle==' : '// ==UserScript=='; + this.META_END_COMMENT = this.isUserCSS ? '==/UserStyle== */' : '// ==/UserScript=='; + + this.code = code; + this.get_meta_block(); + this.get_code_block(); + this.parse_meta(); + this.calculate_applies_to_names(); - for (const [key, value] of Object.entries(meta)) { - if (/grant/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (grantSet.has(v)) { - continue; + const { data_meta } = this; + if (Array.isArray(data_meta.grant)) { + data_meta.grant = union(data_meta.grant); + } + if (data_meta.resource) { + const obj = {}; + if (typeof data_meta.resource === 'string') { + const reg = /(.+)\s+(.+)/.exec(data_meta.resource); + if (reg) { + obj[reg[1].trim()] = reg[2]; } - grantSet.add(v); - } - } else if (/antifeature/.test(key)) { - for (const v of normalizeTarget(value, false)) { - if (afSet.has(v)) { - continue; + } else { + for (const r of data_meta.resource) { + const reg = /(.+)\s+(http.+)/.exec(r); + if (reg) { + obj[reg[1].trim()] = reg[2]; + } } - afSet.add(v); } + data_meta.resource = obj; } + Object.assign(this, { + code_size: [Network.format(code.length)], + meta: data_meta + }); + + return this; } - Object.assign(obj, { - code_meta: meta, - code_match: applies_to_names, - code_grant: [...grantSet], - antifeatures: [...afSet] - }); - return code; -}; +} -export { - calculate_applies_to_names, - get_meta_block, - parse_meta, - reqCode -} \ No newline at end of file +export { ParseUserJS }; diff --git a/src/js/settings.js b/src/js/settings.js deleted file mode 100644 index c4d51cc..0000000 --- a/src/js/settings.js +++ /dev/null @@ -1,294 +0,0 @@ -'use strict'; -import { err } from './logger.js'; -import { XMap } from './XMap.js'; -import { i18n$ } from './i18n.js'; -import { dom, qs } from './querySelector.js'; -import { ael, make, isNull, isEmpty } from './util.js'; - -let cfg = {}; - -const { hermes } = userjs; - -const renderTheme = (theme) => { - theme = theme || cfg.theme; - if (isEmpty(theme)) { - return; - } - // if (theme === defcfg.theme) { - // return; - // } - const sty = qs('mujs-root').style; - for (const [k, v] of Object.entries(theme)) { - const str = `--mujs-${k}`; - const prop = sty.getPropertyValue(str); - if (isEmpty(v)) { - theme[k] = prop; - } - if (prop === v) { - continue; - } - sty.removeProperty(str); - sty.setProperty(str, v); - } -}; - -const cfgMap = new XMap(); - -//#region Make Config -const cfgpage = qs('form'); -const makecfg = () => { - const exBtn = make('mu-js', 'mujs-sty-flex'); - const exportCFG = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_config'), - dataset: { - command: 'export-cfg' - } - }); - const importCFG = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_config'), - dataset: { - command: 'import-cfg' - } - }); - const exportTheme = make('mujs-btn', 'mujs-export', { - textContent: i18n$('export_theme'), - dataset: { - command: 'export-theme' - } - }); - const importTheme = make('mujs-btn', 'mujs-import', { - textContent: i18n$('import_theme'), - dataset: { - command: 'import-theme' - } - }); - exBtn.append(importCFG, importTheme, exportCFG, exportTheme); - cfgpage.append(exBtn); - - const makerow = (desc, type = null, nm, attrs = {}) => { - desc = desc ?? i18n$('no_license'); - nm = nm ?? i18n$('no_license'); - const sec = make('mujs-section', 'mujs-cfg-section', { - style: nm === 'cache' ? 'display: none;' : '' - }); - const lb = make('label'); - const divDesc = make('mu-js', 'mujs-cfg-desc', { - textContent: desc - }); - lb.append(divDesc); - sec.append(lb); - cfgpage.append(sec); - if (isNull(type)) { - return lb; - } - const inp = make('input', 'mujs-cfg-input', { - type, - dataset: { - name: nm - }, - ...attrs - }); - if (!cfgMap.has(nm)) { - cfgMap.set(nm, inp); - } - if (type === 'checkbox') { - const inlab = make('mu-js', 'mujs-inlab'); - const la = make('label', '', { - onclick() { - inp.dispatchEvent(new MouseEvent('click')); - } - }); - inlab.append(inp, la); - lb.append(inlab); - if (nm.includes('-')) { - return inp; - } - if (/(greasy|sleazy)fork|openuserjs|gi(thub|st)/gi.test(nm)) { - for (const i of cfg.engines) { - if (i.name !== nm) continue; - inp.checked = i.enabled; - inp.dataset.engine = i.name; - ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - // MUJS.rebuild = true; - i.enabled = evt.target.checked; - hermes.send('Save', { - prop: 'engines', - value: cfg.engines - }); - }); - } - } else { - inp.checked = cfg[nm]; - ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - // if (/filterlang/i.test(nm)) { - // MUJS.rebuild = true; - // } - cfg[nm] = evt.target.checked; - hermes.send('Save', { - prop: nm, - value: cfg[nm] - }); - }); - } - } else { - lb.append(inp); - } - return inp; - }; - makerow('Sync with GM', 'checkbox', 'cache'); - makerow(i18n$('redirect'), 'checkbox', 'sleazyredirect'); - makerow(i18n$('filter'), 'checkbox', 'filterlang'); - makerow(i18n$('preview_code'), 'checkbox', 'codePreview'); - for (const inp of [ - makerow('Recommend author', 'checkbox', 'recommend-author'), - makerow('Recommend scripts', 'checkbox', 'recommend-others') - ]) { - const nm = inp.dataset.name === 'recommend-author' ? 'author' : 'others'; - inp.checked = cfg.recommend[nm]; - ael(inp, 'change', (evt) => { - // MUJS.unsaved = true; - cfg.recommend[nm] = evt.target.checked; - hermes.send('Save', { - prop: 'recommend', - value: cfg.recommend - }); - }); - } - makerow('Greasy Fork', 'checkbox', 'greasyfork'); - makerow('Sleazy Fork', 'checkbox', 'sleazyfork'); - makerow('Open UserJS', 'checkbox', 'openuserjs'); - makerow('GitHub API', 'checkbox', 'github'); - const ghAPI = cfg.engines.find((c) => c.name === 'github'); - const ghToken = makerow('GitHub API (Token)', 'text', 'github', { - defaultValue: '', - value: ghAPI.token ?? '', - placeholder: 'Paste Access Token' - // onchange(evt) { - // MUJS.unsaved = true; - // MUJS.rebuild = true; - // if (isNull(legacyMsg)) { - // ghAPI.token = evt.target.value; - // } - // } - }); - ghToken.dataset.engine = 'github-token'; - cfgMap.set('github-token', ghToken); - makerow(`${i18n$('dtime')} (ms)`, 'number', 'time', { - defaultValue: 10000, - value: cfg.time, - min: 0, - step: 500, - onbeforeinput(evt) { - if (evt.target.validity.badInput) { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } else { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } - }, - oninput(evt) { - // MUJS.unsaved = true; - const t = evt.target; - if (t.validity.badInput || (t.validity.rangeUnderflow && t.value !== '-1')) { - dom.cl.add(t, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } else { - dom.cl.remove(t, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - cfg.time = isEmpty(t.value) ? cfg.time : parseFloat(t.value); - } - } - }); - - const cbtn = make('mu-js', 'mujs-sty-flex'); - const savebtn = make('mujs-btn', 'save', { - textContent: i18n$('save'), - dataset: { - command: 'save' - }, - disabled: false - }); - const resetbtn = make('mujs-btn', 'reset', { - textContent: i18n$('reset'), - dataset: { - command: 'reset' - } - }); - const blacklist = make('textarea', '', { - dataset: { - name: 'blacklist' - }, - rows: '10', - autocomplete: false, - spellcheck: false, - wrap: 'soft', - value: JSON.stringify(cfg.blacklist, null, ' '), - oninput(evt) { - let isvalid = true; - try { - cfg.blacklist = JSON.parse(evt.target.value); - isvalid = true; - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } - } - } - }); - const theme = make('textarea', '', { - dataset: { - name: 'theme' - }, - rows: '10', - autocomplete: false, - spellcheck: false, - wrap: 'soft', - value: JSON.stringify(cfg.theme, null, ' '), - oninput(evt) { - let isvalid = true; - try { - cfg.theme = JSON.parse(evt.target.value); - isvalid = true; - renderTheme(JSON.parse(evt.target.value)); - } catch (ex) { - err(ex); - isvalid = false; - } finally { - if (isvalid) { - dom.cl.remove(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', false); - } else { - dom.cl.add(evt.target, 'mujs-invalid'); - dom.prop(savebtn, 'disabled', true); - } - } - } - }); - cfgMap.set('blacklist', blacklist); - cfgMap.set('theme', theme); - cbtn.append(resetbtn, savebtn); - cfgpage.append(blacklist, theme, cbtn); -}; -//#endregion - -/** - * @type {chrome.runtime.Port | browser.runtime.Port | null} - */ -const p = hermes.getPort(); -p.onMessage.addListener((root = {}) => { - const m = root.msg; - if (root.channel === 'Config' && isEmpty(cfg)) { - cfg = m.cfg || cfg; - makecfg(); - } -}); diff --git a/src/js/storage.js b/src/js/storage.js index 3b990c6..19b5300 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -1,7 +1,117 @@ 'use strict'; +import { webext } from './ext.js'; + let api = webext.storage.local; +// #region DEFAULT_CONFIG +/** + * @type { import("../typings/types.d.ts").config } + */ +export const DEFAULT_CONFIG = { + autofetch: true, + // autoinject: true, + autoSort: 'daily_installs', + clearTabCache: true, + // cache: true, + // autoexpand: false, + filterlang: false, + sleazyredirect: false, + // time: 10000, + blacklist: ['userjs-local', 'userjs-finance', 'userjs-social', 'userjs-unsupported'], + preview: { + code: false, + metadata: false + }, + engines: [ + { + enabled: true, + name: 'greasyfork', + query: encodeURIComponent('https://greasyfork.org/scripts/by-site/{host}.json?language=all') + }, + { + enabled: false, + name: 'sleazyfork', + query: encodeURIComponent('https://sleazyfork.org/scripts/by-site/{host}.json?language=all') + }, + // { + // enabled: false, + // name: 'openuserjs', + // query: encodeURIComponent('https://openuserjs.org/?q={host}') + // }, + { + enabled: false, + name: 'github', + token: '', + query: encodeURIComponent( + 'https://api.github.com/search/code?q="// ==UserScript=="+{host}+ "// ==/UserScript=="+in:file+language:js&per_page=30' + ) + } + ], + theme: { + 'even-row': '', + 'odd-row': '', + 'even-err': '', + 'odd-err': '', + 'background-color': '', + 'gf-color': '', + 'sf-color': '', + 'border-b-color': '', + 'gf-btn-color': '', + 'sf-btn-color': '', + 'sf-txt-color': '', + 'txt-color': '', + 'chck-color': '', + 'chck-gf': '', + 'chck-git': '', + 'chck-open': '', + placeholder: '', + 'position-top': '', + 'position-bottom': '', + 'position-left': '', + 'position-right': '', + 'font-family': '' + }, + recommend: { + author: true, + others: true + }, + filters: { + ASCII: { + enabled: false, + name: 'Non-ASCII', + regExp: '[^\\x00-\\x7F\\s]+' + }, + Latin: { + enabled: false, + name: 'Non-Latin', + regExp: '[^\\u0000-\\u024F\\u2000-\\u214F\\s]+' + }, + Games: { + enabled: false, + name: 'Games', + flag: 'iu', + regExp: + 'Aimbot|AntiGame|Agar|agar\\.io|alis\\.io|angel\\.io|ExtencionRipXChetoMalo|AposBot|DFxLite|ZTx-Lite|AposFeedingBot|AposLoader|Balz|Blah Blah|Orc Clan Script|Astro\\s*Empires|^\\s*Attack|^\\s*Battle|BiteFight|Blood\\s*Wars|Bloble|Bonk|Bots|Bots4|Brawler|\\bBvS\\b|Business\\s*Tycoon|Castle\\s*Age|City\\s*Ville|chopcoin\\.io|Comunio|Conquer\\s*Club|CosmoPulse|cursors\\.io|Dark\\s*Orbit|Dead\\s*Frontier|Diep\\.io|\\bDOA\\b|doblons\\.io|DotD|Dossergame|Dragons\\s*of\\s*Atlantis|driftin\\.io|Dugout|\\bDS[a-z]+\\n|elites\\.io|Empire\\s*Board|eRep(ublik)?|Epicmafia|Epic.*War|ExoPlanet|Falcon Tools|Feuerwache|Farming|FarmVille|Fightinfo|Frontier\\s*Ville|Ghost\\s*Trapper|Gladiatus|Goalline|Gondal|gota\\.io|Grepolis|Hobopolis|\\bhwm(\\b|_)|Ikariam|\\bIT2\\b|Jellyneo|Kapi\\s*Hospital|Kings\\s*Age|Kingdoms?\\s*of|knastv(o|oe)gel|Knight\\s*Fight|\\b(Power)?KoC(Atta?ck)?\\b|\\bKOL\\b|Kongregate|Krunker|Last\\s*Emperor|Legends?\\s*of|Light\\s*Rising|lite\\.ext\\.io|Lockerz|\\bLoU\\b|Mafia\\s*(Wars|Mofo)|Menelgame|Mob\\s*Wars|Mouse\\s*Hunt|Molehill\\s*Empire|MooMoo|MyFreeFarm|narwhale\\.io|Neopets|NeoQuest|Nemexia|\\bOGame\\b|Ogar(io)?|Pardus|Pennergame|Pigskin\\s*Empire|PlayerScripts|pokeradar\\.io|Popmundo|Po?we?r\\s*(Bot|Tools)|PsicoTSI|Ravenwood|Schulterglatze|Skribbl|slither\\.io|slitherplus\\.io|slitheriogameplay|SpaceWars|splix\\.io|Survivio|\\bSW_[a-z]+\\n|\\bSnP\\b|The\\s*Crims|The\\s*West|torto\\.io|Travian|Treasure\\s*Isl(and|e)|Tribal\\s*Wars|TW.?PRO|Vampire\\s*Wars|vertix\\.io|War\\s*of\\s*Ninja|World\\s*of\\s*Tanks|West\\s*Wars|wings\\.io|\\bWoD\\b|World\\s*of\\s*Dungeons|wtf\\s*battles|Wurzelimperium|Yohoho|Zombs' + }, + SocialNetworks: { + enabled: false, + name: 'Social Networks', + flag: 'iu', + regExp: + 'Face\\s*book|Google(\\+| Plus)|\\bHabbo|Kaskus|\\bLepra|Leprosorium|MySpace|meinVZ|odnoklassniki|Одноклассники|Orkut|sch(ue|ü)ler(VZ|\\.cc)?|studiVZ|Unfriend|Valenth|VK|vkontakte|ВКонтакте|Qzone|Twitter|TweetDeck' + }, + Clutter: { + enabled: false, + name: 'Clutter', + flag: 'iu', + regExp: + "^\\s*(.{1,3})\\1+\\n|^\\s*(.+?)\\n+\\2\\n*$|^\\s*.{1,5}\\n|do\\s*n('|o)?t (install|download)|nicht installieren|(just )?(\\ban? |\\b)test(ing|s|\\d|\\b)|^\\s*.{0,4}test.{0,4}\\n|\\ntest(ing)?\\s*|^\\s*(\\{@|Smolka|Hacks)|\\[\\d{4,5}\\]|free\\s*download|theme|(night|dark) ?(mode)?" + } + } +}; +// #endregion + /** @type {{ [prefix: string]: StorageArea }} */ export const storageByPrefix = {}; @@ -56,9 +166,7 @@ class StorageArea { async getMulti(ids, transform) { const keys = ids?.map(this.toKey, this); const data = await api.get(keys); - return transform || this.prefix - ? mapEntry.call(data, transform, 'toId', this) - : data; + return transform || this.prefix ? mapEntry.call(data, transform, 'toId', this) : data; } /** @@ -92,10 +200,10 @@ const storage = { api = val; }, /** @return {?StorageArea} */ - forKey: key => storageByPrefix[/^\w+:|$/.exec(key)[0]], + forKey: (key) => storageByPrefix[/^\w+:|$/.exec(key)[0]], base: new StorageArea('base', ''), config: new StorageArea('config', 'cfg:'), - cache: new StorageArea('cache', 'cac:'), + cache: new StorageArea('cache', 'cac:') }; export default storage; diff --git a/src/js/util.js b/src/js/util.js index bb22e94..e890117 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -1,35 +1,33 @@ 'use strict'; + +import { webext } from './ext.js'; import { err } from './logger.js'; // #region Utilities -let isMob; -// const isMobile = /Mobile|Tablet/.test(navigator.userAgent); +const userjs = {}; const getUAData = () => { - if (typeof isMob !== 'undefined') { - return isMob; + if (userjs.isMobile !== undefined) { + return userjs.isMobile; } try { - const { platform, mobile } = navigator.userAgentData ?? {}; - isMob = - /Mobile|Tablet/.test(navigator.userAgent ?? '') || - mobile || - /Android|Apple/.test(platform ?? ''); + if (navigator) { + const { userAgent, userAgentData } = navigator; + const { platform, mobile } = userAgentData ? Object(userAgentData) : {}; + userjs.isMobile = + /Mobile|Tablet/.test(userAgent ? String(userAgent) : '') || + Boolean(mobile) || + /Android|Apple/.test(platform ? String(platform) : ''); + } else { + userjs.isMobile = false; + } } catch (ex) { - err({ cause: 'getUAData', message: ex.message }); - isMob = false; + userjs.isMobile = false; + ex.cause = 'getUAData'; + err(ex); } - return isMob; + return userjs.isMobile; }; const isMobile = getUAData(); -/** - * @type { import("../typings/types").hasOwn } - */ -const hasOwn = (obj, prop) => { - if (typeof Object.hasOwn !== 'undefined') { - return Object.hasOwn(obj, prop); - } - return Object.prototype.hasOwnProperty.call(obj, prop); -}; const isString = (val) => typeof val === 'string'; /** * @type { import("../typings/types").objToStr } @@ -38,16 +36,21 @@ const objToStr = (obj) => { return Object.prototype.toString.call(obj); }; /** - * @type { import("../typings/types").strToURL } + * @template {string | URL} S + * @param {S} str */ const strToURL = (str) => { + let url; try { - str = str ?? window.location ?? 'about:blank'; - return objToStr(str).includes('URL') ? str : new URL(str); + url = objToStr(str).includes('URL') ? str : new URL(str); } catch (ex) { - err({ cause: 'strToURL', message: ex.message }); - return window.location; + ex.cause = 'strToURL'; + err(ex); + } + if (url !== undefined) { + return url } + return str; }; /** * @type { import("../typings/types").isRegExp } @@ -119,7 +122,7 @@ const normalizeTarget = (target, toQuery = true, root) => { return Array.from(target); }; /** - * @type { import("../typings/types").ael } + * @type { import("../typings/types.d.ts").ael } */ const ael = (el, type, listener, options = {}) => { try { @@ -134,11 +137,12 @@ const ael = (el, type, listener, options = {}) => { elem.addEventListener(type, listener, options); } } catch (ex) { + ex.cause = 'ael'; err(ex); } }; /** - * @type { import("../typings/types").formAttrs } + * @type { import("../typings/types.d.ts").formAttrs } */ const formAttrs = (elem, attr = {}) => { if (!elem) { @@ -162,29 +166,28 @@ const formAttrs = (elem, attr = {}) => { return elem; }; /** - * @type { import("../typings/types").make } + * @type { import("../typings/types.d.ts").make } */ const make = (tagName, cname, attrs) => { let el; try { - // el = - // tagName === 'fragment' ? document.createDocumentFragment() : document.createElement(tagName); el = document.createElement(tagName); if (!isEmpty(cname)) { - if (isString(cname)) { + if (typeof cname === 'string') { el.className = cname; } else if (isObj(cname)) { formAttrs(el, cname); } } if (!isEmpty(attrs)) { - if (isString(attrs)) { + if (typeof attrs === 'string') { el.textContent = attrs; } else if (isObj(attrs)) { formAttrs(el, attrs); } } } catch (ex) { + ex.cause = 'make'; err(ex); } return el; @@ -198,57 +201,119 @@ const openInTab = async (url) => { const newTab = await webext.tabs.create({ url }); return newTab; }; -const intersect = (a = [], b = []) => { - for (const v of a) { - if (b.includes(v)) { +const union = (...arr) => [...new Set(arr.flat())]; +const loadFilters = (cfg) => { + /** @type {Map} */ + const pool = new Map(); + const handles = { + pool, + enabled() { + return [...pool.values()].filter((o) => o.enabled); + }, + refresh() { + if (!Object.is(pool.size, 0)) pool.clear(); + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); + } + return this; + }, + get(str) { + return [...pool.values()].find((v) => v.keyReg.test(str) || v.valueReg.test(str)); + }, + /** + * @param { import("../typings/types.d.ts").GSForkQuery } param0 + */ + match({ name, users }) { + const p = handles.enabled(); + if (Object.is(p.length, 0)) return true; + for (const v of p) { + if ([{ name }, ...users].find((o) => o.name.match(v.reg))) return false; + } return true; } + }; + for (const [key, value] of Object.entries(cfg.filters)) { + if (!pool.has(key)) + pool.set(key, { + ...value, + reg: new RegExp(value.regExp, value.flag), + keyReg: new RegExp(key.trim().toLocaleLowerCase(), 'gi'), + valueReg: new RegExp(value.name.trim().toLocaleLowerCase(), 'gi') + }); } - for (const v of b) { - if (a.includes(v)) { - return true; + return handles.refresh(); +}; +/** + * @param {string} txt + */ +const formatURL = (txt) => + txt + .split('.') + .splice(-2) + .join('.') + .replace(/\/|https:/g, ''); +const matchesFromHostnames = (hostnames) => { + const out = []; + for (const hn of hostnames) { + if (hn === '*' || hn === 'all-urls') { + out.length = 0; + out.push(''); + break; } + out.push(`*://*.${hn}/*`); } - return false; + return out; }; -const template = { - data: { - id: 0, - bad_ratings: 0, - good_ratings: 0, - ok_ratings: 0, - daily_installs: 0, - total_installs: 0, - name: 'NOT FOUND', - description: 'NOT FOUND', - version: '0.0.0', - url: 'about:blank', - code_url: 'about:blank', - created_at: Date.now(), - code_updated_at: Date.now(), - users: [ - { - name: '', - url: '' - } - ] - }, - merge(obj = {}) { - for (const key in template.data) { - if (hasOwn(obj, key)) continue; - obj[key] = template.data[key]; +/** + * @param {string[]} origins + */ +const hostnamesFromMatches = (origins) => { + const out = []; + for (const origin of origins) { + if (origin === '') { + out.push('all-urls'); + continue; + } + const match = /^\*:\/\/(?:\*\.)?([^/]+)\/\*/.exec(origin); + if (match === null) { + continue; } - return obj; + out.push(match[1]); + } + return out; +}; +/** + * @param {string} hn + */ +const normalizedHostname = (hn) => { + return hn.replace(/^www\./, ''); +}; +/** + * @param {string} str + */ +const decode = (str) => { + try { + if (decodeURI(str) !== decodeURIComponent(str)) { + return decode(decodeURIComponent(str)); + } + } catch (ex) { + err(ex); } + return str; }; // #endregion export { - isMobile, - hasOwn, + decode, + formatURL, objToStr, strToURL, - intersect, isRegExp, isElem, isObj, @@ -262,5 +327,9 @@ export { formAttrs, make, openInTab, - template + union, + loadFilters, + matchesFromHostnames, + hostnamesFromMatches, + normalizedHostname }; diff --git a/src/manifest/chrome.json b/src/manifest/chrome.json index 5581779..78eefbe 100644 --- a/src/manifest/chrome.json +++ b/src/manifest/chrome.json @@ -1,38 +1,41 @@ { - "manifest_version": 2, + "manifest_version": 3, "default_locale": "en", "name": "__MSG_extName__", "description": "__MSG_extShortDesc__", "short_name": "userJSPlus", "background": { - "page": "background.html" + "service_worker": "/js/background.js", + "type": "module" }, - "browser_action": { + "action": { "default_icon": { - "16": "img/icon_16.png", + "16": "img/icon_16.png", "32": "img/icon_32.png", - "64": "img/icon_64.png" + "48": "img/icon_48.png", + "64": "img/icon_64.png", + "128": "img/icon_128.png" }, "default_popup": "popup.html", "default_title": "Magic UserJS+" }, - "options_ui": { - "page": "popup.html?mujs=settings", - "open_in_tab": true - }, + "minimum_chrome_version": "122.0", + "options_page": "popup.html?mujs=settings", "permissions": [ - "tabs", - "", "storage", "unlimitedStorage", + "tabs", + "activeTab", "webRequest" ], - "content_security_policy": "script-src 'self'; object-src 'self'", + "host_permissions": [ + "https://*/*" + ], "icons": { "16": "img/icon_16.png", "32": "img/icon_32.png", + "48": "img/icon_48.png", "64": "img/icon_64.png", "128": "img/icon_128.png" - }, - "commands": {} + } } diff --git a/src/manifest/firefox.json b/src/manifest/firefox.json index cbdb471..3a7ac1b 100644 --- a/src/manifest/firefox.json +++ b/src/manifest/firefox.json @@ -1,48 +1,58 @@ { - "$schema": "https://json.schemastore.org/webextension.json", - "manifest_version": 2, + "manifest_version": 3, "default_locale": "en", "name": "__MSG_extName__", "description": "__MSG_extShortDesc__", "short_name": "userJSPlus", "background": { - "page": "background.html" + "scripts": [ + "/js/background.js" + ], + "type": "module" }, - "browser_action": { - "browser_style": false, + "action": { "default_area": "navbar", "default_icon": { - "16": "img/icon_16.png", + "16": "img/icon_16.png", "32": "img/icon_32.png", - "64": "img/icon_64.png" + "48": "img/icon_48.png", + "64": "img/icon_64.png", + "128": "img/icon_128.png" }, "default_popup": "popup.html", "default_title": "Magic UserJS+" }, + "browser_specific_settings": { + "gecko": { + "id": "uscriptplus@mol.com", + "strict_min_version": "128.0" + }, + "gecko_android": { + "strict_min_version": "128.0" + } + }, "options_ui": { - "page": "popup.html?mujs=settings", - "open_in_tab": true + "open_in_tab": true, + "page": "popup.html?mujs=settings" }, "permissions": [ - "tabs", - "", "storage", "unlimitedStorage", + "tabs", + "activeTab", "webRequest" ], - "browser_specific_settings": { - "gecko": { - "id": "uscriptplus@mol.com", - "strict_min_version": "91.1.0" - } + "host_permissions": [ + "https://*/*" + ], + "content_security_policy": { + "extension_pages": "script-src 'self'; object-src 'self'" }, - "content_security_policy": "script-src 'self'; object-src 'self'", "icons": { - "16": "img/userjs.svg", - "32": "img/userjs.svg", - "48": "img/userjs.svg", - "64": "img/userjs.svg", - "96": "img/userjs.svg" - }, - "commands": {} + "16": "img/icon_16.png", + "32": "img/icon_32.png", + "48": "img/icon_48.png", + "64": "img/icon_64.png", + "128": "img/icon_128.png" + } } diff --git a/src/sass/_main.scss b/src/sass/_main.scss index 7834a03..c7e78e9 100644 --- a/src/sass/_main.scss +++ b/src/sass/_main.scss @@ -30,6 +30,12 @@ mujs-root { } mujs-root * { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + scrollbar-color: var(--mujs-txt-color, hsl(0, 0%, 100%)) hsl(224, 14%, 21%); + scrollbar-width: thin; + @supports not (scrollbar-width: thin) { ::-webkit-scrollbar { width: 1.4vw; @@ -47,30 +53,29 @@ mujs-root * { ::-webkit-scrollbar-thumb { border-radius: 16px; background-color: var(--mujs-txt-color, hsl(0, 0%, 100%)); - background-image: -webkit-linear-gradient(45deg, - hsla(0, 0%, 100%, 0.2) 25%, - transparent 25%, - transparent 50%, - hsla(0, 0%, 100%, 0.2) 50%, - hsla(0, 0%, 100%, 0.2) 75%, - transparent 75%, - transparent); + background-image: -webkit-linear-gradient( + 45deg, + hsla(0, 0%, 100%, 0.2) 25%, + transparent 25%, + transparent 50%, + hsla(0, 0%, 100%, 0.2) 50%, + hsla(0, 0%, 100%, 0.2) 75%, + transparent 75%, + transparent + ); } ::-webkit-scrollbar-thumb:hover { background: var(--mujs-txt-color, hsl(0, 0%, 100%)); } } - - scrollbar-color: var(--mujs-txt-color, hsl(0, 0%, 100%)) hsl(224, 14%, 21%); - scrollbar-width: thin; } @at-root mu-js { line-height: normal; } -mujs-section>label, +mujs-section > label, .mujs-homepag e, td.mujs-list, .install { @@ -82,15 +87,11 @@ td.mujs-list, font-weight: 700; } -mujs-section>label, +mujs-section > label, td.mujs-list { font-weight: 500; } -.mujs-sty-flex>mujs-btn { - margin: auto; -} - .mujs-invalid { border-radius: 8px !important; border-width: 2px !important; @@ -111,27 +112,27 @@ mujs-row { } mujs-column { - @media screen and (max-width: 800px) { - flex-flow: row wrap; - } - count-frame { - &[data-counter="greasyfork"] { + &[data-counter='greasyfork'] { background: var(--mujs-gf-color, hsl(204, 100%, 40%)); } - &[data-counter="sleazyfork"] { + &[data-counter='sleazyfork'] { background: var(--mujs-sf-color, hsl(12, 86%, 50%)); } - &[data-counter="github"] { + &[data-counter='github'] { background: hsla(213, 13%, 16%); } - &[data-counter="openuserjs"] { + &[data-counter='openuserjs'] { background: hsla(12, 86%, 50%, 0.568); } } + + @media screen and (max-width: 800px) { + flex-flow: row wrap; + } } mujs-row { @@ -148,6 +149,13 @@ mu-js { } mujs-main { + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + background: var(--mujs-background-color, hsl(222, 14%, 33%)) !important; + border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); + border-radius: 16px; + @media screen and (max-height: 720px) { &:not(.webext-page) { height: 100% !important; @@ -157,13 +165,6 @@ mujs-main { } } - width: 100%; - width: -moz-available; - width: -webkit-fill-available; - background: var(--mujs-background-color, hsl(222, 14%, 33%)) !important; - border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - border-radius: 16px; - &.expanded { height: 100% !important; bottom: 0rem !important; @@ -187,7 +188,7 @@ mujs-main { flex-direction: column !important; } - >* { + > * { width: 100%; width: -moz-available; width: -webkit-fill-available; @@ -195,7 +196,7 @@ mujs-main { mujs-toolbar { order: 0; - padding: .5em; + padding: 0.5em; display: flex; place-content: space-between; @@ -209,7 +210,7 @@ mujs-main { order: 999999999999; } - >* { + > * { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; @@ -240,6 +241,10 @@ mujs-main { border-radius: 4px; background: transparent; + @media screen and (max-width: 800px) { + min-width: 6em !important; + } + &.active { background: var(--mujs-even-row, hsl(222, 14%, 18%)); } @@ -289,25 +294,33 @@ mujs-main { // background: var(--mujs-background-color, hsl(222, 14%, 33%)); background: hsla(0, 0%, 0%, 0); color: var(--mujs-txt-color, hsl(0, 0%, 100%)); - } - input:not([type='checkbox']) { - border: transparent; - outline: none !important; + &:not([type='checkbox']) { + border: transparent; + outline: none !important; + } } + mujs-page, textarea { background: inherit; overflow-y: auto; - color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // background: var(--mujs-even-row, hsl(222, 14%, 18%)); - // border: 1px solid transparent; border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - border-radius: 10px; - resize: vertical; + border-radius: 5px; outline: none; font-family: monospace; font-size: 14px; + } + + mujs-page { + padding: 0.5em; + margin: 0.5em; + } + + textarea { + overflow-y: auto; + color: var(--mujs-placeholder, hsl(81, 56%, 54%)); + resize: vertical; &:focus { outline: none; @@ -328,7 +341,7 @@ mujs-main { text-align: center; border-radius: 16px; - >* { + > * { min-height: 50px; } @@ -347,20 +360,19 @@ mujs-main { align-items: center; justify-content: center; - svg, - img { + svg { width: 14px; height: 14px; background: transparent; } - >.prompt { + > .prompt { position: absolute; background: var(--mujs-background-color, hsl(222, 14%, 33%)) !important; border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); border-radius: 16px; text-align: center; - padding: .5em; + padding: 0.5em; z-index: 1; .prompt-head { @@ -370,12 +382,12 @@ mujs-main { .prompt-body { display: grid; grid-auto-flow: column; - grid-gap: .5em; - padding-top: .5em; + grid-gap: 0.5em; + padding-top: 0.5em; } mujs-btn { - &[data-command="prompt-deny"] { + &.prompt-deny { background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); @@ -385,7 +397,7 @@ mujs-main { } } - &[data-command="prompt-confirm"] { + &.prompt-confirm { background: var(--mujs-gf-color, hsl(204, 100%, 40%)); border-color: var(--mujs-gf-color, hsl(204, 100%, 40%)); @@ -450,12 +462,9 @@ mujs-header { order: 1; display: flex; border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - // border-top-left-radius: 10px; - // border-top-right-radius: 10px; - // padding: .5em; - padding-left: .5em; - padding-right: .5em; - padding-bottom: .5em; + padding-left: 0.5em; + padding-right: 0.5em; + padding-bottom: 0.5em; font-size: 1em; place-content: space-between; height: fit-content; @@ -463,7 +472,7 @@ mujs-header { height: -webkit-fit-content; gap: 1em; - >*:not(mujs-url) { + > *:not(mujs-url) { height: fit-content; height: -moz-fit-content; height: -webkit-fit-content; @@ -473,7 +482,7 @@ mujs-header { order: 0; flex-grow: 1; - >input { + > input { width: 100%; height: 100%; background: var(--mujs-even-row, hsl(222, 14%, 18%)); @@ -486,10 +495,6 @@ mujs-header { order: 1; } - .mujs-sty-flex { - order: 2; - } - .btn-frame { order: 999999999999; } @@ -500,7 +505,6 @@ mujs-body { overflow-x: hidden; padding: 0px; height: 100%; - border: 1px solid var(--mujs-border-b-color, hsla(0, 0%, 0%, 0)); border-bottom-left-radius: 16px; border-bottom-right-radius: 16px; @@ -528,8 +532,12 @@ mujs-body { } table { + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + @media screen and (max-width: 1180px) { - thead>tr { + thead > tr { display: table-column; } @@ -538,24 +546,46 @@ mujs-body { display: flex; flex-flow: row wrap; align-items: center; - border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - padding-top: .5em; - padding-bottom: .5em; + padding-top: 0.5em; + padding-bottom: 0.5em; td { margin: auto; - border-bottom: 1px solid transparent; - >mujs-a, - >mu-js, - >mujs-column { + > mujs-a, + > mu-js, + > mujs-column { text-align: center; justify-content: center; } + > mujs-a { + width: 100%; + } + + > mujs-column { + @media screen and (max-width: 800px) { + flex-flow: column wrap; + + > mujs-row { + align-content: center; + } + + mujs-column { + justify-content: center; + } + } + } + &:not(.mujs-name, .install-btn) { width: 25%; } + + &.install-btn { + @media screen and (max-width: 800px) { + width: 100%; + } + } } .mujs-name { @@ -576,40 +606,40 @@ mujs-body { } } - width: 100%; - width: -moz-available; - width: -webkit-fill-available; - - th, - td { - border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); - } - th { position: -webkit-sticky; position: sticky; top: 0; background: hsla(222, 14%, 33%, 0.75); + border-bottom: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); &.mujs-header-name { + width: 50%; + @media screen and (max-width: 800px) { width: auto !important; } - - width: 50%; } } .frame { &:nth-child(even) { background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + + textarea { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; + } } &:nth-child(odd) { background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; + + textarea { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + } } - &:not([data-engine="sleazyfork"], [data-engine="greasyfork"]) { + &:not([data-engine='sleazyfork'], [data-engine='greasyfork']) { mujs-a { color: var(--mujs-sf-txt-color, hsl(12, 79%, 55%)); } @@ -625,8 +655,8 @@ mujs-body { } } - &[data-engine="sleazyfork"], - &[data-engine="greasyfork"] { + &[data-engine='sleazyfork'], + &[data-engine='greasyfork'] { mujs-a { color: var(--mujs-gf-color, hsl(197, 100%, 50%)); @@ -694,19 +724,19 @@ mujs-body { } .mujs-ratings { - &[data-el="good"] { + &[data-el='good'] { border-color: hsl(120, 50%, 40%); background-color: hsla(120, 50%, 40%, 0.102); color: hsl(120, 100%, 60%); } - &[data-el="ok"] { + &[data-el='ok'] { border-color: hsl(60, 100%, 30%); background-color: hsla(60, 100%, 30%, 0.102); color: hsl(60, 100%, 50%); } - &[data-el="bad"] { + &[data-el='bad'] { border-color: hsl(0, 100%, 30%); background-color: hsla(0, 50%, 40%, 0.102); color: hsl(0, 100%, 50%); @@ -714,22 +744,18 @@ mujs-body { } svg { + width: 12px; + height: 12px; fill: currentColor; - } - - svg, - img { - width: 14px; - height: 14px; background: transparent; } - >td:not(.mujs-name) { + > td:not(.mujs-name) { text-align: center; } - >.mujs-name { - >mujs-a { + > .mujs-name { + > mujs-a { width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; @@ -742,34 +768,38 @@ mujs-body { height: fit-content; } - >mu-jsbtn { + > mu-jsbtn { margin: auto; } - >mujs-column { - >mu-jsbtn { + > mujs-column { + > mu-jsbtn { padding: 0px 7px; + + @media screen and (max-width: 800px) { + width: 100%; + } } } } - >.mujs-uframe { - >mujs-a { + > .mujs-uframe { + > mujs-a { font-size: 16px; font-weight: 500; - padding-left: .5rem; - padding-right: .5rem; + padding-left: 0.5rem; + padding-right: 0.5rem; } } - [data-el="more-info"] { - >mujs-row { - gap: .25em; + [data-el='more-info'] { + > mujs-row { + gap: 0.25em; } } - [data-el="matches"] { - gap: .25em; + [data-el='matches'] { + gap: 0.25em; max-width: 40em; .mujs-grants { @@ -783,9 +813,9 @@ mujs-body { width: -moz-fit-content; width: fit-content; max-height: 5em; - gap: .2em; + gap: 0.2em; - >mujs-a { + > mujs-a { display: inline; &:not([data-command]) { @@ -794,15 +824,35 @@ mujs-body { } &::after { - content: ", "; + content: ', '; color: var(--mujs-txt-color, hsl(0, 0%, 100%)); } &:last-child::after { - content: ""; + content: ''; } } } + + @media screen and (max-width: 800px) { + align-self: center; + width: 30em !important; + } + } + + [data-name='license'] { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + + @media screen and (max-width: 800px) { + width: 100% !important; + width: -moz-available !important; + width: -webkit-fill-available !important; + } } } } @@ -815,46 +865,93 @@ mujs-body { } .mujs-cfg { - @media screen and (max-height: 720px) { - &:not(.webext-page) { - height: 100%; - height: -moz-available; - height: -webkit-fill-available; - width: 100%; - width: -moz-available; - width: -webkit-fill-available; - overflow-x: auto; - padding: .5em; - } - } - - height: fit-content; - height: -moz-fit-content; - height: -webkit-fit-content; - mujs-section { border-radius: 16px; - padding: .5em; + padding: 0.5em; &:nth-child(even) { background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + + input, + select { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); + } + + select option { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); + + &:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + } + } } &:nth-child(odd) { background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; + + input, + select { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); + } + + select option { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); + + &:hover { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)) !important; + } + } + } + + &[data-name='theme'] { + .sub-section { + border-radius: 4px; + + &:nth-child(even) { + background: var(--mujs-even-row, hsl(222, 14%, 18%)); + } + &:nth-child(odd) { + background: var(--mujs-odd-row, hsl(222, 14%, 33%)); + } + } + input, + select { + background: inherit; + } + + select option { + background: inherit; + + &:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + } + } + } + + svg { + width: 14px; + height: 14px; + fill: currentColor; + background: transparent; } - &[data-name='theme'], + // &[data-name='theme'], + &[data-name='exp'], &[data-name='blacklist'] { display: flex; justify-content: space-between; flex-direction: column; gap: 0.25em; - // mujs-add { - // order: 999999999999; - // font-size: 20px; - // } + > mujs-btn { + &:hover { + background: var(--mujs-even-row, hsl(222, 14%, 18%)) !important; + } + + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + } } input[type='text']::-webkit-input-placeholder { @@ -877,18 +974,16 @@ mujs-body { color: var(--mujs-placeholder, hsl(81, 56%, 54%)); } - >label { - - // [data-theme] + > label { &:not([data-blacklist]) { display: flex; justify-content: space-between; } - // &[data-theme], &[data-blacklist] { display: grid; grid-auto-flow: column; + &:not(.new-list) { grid-template-columns: repeat(2, 1fr); } @@ -902,48 +997,44 @@ mujs-body { } } - &.sub-section { - padding: 0.20em; - } - - // display: flex; - // justify-content: space-between; - input:not([type='checkbox']) { font-size: 14px; position: relative; border-radius: 4px; border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); } + } - // input[type='text']::-webkit-input-placeholder { - // color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // } + select, + select option { + color: var(--mujs-txt-color, hsl(0, 0%, 100%)); + border: 1px solid transparent; + list-style: none; + outline-style: none; + pointer-events: auto; + } - // input[type='text']::-moz-placeholder { - // color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // } + select { + text-align: center; + border-radius: 4px; + } - // input[type='text']:-ms-input-placeholder { - // color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // } + > *.sub-section { + padding: 0.2em; - // input[type='text']::-ms-input-placeholder { - // color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // } + &[data-engine] { + flex-wrap: wrap; - // input[type='text']::placeholder { - // color: var(--mujs-placeholder, hsl(81, 56%, 54%)); - // } - } + input { + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + } + } - >input { - width: 100%; - width: -moz-available; - width: -webkit-fill-available; - margin-top: 5px; - border-radius: 4px; - border: 1px solid var(--mujs-txt-color, hsl(0, 0%, 100%)); + input[type='text'] { + margin: 0.2em 0px; + } } } @@ -954,7 +1045,7 @@ mujs-body { input[type='checkbox'] { display: none; - &:checked+label { + &:checked + label { margin-left: 0; background: var(--mujs-chck-color, hsla(0, 0%, 100%, 0.568)); @@ -963,16 +1054,19 @@ mujs-body { } } - &[data-name='greasyfork']:checked+label, - &[data-name='sleazyfork']:checked+label { - background: var(--mujs-chck-gf, hsla(197, 100%, 50%, 0.568)); + &[data-name='greasyfork']:checked + label { + background: var(--mujs-gf-color, hsl(204, 100%, 40%)); + } + + &[data-name='sleazyfork']:checked + label { + background: var(--mujs-sf-color, hsl(12, 86%, 50%)); } - &[data-name='openuserjs']:checked+label { + &[data-name='openuserjs']:checked + label { background: var(--mujs-chck-open, hsla(12, 86%, 50%, 0.568)); } - &[data-name='github']:checked+label { + &[data-name='github']:checked + label { background: var(--mujs-chck-git, hsla(213, 13%, 16%, 0.568)); } } @@ -1002,7 +1096,9 @@ mujs-body { .mujs-sty-flex { mujs-btn { - &[data-command="reset"] { + margin: auto; + + &[data-command='reset'] { background: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); border-color: var(--mujs-sf-btn-color, hsl(12, 86%, 50%)); @@ -1012,7 +1108,7 @@ mujs-body { } } - &[data-command="save"] { + &[data-command='save'] { background: var(--mujs-gf-color, hsl(204, 100%, 40%)); border-color: var(--mujs-gf-color, hsl(204, 100%, 40%)); @@ -1027,6 +1123,23 @@ mujs-body { &:not(.webext-page) { margin: 1rem 25rem; } + + height: fit-content; + height: -moz-fit-content; + height: -webkit-fit-content; + + @media screen and (max-height: 720px) { + &:not(.webext-page) { + height: 100%; + height: -moz-available; + height: -webkit-fill-available; + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + overflow-x: auto; + padding: 0.5em; + } + } } mujs-a { @@ -1036,7 +1149,7 @@ mujs-a { .mujs-name { display: flex; flex-flow: column wrap; - gap: .5em; + gap: 0.5em; span { font-size: 0.8em !important; @@ -1057,13 +1170,9 @@ mujs-btn { padding: 6px 15px; svg { - fill: var(--mujs-txt-color, hsl(0, 0%, 100%)); - } - - svg, - img { width: 14px; height: 14px; + fill: var(--mujs-txt-color, hsl(0, 0%, 100%)); } } @@ -1082,25 +1191,11 @@ mu-jsbtn { mujs-a, mu-jsbtn, .mujs-pointer, -.mujs-cfg mujs-section *:not(input[type='password'], input[type='text'], input[type='number'], [data-theme], [data-blacklist]), +.mujs-cfg + mujs-section + *:not(input[type='text'], input[type='number'], [data-theme], [data-blacklist]), .mainbtn, .mainframe, mujs-btn { cursor: pointer !important; } - -// .install-link { -// transition: box-shadow .2s; -// display: inline-block; -// background-color: hsl(120, 100%, 16%); -// padding: .5em 1em; -// color: hsl(0, 0%, 100%); -// text-decoration: none; -// border-radius: .25rem 0 0 .25rem; -// } - -// .install-help { -// background-color: hsl(120, 67%, 35%); -// color: hsl(0, 0%, 100%); -// border-radius: 0 .25rem .25rem 0; -// } diff --git a/src/sass/fontawesome/_animated.scss b/src/sass/fontawesome/_animated.scss new file mode 100644 index 0000000..779125e --- /dev/null +++ b/src/sass/fontawesome/_animated.scss @@ -0,0 +1,152 @@ +// animating icons +// -------------------------- + +.#{$fa-css-prefix}-beat { + animation-name: #{$fa-css-prefix}-beat; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); +} + +.#{$fa-css-prefix}-bounce { + animation-name: #{$fa-css-prefix}-bounce; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(0.280, 0.840, 0.420, 1)); +} + +.#{$fa-css-prefix}-fade { + animation-name: #{$fa-css-prefix}-fade; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); +} + +.#{$fa-css-prefix}-beat-fade { + animation-name: #{$fa-css-prefix}-beat-fade; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1)); +} + +.#{$fa-css-prefix}-flip { + animation-name: #{$fa-css-prefix}-flip; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out); +} + +.#{$fa-css-prefix}-shake { + animation-name: #{$fa-css-prefix}-shake; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); +} + +.#{$fa-css-prefix}-spin { + animation-name: #{$fa-css-prefix}-spin; + animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s); + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 2s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear); +} + +.#{$fa-css-prefix}-spin-reverse { + --#{$fa-css-prefix}-animation-direction: reverse; +} + +.#{$fa-css-prefix}-pulse, +.#{$fa-css-prefix}-spin-pulse { + animation-name: #{$fa-css-prefix}-spin; + animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal); + animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s); + animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite); + animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, steps(8)); +} + +// if agent or operating system prefers reduced motion, disable animations +// see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/ +// see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion +@media (prefers-reduced-motion: reduce) { + .#{$fa-css-prefix}-beat, + .#{$fa-css-prefix}-bounce, + .#{$fa-css-prefix}-fade, + .#{$fa-css-prefix}-beat-fade, + .#{$fa-css-prefix}-flip, + .#{$fa-css-prefix}-pulse, + .#{$fa-css-prefix}-shake, + .#{$fa-css-prefix}-spin, + .#{$fa-css-prefix}-spin-pulse { + animation-delay: -1ms; + animation-duration: 1ms; + animation-iteration-count: 1; + transition-delay: 0s; + transition-duration: 0s; + } +} + +@keyframes #{$fa-css-prefix}-beat { + 0%, 90% { transform: scale(1); } + 45% { transform: scale(var(--#{$fa-css-prefix}-beat-scale, 1.25)); } +} + +@keyframes #{$fa-css-prefix}-bounce { + 0% { transform: scale(1,1) translateY(0); } + 10% { transform: scale(var(--#{$fa-css-prefix}-bounce-start-scale-x, 1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { transform: scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x, 0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y, 1.1)) translateY(var(--#{$fa-css-prefix}-bounce-height, -0.5em)); } + 50% { transform: scale(var(--#{$fa-css-prefix}-bounce-land-scale-x, 1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { transform: scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound, -0.125em)); } + 64% { transform: scale(1,1) translateY(0); } + 100% { transform: scale(1,1) translateY(0); } +} + +@keyframes #{$fa-css-prefix}-fade { + 50% { opacity: var(--#{$fa-css-prefix}-fade-opacity, 0.4); } +} + +@keyframes #{$fa-css-prefix}-beat-fade { + 0%, 100% { + opacity: var(--#{$fa-css-prefix}-beat-fade-opacity, 0.4); + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(var(--#{$fa-css-prefix}-beat-fade-scale, 1.125)); + } +} + +@keyframes #{$fa-css-prefix}-flip { + 50% { + transform: rotate3d(var(--#{$fa-css-prefix}-flip-x, 0), var(--#{$fa-css-prefix}-flip-y, 1), var(--#{$fa-css-prefix}-flip-z, 0), var(--#{$fa-css-prefix}-flip-angle, -180deg)); + } +} + +@keyframes #{$fa-css-prefix}-shake { + 0% { transform: rotate(-15deg); } + 4% { transform: rotate(15deg); } + 8%, 24% { transform: rotate(-18deg); } + 12%, 28% { transform: rotate(18deg); } + 16% { transform: rotate(-22deg); } + 20% { transform: rotate(22deg); } + 32% { transform: rotate(-12deg); } + 36% { transform: rotate(12deg); } + 40%, 100% { transform: rotate(0deg); } +} + +@keyframes #{$fa-css-prefix}-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/src/sass/fontawesome/_bordered-pulled.scss b/src/sass/fontawesome/_bordered-pulled.scss new file mode 100644 index 0000000..06bf72d --- /dev/null +++ b/src/sass/fontawesome/_bordered-pulled.scss @@ -0,0 +1,20 @@ +// bordered + pulled icons +// ------------------------- + +.#{$fa-css-prefix}-border { + border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color}); + border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius}); + border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style}); + border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width}); + padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding}); +} + +.#{$fa-css-prefix}-pull-left { + float: left; + margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); +} + +.#{$fa-css-prefix}-pull-right { + float: right; + margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin}); +} diff --git a/src/sass/fontawesome/_core.scss b/src/sass/fontawesome/_core.scss new file mode 100644 index 0000000..a850b0f --- /dev/null +++ b/src/sass/fontawesome/_core.scss @@ -0,0 +1,49 @@ +// base icon class definition +// ------------------------- + +.#{$fa-css-prefix} { + font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}'); + font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style}); +} + +.fas, +.far, +.fab, +.#{$fa-css-prefix}-solid, +.#{$fa-css-prefix}-regular, +.#{$fa-css-prefix}-brands, +.#{$fa-css-prefix} { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: var(--#{$fa-css-prefix}-display, #{$fa-display}); + font-style: normal; + font-variant: normal; + line-height: 1; + text-rendering: auto; +} + +.fas::before, +.far::before, +.fab::before, +.#{$fa-css-prefix}-solid::before, +.#{$fa-css-prefix}-regular::before, +.#{$fa-css-prefix}-brands::before, +.#{$fa-css-prefix}::before { + content: var(#{$fa-icon-property}); +} + +.#{$fa-css-prefix}-classic, +.fas, +.#{$fa-css-prefix}-solid, +.far, +.#{$fa-css-prefix}-regular { + font-family: 'Font Awesome 6 Free'; +} +.#{$fa-css-prefix}-brands, +.fab { + font-family: 'Font Awesome 6 Brands'; +} + +%fa-icon { + @include fa-icon; +} diff --git a/src/sass/fontawesome/_fixed-width.scss b/src/sass/fontawesome/_fixed-width.scss new file mode 100644 index 0000000..7234236 --- /dev/null +++ b/src/sass/fontawesome/_fixed-width.scss @@ -0,0 +1,7 @@ +// fixed-width icons +// ------------------------- + +.#{$fa-css-prefix}-fw { + text-align: center; + width: $fa-fw-width; +} diff --git a/src/sass/fontawesome/_functions.scss b/src/sass/fontawesome/_functions.scss new file mode 100644 index 0000000..a17ffe8 --- /dev/null +++ b/src/sass/fontawesome/_functions.scss @@ -0,0 +1,57 @@ +// functions +// -------------------------- + +// fa-content: convenience function used to set content property +@function fa-content($fa-var) { + @return unquote("\"#{ $fa-var }\""); +} + +// fa-divide: Originally obtained from the Bootstrap https://github.com/twbs/bootstrap +// +// Licensed under: The MIT License (MIT) +// +// Copyright (c) 2011-2021 Twitter, Inc. +// Copyright (c) 2011-2021 The Bootstrap Authors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +@function fa-divide($dividend, $divisor, $precision: 10) { + $sign: if($dividend > 0 and $divisor > 0, 1, -1); + $dividend: abs($dividend); + $divisor: abs($divisor); + $quotient: 0; + $remainder: $dividend; + @if $dividend == 0 { + @return 0; + } + @if $divisor == 0 { + @error "Cannot divide by 0"; + } + @if $divisor == 1 { + @return $dividend; + } + @while $remainder >= $divisor { + $quotient: $quotient + 1; + $remainder: $remainder - $divisor; + } + @if $remainder > 0 and $precision > 0 { + $remainder: fa-divide($remainder * 10, $divisor, $precision - 1) * .1; + } + @return ($quotient + $remainder) * $sign; +} diff --git a/src/sass/fontawesome/_icons.scss b/src/sass/fontawesome/_icons.scss new file mode 100644 index 0000000..2f13535 --- /dev/null +++ b/src/sass/fontawesome/_icons.scss @@ -0,0 +1,12 @@ +// specific icon class definition +// ------------------------- + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ + +@each $name, $icon in $fa-icons { + .#{$fa-css-prefix}-#{$name} { + #{$fa-icon-property}: unquote("\"#{ $icon }\""); + } +} + diff --git a/src/sass/fontawesome/_list.scss b/src/sass/fontawesome/_list.scss new file mode 100644 index 0000000..120506a --- /dev/null +++ b/src/sass/fontawesome/_list.scss @@ -0,0 +1,18 @@ +// icons in a list +// ------------------------- + +.#{$fa-css-prefix}-ul { + list-style-type: none; + margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin}); + padding-left: 0; + + > li { position: relative; } +} + +.#{$fa-css-prefix}-li { + left: calc(-1 * var(--#{$fa-css-prefix}-li-width, #{$fa-li-width})); + position: absolute; + text-align: center; + width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}); + line-height: inherit; +} diff --git a/src/sass/fontawesome/_mixins.scss b/src/sass/fontawesome/_mixins.scss new file mode 100644 index 0000000..71f99ad --- /dev/null +++ b/src/sass/fontawesome/_mixins.scss @@ -0,0 +1,65 @@ +// mixins +// -------------------------- + +// base rendering for an icon +@mixin fa-icon { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + display: inline-block; + font-style: normal; + font-variant: normal; + font-weight: normal; + line-height: 1; +} + +// sets relative font-sizing and alignment (in _sizing) +@mixin fa-size ($font-size) { + font-size: fa-divide($font-size, $fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base + line-height: fa-divide(1, $font-size) * 1em; // sets the line-height of the icon back to that of it's parent + vertical-align: (fa-divide(6, $font-size) - fa-divide(3, 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender +} + +// only display content to screen readers +// see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/ +// see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/ +@mixin fa-sr-only() { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +// use in conjunction with .sr-only to only display content when it's focused +@mixin fa-sr-only-focusable() { + &:not(:focus) { + @include fa-sr-only(); + } +} + +// sets a specific icon family to use alongside style + icon mixins +@mixin fa-family-classic() { + @extend .fa-classic; +} + +// convenience mixins for declaring pseudo-elements by CSS variable, +// including all style-specific font properties +@mixin fa-icon-solid($fa-var) { + @extend .fa-solid; + + & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } +} +@mixin fa-icon-regular($fa-var) { + @extend .fa-regular; + + & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } +} +@mixin fa-icon-brands($fa-var) { + @extend .fa-brands; + + & { #{$fa-icon-property}: unquote("\"#{ $fa-var }\""); #{$fa-duotone-icon-property}: unquote("\"#{ $fa-var }#{ $fa-var }\""); } +} diff --git a/src/sass/fontawesome/_rotated-flipped.scss b/src/sass/fontawesome/_rotated-flipped.scss new file mode 100644 index 0000000..0bd4b7c --- /dev/null +++ b/src/sass/fontawesome/_rotated-flipped.scss @@ -0,0 +1,31 @@ +// rotating + flipping icons +// ------------------------- + +.#{$fa-css-prefix}-rotate-90 { + transform: rotate(90deg); +} + +.#{$fa-css-prefix}-rotate-180 { + transform: rotate(180deg); +} + +.#{$fa-css-prefix}-rotate-270 { + transform: rotate(270deg); +} + +.#{$fa-css-prefix}-flip-horizontal { + transform: scale(-1, 1); +} + +.#{$fa-css-prefix}-flip-vertical { + transform: scale(1, -1); +} + +.#{$fa-css-prefix}-flip-both, +.#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { + transform: scale(-1, -1); +} + +.#{$fa-css-prefix}-rotate-by { + transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, 0)); +} diff --git a/src/sass/fontawesome/_screen-reader.scss b/src/sass/fontawesome/_screen-reader.scss new file mode 100644 index 0000000..2beb887 --- /dev/null +++ b/src/sass/fontawesome/_screen-reader.scss @@ -0,0 +1,14 @@ +// screen-reader utilities +// ------------------------- + +// only display content to screen readers +.sr-only, +.#{$fa-css-prefix}-sr-only { + @include fa-sr-only; +} + +// use in conjunction with .sr-only to only display content when it's focused +.sr-only-focusable, +.#{$fa-css-prefix}-sr-only-focusable { + @include fa-sr-only-focusable; +} diff --git a/src/sass/fontawesome/_shims.scss b/src/sass/fontawesome/_shims.scss new file mode 100644 index 0000000..3fb6d41 --- /dev/null +++ b/src/sass/fontawesome/_shims.scss @@ -0,0 +1,1578 @@ +.#{$fa-css-prefix}.#{$fa-css-prefix}-glass { #{$fa-icon-property}: unquote("\"#{ $fa-var-martini-glass-empty }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-envelope-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-envelope-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-envelope }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-star }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-remove { #{$fa-icon-property}: unquote("\"#{ $fa-var-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-close { #{$fa-icon-property}: unquote("\"#{ $fa-var-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-gear { #{$fa-icon-property}: unquote("\"#{ $fa-var-gear }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-trash-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-trash-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-trash-can }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-home { #{$fa-icon-property}: unquote("\"#{ $fa-var-house }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-clock-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-clock-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-clock }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-down { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-up { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-play-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-play-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-play }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-repeat { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-rotate-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rotate-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-rotate-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-refresh { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrows-rotate }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-list-alt { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-list-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-rectangle-list }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-dedent { #{$fa-icon-property}: unquote("\"#{ $fa-var-outdent }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-video-camera { #{$fa-icon-property}: unquote("\"#{ $fa-var-video }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-picture-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-picture-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-photo { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-photo { #{$fa-icon-property}: unquote("\"#{ $fa-var-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-image { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-image { #{$fa-icon-property}: unquote("\"#{ $fa-var-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-map-marker { #{$fa-icon-property}: unquote("\"#{ $fa-var-location-dot }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pencil-square-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pencil-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-pen-to-square }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-edit { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-edit { #{$fa-icon-property}: unquote("\"#{ $fa-var-pen-to-square }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-share-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-share-from-square }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-check-square-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-check-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-check }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrows { #{$fa-icon-property}: unquote("\"#{ $fa-var-up-down-left-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-times-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-times-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-check-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-check-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-check }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mail-forward { #{$fa-icon-property}: unquote("\"#{ $fa-var-share }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-expand { #{$fa-icon-property}: unquote("\"#{ $fa-var-up-right-and-down-left-from-center }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-compress { #{$fa-icon-property}: unquote("\"#{ $fa-var-down-left-and-up-right-to-center }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-eye { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-eye-slash { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-warning { #{$fa-icon-property}: unquote("\"#{ $fa-var-triangle-exclamation }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar-days }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrows-v { #{$fa-icon-property}: unquote("\"#{ $fa-var-up-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrows-h { #{$fa-icon-property}: unquote("\"#{ $fa-var-left-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bar-chart { #{$fa-icon-property}: unquote("\"#{ $fa-var-chart-column }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bar-chart-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-chart-column }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-twitter-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-twitter-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-twitter }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-facebook }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-gears { #{$fa-icon-property}: unquote("\"#{ $fa-var-gears }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thumbs-o-up { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-thumbs-o-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-thumbs-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thumbs-o-down { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-thumbs-o-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-thumbs-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-heart-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-heart-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-heart }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sign-out { #{$fa-icon-property}: unquote("\"#{ $fa-var-right-from-bracket }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-linkedin-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-linkedin-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-linkedin }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thumb-tack { #{$fa-icon-property}: unquote("\"#{ $fa-var-thumbtack }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-external-link { #{$fa-icon-property}: unquote("\"#{ $fa-var-up-right-from-square }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sign-in { #{$fa-icon-property}: unquote("\"#{ $fa-var-right-to-bracket }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-github-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-github-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-github }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-lemon-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-lemon-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-lemon }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-square-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-square }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bookmark-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bookmark-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-bookmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-twitter { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook { #{$fa-icon-property}: unquote("\"#{ $fa-var-facebook-f }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-f { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-f { #{$fa-icon-property}: unquote("\"#{ $fa-var-facebook-f }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-github { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-credit-card { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-feed { #{$fa-icon-property}: unquote("\"#{ $fa-var-rss }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hdd-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hdd-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hard-drive }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-right { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-point-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-left { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-point-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-up { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-point-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-down { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-o-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-point-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-globe { #{$fa-icon-property}: unquote("\"#{ $fa-var-earth-americas }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-tasks { #{$fa-icon-property}: unquote("\"#{ $fa-var-bars-progress }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrows-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-maximize }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-group { #{$fa-icon-property}: unquote("\"#{ $fa-var-users }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-chain { #{$fa-icon-property}: unquote("\"#{ $fa-var-link }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cut { #{$fa-icon-property}: unquote("\"#{ $fa-var-scissors }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-files-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-files-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-copy }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-floppy-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-floppy-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-floppy-disk }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-save { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-save { #{$fa-icon-property}: unquote("\"#{ $fa-var-floppy-disk }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-navicon { #{$fa-icon-property}: unquote("\"#{ $fa-var-bars }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-reorder { #{$fa-icon-property}: unquote("\"#{ $fa-var-bars }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-magic { #{$fa-icon-property}: unquote("\"#{ $fa-var-wand-magic-sparkles }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pinterest { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pinterest-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pinterest-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-pinterest }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-google-plus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus { #{$fa-icon-property}: unquote("\"#{ $fa-var-google-plus-g }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-money { #{$fa-icon-property}: unquote("\"#{ $fa-var-money-bill-1 }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-unsorted { #{$fa-icon-property}: unquote("\"#{ $fa-var-sort }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-desc { #{$fa-icon-property}: unquote("\"#{ $fa-var-sort-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-asc { #{$fa-icon-property}: unquote("\"#{ $fa-var-sort-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-linkedin { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-linkedin { #{$fa-icon-property}: unquote("\"#{ $fa-var-linkedin-in }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rotate-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-rotate-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-legal { #{$fa-icon-property}: unquote("\"#{ $fa-var-gavel }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-tachometer { #{$fa-icon-property}: unquote("\"#{ $fa-var-gauge-high }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-dashboard { #{$fa-icon-property}: unquote("\"#{ $fa-var-gauge-high }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-comment-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-comment-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-comment }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-comments-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-comments-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-comments }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-flash { #{$fa-icon-property}: unquote("\"#{ $fa-var-bolt }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-clipboard { #{$fa-icon-property}: unquote("\"#{ $fa-var-paste }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-lightbulb-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-lightbulb-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-lightbulb }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-exchange { #{$fa-icon-property}: unquote("\"#{ $fa-var-right-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cloud-download { #{$fa-icon-property}: unquote("\"#{ $fa-var-cloud-arrow-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cloud-upload { #{$fa-icon-property}: unquote("\"#{ $fa-var-cloud-arrow-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bell-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bell-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-bell }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cutlery { #{$fa-icon-property}: unquote("\"#{ $fa-var-utensils }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-text-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-text-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-lines }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-building-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-building-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-building }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hospital-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hospital-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hospital }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-tablet { #{$fa-icon-property}: unquote("\"#{ $fa-var-tablet-screen-button }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mobile { #{$fa-icon-property}: unquote("\"#{ $fa-var-mobile-screen-button }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mobile-phone { #{$fa-icon-property}: unquote("\"#{ $fa-var-mobile-screen-button }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mail-reply { #{$fa-icon-property}: unquote("\"#{ $fa-var-reply }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-github-alt { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-folder-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-folder-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-folder }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-folder-open-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-folder-open-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-folder-open }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-smile-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-smile-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-face-smile }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-frown-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-frown-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-face-frown }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-meh-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-meh-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-face-meh }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-keyboard-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-keyboard-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-keyboard }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-flag-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-flag-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-flag }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mail-reply-all { #{$fa-icon-property}: unquote("\"#{ $fa-var-reply-all }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-star-half-stroke }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-empty { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-empty { #{$fa-icon-property}: unquote("\"#{ $fa-var-star-half-stroke }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-full { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-star-half-full { #{$fa-icon-property}: unquote("\"#{ $fa-var-star-half-stroke }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-code-fork { #{$fa-icon-property}: unquote("\"#{ $fa-var-code-branch }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-chain-broken { #{$fa-icon-property}: unquote("\"#{ $fa-var-link-slash }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-unlink { #{$fa-icon-property}: unquote("\"#{ $fa-var-link-slash }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-maxcdn { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-html5 { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-css3 { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-unlock-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-unlock }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-minus-square-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-minus-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-minus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-level-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-turn-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-level-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-turn-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pencil-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-pen }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-external-link-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-up-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-compass { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-down { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-down { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-down }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-up { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-up { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-up }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-right { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-right { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-eur { #{$fa-icon-property}: unquote("\"#{ $fa-var-euro-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-euro { #{$fa-icon-property}: unquote("\"#{ $fa-var-euro-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-gbp { #{$fa-icon-property}: unquote("\"#{ $fa-var-sterling-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-usd { #{$fa-icon-property}: unquote("\"#{ $fa-var-dollar-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-dollar { #{$fa-icon-property}: unquote("\"#{ $fa-var-dollar-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-inr { #{$fa-icon-property}: unquote("\"#{ $fa-var-indian-rupee-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rupee { #{$fa-icon-property}: unquote("\"#{ $fa-var-indian-rupee-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-jpy { #{$fa-icon-property}: unquote("\"#{ $fa-var-yen-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cny { #{$fa-icon-property}: unquote("\"#{ $fa-var-yen-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rmb { #{$fa-icon-property}: unquote("\"#{ $fa-var-yen-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-yen { #{$fa-icon-property}: unquote("\"#{ $fa-var-yen-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rub { #{$fa-icon-property}: unquote("\"#{ $fa-var-ruble-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-ruble { #{$fa-icon-property}: unquote("\"#{ $fa-var-ruble-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rouble { #{$fa-icon-property}: unquote("\"#{ $fa-var-ruble-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-krw { #{$fa-icon-property}: unquote("\"#{ $fa-var-won-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-won { #{$fa-icon-property}: unquote("\"#{ $fa-var-won-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-btc { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bitcoin { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bitcoin { #{$fa-icon-property}: unquote("\"#{ $fa-var-btc }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-text { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-lines }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-alpha-asc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-a-z }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-alpha-desc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-z-a }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-amount-asc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-short-wide }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-amount-desc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-wide-short }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-numeric-asc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-1-9 }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sort-numeric-desc { #{$fa-icon-property}: unquote("\"#{ $fa-var-arrow-down-9-1 }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-youtube-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-youtube-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-youtube }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-youtube { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-xing { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-xing-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-xing-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-xing }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-youtube-play { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-youtube-play { #{$fa-icon-property}: unquote("\"#{ $fa-var-youtube }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-dropbox { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-stack-overflow { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-instagram { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-flickr { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-adn { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bitbucket { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bitbucket-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bitbucket-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-bitbucket }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-tumblr { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-tumblr-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-tumblr-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-tumblr }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-long-arrow-down { #{$fa-icon-property}: unquote("\"#{ $fa-var-down-long }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-long-arrow-up { #{$fa-icon-property}: unquote("\"#{ $fa-var-up-long }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-long-arrow-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-left-long }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-long-arrow-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-right-long }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-apple { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-windows { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-android { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-linux { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-dribbble { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-skype { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-foursquare { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-trello { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gratipay { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gittip { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gittip { #{$fa-icon-property}: unquote("\"#{ $fa-var-gratipay }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sun-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-sun-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-sun }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-moon-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-moon-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-moon }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-vk { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-weibo { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-renren { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pagelines { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-stack-exchange { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-right { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-right { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-right }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-left { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-arrow-circle-o-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-left { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-caret-square-o-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-left { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-toggle-left { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-caret-left }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-dot-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-dot-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-dot }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-vimeo-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-vimeo-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-vimeo }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-try { #{$fa-icon-property}: unquote("\"#{ $fa-var-turkish-lira-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-turkish-lira { #{$fa-icon-property}: unquote("\"#{ $fa-var-turkish-lira-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-plus-square-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-plus-square-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-plus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-slack { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wordpress { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-openid { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-institution { #{$fa-icon-property}: unquote("\"#{ $fa-var-building-columns }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bank { #{$fa-icon-property}: unquote("\"#{ $fa-var-building-columns }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-mortar-board { #{$fa-icon-property}: unquote("\"#{ $fa-var-graduation-cap }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-yahoo { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-reddit { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-reddit-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-reddit-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-reddit }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-stumbleupon-circle { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-stumbleupon { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-delicious { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-digg { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pied-piper-pp { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pied-piper-alt { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-drupal { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-joomla { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-behance { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-behance-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-behance-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-behance }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-steam { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-steam-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-steam-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-steam }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-automobile { #{$fa-icon-property}: unquote("\"#{ $fa-var-car }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cab { #{$fa-icon-property}: unquote("\"#{ $fa-var-taxi }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-spotify { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-deviantart { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-soundcloud { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-pdf-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-pdf-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-pdf }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-word-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-word-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-word }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-excel-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-excel-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-excel }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-powerpoint-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-powerpoint-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-powerpoint }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-image-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-image-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-photo-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-photo-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-picture-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-picture-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-image }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-archive-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-archive-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-zipper }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-zip-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-zip-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-zipper }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-audio-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-audio-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-audio }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-sound-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-sound-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-audio }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-video-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-video-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-video }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-movie-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-movie-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-video }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-code-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-file-code-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-file-code }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-vine { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-codepen { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-jsfiddle { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-life-bouy { #{$fa-icon-property}: unquote("\"#{ $fa-var-life-ring }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-life-buoy { #{$fa-icon-property}: unquote("\"#{ $fa-var-life-ring }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-life-saver { #{$fa-icon-property}: unquote("\"#{ $fa-var-life-ring }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-support { #{$fa-icon-property}: unquote("\"#{ $fa-var-life-ring }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-circle-o-notch { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-notch }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-rebel { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-ra { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-ra { #{$fa-icon-property}: unquote("\"#{ $fa-var-rebel }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-resistance { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-resistance { #{$fa-icon-property}: unquote("\"#{ $fa-var-rebel }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-empire { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-ge { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-ge { #{$fa-icon-property}: unquote("\"#{ $fa-var-empire }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-git-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-git-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-git }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-git { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hacker-news { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-y-combinator-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-y-combinator-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-hacker-news }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-yc-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-yc-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-hacker-news }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-tencent-weibo { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-qq { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-weixin { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wechat { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wechat { #{$fa-icon-property}: unquote("\"#{ $fa-var-weixin }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-send { #{$fa-icon-property}: unquote("\"#{ $fa-var-paper-plane }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-paper-plane-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-paper-plane-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-paper-plane }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-send-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-send-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-paper-plane }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-circle-thin { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-circle-thin { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-header { #{$fa-icon-property}: unquote("\"#{ $fa-var-heading }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-futbol-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-futbol-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-futbol }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-soccer-ball-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-soccer-ball-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-futbol }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-slideshare { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-twitch { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-yelp { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-newspaper-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-newspaper-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-newspaper }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-paypal { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-wallet { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-visa { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-mastercard { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-discover { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-amex { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-paypal { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-stripe { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bell-slash-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bell-slash-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-bell-slash }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-trash { #{$fa-icon-property}: unquote("\"#{ $fa-var-trash-can }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-copyright { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-eyedropper { #{$fa-icon-property}: unquote("\"#{ $fa-var-eye-dropper }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-area-chart { #{$fa-icon-property}: unquote("\"#{ $fa-var-chart-area }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pie-chart { #{$fa-icon-property}: unquote("\"#{ $fa-var-chart-pie }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-line-chart { #{$fa-icon-property}: unquote("\"#{ $fa-var-chart-line }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-lastfm { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-lastfm-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-lastfm-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-lastfm }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-ioxhost { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-angellist { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc { #{$fa-icon-property}: unquote("\"#{ $fa-var-closed-captioning }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-ils { #{$fa-icon-property}: unquote("\"#{ $fa-var-shekel-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-shekel { #{$fa-icon-property}: unquote("\"#{ $fa-var-shekel-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-sheqel { #{$fa-icon-property}: unquote("\"#{ $fa-var-shekel-sign }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-buysellads { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-connectdevelop { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-dashcube { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-forumbee { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-leanpub { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-sellsy { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-shirtsinbulk { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-simplybuilt { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-skyatlas { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-diamond { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-diamond { #{$fa-icon-property}: unquote("\"#{ $fa-var-gem }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-transgender { #{$fa-icon-property}: unquote("\"#{ $fa-var-mars-and-venus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-intersex { #{$fa-icon-property}: unquote("\"#{ $fa-var-mars-and-venus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-transgender-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-transgender }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-official { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-facebook-official { #{$fa-icon-property}: unquote("\"#{ $fa-var-facebook }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pinterest-p { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-whatsapp { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hotel { #{$fa-icon-property}: unquote("\"#{ $fa-var-bed }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-viacoin { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-medium { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-y-combinator { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-yc { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-yc { #{$fa-icon-property}: unquote("\"#{ $fa-var-y-combinator }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-optin-monster { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-opencart { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-expeditedssl { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery-4 { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-full }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-full }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery-3 { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-three-quarters }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery-2 { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-half }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery-1 { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-quarter }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-battery-0 { #{$fa-icon-property}: unquote("\"#{ $fa-var-battery-empty }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-object-group { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-object-ungroup { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-sticky-note-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-sticky-note-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-note-sticky }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-jcb { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-cc-diners-club { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-clone { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hourglass-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hourglass }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hourglass-1 { #{$fa-icon-property}: unquote("\"#{ $fa-var-hourglass-start }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hourglass-2 { #{$fa-icon-property}: unquote("\"#{ $fa-var-hourglass-half }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hourglass-3 { #{$fa-icon-property}: unquote("\"#{ $fa-var-hourglass-end }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-rock-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-rock-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-back-fist }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-grab-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-grab-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-back-fist }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-paper-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-paper-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-stop-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-stop-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-scissors-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-scissors-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-scissors }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-lizard-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-lizard-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-lizard }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-spock-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-spock-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-spock }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-pointer-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-pointer-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-pointer }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-peace-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-hand-peace-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-hand-peace }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-registered { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-creative-commons { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gg { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gg-circle { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-odnoklassniki { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-odnoklassniki-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-odnoklassniki-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-odnoklassniki }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-get-pocket { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wikipedia-w { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-safari { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-chrome { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-firefox { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-opera { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-internet-explorer { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-television { #{$fa-icon-property}: unquote("\"#{ $fa-var-tv }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-contao { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-500px { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-amazon { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-plus-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-plus-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar-plus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-minus-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-minus-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar-minus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-times-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-times-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-check-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-calendar-check-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-calendar-check }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-map-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-map-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-map }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-commenting { #{$fa-icon-property}: unquote("\"#{ $fa-var-comment-dots }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-commenting-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-commenting-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-comment-dots }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-houzz { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-vimeo { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-vimeo { #{$fa-icon-property}: unquote("\"#{ $fa-var-vimeo-v }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-black-tie { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-fonticons { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-reddit-alien { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-edge { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-credit-card-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-credit-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-codiepie { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-modx { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-fort-awesome { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-usb { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-product-hunt { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-mixcloud { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-scribd { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pause-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-pause-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-pause }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-stop-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-stop-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-stop }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bluetooth { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-bluetooth-b { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-gitlab { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wpbeginner { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wpforms { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-envira { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wheelchair-alt { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wheelchair-alt { #{$fa-icon-property}: unquote("\"#{ $fa-var-accessible-icon }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-question-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-question-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-question }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-volume-control-phone { #{$fa-icon-property}: unquote("\"#{ $fa-var-phone-volume }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-asl-interpreting { #{$fa-icon-property}: unquote("\"#{ $fa-var-hands-asl-interpreting }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-deafness { #{$fa-icon-property}: unquote("\"#{ $fa-var-ear-deaf }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-hard-of-hearing { #{$fa-icon-property}: unquote("\"#{ $fa-var-ear-deaf }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-glide { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-glide-g { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-signing { #{$fa-icon-property}: unquote("\"#{ $fa-var-hands }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-viadeo { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-viadeo-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-viadeo-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-viadeo }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-snapchat { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-snapchat-ghost { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-snapchat-ghost { #{$fa-icon-property}: unquote("\"#{ $fa-var-snapchat }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-snapchat-square { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-snapchat-square { #{$fa-icon-property}: unquote("\"#{ $fa-var-square-snapchat }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-pied-piper { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-first-order { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-yoast { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-themeisle { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-official { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-official { #{$fa-icon-property}: unquote("\"#{ $fa-var-google-plus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-circle { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-google-plus-circle { #{$fa-icon-property}: unquote("\"#{ $fa-var-google-plus }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-font-awesome { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-fa { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-fa { #{$fa-icon-property}: unquote("\"#{ $fa-var-font-awesome }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-handshake-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-handshake-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-handshake }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-envelope-open-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-envelope-open-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-envelope-open }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-linode { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-address-book-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-address-book-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-address-book }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-vcard { #{$fa-icon-property}: unquote("\"#{ $fa-var-address-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-address-card-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-address-card-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-address-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-vcard-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-vcard-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-address-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-user-circle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-user-circle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-circle-user }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-user-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-user-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-user }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-id-badge { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-drivers-license { #{$fa-icon-property}: unquote("\"#{ $fa-var-id-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-id-card-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-id-card-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-id-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-drivers-license-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-drivers-license-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-id-card }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-quora { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-free-code-camp { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-telegram { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer-4 { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-full }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-full }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer-3 { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-three-quarters }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer-2 { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-half }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer-1 { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-quarter }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-thermometer-0 { #{$fa-icon-property}: unquote("\"#{ $fa-var-temperature-empty }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bathtub { #{$fa-icon-property}: unquote("\"#{ $fa-var-bath }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-s15 { #{$fa-icon-property}: unquote("\"#{ $fa-var-bath }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-window-maximize { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-window-restore { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-times-rectangle { #{$fa-icon-property}: unquote("\"#{ $fa-var-rectangle-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-window-close-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-window-close-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-rectangle-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-times-rectangle-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-times-rectangle-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-rectangle-xmark }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-bandcamp { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-grav { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-etsy { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-imdb { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-ravelry { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-eercast { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-eercast { #{$fa-icon-property}: unquote("\"#{ $fa-var-sellcast }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-snowflake-o { + font-family: 'Font Awesome 6 Free'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-snowflake-o { #{$fa-icon-property}: unquote("\"#{ $fa-var-snowflake }\""); } +.#{$fa-css-prefix}.#{$fa-css-prefix}-superpowers { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-wpexplorer { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} +.#{$fa-css-prefix}.#{$fa-css-prefix}-meetup { + font-family: 'Font Awesome 6 Brands'; + font-weight: 400; +} diff --git a/src/sass/fontawesome/_sizing.scss b/src/sass/fontawesome/_sizing.scss new file mode 100644 index 0000000..e171e7d --- /dev/null +++ b/src/sass/fontawesome/_sizing.scss @@ -0,0 +1,16 @@ +// sizing icons +// ------------------------- + +// literal magnification scale +@for $i from 1 through 10 { + .#{$fa-css-prefix}-#{$i}x { + font-size: $i * 1em; + } +} + +// step-based scale (with alignment) +@each $size, $value in $fa-sizes { + .#{$fa-css-prefix}-#{$size} { + @include fa-size($value); + } +} diff --git a/src/sass/fontawesome/_stacked.scss b/src/sass/fontawesome/_stacked.scss new file mode 100644 index 0000000..d9a9d4e --- /dev/null +++ b/src/sass/fontawesome/_stacked.scss @@ -0,0 +1,32 @@ +// stacking icons +// ------------------------- + +.#{$fa-css-prefix}-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: $fa-stack-vertical-align; + width: $fa-stack-width; +} + +.#{$fa-css-prefix}-stack-1x, +.#{$fa-css-prefix}-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; + z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index}); +} + +.#{$fa-css-prefix}-stack-1x { + line-height: inherit; +} + +.#{$fa-css-prefix}-stack-2x { + font-size: 2em; +} + +.#{$fa-css-prefix}-inverse { + color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse}); +} diff --git a/src/sass/fontawesome/_variables.scss b/src/sass/fontawesome/_variables.scss new file mode 100644 index 0000000..87d19d3 --- /dev/null +++ b/src/sass/fontawesome/_variables.scss @@ -0,0 +1,5044 @@ +// variables +// -------------------------- + +$fa-css-prefix : fa !default; +$fa-style : 900 !default; +$fa-style-family : "Font Awesome 6 Free" !default; + +$fa-icon-property : --fa; +$fa-duotone-icon-property : --fa--fa; + +$fa-display : inline-block !default; + +$fa-fw-width : fa-divide(20em, 16) !default; +$fa-inverse : #fff !default; + +$fa-border-color : #eee !default; +$fa-border-padding : .2em .25em .15em !default; +$fa-border-radius : .1em !default; +$fa-border-style : solid !default; +$fa-border-width : .08em !default; + +$fa-size-scale-2xs : 10 !default; +$fa-size-scale-xs : 12 !default; +$fa-size-scale-sm : 14 !default; +$fa-size-scale-base : 16 !default; +$fa-size-scale-lg : 20 !default; +$fa-size-scale-xl : 24 !default; +$fa-size-scale-2xl : 32 !default; + +$fa-sizes: ( + "2xs" : $fa-size-scale-2xs, + "xs" : $fa-size-scale-xs, + "sm" : $fa-size-scale-sm, + "lg" : $fa-size-scale-lg, + "xl" : $fa-size-scale-xl, + "2xl" : $fa-size-scale-2xl +) !default; + +$fa-li-width : 2em !default; +$fa-li-margin : $fa-li-width * fa-divide(5, 4) !default; + +$fa-pull-margin : .3em !default; + +$fa-primary-opacity : 1 !default; +$fa-secondary-opacity : .4 !default; + +$fa-stack-vertical-align : middle !default; +$fa-stack-width : ($fa-fw-width * 2) !default; +$fa-stack-z-index : auto !default; + +$fa-font-display : block !default; +$fa-font-path : "../../webfonts" !default; + +$fa-var-0: \30; +$fa-var-1: \31; +$fa-var-2: \32; +$fa-var-3: \33; +$fa-var-4: \34; +$fa-var-5: \35; +$fa-var-6: \36; +$fa-var-7: \37; +$fa-var-8: \38; +$fa-var-9: \39; +$fa-var-fill-drip: \f576; +$fa-var-arrows-to-circle: \e4bd; +$fa-var-circle-chevron-right: \f138; +$fa-var-chevron-circle-right: \f138; +$fa-var-at: \40; +$fa-var-trash-can: \f2ed; +$fa-var-trash-alt: \f2ed; +$fa-var-text-height: \f034; +$fa-var-user-xmark: \f235; +$fa-var-user-times: \f235; +$fa-var-stethoscope: \f0f1; +$fa-var-message: \f27a; +$fa-var-comment-alt: \f27a; +$fa-var-info: \f129; +$fa-var-down-left-and-up-right-to-center: \f422; +$fa-var-compress-alt: \f422; +$fa-var-explosion: \e4e9; +$fa-var-file-lines: \f15c; +$fa-var-file-alt: \f15c; +$fa-var-file-text: \f15c; +$fa-var-wave-square: \f83e; +$fa-var-ring: \f70b; +$fa-var-building-un: \e4d9; +$fa-var-dice-three: \f527; +$fa-var-calendar-days: \f073; +$fa-var-calendar-alt: \f073; +$fa-var-anchor-circle-check: \e4aa; +$fa-var-building-circle-arrow-right: \e4d1; +$fa-var-volleyball: \f45f; +$fa-var-volleyball-ball: \f45f; +$fa-var-arrows-up-to-line: \e4c2; +$fa-var-sort-down: \f0dd; +$fa-var-sort-desc: \f0dd; +$fa-var-circle-minus: \f056; +$fa-var-minus-circle: \f056; +$fa-var-door-open: \f52b; +$fa-var-right-from-bracket: \f2f5; +$fa-var-sign-out-alt: \f2f5; +$fa-var-atom: \f5d2; +$fa-var-soap: \e06e; +$fa-var-icons: \f86d; +$fa-var-heart-music-camera-bolt: \f86d; +$fa-var-microphone-lines-slash: \f539; +$fa-var-microphone-alt-slash: \f539; +$fa-var-bridge-circle-check: \e4c9; +$fa-var-pump-medical: \e06a; +$fa-var-fingerprint: \f577; +$fa-var-hand-point-right: \f0a4; +$fa-var-magnifying-glass-location: \f689; +$fa-var-search-location: \f689; +$fa-var-forward-step: \f051; +$fa-var-step-forward: \f051; +$fa-var-face-smile-beam: \f5b8; +$fa-var-smile-beam: \f5b8; +$fa-var-flag-checkered: \f11e; +$fa-var-football: \f44e; +$fa-var-football-ball: \f44e; +$fa-var-school-circle-exclamation: \e56c; +$fa-var-crop: \f125; +$fa-var-angles-down: \f103; +$fa-var-angle-double-down: \f103; +$fa-var-users-rectangle: \e594; +$fa-var-people-roof: \e537; +$fa-var-people-line: \e534; +$fa-var-beer-mug-empty: \f0fc; +$fa-var-beer: \f0fc; +$fa-var-diagram-predecessor: \e477; +$fa-var-arrow-up-long: \f176; +$fa-var-long-arrow-up: \f176; +$fa-var-fire-flame-simple: \f46a; +$fa-var-burn: \f46a; +$fa-var-person: \f183; +$fa-var-male: \f183; +$fa-var-laptop: \f109; +$fa-var-file-csv: \f6dd; +$fa-var-menorah: \f676; +$fa-var-truck-plane: \e58f; +$fa-var-record-vinyl: \f8d9; +$fa-var-face-grin-stars: \f587; +$fa-var-grin-stars: \f587; +$fa-var-bong: \f55c; +$fa-var-spaghetti-monster-flying: \f67b; +$fa-var-pastafarianism: \f67b; +$fa-var-arrow-down-up-across-line: \e4af; +$fa-var-spoon: \f2e5; +$fa-var-utensil-spoon: \f2e5; +$fa-var-jar-wheat: \e517; +$fa-var-envelopes-bulk: \f674; +$fa-var-mail-bulk: \f674; +$fa-var-file-circle-exclamation: \e4eb; +$fa-var-circle-h: \f47e; +$fa-var-hospital-symbol: \f47e; +$fa-var-pager: \f815; +$fa-var-address-book: \f2b9; +$fa-var-contact-book: \f2b9; +$fa-var-strikethrough: \f0cc; +$fa-var-k: \4b; +$fa-var-landmark-flag: \e51c; +$fa-var-pencil: \f303; +$fa-var-pencil-alt: \f303; +$fa-var-backward: \f04a; +$fa-var-caret-right: \f0da; +$fa-var-comments: \f086; +$fa-var-paste: \f0ea; +$fa-var-file-clipboard: \f0ea; +$fa-var-code-pull-request: \e13c; +$fa-var-clipboard-list: \f46d; +$fa-var-truck-ramp-box: \f4de; +$fa-var-truck-loading: \f4de; +$fa-var-user-check: \f4fc; +$fa-var-vial-virus: \e597; +$fa-var-sheet-plastic: \e571; +$fa-var-blog: \f781; +$fa-var-user-ninja: \f504; +$fa-var-person-arrow-up-from-line: \e539; +$fa-var-scroll-torah: \f6a0; +$fa-var-torah: \f6a0; +$fa-var-broom-ball: \f458; +$fa-var-quidditch: \f458; +$fa-var-quidditch-broom-ball: \f458; +$fa-var-toggle-off: \f204; +$fa-var-box-archive: \f187; +$fa-var-archive: \f187; +$fa-var-person-drowning: \e545; +$fa-var-arrow-down-9-1: \f886; +$fa-var-sort-numeric-desc: \f886; +$fa-var-sort-numeric-down-alt: \f886; +$fa-var-face-grin-tongue-squint: \f58a; +$fa-var-grin-tongue-squint: \f58a; +$fa-var-spray-can: \f5bd; +$fa-var-truck-monster: \f63b; +$fa-var-w: \57; +$fa-var-earth-africa: \f57c; +$fa-var-globe-africa: \f57c; +$fa-var-rainbow: \f75b; +$fa-var-circle-notch: \f1ce; +$fa-var-tablet-screen-button: \f3fa; +$fa-var-tablet-alt: \f3fa; +$fa-var-paw: \f1b0; +$fa-var-cloud: \f0c2; +$fa-var-trowel-bricks: \e58a; +$fa-var-face-flushed: \f579; +$fa-var-flushed: \f579; +$fa-var-hospital-user: \f80d; +$fa-var-tent-arrow-left-right: \e57f; +$fa-var-gavel: \f0e3; +$fa-var-legal: \f0e3; +$fa-var-binoculars: \f1e5; +$fa-var-microphone-slash: \f131; +$fa-var-box-tissue: \e05b; +$fa-var-motorcycle: \f21c; +$fa-var-bell-concierge: \f562; +$fa-var-concierge-bell: \f562; +$fa-var-pen-ruler: \f5ae; +$fa-var-pencil-ruler: \f5ae; +$fa-var-people-arrows: \e068; +$fa-var-people-arrows-left-right: \e068; +$fa-var-mars-and-venus-burst: \e523; +$fa-var-square-caret-right: \f152; +$fa-var-caret-square-right: \f152; +$fa-var-scissors: \f0c4; +$fa-var-cut: \f0c4; +$fa-var-sun-plant-wilt: \e57a; +$fa-var-toilets-portable: \e584; +$fa-var-hockey-puck: \f453; +$fa-var-table: \f0ce; +$fa-var-magnifying-glass-arrow-right: \e521; +$fa-var-tachograph-digital: \f566; +$fa-var-digital-tachograph: \f566; +$fa-var-users-slash: \e073; +$fa-var-clover: \e139; +$fa-var-reply: \f3e5; +$fa-var-mail-reply: \f3e5; +$fa-var-star-and-crescent: \f699; +$fa-var-house-fire: \e50c; +$fa-var-square-minus: \f146; +$fa-var-minus-square: \f146; +$fa-var-helicopter: \f533; +$fa-var-compass: \f14e; +$fa-var-square-caret-down: \f150; +$fa-var-caret-square-down: \f150; +$fa-var-file-circle-question: \e4ef; +$fa-var-laptop-code: \f5fc; +$fa-var-swatchbook: \f5c3; +$fa-var-prescription-bottle: \f485; +$fa-var-bars: \f0c9; +$fa-var-navicon: \f0c9; +$fa-var-people-group: \e533; +$fa-var-hourglass-end: \f253; +$fa-var-hourglass-3: \f253; +$fa-var-heart-crack: \f7a9; +$fa-var-heart-broken: \f7a9; +$fa-var-square-up-right: \f360; +$fa-var-external-link-square-alt: \f360; +$fa-var-face-kiss-beam: \f597; +$fa-var-kiss-beam: \f597; +$fa-var-film: \f008; +$fa-var-ruler-horizontal: \f547; +$fa-var-people-robbery: \e536; +$fa-var-lightbulb: \f0eb; +$fa-var-caret-left: \f0d9; +$fa-var-circle-exclamation: \f06a; +$fa-var-exclamation-circle: \f06a; +$fa-var-school-circle-xmark: \e56d; +$fa-var-arrow-right-from-bracket: \f08b; +$fa-var-sign-out: \f08b; +$fa-var-circle-chevron-down: \f13a; +$fa-var-chevron-circle-down: \f13a; +$fa-var-unlock-keyhole: \f13e; +$fa-var-unlock-alt: \f13e; +$fa-var-cloud-showers-heavy: \f740; +$fa-var-headphones-simple: \f58f; +$fa-var-headphones-alt: \f58f; +$fa-var-sitemap: \f0e8; +$fa-var-circle-dollar-to-slot: \f4b9; +$fa-var-donate: \f4b9; +$fa-var-memory: \f538; +$fa-var-road-spikes: \e568; +$fa-var-fire-burner: \e4f1; +$fa-var-flag: \f024; +$fa-var-hanukiah: \f6e6; +$fa-var-feather: \f52d; +$fa-var-volume-low: \f027; +$fa-var-volume-down: \f027; +$fa-var-comment-slash: \f4b3; +$fa-var-cloud-sun-rain: \f743; +$fa-var-compress: \f066; +$fa-var-wheat-awn: \e2cd; +$fa-var-wheat-alt: \e2cd; +$fa-var-ankh: \f644; +$fa-var-hands-holding-child: \e4fa; +$fa-var-asterisk: \2a; +$fa-var-square-check: \f14a; +$fa-var-check-square: \f14a; +$fa-var-peseta-sign: \e221; +$fa-var-heading: \f1dc; +$fa-var-header: \f1dc; +$fa-var-ghost: \f6e2; +$fa-var-list: \f03a; +$fa-var-list-squares: \f03a; +$fa-var-square-phone-flip: \f87b; +$fa-var-phone-square-alt: \f87b; +$fa-var-cart-plus: \f217; +$fa-var-gamepad: \f11b; +$fa-var-circle-dot: \f192; +$fa-var-dot-circle: \f192; +$fa-var-face-dizzy: \f567; +$fa-var-dizzy: \f567; +$fa-var-egg: \f7fb; +$fa-var-house-medical-circle-xmark: \e513; +$fa-var-campground: \f6bb; +$fa-var-folder-plus: \f65e; +$fa-var-futbol: \f1e3; +$fa-var-futbol-ball: \f1e3; +$fa-var-soccer-ball: \f1e3; +$fa-var-paintbrush: \f1fc; +$fa-var-paint-brush: \f1fc; +$fa-var-lock: \f023; +$fa-var-gas-pump: \f52f; +$fa-var-hot-tub-person: \f593; +$fa-var-hot-tub: \f593; +$fa-var-map-location: \f59f; +$fa-var-map-marked: \f59f; +$fa-var-house-flood-water: \e50e; +$fa-var-tree: \f1bb; +$fa-var-bridge-lock: \e4cc; +$fa-var-sack-dollar: \f81d; +$fa-var-pen-to-square: \f044; +$fa-var-edit: \f044; +$fa-var-car-side: \f5e4; +$fa-var-share-nodes: \f1e0; +$fa-var-share-alt: \f1e0; +$fa-var-heart-circle-minus: \e4ff; +$fa-var-hourglass-half: \f252; +$fa-var-hourglass-2: \f252; +$fa-var-microscope: \f610; +$fa-var-sink: \e06d; +$fa-var-bag-shopping: \f290; +$fa-var-shopping-bag: \f290; +$fa-var-arrow-down-z-a: \f881; +$fa-var-sort-alpha-desc: \f881; +$fa-var-sort-alpha-down-alt: \f881; +$fa-var-mitten: \f7b5; +$fa-var-person-rays: \e54d; +$fa-var-users: \f0c0; +$fa-var-eye-slash: \f070; +$fa-var-flask-vial: \e4f3; +$fa-var-hand: \f256; +$fa-var-hand-paper: \f256; +$fa-var-om: \f679; +$fa-var-worm: \e599; +$fa-var-house-circle-xmark: \e50b; +$fa-var-plug: \f1e6; +$fa-var-chevron-up: \f077; +$fa-var-hand-spock: \f259; +$fa-var-stopwatch: \f2f2; +$fa-var-face-kiss: \f596; +$fa-var-kiss: \f596; +$fa-var-bridge-circle-xmark: \e4cb; +$fa-var-face-grin-tongue: \f589; +$fa-var-grin-tongue: \f589; +$fa-var-chess-bishop: \f43a; +$fa-var-face-grin-wink: \f58c; +$fa-var-grin-wink: \f58c; +$fa-var-ear-deaf: \f2a4; +$fa-var-deaf: \f2a4; +$fa-var-deafness: \f2a4; +$fa-var-hard-of-hearing: \f2a4; +$fa-var-road-circle-check: \e564; +$fa-var-dice-five: \f523; +$fa-var-square-rss: \f143; +$fa-var-rss-square: \f143; +$fa-var-land-mine-on: \e51b; +$fa-var-i-cursor: \f246; +$fa-var-stamp: \f5bf; +$fa-var-stairs: \e289; +$fa-var-i: \49; +$fa-var-hryvnia-sign: \f6f2; +$fa-var-hryvnia: \f6f2; +$fa-var-pills: \f484; +$fa-var-face-grin-wide: \f581; +$fa-var-grin-alt: \f581; +$fa-var-tooth: \f5c9; +$fa-var-v: \56; +$fa-var-bangladeshi-taka-sign: \e2e6; +$fa-var-bicycle: \f206; +$fa-var-staff-snake: \e579; +$fa-var-rod-asclepius: \e579; +$fa-var-rod-snake: \e579; +$fa-var-staff-aesculapius: \e579; +$fa-var-head-side-cough-slash: \e062; +$fa-var-truck-medical: \f0f9; +$fa-var-ambulance: \f0f9; +$fa-var-wheat-awn-circle-exclamation: \e598; +$fa-var-snowman: \f7d0; +$fa-var-mortar-pestle: \f5a7; +$fa-var-road-barrier: \e562; +$fa-var-school: \f549; +$fa-var-igloo: \f7ae; +$fa-var-joint: \f595; +$fa-var-angle-right: \f105; +$fa-var-horse: \f6f0; +$fa-var-q: \51; +$fa-var-g: \47; +$fa-var-notes-medical: \f481; +$fa-var-temperature-half: \f2c9; +$fa-var-temperature-2: \f2c9; +$fa-var-thermometer-2: \f2c9; +$fa-var-thermometer-half: \f2c9; +$fa-var-dong-sign: \e169; +$fa-var-capsules: \f46b; +$fa-var-poo-storm: \f75a; +$fa-var-poo-bolt: \f75a; +$fa-var-face-frown-open: \f57a; +$fa-var-frown-open: \f57a; +$fa-var-hand-point-up: \f0a6; +$fa-var-money-bill: \f0d6; +$fa-var-bookmark: \f02e; +$fa-var-align-justify: \f039; +$fa-var-umbrella-beach: \f5ca; +$fa-var-helmet-un: \e503; +$fa-var-bullseye: \f140; +$fa-var-bacon: \f7e5; +$fa-var-hand-point-down: \f0a7; +$fa-var-arrow-up-from-bracket: \e09a; +$fa-var-folder: \f07b; +$fa-var-folder-blank: \f07b; +$fa-var-file-waveform: \f478; +$fa-var-file-medical-alt: \f478; +$fa-var-radiation: \f7b9; +$fa-var-chart-simple: \e473; +$fa-var-mars-stroke: \f229; +$fa-var-vial: \f492; +$fa-var-gauge: \f624; +$fa-var-dashboard: \f624; +$fa-var-gauge-med: \f624; +$fa-var-tachometer-alt-average: \f624; +$fa-var-wand-magic-sparkles: \e2ca; +$fa-var-magic-wand-sparkles: \e2ca; +$fa-var-e: \45; +$fa-var-pen-clip: \f305; +$fa-var-pen-alt: \f305; +$fa-var-bridge-circle-exclamation: \e4ca; +$fa-var-user: \f007; +$fa-var-school-circle-check: \e56b; +$fa-var-dumpster: \f793; +$fa-var-van-shuttle: \f5b6; +$fa-var-shuttle-van: \f5b6; +$fa-var-building-user: \e4da; +$fa-var-square-caret-left: \f191; +$fa-var-caret-square-left: \f191; +$fa-var-highlighter: \f591; +$fa-var-key: \f084; +$fa-var-bullhorn: \f0a1; +$fa-var-globe: \f0ac; +$fa-var-synagogue: \f69b; +$fa-var-person-half-dress: \e548; +$fa-var-road-bridge: \e563; +$fa-var-location-arrow: \f124; +$fa-var-c: \43; +$fa-var-tablet-button: \f10a; +$fa-var-building-lock: \e4d6; +$fa-var-pizza-slice: \f818; +$fa-var-money-bill-wave: \f53a; +$fa-var-chart-area: \f1fe; +$fa-var-area-chart: \f1fe; +$fa-var-house-flag: \e50d; +$fa-var-person-circle-minus: \e540; +$fa-var-ban: \f05e; +$fa-var-cancel: \f05e; +$fa-var-camera-rotate: \e0d8; +$fa-var-spray-can-sparkles: \f5d0; +$fa-var-air-freshener: \f5d0; +$fa-var-star: \f005; +$fa-var-repeat: \f363; +$fa-var-cross: \f654; +$fa-var-box: \f466; +$fa-var-venus-mars: \f228; +$fa-var-arrow-pointer: \f245; +$fa-var-mouse-pointer: \f245; +$fa-var-maximize: \f31e; +$fa-var-expand-arrows-alt: \f31e; +$fa-var-charging-station: \f5e7; +$fa-var-shapes: \f61f; +$fa-var-triangle-circle-square: \f61f; +$fa-var-shuffle: \f074; +$fa-var-random: \f074; +$fa-var-person-running: \f70c; +$fa-var-running: \f70c; +$fa-var-mobile-retro: \e527; +$fa-var-grip-lines-vertical: \f7a5; +$fa-var-spider: \f717; +$fa-var-hands-bound: \e4f9; +$fa-var-file-invoice-dollar: \f571; +$fa-var-plane-circle-exclamation: \e556; +$fa-var-x-ray: \f497; +$fa-var-spell-check: \f891; +$fa-var-slash: \f715; +$fa-var-computer-mouse: \f8cc; +$fa-var-mouse: \f8cc; +$fa-var-arrow-right-to-bracket: \f090; +$fa-var-sign-in: \f090; +$fa-var-shop-slash: \e070; +$fa-var-store-alt-slash: \e070; +$fa-var-server: \f233; +$fa-var-virus-covid-slash: \e4a9; +$fa-var-shop-lock: \e4a5; +$fa-var-hourglass-start: \f251; +$fa-var-hourglass-1: \f251; +$fa-var-blender-phone: \f6b6; +$fa-var-building-wheat: \e4db; +$fa-var-person-breastfeeding: \e53a; +$fa-var-right-to-bracket: \f2f6; +$fa-var-sign-in-alt: \f2f6; +$fa-var-venus: \f221; +$fa-var-passport: \f5ab; +$fa-var-thumbtack-slash: \e68f; +$fa-var-thumb-tack-slash: \e68f; +$fa-var-heart-pulse: \f21e; +$fa-var-heartbeat: \f21e; +$fa-var-people-carry-box: \f4ce; +$fa-var-people-carry: \f4ce; +$fa-var-temperature-high: \f769; +$fa-var-microchip: \f2db; +$fa-var-crown: \f521; +$fa-var-weight-hanging: \f5cd; +$fa-var-xmarks-lines: \e59a; +$fa-var-file-prescription: \f572; +$fa-var-weight-scale: \f496; +$fa-var-weight: \f496; +$fa-var-user-group: \f500; +$fa-var-user-friends: \f500; +$fa-var-arrow-up-a-z: \f15e; +$fa-var-sort-alpha-up: \f15e; +$fa-var-chess-knight: \f441; +$fa-var-face-laugh-squint: \f59b; +$fa-var-laugh-squint: \f59b; +$fa-var-wheelchair: \f193; +$fa-var-circle-arrow-up: \f0aa; +$fa-var-arrow-circle-up: \f0aa; +$fa-var-toggle-on: \f205; +$fa-var-person-walking: \f554; +$fa-var-walking: \f554; +$fa-var-l: \4c; +$fa-var-fire: \f06d; +$fa-var-bed-pulse: \f487; +$fa-var-procedures: \f487; +$fa-var-shuttle-space: \f197; +$fa-var-space-shuttle: \f197; +$fa-var-face-laugh: \f599; +$fa-var-laugh: \f599; +$fa-var-folder-open: \f07c; +$fa-var-heart-circle-plus: \e500; +$fa-var-code-fork: \e13b; +$fa-var-city: \f64f; +$fa-var-microphone-lines: \f3c9; +$fa-var-microphone-alt: \f3c9; +$fa-var-pepper-hot: \f816; +$fa-var-unlock: \f09c; +$fa-var-colon-sign: \e140; +$fa-var-headset: \f590; +$fa-var-store-slash: \e071; +$fa-var-road-circle-xmark: \e566; +$fa-var-user-minus: \f503; +$fa-var-mars-stroke-up: \f22a; +$fa-var-mars-stroke-v: \f22a; +$fa-var-champagne-glasses: \f79f; +$fa-var-glass-cheers: \f79f; +$fa-var-clipboard: \f328; +$fa-var-house-circle-exclamation: \e50a; +$fa-var-file-arrow-up: \f574; +$fa-var-file-upload: \f574; +$fa-var-wifi: \f1eb; +$fa-var-wifi-3: \f1eb; +$fa-var-wifi-strong: \f1eb; +$fa-var-bath: \f2cd; +$fa-var-bathtub: \f2cd; +$fa-var-underline: \f0cd; +$fa-var-user-pen: \f4ff; +$fa-var-user-edit: \f4ff; +$fa-var-signature: \f5b7; +$fa-var-stroopwafel: \f551; +$fa-var-bold: \f032; +$fa-var-anchor-lock: \e4ad; +$fa-var-building-ngo: \e4d7; +$fa-var-manat-sign: \e1d5; +$fa-var-not-equal: \f53e; +$fa-var-border-top-left: \f853; +$fa-var-border-style: \f853; +$fa-var-map-location-dot: \f5a0; +$fa-var-map-marked-alt: \f5a0; +$fa-var-jedi: \f669; +$fa-var-square-poll-vertical: \f681; +$fa-var-poll: \f681; +$fa-var-mug-hot: \f7b6; +$fa-var-car-battery: \f5df; +$fa-var-battery-car: \f5df; +$fa-var-gift: \f06b; +$fa-var-dice-two: \f528; +$fa-var-chess-queen: \f445; +$fa-var-glasses: \f530; +$fa-var-chess-board: \f43c; +$fa-var-building-circle-check: \e4d2; +$fa-var-person-chalkboard: \e53d; +$fa-var-mars-stroke-right: \f22b; +$fa-var-mars-stroke-h: \f22b; +$fa-var-hand-back-fist: \f255; +$fa-var-hand-rock: \f255; +$fa-var-square-caret-up: \f151; +$fa-var-caret-square-up: \f151; +$fa-var-cloud-showers-water: \e4e4; +$fa-var-chart-bar: \f080; +$fa-var-bar-chart: \f080; +$fa-var-hands-bubbles: \e05e; +$fa-var-hands-wash: \e05e; +$fa-var-less-than-equal: \f537; +$fa-var-train: \f238; +$fa-var-eye-low-vision: \f2a8; +$fa-var-low-vision: \f2a8; +$fa-var-crow: \f520; +$fa-var-sailboat: \e445; +$fa-var-window-restore: \f2d2; +$fa-var-square-plus: \f0fe; +$fa-var-plus-square: \f0fe; +$fa-var-torii-gate: \f6a1; +$fa-var-frog: \f52e; +$fa-var-bucket: \e4cf; +$fa-var-image: \f03e; +$fa-var-microphone: \f130; +$fa-var-cow: \f6c8; +$fa-var-caret-up: \f0d8; +$fa-var-screwdriver: \f54a; +$fa-var-folder-closed: \e185; +$fa-var-house-tsunami: \e515; +$fa-var-square-nfi: \e576; +$fa-var-arrow-up-from-ground-water: \e4b5; +$fa-var-martini-glass: \f57b; +$fa-var-glass-martini-alt: \f57b; +$fa-var-square-binary: \e69b; +$fa-var-rotate-left: \f2ea; +$fa-var-rotate-back: \f2ea; +$fa-var-rotate-backward: \f2ea; +$fa-var-undo-alt: \f2ea; +$fa-var-table-columns: \f0db; +$fa-var-columns: \f0db; +$fa-var-lemon: \f094; +$fa-var-head-side-mask: \e063; +$fa-var-handshake: \f2b5; +$fa-var-gem: \f3a5; +$fa-var-dolly: \f472; +$fa-var-dolly-box: \f472; +$fa-var-smoking: \f48d; +$fa-var-minimize: \f78c; +$fa-var-compress-arrows-alt: \f78c; +$fa-var-monument: \f5a6; +$fa-var-snowplow: \f7d2; +$fa-var-angles-right: \f101; +$fa-var-angle-double-right: \f101; +$fa-var-cannabis: \f55f; +$fa-var-circle-play: \f144; +$fa-var-play-circle: \f144; +$fa-var-tablets: \f490; +$fa-var-ethernet: \f796; +$fa-var-euro-sign: \f153; +$fa-var-eur: \f153; +$fa-var-euro: \f153; +$fa-var-chair: \f6c0; +$fa-var-circle-check: \f058; +$fa-var-check-circle: \f058; +$fa-var-circle-stop: \f28d; +$fa-var-stop-circle: \f28d; +$fa-var-compass-drafting: \f568; +$fa-var-drafting-compass: \f568; +$fa-var-plate-wheat: \e55a; +$fa-var-icicles: \f7ad; +$fa-var-person-shelter: \e54f; +$fa-var-neuter: \f22c; +$fa-var-id-badge: \f2c1; +$fa-var-marker: \f5a1; +$fa-var-face-laugh-beam: \f59a; +$fa-var-laugh-beam: \f59a; +$fa-var-helicopter-symbol: \e502; +$fa-var-universal-access: \f29a; +$fa-var-circle-chevron-up: \f139; +$fa-var-chevron-circle-up: \f139; +$fa-var-lari-sign: \e1c8; +$fa-var-volcano: \f770; +$fa-var-person-walking-dashed-line-arrow-right: \e553; +$fa-var-sterling-sign: \f154; +$fa-var-gbp: \f154; +$fa-var-pound-sign: \f154; +$fa-var-viruses: \e076; +$fa-var-square-person-confined: \e577; +$fa-var-user-tie: \f508; +$fa-var-arrow-down-long: \f175; +$fa-var-long-arrow-down: \f175; +$fa-var-tent-arrow-down-to-line: \e57e; +$fa-var-certificate: \f0a3; +$fa-var-reply-all: \f122; +$fa-var-mail-reply-all: \f122; +$fa-var-suitcase: \f0f2; +$fa-var-person-skating: \f7c5; +$fa-var-skating: \f7c5; +$fa-var-filter-circle-dollar: \f662; +$fa-var-funnel-dollar: \f662; +$fa-var-camera-retro: \f083; +$fa-var-circle-arrow-down: \f0ab; +$fa-var-arrow-circle-down: \f0ab; +$fa-var-file-import: \f56f; +$fa-var-arrow-right-to-file: \f56f; +$fa-var-square-arrow-up-right: \f14c; +$fa-var-external-link-square: \f14c; +$fa-var-box-open: \f49e; +$fa-var-scroll: \f70e; +$fa-var-spa: \f5bb; +$fa-var-location-pin-lock: \e51f; +$fa-var-pause: \f04c; +$fa-var-hill-avalanche: \e507; +$fa-var-temperature-empty: \f2cb; +$fa-var-temperature-0: \f2cb; +$fa-var-thermometer-0: \f2cb; +$fa-var-thermometer-empty: \f2cb; +$fa-var-bomb: \f1e2; +$fa-var-registered: \f25d; +$fa-var-address-card: \f2bb; +$fa-var-contact-card: \f2bb; +$fa-var-vcard: \f2bb; +$fa-var-scale-unbalanced-flip: \f516; +$fa-var-balance-scale-right: \f516; +$fa-var-subscript: \f12c; +$fa-var-diamond-turn-right: \f5eb; +$fa-var-directions: \f5eb; +$fa-var-burst: \e4dc; +$fa-var-house-laptop: \e066; +$fa-var-laptop-house: \e066; +$fa-var-face-tired: \f5c8; +$fa-var-tired: \f5c8; +$fa-var-money-bills: \e1f3; +$fa-var-smog: \f75f; +$fa-var-crutch: \f7f7; +$fa-var-cloud-arrow-up: \f0ee; +$fa-var-cloud-upload: \f0ee; +$fa-var-cloud-upload-alt: \f0ee; +$fa-var-palette: \f53f; +$fa-var-arrows-turn-right: \e4c0; +$fa-var-vest: \e085; +$fa-var-ferry: \e4ea; +$fa-var-arrows-down-to-people: \e4b9; +$fa-var-seedling: \f4d8; +$fa-var-sprout: \f4d8; +$fa-var-left-right: \f337; +$fa-var-arrows-alt-h: \f337; +$fa-var-boxes-packing: \e4c7; +$fa-var-circle-arrow-left: \f0a8; +$fa-var-arrow-circle-left: \f0a8; +$fa-var-group-arrows-rotate: \e4f6; +$fa-var-bowl-food: \e4c6; +$fa-var-candy-cane: \f786; +$fa-var-arrow-down-wide-short: \f160; +$fa-var-sort-amount-asc: \f160; +$fa-var-sort-amount-down: \f160; +$fa-var-cloud-bolt: \f76c; +$fa-var-thunderstorm: \f76c; +$fa-var-text-slash: \f87d; +$fa-var-remove-format: \f87d; +$fa-var-face-smile-wink: \f4da; +$fa-var-smile-wink: \f4da; +$fa-var-file-word: \f1c2; +$fa-var-file-powerpoint: \f1c4; +$fa-var-arrows-left-right: \f07e; +$fa-var-arrows-h: \f07e; +$fa-var-house-lock: \e510; +$fa-var-cloud-arrow-down: \f0ed; +$fa-var-cloud-download: \f0ed; +$fa-var-cloud-download-alt: \f0ed; +$fa-var-children: \e4e1; +$fa-var-chalkboard: \f51b; +$fa-var-blackboard: \f51b; +$fa-var-user-large-slash: \f4fa; +$fa-var-user-alt-slash: \f4fa; +$fa-var-envelope-open: \f2b6; +$fa-var-handshake-simple-slash: \e05f; +$fa-var-handshake-alt-slash: \e05f; +$fa-var-mattress-pillow: \e525; +$fa-var-guarani-sign: \e19a; +$fa-var-arrows-rotate: \f021; +$fa-var-refresh: \f021; +$fa-var-sync: \f021; +$fa-var-fire-extinguisher: \f134; +$fa-var-cruzeiro-sign: \e152; +$fa-var-greater-than-equal: \f532; +$fa-var-shield-halved: \f3ed; +$fa-var-shield-alt: \f3ed; +$fa-var-book-atlas: \f558; +$fa-var-atlas: \f558; +$fa-var-virus: \e074; +$fa-var-envelope-circle-check: \e4e8; +$fa-var-layer-group: \f5fd; +$fa-var-arrows-to-dot: \e4be; +$fa-var-archway: \f557; +$fa-var-heart-circle-check: \e4fd; +$fa-var-house-chimney-crack: \f6f1; +$fa-var-house-damage: \f6f1; +$fa-var-file-zipper: \f1c6; +$fa-var-file-archive: \f1c6; +$fa-var-square: \f0c8; +$fa-var-martini-glass-empty: \f000; +$fa-var-glass-martini: \f000; +$fa-var-couch: \f4b8; +$fa-var-cedi-sign: \e0df; +$fa-var-italic: \f033; +$fa-var-table-cells-column-lock: \e678; +$fa-var-church: \f51d; +$fa-var-comments-dollar: \f653; +$fa-var-democrat: \f747; +$fa-var-z: \5a; +$fa-var-person-skiing: \f7c9; +$fa-var-skiing: \f7c9; +$fa-var-road-lock: \e567; +$fa-var-a: \41; +$fa-var-temperature-arrow-down: \e03f; +$fa-var-temperature-down: \e03f; +$fa-var-feather-pointed: \f56b; +$fa-var-feather-alt: \f56b; +$fa-var-p: \50; +$fa-var-snowflake: \f2dc; +$fa-var-newspaper: \f1ea; +$fa-var-rectangle-ad: \f641; +$fa-var-ad: \f641; +$fa-var-circle-arrow-right: \f0a9; +$fa-var-arrow-circle-right: \f0a9; +$fa-var-filter-circle-xmark: \e17b; +$fa-var-locust: \e520; +$fa-var-sort: \f0dc; +$fa-var-unsorted: \f0dc; +$fa-var-list-ol: \f0cb; +$fa-var-list-1-2: \f0cb; +$fa-var-list-numeric: \f0cb; +$fa-var-person-dress-burst: \e544; +$fa-var-money-check-dollar: \f53d; +$fa-var-money-check-alt: \f53d; +$fa-var-vector-square: \f5cb; +$fa-var-bread-slice: \f7ec; +$fa-var-language: \f1ab; +$fa-var-face-kiss-wink-heart: \f598; +$fa-var-kiss-wink-heart: \f598; +$fa-var-filter: \f0b0; +$fa-var-question: \3f; +$fa-var-file-signature: \f573; +$fa-var-up-down-left-right: \f0b2; +$fa-var-arrows-alt: \f0b2; +$fa-var-house-chimney-user: \e065; +$fa-var-hand-holding-heart: \f4be; +$fa-var-puzzle-piece: \f12e; +$fa-var-money-check: \f53c; +$fa-var-star-half-stroke: \f5c0; +$fa-var-star-half-alt: \f5c0; +$fa-var-code: \f121; +$fa-var-whiskey-glass: \f7a0; +$fa-var-glass-whiskey: \f7a0; +$fa-var-building-circle-exclamation: \e4d3; +$fa-var-magnifying-glass-chart: \e522; +$fa-var-arrow-up-right-from-square: \f08e; +$fa-var-external-link: \f08e; +$fa-var-cubes-stacked: \e4e6; +$fa-var-won-sign: \f159; +$fa-var-krw: \f159; +$fa-var-won: \f159; +$fa-var-virus-covid: \e4a8; +$fa-var-austral-sign: \e0a9; +$fa-var-f: \46; +$fa-var-leaf: \f06c; +$fa-var-road: \f018; +$fa-var-taxi: \f1ba; +$fa-var-cab: \f1ba; +$fa-var-person-circle-plus: \e541; +$fa-var-chart-pie: \f200; +$fa-var-pie-chart: \f200; +$fa-var-bolt-lightning: \e0b7; +$fa-var-sack-xmark: \e56a; +$fa-var-file-excel: \f1c3; +$fa-var-file-contract: \f56c; +$fa-var-fish-fins: \e4f2; +$fa-var-building-flag: \e4d5; +$fa-var-face-grin-beam: \f582; +$fa-var-grin-beam: \f582; +$fa-var-object-ungroup: \f248; +$fa-var-poop: \f619; +$fa-var-location-pin: \f041; +$fa-var-map-marker: \f041; +$fa-var-kaaba: \f66b; +$fa-var-toilet-paper: \f71e; +$fa-var-helmet-safety: \f807; +$fa-var-hard-hat: \f807; +$fa-var-hat-hard: \f807; +$fa-var-eject: \f052; +$fa-var-circle-right: \f35a; +$fa-var-arrow-alt-circle-right: \f35a; +$fa-var-plane-circle-check: \e555; +$fa-var-face-rolling-eyes: \f5a5; +$fa-var-meh-rolling-eyes: \f5a5; +$fa-var-object-group: \f247; +$fa-var-chart-line: \f201; +$fa-var-line-chart: \f201; +$fa-var-mask-ventilator: \e524; +$fa-var-arrow-right: \f061; +$fa-var-signs-post: \f277; +$fa-var-map-signs: \f277; +$fa-var-cash-register: \f788; +$fa-var-person-circle-question: \e542; +$fa-var-h: \48; +$fa-var-tarp: \e57b; +$fa-var-screwdriver-wrench: \f7d9; +$fa-var-tools: \f7d9; +$fa-var-arrows-to-eye: \e4bf; +$fa-var-plug-circle-bolt: \e55b; +$fa-var-heart: \f004; +$fa-var-mars-and-venus: \f224; +$fa-var-house-user: \e1b0; +$fa-var-home-user: \e1b0; +$fa-var-dumpster-fire: \f794; +$fa-var-house-crack: \e3b1; +$fa-var-martini-glass-citrus: \f561; +$fa-var-cocktail: \f561; +$fa-var-face-surprise: \f5c2; +$fa-var-surprise: \f5c2; +$fa-var-bottle-water: \e4c5; +$fa-var-circle-pause: \f28b; +$fa-var-pause-circle: \f28b; +$fa-var-toilet-paper-slash: \e072; +$fa-var-apple-whole: \f5d1; +$fa-var-apple-alt: \f5d1; +$fa-var-kitchen-set: \e51a; +$fa-var-r: \52; +$fa-var-temperature-quarter: \f2ca; +$fa-var-temperature-1: \f2ca; +$fa-var-thermometer-1: \f2ca; +$fa-var-thermometer-quarter: \f2ca; +$fa-var-cube: \f1b2; +$fa-var-bitcoin-sign: \e0b4; +$fa-var-shield-dog: \e573; +$fa-var-solar-panel: \f5ba; +$fa-var-lock-open: \f3c1; +$fa-var-elevator: \e16d; +$fa-var-money-bill-transfer: \e528; +$fa-var-money-bill-trend-up: \e529; +$fa-var-house-flood-water-circle-arrow-right: \e50f; +$fa-var-square-poll-horizontal: \f682; +$fa-var-poll-h: \f682; +$fa-var-circle: \f111; +$fa-var-backward-fast: \f049; +$fa-var-fast-backward: \f049; +$fa-var-recycle: \f1b8; +$fa-var-user-astronaut: \f4fb; +$fa-var-plane-slash: \e069; +$fa-var-trademark: \f25c; +$fa-var-basketball: \f434; +$fa-var-basketball-ball: \f434; +$fa-var-satellite-dish: \f7c0; +$fa-var-circle-up: \f35b; +$fa-var-arrow-alt-circle-up: \f35b; +$fa-var-mobile-screen-button: \f3cd; +$fa-var-mobile-alt: \f3cd; +$fa-var-volume-high: \f028; +$fa-var-volume-up: \f028; +$fa-var-users-rays: \e593; +$fa-var-wallet: \f555; +$fa-var-clipboard-check: \f46c; +$fa-var-file-audio: \f1c7; +$fa-var-burger: \f805; +$fa-var-hamburger: \f805; +$fa-var-wrench: \f0ad; +$fa-var-bugs: \e4d0; +$fa-var-rupee-sign: \f156; +$fa-var-rupee: \f156; +$fa-var-file-image: \f1c5; +$fa-var-circle-question: \f059; +$fa-var-question-circle: \f059; +$fa-var-plane-departure: \f5b0; +$fa-var-handshake-slash: \e060; +$fa-var-book-bookmark: \e0bb; +$fa-var-code-branch: \f126; +$fa-var-hat-cowboy: \f8c0; +$fa-var-bridge: \e4c8; +$fa-var-phone-flip: \f879; +$fa-var-phone-alt: \f879; +$fa-var-truck-front: \e2b7; +$fa-var-cat: \f6be; +$fa-var-anchor-circle-exclamation: \e4ab; +$fa-var-truck-field: \e58d; +$fa-var-route: \f4d7; +$fa-var-clipboard-question: \e4e3; +$fa-var-panorama: \e209; +$fa-var-comment-medical: \f7f5; +$fa-var-teeth-open: \f62f; +$fa-var-file-circle-minus: \e4ed; +$fa-var-tags: \f02c; +$fa-var-wine-glass: \f4e3; +$fa-var-forward-fast: \f050; +$fa-var-fast-forward: \f050; +$fa-var-face-meh-blank: \f5a4; +$fa-var-meh-blank: \f5a4; +$fa-var-square-parking: \f540; +$fa-var-parking: \f540; +$fa-var-house-signal: \e012; +$fa-var-bars-progress: \f828; +$fa-var-tasks-alt: \f828; +$fa-var-faucet-drip: \e006; +$fa-var-cart-flatbed: \f474; +$fa-var-dolly-flatbed: \f474; +$fa-var-ban-smoking: \f54d; +$fa-var-smoking-ban: \f54d; +$fa-var-terminal: \f120; +$fa-var-mobile-button: \f10b; +$fa-var-house-medical-flag: \e514; +$fa-var-basket-shopping: \f291; +$fa-var-shopping-basket: \f291; +$fa-var-tape: \f4db; +$fa-var-bus-simple: \f55e; +$fa-var-bus-alt: \f55e; +$fa-var-eye: \f06e; +$fa-var-face-sad-cry: \f5b3; +$fa-var-sad-cry: \f5b3; +$fa-var-audio-description: \f29e; +$fa-var-person-military-to-person: \e54c; +$fa-var-file-shield: \e4f0; +$fa-var-user-slash: \f506; +$fa-var-pen: \f304; +$fa-var-tower-observation: \e586; +$fa-var-file-code: \f1c9; +$fa-var-signal: \f012; +$fa-var-signal-5: \f012; +$fa-var-signal-perfect: \f012; +$fa-var-bus: \f207; +$fa-var-heart-circle-xmark: \e501; +$fa-var-house-chimney: \e3af; +$fa-var-home-lg: \e3af; +$fa-var-window-maximize: \f2d0; +$fa-var-face-frown: \f119; +$fa-var-frown: \f119; +$fa-var-prescription: \f5b1; +$fa-var-shop: \f54f; +$fa-var-store-alt: \f54f; +$fa-var-floppy-disk: \f0c7; +$fa-var-save: \f0c7; +$fa-var-vihara: \f6a7; +$fa-var-scale-unbalanced: \f515; +$fa-var-balance-scale-left: \f515; +$fa-var-sort-up: \f0de; +$fa-var-sort-asc: \f0de; +$fa-var-comment-dots: \f4ad; +$fa-var-commenting: \f4ad; +$fa-var-plant-wilt: \e5aa; +$fa-var-diamond: \f219; +$fa-var-face-grin-squint: \f585; +$fa-var-grin-squint: \f585; +$fa-var-hand-holding-dollar: \f4c0; +$fa-var-hand-holding-usd: \f4c0; +$fa-var-chart-diagram: \e695; +$fa-var-bacterium: \e05a; +$fa-var-hand-pointer: \f25a; +$fa-var-drum-steelpan: \f56a; +$fa-var-hand-scissors: \f257; +$fa-var-hands-praying: \f684; +$fa-var-praying-hands: \f684; +$fa-var-arrow-rotate-right: \f01e; +$fa-var-arrow-right-rotate: \f01e; +$fa-var-arrow-rotate-forward: \f01e; +$fa-var-redo: \f01e; +$fa-var-biohazard: \f780; +$fa-var-location-crosshairs: \f601; +$fa-var-location: \f601; +$fa-var-mars-double: \f227; +$fa-var-child-dress: \e59c; +$fa-var-users-between-lines: \e591; +$fa-var-lungs-virus: \e067; +$fa-var-face-grin-tears: \f588; +$fa-var-grin-tears: \f588; +$fa-var-phone: \f095; +$fa-var-calendar-xmark: \f273; +$fa-var-calendar-times: \f273; +$fa-var-child-reaching: \e59d; +$fa-var-head-side-virus: \e064; +$fa-var-user-gear: \f4fe; +$fa-var-user-cog: \f4fe; +$fa-var-arrow-up-1-9: \f163; +$fa-var-sort-numeric-up: \f163; +$fa-var-door-closed: \f52a; +$fa-var-shield-virus: \e06c; +$fa-var-dice-six: \f526; +$fa-var-mosquito-net: \e52c; +$fa-var-file-fragment: \e697; +$fa-var-bridge-water: \e4ce; +$fa-var-person-booth: \f756; +$fa-var-text-width: \f035; +$fa-var-hat-wizard: \f6e8; +$fa-var-pen-fancy: \f5ac; +$fa-var-person-digging: \f85e; +$fa-var-digging: \f85e; +$fa-var-trash: \f1f8; +$fa-var-gauge-simple: \f629; +$fa-var-gauge-simple-med: \f629; +$fa-var-tachometer-average: \f629; +$fa-var-book-medical: \f7e6; +$fa-var-poo: \f2fe; +$fa-var-quote-right: \f10e; +$fa-var-quote-right-alt: \f10e; +$fa-var-shirt: \f553; +$fa-var-t-shirt: \f553; +$fa-var-tshirt: \f553; +$fa-var-cubes: \f1b3; +$fa-var-divide: \f529; +$fa-var-tenge-sign: \f7d7; +$fa-var-tenge: \f7d7; +$fa-var-headphones: \f025; +$fa-var-hands-holding: \f4c2; +$fa-var-hands-clapping: \e1a8; +$fa-var-republican: \f75e; +$fa-var-arrow-left: \f060; +$fa-var-person-circle-xmark: \e543; +$fa-var-ruler: \f545; +$fa-var-align-left: \f036; +$fa-var-dice-d6: \f6d1; +$fa-var-restroom: \f7bd; +$fa-var-j: \4a; +$fa-var-users-viewfinder: \e595; +$fa-var-file-video: \f1c8; +$fa-var-up-right-from-square: \f35d; +$fa-var-external-link-alt: \f35d; +$fa-var-table-cells: \f00a; +$fa-var-th: \f00a; +$fa-var-file-pdf: \f1c1; +$fa-var-book-bible: \f647; +$fa-var-bible: \f647; +$fa-var-o: \4f; +$fa-var-suitcase-medical: \f0fa; +$fa-var-medkit: \f0fa; +$fa-var-user-secret: \f21b; +$fa-var-otter: \f700; +$fa-var-person-dress: \f182; +$fa-var-female: \f182; +$fa-var-comment-dollar: \f651; +$fa-var-business-time: \f64a; +$fa-var-briefcase-clock: \f64a; +$fa-var-table-cells-large: \f009; +$fa-var-th-large: \f009; +$fa-var-book-tanakh: \f827; +$fa-var-tanakh: \f827; +$fa-var-phone-volume: \f2a0; +$fa-var-volume-control-phone: \f2a0; +$fa-var-hat-cowboy-side: \f8c1; +$fa-var-clipboard-user: \f7f3; +$fa-var-child: \f1ae; +$fa-var-lira-sign: \f195; +$fa-var-satellite: \f7bf; +$fa-var-plane-lock: \e558; +$fa-var-tag: \f02b; +$fa-var-comment: \f075; +$fa-var-cake-candles: \f1fd; +$fa-var-birthday-cake: \f1fd; +$fa-var-cake: \f1fd; +$fa-var-envelope: \f0e0; +$fa-var-angles-up: \f102; +$fa-var-angle-double-up: \f102; +$fa-var-paperclip: \f0c6; +$fa-var-arrow-right-to-city: \e4b3; +$fa-var-ribbon: \f4d6; +$fa-var-lungs: \f604; +$fa-var-arrow-up-9-1: \f887; +$fa-var-sort-numeric-up-alt: \f887; +$fa-var-litecoin-sign: \e1d3; +$fa-var-border-none: \f850; +$fa-var-circle-nodes: \e4e2; +$fa-var-parachute-box: \f4cd; +$fa-var-indent: \f03c; +$fa-var-truck-field-un: \e58e; +$fa-var-hourglass: \f254; +$fa-var-hourglass-empty: \f254; +$fa-var-mountain: \f6fc; +$fa-var-user-doctor: \f0f0; +$fa-var-user-md: \f0f0; +$fa-var-circle-info: \f05a; +$fa-var-info-circle: \f05a; +$fa-var-cloud-meatball: \f73b; +$fa-var-camera: \f030; +$fa-var-camera-alt: \f030; +$fa-var-square-virus: \e578; +$fa-var-meteor: \f753; +$fa-var-car-on: \e4dd; +$fa-var-sleigh: \f7cc; +$fa-var-arrow-down-1-9: \f162; +$fa-var-sort-numeric-asc: \f162; +$fa-var-sort-numeric-down: \f162; +$fa-var-hand-holding-droplet: \f4c1; +$fa-var-hand-holding-water: \f4c1; +$fa-var-water: \f773; +$fa-var-calendar-check: \f274; +$fa-var-braille: \f2a1; +$fa-var-prescription-bottle-medical: \f486; +$fa-var-prescription-bottle-alt: \f486; +$fa-var-landmark: \f66f; +$fa-var-truck: \f0d1; +$fa-var-crosshairs: \f05b; +$fa-var-person-cane: \e53c; +$fa-var-tent: \e57d; +$fa-var-vest-patches: \e086; +$fa-var-check-double: \f560; +$fa-var-arrow-down-a-z: \f15d; +$fa-var-sort-alpha-asc: \f15d; +$fa-var-sort-alpha-down: \f15d; +$fa-var-money-bill-wheat: \e52a; +$fa-var-cookie: \f563; +$fa-var-arrow-rotate-left: \f0e2; +$fa-var-arrow-left-rotate: \f0e2; +$fa-var-arrow-rotate-back: \f0e2; +$fa-var-arrow-rotate-backward: \f0e2; +$fa-var-undo: \f0e2; +$fa-var-hard-drive: \f0a0; +$fa-var-hdd: \f0a0; +$fa-var-face-grin-squint-tears: \f586; +$fa-var-grin-squint-tears: \f586; +$fa-var-dumbbell: \f44b; +$fa-var-rectangle-list: \f022; +$fa-var-list-alt: \f022; +$fa-var-tarp-droplet: \e57c; +$fa-var-house-medical-circle-check: \e511; +$fa-var-person-skiing-nordic: \f7ca; +$fa-var-skiing-nordic: \f7ca; +$fa-var-calendar-plus: \f271; +$fa-var-plane-arrival: \f5af; +$fa-var-circle-left: \f359; +$fa-var-arrow-alt-circle-left: \f359; +$fa-var-train-subway: \f239; +$fa-var-subway: \f239; +$fa-var-chart-gantt: \e0e4; +$fa-var-indian-rupee-sign: \e1bc; +$fa-var-indian-rupee: \e1bc; +$fa-var-inr: \e1bc; +$fa-var-crop-simple: \f565; +$fa-var-crop-alt: \f565; +$fa-var-money-bill-1: \f3d1; +$fa-var-money-bill-alt: \f3d1; +$fa-var-left-long: \f30a; +$fa-var-long-arrow-alt-left: \f30a; +$fa-var-dna: \f471; +$fa-var-virus-slash: \e075; +$fa-var-minus: \f068; +$fa-var-subtract: \f068; +$fa-var-chess: \f439; +$fa-var-arrow-left-long: \f177; +$fa-var-long-arrow-left: \f177; +$fa-var-plug-circle-check: \e55c; +$fa-var-street-view: \f21d; +$fa-var-franc-sign: \e18f; +$fa-var-volume-off: \f026; +$fa-var-hands-asl-interpreting: \f2a3; +$fa-var-american-sign-language-interpreting: \f2a3; +$fa-var-asl-interpreting: \f2a3; +$fa-var-hands-american-sign-language-interpreting: \f2a3; +$fa-var-gear: \f013; +$fa-var-cog: \f013; +$fa-var-droplet-slash: \f5c7; +$fa-var-tint-slash: \f5c7; +$fa-var-mosque: \f678; +$fa-var-mosquito: \e52b; +$fa-var-star-of-david: \f69a; +$fa-var-person-military-rifle: \e54b; +$fa-var-cart-shopping: \f07a; +$fa-var-shopping-cart: \f07a; +$fa-var-vials: \f493; +$fa-var-plug-circle-plus: \e55f; +$fa-var-place-of-worship: \f67f; +$fa-var-grip-vertical: \f58e; +$fa-var-hexagon-nodes: \e699; +$fa-var-arrow-turn-up: \f148; +$fa-var-level-up: \f148; +$fa-var-u: \55; +$fa-var-square-root-variable: \f698; +$fa-var-square-root-alt: \f698; +$fa-var-clock: \f017; +$fa-var-clock-four: \f017; +$fa-var-backward-step: \f048; +$fa-var-step-backward: \f048; +$fa-var-pallet: \f482; +$fa-var-faucet: \e005; +$fa-var-baseball-bat-ball: \f432; +$fa-var-s: \53; +$fa-var-timeline: \e29c; +$fa-var-keyboard: \f11c; +$fa-var-caret-down: \f0d7; +$fa-var-house-chimney-medical: \f7f2; +$fa-var-clinic-medical: \f7f2; +$fa-var-temperature-three-quarters: \f2c8; +$fa-var-temperature-3: \f2c8; +$fa-var-thermometer-3: \f2c8; +$fa-var-thermometer-three-quarters: \f2c8; +$fa-var-mobile-screen: \f3cf; +$fa-var-mobile-android-alt: \f3cf; +$fa-var-plane-up: \e22d; +$fa-var-piggy-bank: \f4d3; +$fa-var-battery-half: \f242; +$fa-var-battery-3: \f242; +$fa-var-mountain-city: \e52e; +$fa-var-coins: \f51e; +$fa-var-khanda: \f66d; +$fa-var-sliders: \f1de; +$fa-var-sliders-h: \f1de; +$fa-var-folder-tree: \f802; +$fa-var-network-wired: \f6ff; +$fa-var-map-pin: \f276; +$fa-var-hamsa: \f665; +$fa-var-cent-sign: \e3f5; +$fa-var-flask: \f0c3; +$fa-var-person-pregnant: \e31e; +$fa-var-wand-sparkles: \f72b; +$fa-var-ellipsis-vertical: \f142; +$fa-var-ellipsis-v: \f142; +$fa-var-ticket: \f145; +$fa-var-power-off: \f011; +$fa-var-right-long: \f30b; +$fa-var-long-arrow-alt-right: \f30b; +$fa-var-flag-usa: \f74d; +$fa-var-laptop-file: \e51d; +$fa-var-tty: \f1e4; +$fa-var-teletype: \f1e4; +$fa-var-diagram-next: \e476; +$fa-var-person-rifle: \e54e; +$fa-var-house-medical-circle-exclamation: \e512; +$fa-var-closed-captioning: \f20a; +$fa-var-person-hiking: \f6ec; +$fa-var-hiking: \f6ec; +$fa-var-venus-double: \f226; +$fa-var-images: \f302; +$fa-var-calculator: \f1ec; +$fa-var-people-pulling: \e535; +$fa-var-n: \4e; +$fa-var-cable-car: \f7da; +$fa-var-tram: \f7da; +$fa-var-cloud-rain: \f73d; +$fa-var-building-circle-xmark: \e4d4; +$fa-var-ship: \f21a; +$fa-var-arrows-down-to-line: \e4b8; +$fa-var-download: \f019; +$fa-var-face-grin: \f580; +$fa-var-grin: \f580; +$fa-var-delete-left: \f55a; +$fa-var-backspace: \f55a; +$fa-var-eye-dropper: \f1fb; +$fa-var-eye-dropper-empty: \f1fb; +$fa-var-eyedropper: \f1fb; +$fa-var-file-circle-check: \e5a0; +$fa-var-forward: \f04e; +$fa-var-mobile: \f3ce; +$fa-var-mobile-android: \f3ce; +$fa-var-mobile-phone: \f3ce; +$fa-var-face-meh: \f11a; +$fa-var-meh: \f11a; +$fa-var-align-center: \f037; +$fa-var-book-skull: \f6b7; +$fa-var-book-dead: \f6b7; +$fa-var-id-card: \f2c2; +$fa-var-drivers-license: \f2c2; +$fa-var-outdent: \f03b; +$fa-var-dedent: \f03b; +$fa-var-heart-circle-exclamation: \e4fe; +$fa-var-house: \f015; +$fa-var-home: \f015; +$fa-var-home-alt: \f015; +$fa-var-home-lg-alt: \f015; +$fa-var-calendar-week: \f784; +$fa-var-laptop-medical: \f812; +$fa-var-b: \42; +$fa-var-file-medical: \f477; +$fa-var-dice-one: \f525; +$fa-var-kiwi-bird: \f535; +$fa-var-arrow-right-arrow-left: \f0ec; +$fa-var-exchange: \f0ec; +$fa-var-rotate-right: \f2f9; +$fa-var-redo-alt: \f2f9; +$fa-var-rotate-forward: \f2f9; +$fa-var-utensils: \f2e7; +$fa-var-cutlery: \f2e7; +$fa-var-arrow-up-wide-short: \f161; +$fa-var-sort-amount-up: \f161; +$fa-var-mill-sign: \e1ed; +$fa-var-bowl-rice: \e2eb; +$fa-var-skull: \f54c; +$fa-var-tower-broadcast: \f519; +$fa-var-broadcast-tower: \f519; +$fa-var-truck-pickup: \f63c; +$fa-var-up-long: \f30c; +$fa-var-long-arrow-alt-up: \f30c; +$fa-var-stop: \f04d; +$fa-var-code-merge: \f387; +$fa-var-upload: \f093; +$fa-var-hurricane: \f751; +$fa-var-mound: \e52d; +$fa-var-toilet-portable: \e583; +$fa-var-compact-disc: \f51f; +$fa-var-file-arrow-down: \f56d; +$fa-var-file-download: \f56d; +$fa-var-caravan: \f8ff; +$fa-var-shield-cat: \e572; +$fa-var-bolt: \f0e7; +$fa-var-zap: \f0e7; +$fa-var-glass-water: \e4f4; +$fa-var-oil-well: \e532; +$fa-var-vault: \e2c5; +$fa-var-mars: \f222; +$fa-var-toilet: \f7d8; +$fa-var-plane-circle-xmark: \e557; +$fa-var-yen-sign: \f157; +$fa-var-cny: \f157; +$fa-var-jpy: \f157; +$fa-var-rmb: \f157; +$fa-var-yen: \f157; +$fa-var-ruble-sign: \f158; +$fa-var-rouble: \f158; +$fa-var-rub: \f158; +$fa-var-ruble: \f158; +$fa-var-sun: \f185; +$fa-var-guitar: \f7a6; +$fa-var-face-laugh-wink: \f59c; +$fa-var-laugh-wink: \f59c; +$fa-var-horse-head: \f7ab; +$fa-var-bore-hole: \e4c3; +$fa-var-industry: \f275; +$fa-var-circle-down: \f358; +$fa-var-arrow-alt-circle-down: \f358; +$fa-var-arrows-turn-to-dots: \e4c1; +$fa-var-florin-sign: \e184; +$fa-var-arrow-down-short-wide: \f884; +$fa-var-sort-amount-desc: \f884; +$fa-var-sort-amount-down-alt: \f884; +$fa-var-less-than: \3c; +$fa-var-angle-down: \f107; +$fa-var-car-tunnel: \e4de; +$fa-var-head-side-cough: \e061; +$fa-var-grip-lines: \f7a4; +$fa-var-thumbs-down: \f165; +$fa-var-user-lock: \f502; +$fa-var-arrow-right-long: \f178; +$fa-var-long-arrow-right: \f178; +$fa-var-anchor-circle-xmark: \e4ac; +$fa-var-ellipsis: \f141; +$fa-var-ellipsis-h: \f141; +$fa-var-chess-pawn: \f443; +$fa-var-kit-medical: \f479; +$fa-var-first-aid: \f479; +$fa-var-person-through-window: \e5a9; +$fa-var-toolbox: \f552; +$fa-var-hands-holding-circle: \e4fb; +$fa-var-bug: \f188; +$fa-var-credit-card: \f09d; +$fa-var-credit-card-alt: \f09d; +$fa-var-car: \f1b9; +$fa-var-automobile: \f1b9; +$fa-var-hand-holding-hand: \e4f7; +$fa-var-book-open-reader: \f5da; +$fa-var-book-reader: \f5da; +$fa-var-mountain-sun: \e52f; +$fa-var-arrows-left-right-to-line: \e4ba; +$fa-var-dice-d20: \f6cf; +$fa-var-truck-droplet: \e58c; +$fa-var-file-circle-xmark: \e5a1; +$fa-var-temperature-arrow-up: \e040; +$fa-var-temperature-up: \e040; +$fa-var-medal: \f5a2; +$fa-var-bed: \f236; +$fa-var-square-h: \f0fd; +$fa-var-h-square: \f0fd; +$fa-var-podcast: \f2ce; +$fa-var-temperature-full: \f2c7; +$fa-var-temperature-4: \f2c7; +$fa-var-thermometer-4: \f2c7; +$fa-var-thermometer-full: \f2c7; +$fa-var-bell: \f0f3; +$fa-var-superscript: \f12b; +$fa-var-plug-circle-xmark: \e560; +$fa-var-star-of-life: \f621; +$fa-var-phone-slash: \f3dd; +$fa-var-paint-roller: \f5aa; +$fa-var-handshake-angle: \f4c4; +$fa-var-hands-helping: \f4c4; +$fa-var-location-dot: \f3c5; +$fa-var-map-marker-alt: \f3c5; +$fa-var-file: \f15b; +$fa-var-greater-than: \3e; +$fa-var-person-swimming: \f5c4; +$fa-var-swimmer: \f5c4; +$fa-var-arrow-down: \f063; +$fa-var-droplet: \f043; +$fa-var-tint: \f043; +$fa-var-eraser: \f12d; +$fa-var-earth-americas: \f57d; +$fa-var-earth: \f57d; +$fa-var-earth-america: \f57d; +$fa-var-globe-americas: \f57d; +$fa-var-person-burst: \e53b; +$fa-var-dove: \f4ba; +$fa-var-battery-empty: \f244; +$fa-var-battery-0: \f244; +$fa-var-socks: \f696; +$fa-var-inbox: \f01c; +$fa-var-section: \e447; +$fa-var-gauge-high: \f625; +$fa-var-tachometer-alt: \f625; +$fa-var-tachometer-alt-fast: \f625; +$fa-var-envelope-open-text: \f658; +$fa-var-hospital: \f0f8; +$fa-var-hospital-alt: \f0f8; +$fa-var-hospital-wide: \f0f8; +$fa-var-wine-bottle: \f72f; +$fa-var-chess-rook: \f447; +$fa-var-bars-staggered: \f550; +$fa-var-reorder: \f550; +$fa-var-stream: \f550; +$fa-var-dharmachakra: \f655; +$fa-var-hotdog: \f80f; +$fa-var-person-walking-with-cane: \f29d; +$fa-var-blind: \f29d; +$fa-var-drum: \f569; +$fa-var-ice-cream: \f810; +$fa-var-heart-circle-bolt: \e4fc; +$fa-var-fax: \f1ac; +$fa-var-paragraph: \f1dd; +$fa-var-check-to-slot: \f772; +$fa-var-vote-yea: \f772; +$fa-var-star-half: \f089; +$fa-var-boxes-stacked: \f468; +$fa-var-boxes: \f468; +$fa-var-boxes-alt: \f468; +$fa-var-link: \f0c1; +$fa-var-chain: \f0c1; +$fa-var-ear-listen: \f2a2; +$fa-var-assistive-listening-systems: \f2a2; +$fa-var-tree-city: \e587; +$fa-var-play: \f04b; +$fa-var-font: \f031; +$fa-var-table-cells-row-lock: \e67a; +$fa-var-rupiah-sign: \e23d; +$fa-var-magnifying-glass: \f002; +$fa-var-search: \f002; +$fa-var-table-tennis-paddle-ball: \f45d; +$fa-var-ping-pong-paddle-ball: \f45d; +$fa-var-table-tennis: \f45d; +$fa-var-person-dots-from-line: \f470; +$fa-var-diagnoses: \f470; +$fa-var-trash-can-arrow-up: \f82a; +$fa-var-trash-restore-alt: \f82a; +$fa-var-naira-sign: \e1f6; +$fa-var-cart-arrow-down: \f218; +$fa-var-walkie-talkie: \f8ef; +$fa-var-file-pen: \f31c; +$fa-var-file-edit: \f31c; +$fa-var-receipt: \f543; +$fa-var-square-pen: \f14b; +$fa-var-pen-square: \f14b; +$fa-var-pencil-square: \f14b; +$fa-var-suitcase-rolling: \f5c1; +$fa-var-person-circle-exclamation: \e53f; +$fa-var-chevron-down: \f078; +$fa-var-battery-full: \f240; +$fa-var-battery: \f240; +$fa-var-battery-5: \f240; +$fa-var-skull-crossbones: \f714; +$fa-var-code-compare: \e13a; +$fa-var-list-ul: \f0ca; +$fa-var-list-dots: \f0ca; +$fa-var-school-lock: \e56f; +$fa-var-tower-cell: \e585; +$fa-var-down-long: \f309; +$fa-var-long-arrow-alt-down: \f309; +$fa-var-ranking-star: \e561; +$fa-var-chess-king: \f43f; +$fa-var-person-harassing: \e549; +$fa-var-brazilian-real-sign: \e46c; +$fa-var-landmark-dome: \f752; +$fa-var-landmark-alt: \f752; +$fa-var-arrow-up: \f062; +$fa-var-tv: \f26c; +$fa-var-television: \f26c; +$fa-var-tv-alt: \f26c; +$fa-var-shrimp: \e448; +$fa-var-list-check: \f0ae; +$fa-var-tasks: \f0ae; +$fa-var-jug-detergent: \e519; +$fa-var-circle-user: \f2bd; +$fa-var-user-circle: \f2bd; +$fa-var-user-shield: \f505; +$fa-var-wind: \f72e; +$fa-var-car-burst: \f5e1; +$fa-var-car-crash: \f5e1; +$fa-var-y: \59; +$fa-var-person-snowboarding: \f7ce; +$fa-var-snowboarding: \f7ce; +$fa-var-truck-fast: \f48b; +$fa-var-shipping-fast: \f48b; +$fa-var-fish: \f578; +$fa-var-user-graduate: \f501; +$fa-var-circle-half-stroke: \f042; +$fa-var-adjust: \f042; +$fa-var-clapperboard: \e131; +$fa-var-circle-radiation: \f7ba; +$fa-var-radiation-alt: \f7ba; +$fa-var-baseball: \f433; +$fa-var-baseball-ball: \f433; +$fa-var-jet-fighter-up: \e518; +$fa-var-diagram-project: \f542; +$fa-var-project-diagram: \f542; +$fa-var-copy: \f0c5; +$fa-var-volume-xmark: \f6a9; +$fa-var-volume-mute: \f6a9; +$fa-var-volume-times: \f6a9; +$fa-var-hand-sparkles: \e05d; +$fa-var-grip: \f58d; +$fa-var-grip-horizontal: \f58d; +$fa-var-share-from-square: \f14d; +$fa-var-share-square: \f14d; +$fa-var-child-combatant: \e4e0; +$fa-var-child-rifle: \e4e0; +$fa-var-gun: \e19b; +$fa-var-square-phone: \f098; +$fa-var-phone-square: \f098; +$fa-var-plus: \2b; +$fa-var-add: \2b; +$fa-var-expand: \f065; +$fa-var-computer: \e4e5; +$fa-var-xmark: \f00d; +$fa-var-close: \f00d; +$fa-var-multiply: \f00d; +$fa-var-remove: \f00d; +$fa-var-times: \f00d; +$fa-var-arrows-up-down-left-right: \f047; +$fa-var-arrows: \f047; +$fa-var-chalkboard-user: \f51c; +$fa-var-chalkboard-teacher: \f51c; +$fa-var-peso-sign: \e222; +$fa-var-building-shield: \e4d8; +$fa-var-baby: \f77c; +$fa-var-users-line: \e592; +$fa-var-quote-left: \f10d; +$fa-var-quote-left-alt: \f10d; +$fa-var-tractor: \f722; +$fa-var-trash-arrow-up: \f829; +$fa-var-trash-restore: \f829; +$fa-var-arrow-down-up-lock: \e4b0; +$fa-var-lines-leaning: \e51e; +$fa-var-ruler-combined: \f546; +$fa-var-copyright: \f1f9; +$fa-var-equals: \3d; +$fa-var-blender: \f517; +$fa-var-teeth: \f62e; +$fa-var-shekel-sign: \f20b; +$fa-var-ils: \f20b; +$fa-var-shekel: \f20b; +$fa-var-sheqel: \f20b; +$fa-var-sheqel-sign: \f20b; +$fa-var-map: \f279; +$fa-var-rocket: \f135; +$fa-var-photo-film: \f87c; +$fa-var-photo-video: \f87c; +$fa-var-folder-minus: \f65d; +$fa-var-hexagon-nodes-bolt: \e69a; +$fa-var-store: \f54e; +$fa-var-arrow-trend-up: \e098; +$fa-var-plug-circle-minus: \e55e; +$fa-var-sign-hanging: \f4d9; +$fa-var-sign: \f4d9; +$fa-var-bezier-curve: \f55b; +$fa-var-bell-slash: \f1f6; +$fa-var-tablet: \f3fb; +$fa-var-tablet-android: \f3fb; +$fa-var-school-flag: \e56e; +$fa-var-fill: \f575; +$fa-var-angle-up: \f106; +$fa-var-drumstick-bite: \f6d7; +$fa-var-holly-berry: \f7aa; +$fa-var-chevron-left: \f053; +$fa-var-bacteria: \e059; +$fa-var-hand-lizard: \f258; +$fa-var-notdef: \e1fe; +$fa-var-disease: \f7fa; +$fa-var-briefcase-medical: \f469; +$fa-var-genderless: \f22d; +$fa-var-chevron-right: \f054; +$fa-var-retweet: \f079; +$fa-var-car-rear: \f5de; +$fa-var-car-alt: \f5de; +$fa-var-pump-soap: \e06b; +$fa-var-video-slash: \f4e2; +$fa-var-battery-quarter: \f243; +$fa-var-battery-2: \f243; +$fa-var-radio: \f8d7; +$fa-var-baby-carriage: \f77d; +$fa-var-carriage-baby: \f77d; +$fa-var-traffic-light: \f637; +$fa-var-thermometer: \f491; +$fa-var-vr-cardboard: \f729; +$fa-var-hand-middle-finger: \f806; +$fa-var-percent: \25; +$fa-var-percentage: \25; +$fa-var-truck-moving: \f4df; +$fa-var-glass-water-droplet: \e4f5; +$fa-var-display: \e163; +$fa-var-face-smile: \f118; +$fa-var-smile: \f118; +$fa-var-thumbtack: \f08d; +$fa-var-thumb-tack: \f08d; +$fa-var-trophy: \f091; +$fa-var-person-praying: \f683; +$fa-var-pray: \f683; +$fa-var-hammer: \f6e3; +$fa-var-hand-peace: \f25b; +$fa-var-rotate: \f2f1; +$fa-var-sync-alt: \f2f1; +$fa-var-spinner: \f110; +$fa-var-robot: \f544; +$fa-var-peace: \f67c; +$fa-var-gears: \f085; +$fa-var-cogs: \f085; +$fa-var-warehouse: \f494; +$fa-var-arrow-up-right-dots: \e4b7; +$fa-var-splotch: \f5bc; +$fa-var-face-grin-hearts: \f584; +$fa-var-grin-hearts: \f584; +$fa-var-dice-four: \f524; +$fa-var-sim-card: \f7c4; +$fa-var-transgender: \f225; +$fa-var-transgender-alt: \f225; +$fa-var-mercury: \f223; +$fa-var-arrow-turn-down: \f149; +$fa-var-level-down: \f149; +$fa-var-person-falling-burst: \e547; +$fa-var-award: \f559; +$fa-var-ticket-simple: \f3ff; +$fa-var-ticket-alt: \f3ff; +$fa-var-building: \f1ad; +$fa-var-angles-left: \f100; +$fa-var-angle-double-left: \f100; +$fa-var-qrcode: \f029; +$fa-var-clock-rotate-left: \f1da; +$fa-var-history: \f1da; +$fa-var-face-grin-beam-sweat: \f583; +$fa-var-grin-beam-sweat: \f583; +$fa-var-file-export: \f56e; +$fa-var-arrow-right-from-file: \f56e; +$fa-var-shield: \f132; +$fa-var-shield-blank: \f132; +$fa-var-arrow-up-short-wide: \f885; +$fa-var-sort-amount-up-alt: \f885; +$fa-var-comment-nodes: \e696; +$fa-var-house-medical: \e3b2; +$fa-var-golf-ball-tee: \f450; +$fa-var-golf-ball: \f450; +$fa-var-circle-chevron-left: \f137; +$fa-var-chevron-circle-left: \f137; +$fa-var-house-chimney-window: \e00d; +$fa-var-pen-nib: \f5ad; +$fa-var-tent-arrow-turn-left: \e580; +$fa-var-tents: \e582; +$fa-var-wand-magic: \f0d0; +$fa-var-magic: \f0d0; +$fa-var-dog: \f6d3; +$fa-var-carrot: \f787; +$fa-var-moon: \f186; +$fa-var-wine-glass-empty: \f5ce; +$fa-var-wine-glass-alt: \f5ce; +$fa-var-cheese: \f7ef; +$fa-var-yin-yang: \f6ad; +$fa-var-music: \f001; +$fa-var-code-commit: \f386; +$fa-var-temperature-low: \f76b; +$fa-var-person-biking: \f84a; +$fa-var-biking: \f84a; +$fa-var-broom: \f51a; +$fa-var-shield-heart: \e574; +$fa-var-gopuram: \f664; +$fa-var-earth-oceania: \e47b; +$fa-var-globe-oceania: \e47b; +$fa-var-square-xmark: \f2d3; +$fa-var-times-square: \f2d3; +$fa-var-xmark-square: \f2d3; +$fa-var-hashtag: \23; +$fa-var-up-right-and-down-left-from-center: \f424; +$fa-var-expand-alt: \f424; +$fa-var-oil-can: \f613; +$fa-var-t: \54; +$fa-var-hippo: \f6ed; +$fa-var-chart-column: \e0e3; +$fa-var-infinity: \f534; +$fa-var-vial-circle-check: \e596; +$fa-var-person-arrow-down-to-line: \e538; +$fa-var-voicemail: \f897; +$fa-var-fan: \f863; +$fa-var-person-walking-luggage: \e554; +$fa-var-up-down: \f338; +$fa-var-arrows-alt-v: \f338; +$fa-var-cloud-moon-rain: \f73c; +$fa-var-calendar: \f133; +$fa-var-trailer: \e041; +$fa-var-bahai: \f666; +$fa-var-haykal: \f666; +$fa-var-sd-card: \f7c2; +$fa-var-dragon: \f6d5; +$fa-var-shoe-prints: \f54b; +$fa-var-circle-plus: \f055; +$fa-var-plus-circle: \f055; +$fa-var-face-grin-tongue-wink: \f58b; +$fa-var-grin-tongue-wink: \f58b; +$fa-var-hand-holding: \f4bd; +$fa-var-plug-circle-exclamation: \e55d; +$fa-var-link-slash: \f127; +$fa-var-chain-broken: \f127; +$fa-var-chain-slash: \f127; +$fa-var-unlink: \f127; +$fa-var-clone: \f24d; +$fa-var-person-walking-arrow-loop-left: \e551; +$fa-var-arrow-up-z-a: \f882; +$fa-var-sort-alpha-up-alt: \f882; +$fa-var-fire-flame-curved: \f7e4; +$fa-var-fire-alt: \f7e4; +$fa-var-tornado: \f76f; +$fa-var-file-circle-plus: \e494; +$fa-var-book-quran: \f687; +$fa-var-quran: \f687; +$fa-var-anchor: \f13d; +$fa-var-border-all: \f84c; +$fa-var-face-angry: \f556; +$fa-var-angry: \f556; +$fa-var-cookie-bite: \f564; +$fa-var-arrow-trend-down: \e097; +$fa-var-rss: \f09e; +$fa-var-feed: \f09e; +$fa-var-draw-polygon: \f5ee; +$fa-var-scale-balanced: \f24e; +$fa-var-balance-scale: \f24e; +$fa-var-gauge-simple-high: \f62a; +$fa-var-tachometer: \f62a; +$fa-var-tachometer-fast: \f62a; +$fa-var-shower: \f2cc; +$fa-var-desktop: \f390; +$fa-var-desktop-alt: \f390; +$fa-var-m: \4d; +$fa-var-table-list: \f00b; +$fa-var-th-list: \f00b; +$fa-var-comment-sms: \f7cd; +$fa-var-sms: \f7cd; +$fa-var-book: \f02d; +$fa-var-user-plus: \f234; +$fa-var-check: \f00c; +$fa-var-battery-three-quarters: \f241; +$fa-var-battery-4: \f241; +$fa-var-house-circle-check: \e509; +$fa-var-angle-left: \f104; +$fa-var-diagram-successor: \e47a; +$fa-var-truck-arrow-right: \e58b; +$fa-var-arrows-split-up-and-left: \e4bc; +$fa-var-hand-fist: \f6de; +$fa-var-fist-raised: \f6de; +$fa-var-cloud-moon: \f6c3; +$fa-var-briefcase: \f0b1; +$fa-var-person-falling: \e546; +$fa-var-image-portrait: \f3e0; +$fa-var-portrait: \f3e0; +$fa-var-user-tag: \f507; +$fa-var-rug: \e569; +$fa-var-earth-europe: \f7a2; +$fa-var-globe-europe: \f7a2; +$fa-var-cart-flatbed-suitcase: \f59d; +$fa-var-luggage-cart: \f59d; +$fa-var-rectangle-xmark: \f410; +$fa-var-rectangle-times: \f410; +$fa-var-times-rectangle: \f410; +$fa-var-window-close: \f410; +$fa-var-baht-sign: \e0ac; +$fa-var-book-open: \f518; +$fa-var-book-journal-whills: \f66a; +$fa-var-journal-whills: \f66a; +$fa-var-handcuffs: \e4f8; +$fa-var-triangle-exclamation: \f071; +$fa-var-exclamation-triangle: \f071; +$fa-var-warning: \f071; +$fa-var-database: \f1c0; +$fa-var-share: \f064; +$fa-var-mail-forward: \f064; +$fa-var-bottle-droplet: \e4c4; +$fa-var-mask-face: \e1d7; +$fa-var-hill-rockslide: \e508; +$fa-var-right-left: \f362; +$fa-var-exchange-alt: \f362; +$fa-var-paper-plane: \f1d8; +$fa-var-road-circle-exclamation: \e565; +$fa-var-dungeon: \f6d9; +$fa-var-align-right: \f038; +$fa-var-money-bill-1-wave: \f53b; +$fa-var-money-bill-wave-alt: \f53b; +$fa-var-life-ring: \f1cd; +$fa-var-hands: \f2a7; +$fa-var-sign-language: \f2a7; +$fa-var-signing: \f2a7; +$fa-var-calendar-day: \f783; +$fa-var-water-ladder: \f5c5; +$fa-var-ladder-water: \f5c5; +$fa-var-swimming-pool: \f5c5; +$fa-var-arrows-up-down: \f07d; +$fa-var-arrows-v: \f07d; +$fa-var-face-grimace: \f57f; +$fa-var-grimace: \f57f; +$fa-var-wheelchair-move: \e2ce; +$fa-var-wheelchair-alt: \e2ce; +$fa-var-turn-down: \f3be; +$fa-var-level-down-alt: \f3be; +$fa-var-person-walking-arrow-right: \e552; +$fa-var-square-envelope: \f199; +$fa-var-envelope-square: \f199; +$fa-var-dice: \f522; +$fa-var-bowling-ball: \f436; +$fa-var-brain: \f5dc; +$fa-var-bandage: \f462; +$fa-var-band-aid: \f462; +$fa-var-calendar-minus: \f272; +$fa-var-circle-xmark: \f057; +$fa-var-times-circle: \f057; +$fa-var-xmark-circle: \f057; +$fa-var-gifts: \f79c; +$fa-var-hotel: \f594; +$fa-var-earth-asia: \f57e; +$fa-var-globe-asia: \f57e; +$fa-var-id-card-clip: \f47f; +$fa-var-id-card-alt: \f47f; +$fa-var-magnifying-glass-plus: \f00e; +$fa-var-search-plus: \f00e; +$fa-var-thumbs-up: \f164; +$fa-var-user-clock: \f4fd; +$fa-var-hand-dots: \f461; +$fa-var-allergies: \f461; +$fa-var-file-invoice: \f570; +$fa-var-window-minimize: \f2d1; +$fa-var-mug-saucer: \f0f4; +$fa-var-coffee: \f0f4; +$fa-var-brush: \f55d; +$fa-var-file-half-dashed: \e698; +$fa-var-mask: \f6fa; +$fa-var-magnifying-glass-minus: \f010; +$fa-var-search-minus: \f010; +$fa-var-ruler-vertical: \f548; +$fa-var-user-large: \f406; +$fa-var-user-alt: \f406; +$fa-var-train-tram: \e5b4; +$fa-var-user-nurse: \f82f; +$fa-var-syringe: \f48e; +$fa-var-cloud-sun: \f6c4; +$fa-var-stopwatch-20: \e06f; +$fa-var-square-full: \f45c; +$fa-var-magnet: \f076; +$fa-var-jar: \e516; +$fa-var-note-sticky: \f249; +$fa-var-sticky-note: \f249; +$fa-var-bug-slash: \e490; +$fa-var-arrow-up-from-water-pump: \e4b6; +$fa-var-bone: \f5d7; +$fa-var-table-cells-row-unlock: \e691; +$fa-var-user-injured: \f728; +$fa-var-face-sad-tear: \f5b4; +$fa-var-sad-tear: \f5b4; +$fa-var-plane: \f072; +$fa-var-tent-arrows-down: \e581; +$fa-var-exclamation: \21; +$fa-var-arrows-spin: \e4bb; +$fa-var-print: \f02f; +$fa-var-turkish-lira-sign: \e2bb; +$fa-var-try: \e2bb; +$fa-var-turkish-lira: \e2bb; +$fa-var-dollar-sign: \24; +$fa-var-dollar: \24; +$fa-var-usd: \24; +$fa-var-x: \58; +$fa-var-magnifying-glass-dollar: \f688; +$fa-var-search-dollar: \f688; +$fa-var-users-gear: \f509; +$fa-var-users-cog: \f509; +$fa-var-person-military-pointing: \e54a; +$fa-var-building-columns: \f19c; +$fa-var-bank: \f19c; +$fa-var-institution: \f19c; +$fa-var-museum: \f19c; +$fa-var-university: \f19c; +$fa-var-umbrella: \f0e9; +$fa-var-trowel: \e589; +$fa-var-d: \44; +$fa-var-stapler: \e5af; +$fa-var-masks-theater: \f630; +$fa-var-theater-masks: \f630; +$fa-var-kip-sign: \e1c4; +$fa-var-hand-point-left: \f0a5; +$fa-var-handshake-simple: \f4c6; +$fa-var-handshake-alt: \f4c6; +$fa-var-jet-fighter: \f0fb; +$fa-var-fighter-jet: \f0fb; +$fa-var-square-share-nodes: \f1e1; +$fa-var-share-alt-square: \f1e1; +$fa-var-barcode: \f02a; +$fa-var-plus-minus: \e43c; +$fa-var-video: \f03d; +$fa-var-video-camera: \f03d; +$fa-var-graduation-cap: \f19d; +$fa-var-mortar-board: \f19d; +$fa-var-hand-holding-medical: \e05c; +$fa-var-person-circle-check: \e53e; +$fa-var-turn-up: \f3bf; +$fa-var-level-up-alt: \f3bf; + +$fa-var-monero: \f3d0; +$fa-var-hooli: \f427; +$fa-var-yelp: \f1e9; +$fa-var-cc-visa: \f1f0; +$fa-var-lastfm: \f202; +$fa-var-shopware: \f5b5; +$fa-var-creative-commons-nc: \f4e8; +$fa-var-aws: \f375; +$fa-var-redhat: \f7bc; +$fa-var-yoast: \f2b1; +$fa-var-cloudflare: \e07d; +$fa-var-ups: \f7e0; +$fa-var-pixiv: \e640; +$fa-var-wpexplorer: \f2de; +$fa-var-dyalog: \f399; +$fa-var-bity: \f37a; +$fa-var-stackpath: \f842; +$fa-var-buysellads: \f20d; +$fa-var-first-order: \f2b0; +$fa-var-modx: \f285; +$fa-var-guilded: \e07e; +$fa-var-vnv: \f40b; +$fa-var-square-js: \f3b9; +$fa-var-js-square: \f3b9; +$fa-var-microsoft: \f3ca; +$fa-var-qq: \f1d6; +$fa-var-orcid: \f8d2; +$fa-var-java: \f4e4; +$fa-var-invision: \f7b0; +$fa-var-creative-commons-pd-alt: \f4ed; +$fa-var-centercode: \f380; +$fa-var-glide-g: \f2a6; +$fa-var-drupal: \f1a9; +$fa-var-jxl: \e67b; +$fa-var-dart-lang: \e693; +$fa-var-hire-a-helper: \f3b0; +$fa-var-creative-commons-by: \f4e7; +$fa-var-unity: \e049; +$fa-var-whmcs: \f40d; +$fa-var-rocketchat: \f3e8; +$fa-var-vk: \f189; +$fa-var-untappd: \f405; +$fa-var-mailchimp: \f59e; +$fa-var-css3-alt: \f38b; +$fa-var-square-reddit: \f1a2; +$fa-var-reddit-square: \f1a2; +$fa-var-vimeo-v: \f27d; +$fa-var-contao: \f26d; +$fa-var-square-font-awesome: \e5ad; +$fa-var-deskpro: \f38f; +$fa-var-brave: \e63c; +$fa-var-sistrix: \f3ee; +$fa-var-square-instagram: \e055; +$fa-var-instagram-square: \e055; +$fa-var-battle-net: \f835; +$fa-var-the-red-yeti: \f69d; +$fa-var-square-hacker-news: \f3af; +$fa-var-hacker-news-square: \f3af; +$fa-var-edge: \f282; +$fa-var-threads: \e618; +$fa-var-napster: \f3d2; +$fa-var-square-snapchat: \f2ad; +$fa-var-snapchat-square: \f2ad; +$fa-var-google-plus-g: \f0d5; +$fa-var-artstation: \f77a; +$fa-var-markdown: \f60f; +$fa-var-sourcetree: \f7d3; +$fa-var-google-plus: \f2b3; +$fa-var-diaspora: \f791; +$fa-var-foursquare: \f180; +$fa-var-stack-overflow: \f16c; +$fa-var-github-alt: \f113; +$fa-var-phoenix-squadron: \f511; +$fa-var-pagelines: \f18c; +$fa-var-algolia: \f36c; +$fa-var-red-river: \f3e3; +$fa-var-creative-commons-sa: \f4ef; +$fa-var-safari: \f267; +$fa-var-google: \f1a0; +$fa-var-square-font-awesome-stroke: \f35c; +$fa-var-font-awesome-alt: \f35c; +$fa-var-atlassian: \f77b; +$fa-var-linkedin-in: \f0e1; +$fa-var-digital-ocean: \f391; +$fa-var-nimblr: \f5a8; +$fa-var-chromecast: \f838; +$fa-var-evernote: \f839; +$fa-var-hacker-news: \f1d4; +$fa-var-creative-commons-sampling: \f4f0; +$fa-var-adversal: \f36a; +$fa-var-creative-commons: \f25e; +$fa-var-watchman-monitoring: \e087; +$fa-var-fonticons: \f280; +$fa-var-weixin: \f1d7; +$fa-var-shirtsinbulk: \f214; +$fa-var-codepen: \f1cb; +$fa-var-git-alt: \f841; +$fa-var-lyft: \f3c3; +$fa-var-rev: \f5b2; +$fa-var-windows: \f17a; +$fa-var-wizards-of-the-coast: \f730; +$fa-var-square-viadeo: \f2aa; +$fa-var-viadeo-square: \f2aa; +$fa-var-meetup: \f2e0; +$fa-var-centos: \f789; +$fa-var-adn: \f170; +$fa-var-cloudsmith: \f384; +$fa-var-opensuse: \e62b; +$fa-var-pied-piper-alt: \f1a8; +$fa-var-square-dribbble: \f397; +$fa-var-dribbble-square: \f397; +$fa-var-codiepie: \f284; +$fa-var-node: \f419; +$fa-var-mix: \f3cb; +$fa-var-steam: \f1b6; +$fa-var-cc-apple-pay: \f416; +$fa-var-scribd: \f28a; +$fa-var-debian: \e60b; +$fa-var-openid: \f19b; +$fa-var-instalod: \e081; +$fa-var-files-pinwheel: \e69f; +$fa-var-expeditedssl: \f23e; +$fa-var-sellcast: \f2da; +$fa-var-square-twitter: \f081; +$fa-var-twitter-square: \f081; +$fa-var-r-project: \f4f7; +$fa-var-delicious: \f1a5; +$fa-var-freebsd: \f3a4; +$fa-var-vuejs: \f41f; +$fa-var-accusoft: \f369; +$fa-var-ioxhost: \f208; +$fa-var-fonticons-fi: \f3a2; +$fa-var-app-store: \f36f; +$fa-var-cc-mastercard: \f1f1; +$fa-var-itunes-note: \f3b5; +$fa-var-golang: \e40f; +$fa-var-kickstarter: \f3bb; +$fa-var-square-kickstarter: \f3bb; +$fa-var-grav: \f2d6; +$fa-var-weibo: \f18a; +$fa-var-uncharted: \e084; +$fa-var-firstdraft: \f3a1; +$fa-var-square-youtube: \f431; +$fa-var-youtube-square: \f431; +$fa-var-wikipedia-w: \f266; +$fa-var-wpressr: \f3e4; +$fa-var-rendact: \f3e4; +$fa-var-angellist: \f209; +$fa-var-galactic-republic: \f50c; +$fa-var-nfc-directional: \e530; +$fa-var-skype: \f17e; +$fa-var-joget: \f3b7; +$fa-var-fedora: \f798; +$fa-var-stripe-s: \f42a; +$fa-var-meta: \e49b; +$fa-var-laravel: \f3bd; +$fa-var-hotjar: \f3b1; +$fa-var-bluetooth-b: \f294; +$fa-var-square-letterboxd: \e62e; +$fa-var-sticker-mule: \f3f7; +$fa-var-creative-commons-zero: \f4f3; +$fa-var-hips: \f452; +$fa-var-css: \e6a2; +$fa-var-behance: \f1b4; +$fa-var-reddit: \f1a1; +$fa-var-discord: \f392; +$fa-var-chrome: \f268; +$fa-var-app-store-ios: \f370; +$fa-var-cc-discover: \f1f2; +$fa-var-wpbeginner: \f297; +$fa-var-confluence: \f78d; +$fa-var-shoelace: \e60c; +$fa-var-mdb: \f8ca; +$fa-var-dochub: \f394; +$fa-var-accessible-icon: \f368; +$fa-var-ebay: \f4f4; +$fa-var-amazon: \f270; +$fa-var-unsplash: \e07c; +$fa-var-yarn: \f7e3; +$fa-var-square-steam: \f1b7; +$fa-var-steam-square: \f1b7; +$fa-var-500px: \f26e; +$fa-var-square-vimeo: \f194; +$fa-var-vimeo-square: \f194; +$fa-var-asymmetrik: \f372; +$fa-var-font-awesome: \f2b4; +$fa-var-font-awesome-flag: \f2b4; +$fa-var-font-awesome-logo-full: \f2b4; +$fa-var-gratipay: \f184; +$fa-var-apple: \f179; +$fa-var-hive: \e07f; +$fa-var-gitkraken: \f3a6; +$fa-var-keybase: \f4f5; +$fa-var-apple-pay: \f415; +$fa-var-padlet: \e4a0; +$fa-var-amazon-pay: \f42c; +$fa-var-square-github: \f092; +$fa-var-github-square: \f092; +$fa-var-stumbleupon: \f1a4; +$fa-var-fedex: \f797; +$fa-var-phoenix-framework: \f3dc; +$fa-var-shopify: \e057; +$fa-var-neos: \f612; +$fa-var-square-threads: \e619; +$fa-var-hackerrank: \f5f7; +$fa-var-researchgate: \f4f8; +$fa-var-swift: \f8e1; +$fa-var-angular: \f420; +$fa-var-speakap: \f3f3; +$fa-var-angrycreative: \f36e; +$fa-var-y-combinator: \f23b; +$fa-var-empire: \f1d1; +$fa-var-envira: \f299; +$fa-var-google-scholar: \e63b; +$fa-var-square-gitlab: \e5ae; +$fa-var-gitlab-square: \e5ae; +$fa-var-studiovinari: \f3f8; +$fa-var-pied-piper: \f2ae; +$fa-var-wordpress: \f19a; +$fa-var-product-hunt: \f288; +$fa-var-firefox: \f269; +$fa-var-linode: \f2b8; +$fa-var-goodreads: \f3a8; +$fa-var-square-odnoklassniki: \f264; +$fa-var-odnoklassniki-square: \f264; +$fa-var-jsfiddle: \f1cc; +$fa-var-sith: \f512; +$fa-var-themeisle: \f2b2; +$fa-var-page4: \f3d7; +$fa-var-hashnode: \e499; +$fa-var-react: \f41b; +$fa-var-cc-paypal: \f1f4; +$fa-var-squarespace: \f5be; +$fa-var-cc-stripe: \f1f5; +$fa-var-creative-commons-share: \f4f2; +$fa-var-bitcoin: \f379; +$fa-var-keycdn: \f3ba; +$fa-var-opera: \f26a; +$fa-var-itch-io: \f83a; +$fa-var-umbraco: \f8e8; +$fa-var-galactic-senate: \f50d; +$fa-var-ubuntu: \f7df; +$fa-var-draft2digital: \f396; +$fa-var-stripe: \f429; +$fa-var-houzz: \f27c; +$fa-var-gg: \f260; +$fa-var-dhl: \f790; +$fa-var-square-pinterest: \f0d3; +$fa-var-pinterest-square: \f0d3; +$fa-var-xing: \f168; +$fa-var-blackberry: \f37b; +$fa-var-creative-commons-pd: \f4ec; +$fa-var-playstation: \f3df; +$fa-var-quinscape: \f459; +$fa-var-less: \f41d; +$fa-var-blogger-b: \f37d; +$fa-var-opencart: \f23d; +$fa-var-vine: \f1ca; +$fa-var-signal-messenger: \e663; +$fa-var-paypal: \f1ed; +$fa-var-gitlab: \f296; +$fa-var-typo3: \f42b; +$fa-var-reddit-alien: \f281; +$fa-var-yahoo: \f19e; +$fa-var-dailymotion: \e052; +$fa-var-affiliatetheme: \f36b; +$fa-var-pied-piper-pp: \f1a7; +$fa-var-bootstrap: \f836; +$fa-var-odnoklassniki: \f263; +$fa-var-nfc-symbol: \e531; +$fa-var-mintbit: \e62f; +$fa-var-ethereum: \f42e; +$fa-var-speaker-deck: \f83c; +$fa-var-creative-commons-nc-eu: \f4e9; +$fa-var-patreon: \f3d9; +$fa-var-avianex: \f374; +$fa-var-ello: \f5f1; +$fa-var-gofore: \f3a7; +$fa-var-bimobject: \f378; +$fa-var-brave-reverse: \e63d; +$fa-var-facebook-f: \f39e; +$fa-var-square-google-plus: \f0d4; +$fa-var-google-plus-square: \f0d4; +$fa-var-web-awesome: \e682; +$fa-var-mandalorian: \f50f; +$fa-var-first-order-alt: \f50a; +$fa-var-osi: \f41a; +$fa-var-google-wallet: \f1ee; +$fa-var-d-and-d-beyond: \f6ca; +$fa-var-periscope: \f3da; +$fa-var-fulcrum: \f50b; +$fa-var-cloudscale: \f383; +$fa-var-forumbee: \f211; +$fa-var-mizuni: \f3cc; +$fa-var-schlix: \f3ea; +$fa-var-square-xing: \f169; +$fa-var-xing-square: \f169; +$fa-var-bandcamp: \f2d5; +$fa-var-wpforms: \f298; +$fa-var-cloudversify: \f385; +$fa-var-usps: \f7e1; +$fa-var-megaport: \f5a3; +$fa-var-magento: \f3c4; +$fa-var-spotify: \f1bc; +$fa-var-optin-monster: \f23c; +$fa-var-fly: \f417; +$fa-var-square-bluesky: \e6a3; +$fa-var-aviato: \f421; +$fa-var-itunes: \f3b4; +$fa-var-cuttlefish: \f38c; +$fa-var-blogger: \f37c; +$fa-var-flickr: \f16e; +$fa-var-viber: \f409; +$fa-var-soundcloud: \f1be; +$fa-var-digg: \f1a6; +$fa-var-tencent-weibo: \f1d5; +$fa-var-letterboxd: \e62d; +$fa-var-symfony: \f83d; +$fa-var-maxcdn: \f136; +$fa-var-etsy: \f2d7; +$fa-var-facebook-messenger: \f39f; +$fa-var-audible: \f373; +$fa-var-think-peaks: \f731; +$fa-var-bilibili: \e3d9; +$fa-var-erlang: \f39d; +$fa-var-x-twitter: \e61b; +$fa-var-cotton-bureau: \f89e; +$fa-var-dashcube: \f210; +$fa-var-42-group: \e080; +$fa-var-innosoft: \e080; +$fa-var-stack-exchange: \f18d; +$fa-var-elementor: \f430; +$fa-var-square-pied-piper: \e01e; +$fa-var-pied-piper-square: \e01e; +$fa-var-creative-commons-nd: \f4eb; +$fa-var-palfed: \f3d8; +$fa-var-superpowers: \f2dd; +$fa-var-resolving: \f3e7; +$fa-var-xbox: \f412; +$fa-var-square-web-awesome-stroke: \e684; +$fa-var-searchengin: \f3eb; +$fa-var-tiktok: \e07b; +$fa-var-square-facebook: \f082; +$fa-var-facebook-square: \f082; +$fa-var-renren: \f18b; +$fa-var-linux: \f17c; +$fa-var-glide: \f2a5; +$fa-var-linkedin: \f08c; +$fa-var-hubspot: \f3b2; +$fa-var-deploydog: \f38e; +$fa-var-twitch: \f1e8; +$fa-var-flutter: \e694; +$fa-var-ravelry: \f2d9; +$fa-var-mixer: \e056; +$fa-var-square-lastfm: \f203; +$fa-var-lastfm-square: \f203; +$fa-var-vimeo: \f40a; +$fa-var-mendeley: \f7b3; +$fa-var-uniregistry: \f404; +$fa-var-figma: \f799; +$fa-var-creative-commons-remix: \f4ee; +$fa-var-cc-amazon-pay: \f42d; +$fa-var-dropbox: \f16b; +$fa-var-instagram: \f16d; +$fa-var-cmplid: \e360; +$fa-var-upwork: \e641; +$fa-var-facebook: \f09a; +$fa-var-gripfire: \f3ac; +$fa-var-jedi-order: \f50e; +$fa-var-uikit: \f403; +$fa-var-fort-awesome-alt: \f3a3; +$fa-var-phabricator: \f3db; +$fa-var-ussunnah: \f407; +$fa-var-earlybirds: \f39a; +$fa-var-trade-federation: \f513; +$fa-var-autoprefixer: \f41c; +$fa-var-whatsapp: \f232; +$fa-var-square-upwork: \e67c; +$fa-var-slideshare: \f1e7; +$fa-var-google-play: \f3ab; +$fa-var-viadeo: \f2a9; +$fa-var-line: \f3c0; +$fa-var-google-drive: \f3aa; +$fa-var-servicestack: \f3ec; +$fa-var-simplybuilt: \f215; +$fa-var-bitbucket: \f171; +$fa-var-imdb: \f2d8; +$fa-var-deezer: \e077; +$fa-var-raspberry-pi: \f7bb; +$fa-var-jira: \f7b1; +$fa-var-docker: \f395; +$fa-var-screenpal: \e570; +$fa-var-bluetooth: \f293; +$fa-var-gitter: \f426; +$fa-var-d-and-d: \f38d; +$fa-var-microblog: \e01a; +$fa-var-cc-diners-club: \f24c; +$fa-var-gg-circle: \f261; +$fa-var-pied-piper-hat: \f4e5; +$fa-var-kickstarter-k: \f3bc; +$fa-var-yandex: \f413; +$fa-var-readme: \f4d5; +$fa-var-html5: \f13b; +$fa-var-sellsy: \f213; +$fa-var-square-web-awesome: \e683; +$fa-var-sass: \f41e; +$fa-var-wirsindhandwerk: \e2d0; +$fa-var-wsh: \e2d0; +$fa-var-buromobelexperte: \f37f; +$fa-var-salesforce: \f83b; +$fa-var-octopus-deploy: \e082; +$fa-var-medapps: \f3c6; +$fa-var-ns8: \f3d5; +$fa-var-pinterest-p: \f231; +$fa-var-apper: \f371; +$fa-var-fort-awesome: \f286; +$fa-var-waze: \f83f; +$fa-var-bluesky: \e671; +$fa-var-cc-jcb: \f24b; +$fa-var-snapchat: \f2ab; +$fa-var-snapchat-ghost: \f2ab; +$fa-var-fantasy-flight-games: \f6dc; +$fa-var-rust: \e07a; +$fa-var-wix: \f5cf; +$fa-var-square-behance: \f1b5; +$fa-var-behance-square: \f1b5; +$fa-var-supple: \f3f9; +$fa-var-webflow: \e65c; +$fa-var-rebel: \f1d0; +$fa-var-css3: \f13c; +$fa-var-staylinked: \f3f5; +$fa-var-kaggle: \f5fa; +$fa-var-space-awesome: \e5ac; +$fa-var-deviantart: \f1bd; +$fa-var-cpanel: \f388; +$fa-var-goodreads-g: \f3a9; +$fa-var-square-git: \f1d2; +$fa-var-git-square: \f1d2; +$fa-var-square-tumblr: \f174; +$fa-var-tumblr-square: \f174; +$fa-var-trello: \f181; +$fa-var-creative-commons-nc-jp: \f4ea; +$fa-var-get-pocket: \f265; +$fa-var-perbyte: \e083; +$fa-var-grunt: \f3ad; +$fa-var-weebly: \f5cc; +$fa-var-connectdevelop: \f20e; +$fa-var-leanpub: \f212; +$fa-var-black-tie: \f27e; +$fa-var-themeco: \f5c6; +$fa-var-python: \f3e2; +$fa-var-android: \f17b; +$fa-var-bots: \e340; +$fa-var-free-code-camp: \f2c5; +$fa-var-hornbill: \f592; +$fa-var-js: \f3b8; +$fa-var-ideal: \e013; +$fa-var-git: \f1d3; +$fa-var-dev: \f6cc; +$fa-var-sketch: \f7c6; +$fa-var-yandex-international: \f414; +$fa-var-cc-amex: \f1f3; +$fa-var-uber: \f402; +$fa-var-github: \f09b; +$fa-var-php: \f457; +$fa-var-alipay: \f642; +$fa-var-youtube: \f167; +$fa-var-skyatlas: \f216; +$fa-var-firefox-browser: \e007; +$fa-var-replyd: \f3e6; +$fa-var-suse: \f7d6; +$fa-var-jenkins: \f3b6; +$fa-var-twitter: \f099; +$fa-var-rockrms: \f3e9; +$fa-var-pinterest: \f0d2; +$fa-var-buffer: \f837; +$fa-var-npm: \f3d4; +$fa-var-yammer: \f840; +$fa-var-btc: \f15a; +$fa-var-dribbble: \f17d; +$fa-var-stumbleupon-circle: \f1a3; +$fa-var-internet-explorer: \f26b; +$fa-var-stubber: \e5c7; +$fa-var-telegram: \f2c6; +$fa-var-telegram-plane: \f2c6; +$fa-var-old-republic: \f510; +$fa-var-odysee: \e5c6; +$fa-var-square-whatsapp: \f40c; +$fa-var-whatsapp-square: \f40c; +$fa-var-node-js: \f3d3; +$fa-var-edge-legacy: \e078; +$fa-var-slack: \f198; +$fa-var-slack-hash: \f198; +$fa-var-medrt: \f3c8; +$fa-var-usb: \f287; +$fa-var-tumblr: \f173; +$fa-var-vaadin: \f408; +$fa-var-quora: \f2c4; +$fa-var-square-x-twitter: \e61a; +$fa-var-reacteurope: \f75d; +$fa-var-medium: \f23a; +$fa-var-medium-m: \f23a; +$fa-var-amilia: \f36d; +$fa-var-mixcloud: \f289; +$fa-var-flipboard: \f44d; +$fa-var-viacoin: \f237; +$fa-var-critical-role: \f6c9; +$fa-var-sitrox: \e44a; +$fa-var-discourse: \f393; +$fa-var-joomla: \f1aa; +$fa-var-mastodon: \f4f6; +$fa-var-airbnb: \f834; +$fa-var-wolf-pack-battalion: \f514; +$fa-var-buy-n-large: \f8a6; +$fa-var-gulp: \f3ae; +$fa-var-creative-commons-sampling-plus: \f4f1; +$fa-var-strava: \f428; +$fa-var-ember: \f423; +$fa-var-canadian-maple-leaf: \f785; +$fa-var-teamspeak: \f4f9; +$fa-var-pushed: \f3e1; +$fa-var-wordpress-simple: \f411; +$fa-var-nutritionix: \f3d6; +$fa-var-wodu: \e088; +$fa-var-google-pay: \e079; +$fa-var-intercom: \f7af; +$fa-var-zhihu: \f63f; +$fa-var-korvue: \f42f; +$fa-var-pix: \e43a; +$fa-var-steam-symbol: \f3f6; + +$fa-icons: ( + "0": $fa-var-0, + "1": $fa-var-1, + "2": $fa-var-2, + "3": $fa-var-3, + "4": $fa-var-4, + "5": $fa-var-5, + "6": $fa-var-6, + "7": $fa-var-7, + "8": $fa-var-8, + "9": $fa-var-9, + "fill-drip": $fa-var-fill-drip, + "arrows-to-circle": $fa-var-arrows-to-circle, + "circle-chevron-right": $fa-var-circle-chevron-right, + "chevron-circle-right": $fa-var-chevron-circle-right, + "at": $fa-var-at, + "trash-can": $fa-var-trash-can, + "trash-alt": $fa-var-trash-alt, + "text-height": $fa-var-text-height, + "user-xmark": $fa-var-user-xmark, + "user-times": $fa-var-user-times, + "stethoscope": $fa-var-stethoscope, + "message": $fa-var-message, + "comment-alt": $fa-var-comment-alt, + "info": $fa-var-info, + "down-left-and-up-right-to-center": $fa-var-down-left-and-up-right-to-center, + "compress-alt": $fa-var-compress-alt, + "explosion": $fa-var-explosion, + "file-lines": $fa-var-file-lines, + "file-alt": $fa-var-file-alt, + "file-text": $fa-var-file-text, + "wave-square": $fa-var-wave-square, + "ring": $fa-var-ring, + "building-un": $fa-var-building-un, + "dice-three": $fa-var-dice-three, + "calendar-days": $fa-var-calendar-days, + "calendar-alt": $fa-var-calendar-alt, + "anchor-circle-check": $fa-var-anchor-circle-check, + "building-circle-arrow-right": $fa-var-building-circle-arrow-right, + "volleyball": $fa-var-volleyball, + "volleyball-ball": $fa-var-volleyball-ball, + "arrows-up-to-line": $fa-var-arrows-up-to-line, + "sort-down": $fa-var-sort-down, + "sort-desc": $fa-var-sort-desc, + "circle-minus": $fa-var-circle-minus, + "minus-circle": $fa-var-minus-circle, + "door-open": $fa-var-door-open, + "right-from-bracket": $fa-var-right-from-bracket, + "sign-out-alt": $fa-var-sign-out-alt, + "atom": $fa-var-atom, + "soap": $fa-var-soap, + "icons": $fa-var-icons, + "heart-music-camera-bolt": $fa-var-heart-music-camera-bolt, + "microphone-lines-slash": $fa-var-microphone-lines-slash, + "microphone-alt-slash": $fa-var-microphone-alt-slash, + "bridge-circle-check": $fa-var-bridge-circle-check, + "pump-medical": $fa-var-pump-medical, + "fingerprint": $fa-var-fingerprint, + "hand-point-right": $fa-var-hand-point-right, + "magnifying-glass-location": $fa-var-magnifying-glass-location, + "search-location": $fa-var-search-location, + "forward-step": $fa-var-forward-step, + "step-forward": $fa-var-step-forward, + "face-smile-beam": $fa-var-face-smile-beam, + "smile-beam": $fa-var-smile-beam, + "flag-checkered": $fa-var-flag-checkered, + "football": $fa-var-football, + "football-ball": $fa-var-football-ball, + "school-circle-exclamation": $fa-var-school-circle-exclamation, + "crop": $fa-var-crop, + "angles-down": $fa-var-angles-down, + "angle-double-down": $fa-var-angle-double-down, + "users-rectangle": $fa-var-users-rectangle, + "people-roof": $fa-var-people-roof, + "people-line": $fa-var-people-line, + "beer-mug-empty": $fa-var-beer-mug-empty, + "beer": $fa-var-beer, + "diagram-predecessor": $fa-var-diagram-predecessor, + "arrow-up-long": $fa-var-arrow-up-long, + "long-arrow-up": $fa-var-long-arrow-up, + "fire-flame-simple": $fa-var-fire-flame-simple, + "burn": $fa-var-burn, + "person": $fa-var-person, + "male": $fa-var-male, + "laptop": $fa-var-laptop, + "file-csv": $fa-var-file-csv, + "menorah": $fa-var-menorah, + "truck-plane": $fa-var-truck-plane, + "record-vinyl": $fa-var-record-vinyl, + "face-grin-stars": $fa-var-face-grin-stars, + "grin-stars": $fa-var-grin-stars, + "bong": $fa-var-bong, + "spaghetti-monster-flying": $fa-var-spaghetti-monster-flying, + "pastafarianism": $fa-var-pastafarianism, + "arrow-down-up-across-line": $fa-var-arrow-down-up-across-line, + "spoon": $fa-var-spoon, + "utensil-spoon": $fa-var-utensil-spoon, + "jar-wheat": $fa-var-jar-wheat, + "envelopes-bulk": $fa-var-envelopes-bulk, + "mail-bulk": $fa-var-mail-bulk, + "file-circle-exclamation": $fa-var-file-circle-exclamation, + "circle-h": $fa-var-circle-h, + "hospital-symbol": $fa-var-hospital-symbol, + "pager": $fa-var-pager, + "address-book": $fa-var-address-book, + "contact-book": $fa-var-contact-book, + "strikethrough": $fa-var-strikethrough, + "k": $fa-var-k, + "landmark-flag": $fa-var-landmark-flag, + "pencil": $fa-var-pencil, + "pencil-alt": $fa-var-pencil-alt, + "backward": $fa-var-backward, + "caret-right": $fa-var-caret-right, + "comments": $fa-var-comments, + "paste": $fa-var-paste, + "file-clipboard": $fa-var-file-clipboard, + "code-pull-request": $fa-var-code-pull-request, + "clipboard-list": $fa-var-clipboard-list, + "truck-ramp-box": $fa-var-truck-ramp-box, + "truck-loading": $fa-var-truck-loading, + "user-check": $fa-var-user-check, + "vial-virus": $fa-var-vial-virus, + "sheet-plastic": $fa-var-sheet-plastic, + "blog": $fa-var-blog, + "user-ninja": $fa-var-user-ninja, + "person-arrow-up-from-line": $fa-var-person-arrow-up-from-line, + "scroll-torah": $fa-var-scroll-torah, + "torah": $fa-var-torah, + "broom-ball": $fa-var-broom-ball, + "quidditch": $fa-var-quidditch, + "quidditch-broom-ball": $fa-var-quidditch-broom-ball, + "toggle-off": $fa-var-toggle-off, + "box-archive": $fa-var-box-archive, + "archive": $fa-var-archive, + "person-drowning": $fa-var-person-drowning, + "arrow-down-9-1": $fa-var-arrow-down-9-1, + "sort-numeric-desc": $fa-var-sort-numeric-desc, + "sort-numeric-down-alt": $fa-var-sort-numeric-down-alt, + "face-grin-tongue-squint": $fa-var-face-grin-tongue-squint, + "grin-tongue-squint": $fa-var-grin-tongue-squint, + "spray-can": $fa-var-spray-can, + "truck-monster": $fa-var-truck-monster, + "w": $fa-var-w, + "earth-africa": $fa-var-earth-africa, + "globe-africa": $fa-var-globe-africa, + "rainbow": $fa-var-rainbow, + "circle-notch": $fa-var-circle-notch, + "tablet-screen-button": $fa-var-tablet-screen-button, + "tablet-alt": $fa-var-tablet-alt, + "paw": $fa-var-paw, + "cloud": $fa-var-cloud, + "trowel-bricks": $fa-var-trowel-bricks, + "face-flushed": $fa-var-face-flushed, + "flushed": $fa-var-flushed, + "hospital-user": $fa-var-hospital-user, + "tent-arrow-left-right": $fa-var-tent-arrow-left-right, + "gavel": $fa-var-gavel, + "legal": $fa-var-legal, + "binoculars": $fa-var-binoculars, + "microphone-slash": $fa-var-microphone-slash, + "box-tissue": $fa-var-box-tissue, + "motorcycle": $fa-var-motorcycle, + "bell-concierge": $fa-var-bell-concierge, + "concierge-bell": $fa-var-concierge-bell, + "pen-ruler": $fa-var-pen-ruler, + "pencil-ruler": $fa-var-pencil-ruler, + "people-arrows": $fa-var-people-arrows, + "people-arrows-left-right": $fa-var-people-arrows-left-right, + "mars-and-venus-burst": $fa-var-mars-and-venus-burst, + "square-caret-right": $fa-var-square-caret-right, + "caret-square-right": $fa-var-caret-square-right, + "scissors": $fa-var-scissors, + "cut": $fa-var-cut, + "sun-plant-wilt": $fa-var-sun-plant-wilt, + "toilets-portable": $fa-var-toilets-portable, + "hockey-puck": $fa-var-hockey-puck, + "table": $fa-var-table, + "magnifying-glass-arrow-right": $fa-var-magnifying-glass-arrow-right, + "tachograph-digital": $fa-var-tachograph-digital, + "digital-tachograph": $fa-var-digital-tachograph, + "users-slash": $fa-var-users-slash, + "clover": $fa-var-clover, + "reply": $fa-var-reply, + "mail-reply": $fa-var-mail-reply, + "star-and-crescent": $fa-var-star-and-crescent, + "house-fire": $fa-var-house-fire, + "square-minus": $fa-var-square-minus, + "minus-square": $fa-var-minus-square, + "helicopter": $fa-var-helicopter, + "compass": $fa-var-compass, + "square-caret-down": $fa-var-square-caret-down, + "caret-square-down": $fa-var-caret-square-down, + "file-circle-question": $fa-var-file-circle-question, + "laptop-code": $fa-var-laptop-code, + "swatchbook": $fa-var-swatchbook, + "prescription-bottle": $fa-var-prescription-bottle, + "bars": $fa-var-bars, + "navicon": $fa-var-navicon, + "people-group": $fa-var-people-group, + "hourglass-end": $fa-var-hourglass-end, + "hourglass-3": $fa-var-hourglass-3, + "heart-crack": $fa-var-heart-crack, + "heart-broken": $fa-var-heart-broken, + "square-up-right": $fa-var-square-up-right, + "external-link-square-alt": $fa-var-external-link-square-alt, + "face-kiss-beam": $fa-var-face-kiss-beam, + "kiss-beam": $fa-var-kiss-beam, + "film": $fa-var-film, + "ruler-horizontal": $fa-var-ruler-horizontal, + "people-robbery": $fa-var-people-robbery, + "lightbulb": $fa-var-lightbulb, + "caret-left": $fa-var-caret-left, + "circle-exclamation": $fa-var-circle-exclamation, + "exclamation-circle": $fa-var-exclamation-circle, + "school-circle-xmark": $fa-var-school-circle-xmark, + "arrow-right-from-bracket": $fa-var-arrow-right-from-bracket, + "sign-out": $fa-var-sign-out, + "circle-chevron-down": $fa-var-circle-chevron-down, + "chevron-circle-down": $fa-var-chevron-circle-down, + "unlock-keyhole": $fa-var-unlock-keyhole, + "unlock-alt": $fa-var-unlock-alt, + "cloud-showers-heavy": $fa-var-cloud-showers-heavy, + "headphones-simple": $fa-var-headphones-simple, + "headphones-alt": $fa-var-headphones-alt, + "sitemap": $fa-var-sitemap, + "circle-dollar-to-slot": $fa-var-circle-dollar-to-slot, + "donate": $fa-var-donate, + "memory": $fa-var-memory, + "road-spikes": $fa-var-road-spikes, + "fire-burner": $fa-var-fire-burner, + "flag": $fa-var-flag, + "hanukiah": $fa-var-hanukiah, + "feather": $fa-var-feather, + "volume-low": $fa-var-volume-low, + "volume-down": $fa-var-volume-down, + "comment-slash": $fa-var-comment-slash, + "cloud-sun-rain": $fa-var-cloud-sun-rain, + "compress": $fa-var-compress, + "wheat-awn": $fa-var-wheat-awn, + "wheat-alt": $fa-var-wheat-alt, + "ankh": $fa-var-ankh, + "hands-holding-child": $fa-var-hands-holding-child, + "asterisk": $fa-var-asterisk, + "square-check": $fa-var-square-check, + "check-square": $fa-var-check-square, + "peseta-sign": $fa-var-peseta-sign, + "heading": $fa-var-heading, + "header": $fa-var-header, + "ghost": $fa-var-ghost, + "list": $fa-var-list, + "list-squares": $fa-var-list-squares, + "square-phone-flip": $fa-var-square-phone-flip, + "phone-square-alt": $fa-var-phone-square-alt, + "cart-plus": $fa-var-cart-plus, + "gamepad": $fa-var-gamepad, + "circle-dot": $fa-var-circle-dot, + "dot-circle": $fa-var-dot-circle, + "face-dizzy": $fa-var-face-dizzy, + "dizzy": $fa-var-dizzy, + "egg": $fa-var-egg, + "house-medical-circle-xmark": $fa-var-house-medical-circle-xmark, + "campground": $fa-var-campground, + "folder-plus": $fa-var-folder-plus, + "futbol": $fa-var-futbol, + "futbol-ball": $fa-var-futbol-ball, + "soccer-ball": $fa-var-soccer-ball, + "paintbrush": $fa-var-paintbrush, + "paint-brush": $fa-var-paint-brush, + "lock": $fa-var-lock, + "gas-pump": $fa-var-gas-pump, + "hot-tub-person": $fa-var-hot-tub-person, + "hot-tub": $fa-var-hot-tub, + "map-location": $fa-var-map-location, + "map-marked": $fa-var-map-marked, + "house-flood-water": $fa-var-house-flood-water, + "tree": $fa-var-tree, + "bridge-lock": $fa-var-bridge-lock, + "sack-dollar": $fa-var-sack-dollar, + "pen-to-square": $fa-var-pen-to-square, + "edit": $fa-var-edit, + "car-side": $fa-var-car-side, + "share-nodes": $fa-var-share-nodes, + "share-alt": $fa-var-share-alt, + "heart-circle-minus": $fa-var-heart-circle-minus, + "hourglass-half": $fa-var-hourglass-half, + "hourglass-2": $fa-var-hourglass-2, + "microscope": $fa-var-microscope, + "sink": $fa-var-sink, + "bag-shopping": $fa-var-bag-shopping, + "shopping-bag": $fa-var-shopping-bag, + "arrow-down-z-a": $fa-var-arrow-down-z-a, + "sort-alpha-desc": $fa-var-sort-alpha-desc, + "sort-alpha-down-alt": $fa-var-sort-alpha-down-alt, + "mitten": $fa-var-mitten, + "person-rays": $fa-var-person-rays, + "users": $fa-var-users, + "eye-slash": $fa-var-eye-slash, + "flask-vial": $fa-var-flask-vial, + "hand": $fa-var-hand, + "hand-paper": $fa-var-hand-paper, + "om": $fa-var-om, + "worm": $fa-var-worm, + "house-circle-xmark": $fa-var-house-circle-xmark, + "plug": $fa-var-plug, + "chevron-up": $fa-var-chevron-up, + "hand-spock": $fa-var-hand-spock, + "stopwatch": $fa-var-stopwatch, + "face-kiss": $fa-var-face-kiss, + "kiss": $fa-var-kiss, + "bridge-circle-xmark": $fa-var-bridge-circle-xmark, + "face-grin-tongue": $fa-var-face-grin-tongue, + "grin-tongue": $fa-var-grin-tongue, + "chess-bishop": $fa-var-chess-bishop, + "face-grin-wink": $fa-var-face-grin-wink, + "grin-wink": $fa-var-grin-wink, + "ear-deaf": $fa-var-ear-deaf, + "deaf": $fa-var-deaf, + "deafness": $fa-var-deafness, + "hard-of-hearing": $fa-var-hard-of-hearing, + "road-circle-check": $fa-var-road-circle-check, + "dice-five": $fa-var-dice-five, + "square-rss": $fa-var-square-rss, + "rss-square": $fa-var-rss-square, + "land-mine-on": $fa-var-land-mine-on, + "i-cursor": $fa-var-i-cursor, + "stamp": $fa-var-stamp, + "stairs": $fa-var-stairs, + "i": $fa-var-i, + "hryvnia-sign": $fa-var-hryvnia-sign, + "hryvnia": $fa-var-hryvnia, + "pills": $fa-var-pills, + "face-grin-wide": $fa-var-face-grin-wide, + "grin-alt": $fa-var-grin-alt, + "tooth": $fa-var-tooth, + "v": $fa-var-v, + "bangladeshi-taka-sign": $fa-var-bangladeshi-taka-sign, + "bicycle": $fa-var-bicycle, + "staff-snake": $fa-var-staff-snake, + "rod-asclepius": $fa-var-rod-asclepius, + "rod-snake": $fa-var-rod-snake, + "staff-aesculapius": $fa-var-staff-aesculapius, + "head-side-cough-slash": $fa-var-head-side-cough-slash, + "truck-medical": $fa-var-truck-medical, + "ambulance": $fa-var-ambulance, + "wheat-awn-circle-exclamation": $fa-var-wheat-awn-circle-exclamation, + "snowman": $fa-var-snowman, + "mortar-pestle": $fa-var-mortar-pestle, + "road-barrier": $fa-var-road-barrier, + "school": $fa-var-school, + "igloo": $fa-var-igloo, + "joint": $fa-var-joint, + "angle-right": $fa-var-angle-right, + "horse": $fa-var-horse, + "q": $fa-var-q, + "g": $fa-var-g, + "notes-medical": $fa-var-notes-medical, + "temperature-half": $fa-var-temperature-half, + "temperature-2": $fa-var-temperature-2, + "thermometer-2": $fa-var-thermometer-2, + "thermometer-half": $fa-var-thermometer-half, + "dong-sign": $fa-var-dong-sign, + "capsules": $fa-var-capsules, + "poo-storm": $fa-var-poo-storm, + "poo-bolt": $fa-var-poo-bolt, + "face-frown-open": $fa-var-face-frown-open, + "frown-open": $fa-var-frown-open, + "hand-point-up": $fa-var-hand-point-up, + "money-bill": $fa-var-money-bill, + "bookmark": $fa-var-bookmark, + "align-justify": $fa-var-align-justify, + "umbrella-beach": $fa-var-umbrella-beach, + "helmet-un": $fa-var-helmet-un, + "bullseye": $fa-var-bullseye, + "bacon": $fa-var-bacon, + "hand-point-down": $fa-var-hand-point-down, + "arrow-up-from-bracket": $fa-var-arrow-up-from-bracket, + "folder": $fa-var-folder, + "folder-blank": $fa-var-folder-blank, + "file-waveform": $fa-var-file-waveform, + "file-medical-alt": $fa-var-file-medical-alt, + "radiation": $fa-var-radiation, + "chart-simple": $fa-var-chart-simple, + "mars-stroke": $fa-var-mars-stroke, + "vial": $fa-var-vial, + "gauge": $fa-var-gauge, + "dashboard": $fa-var-dashboard, + "gauge-med": $fa-var-gauge-med, + "tachometer-alt-average": $fa-var-tachometer-alt-average, + "wand-magic-sparkles": $fa-var-wand-magic-sparkles, + "magic-wand-sparkles": $fa-var-magic-wand-sparkles, + "e": $fa-var-e, + "pen-clip": $fa-var-pen-clip, + "pen-alt": $fa-var-pen-alt, + "bridge-circle-exclamation": $fa-var-bridge-circle-exclamation, + "user": $fa-var-user, + "school-circle-check": $fa-var-school-circle-check, + "dumpster": $fa-var-dumpster, + "van-shuttle": $fa-var-van-shuttle, + "shuttle-van": $fa-var-shuttle-van, + "building-user": $fa-var-building-user, + "square-caret-left": $fa-var-square-caret-left, + "caret-square-left": $fa-var-caret-square-left, + "highlighter": $fa-var-highlighter, + "key": $fa-var-key, + "bullhorn": $fa-var-bullhorn, + "globe": $fa-var-globe, + "synagogue": $fa-var-synagogue, + "person-half-dress": $fa-var-person-half-dress, + "road-bridge": $fa-var-road-bridge, + "location-arrow": $fa-var-location-arrow, + "c": $fa-var-c, + "tablet-button": $fa-var-tablet-button, + "building-lock": $fa-var-building-lock, + "pizza-slice": $fa-var-pizza-slice, + "money-bill-wave": $fa-var-money-bill-wave, + "chart-area": $fa-var-chart-area, + "area-chart": $fa-var-area-chart, + "house-flag": $fa-var-house-flag, + "person-circle-minus": $fa-var-person-circle-minus, + "ban": $fa-var-ban, + "cancel": $fa-var-cancel, + "camera-rotate": $fa-var-camera-rotate, + "spray-can-sparkles": $fa-var-spray-can-sparkles, + "air-freshener": $fa-var-air-freshener, + "star": $fa-var-star, + "repeat": $fa-var-repeat, + "cross": $fa-var-cross, + "box": $fa-var-box, + "venus-mars": $fa-var-venus-mars, + "arrow-pointer": $fa-var-arrow-pointer, + "mouse-pointer": $fa-var-mouse-pointer, + "maximize": $fa-var-maximize, + "expand-arrows-alt": $fa-var-expand-arrows-alt, + "charging-station": $fa-var-charging-station, + "shapes": $fa-var-shapes, + "triangle-circle-square": $fa-var-triangle-circle-square, + "shuffle": $fa-var-shuffle, + "random": $fa-var-random, + "person-running": $fa-var-person-running, + "running": $fa-var-running, + "mobile-retro": $fa-var-mobile-retro, + "grip-lines-vertical": $fa-var-grip-lines-vertical, + "spider": $fa-var-spider, + "hands-bound": $fa-var-hands-bound, + "file-invoice-dollar": $fa-var-file-invoice-dollar, + "plane-circle-exclamation": $fa-var-plane-circle-exclamation, + "x-ray": $fa-var-x-ray, + "spell-check": $fa-var-spell-check, + "slash": $fa-var-slash, + "computer-mouse": $fa-var-computer-mouse, + "mouse": $fa-var-mouse, + "arrow-right-to-bracket": $fa-var-arrow-right-to-bracket, + "sign-in": $fa-var-sign-in, + "shop-slash": $fa-var-shop-slash, + "store-alt-slash": $fa-var-store-alt-slash, + "server": $fa-var-server, + "virus-covid-slash": $fa-var-virus-covid-slash, + "shop-lock": $fa-var-shop-lock, + "hourglass-start": $fa-var-hourglass-start, + "hourglass-1": $fa-var-hourglass-1, + "blender-phone": $fa-var-blender-phone, + "building-wheat": $fa-var-building-wheat, + "person-breastfeeding": $fa-var-person-breastfeeding, + "right-to-bracket": $fa-var-right-to-bracket, + "sign-in-alt": $fa-var-sign-in-alt, + "venus": $fa-var-venus, + "passport": $fa-var-passport, + "thumbtack-slash": $fa-var-thumbtack-slash, + "thumb-tack-slash": $fa-var-thumb-tack-slash, + "heart-pulse": $fa-var-heart-pulse, + "heartbeat": $fa-var-heartbeat, + "people-carry-box": $fa-var-people-carry-box, + "people-carry": $fa-var-people-carry, + "temperature-high": $fa-var-temperature-high, + "microchip": $fa-var-microchip, + "crown": $fa-var-crown, + "weight-hanging": $fa-var-weight-hanging, + "xmarks-lines": $fa-var-xmarks-lines, + "file-prescription": $fa-var-file-prescription, + "weight-scale": $fa-var-weight-scale, + "weight": $fa-var-weight, + "user-group": $fa-var-user-group, + "user-friends": $fa-var-user-friends, + "arrow-up-a-z": $fa-var-arrow-up-a-z, + "sort-alpha-up": $fa-var-sort-alpha-up, + "chess-knight": $fa-var-chess-knight, + "face-laugh-squint": $fa-var-face-laugh-squint, + "laugh-squint": $fa-var-laugh-squint, + "wheelchair": $fa-var-wheelchair, + "circle-arrow-up": $fa-var-circle-arrow-up, + "arrow-circle-up": $fa-var-arrow-circle-up, + "toggle-on": $fa-var-toggle-on, + "person-walking": $fa-var-person-walking, + "walking": $fa-var-walking, + "l": $fa-var-l, + "fire": $fa-var-fire, + "bed-pulse": $fa-var-bed-pulse, + "procedures": $fa-var-procedures, + "shuttle-space": $fa-var-shuttle-space, + "space-shuttle": $fa-var-space-shuttle, + "face-laugh": $fa-var-face-laugh, + "laugh": $fa-var-laugh, + "folder-open": $fa-var-folder-open, + "heart-circle-plus": $fa-var-heart-circle-plus, + "code-fork": $fa-var-code-fork, + "city": $fa-var-city, + "microphone-lines": $fa-var-microphone-lines, + "microphone-alt": $fa-var-microphone-alt, + "pepper-hot": $fa-var-pepper-hot, + "unlock": $fa-var-unlock, + "colon-sign": $fa-var-colon-sign, + "headset": $fa-var-headset, + "store-slash": $fa-var-store-slash, + "road-circle-xmark": $fa-var-road-circle-xmark, + "user-minus": $fa-var-user-minus, + "mars-stroke-up": $fa-var-mars-stroke-up, + "mars-stroke-v": $fa-var-mars-stroke-v, + "champagne-glasses": $fa-var-champagne-glasses, + "glass-cheers": $fa-var-glass-cheers, + "clipboard": $fa-var-clipboard, + "house-circle-exclamation": $fa-var-house-circle-exclamation, + "file-arrow-up": $fa-var-file-arrow-up, + "file-upload": $fa-var-file-upload, + "wifi": $fa-var-wifi, + "wifi-3": $fa-var-wifi-3, + "wifi-strong": $fa-var-wifi-strong, + "bath": $fa-var-bath, + "bathtub": $fa-var-bathtub, + "underline": $fa-var-underline, + "user-pen": $fa-var-user-pen, + "user-edit": $fa-var-user-edit, + "signature": $fa-var-signature, + "stroopwafel": $fa-var-stroopwafel, + "bold": $fa-var-bold, + "anchor-lock": $fa-var-anchor-lock, + "building-ngo": $fa-var-building-ngo, + "manat-sign": $fa-var-manat-sign, + "not-equal": $fa-var-not-equal, + "border-top-left": $fa-var-border-top-left, + "border-style": $fa-var-border-style, + "map-location-dot": $fa-var-map-location-dot, + "map-marked-alt": $fa-var-map-marked-alt, + "jedi": $fa-var-jedi, + "square-poll-vertical": $fa-var-square-poll-vertical, + "poll": $fa-var-poll, + "mug-hot": $fa-var-mug-hot, + "car-battery": $fa-var-car-battery, + "battery-car": $fa-var-battery-car, + "gift": $fa-var-gift, + "dice-two": $fa-var-dice-two, + "chess-queen": $fa-var-chess-queen, + "glasses": $fa-var-glasses, + "chess-board": $fa-var-chess-board, + "building-circle-check": $fa-var-building-circle-check, + "person-chalkboard": $fa-var-person-chalkboard, + "mars-stroke-right": $fa-var-mars-stroke-right, + "mars-stroke-h": $fa-var-mars-stroke-h, + "hand-back-fist": $fa-var-hand-back-fist, + "hand-rock": $fa-var-hand-rock, + "square-caret-up": $fa-var-square-caret-up, + "caret-square-up": $fa-var-caret-square-up, + "cloud-showers-water": $fa-var-cloud-showers-water, + "chart-bar": $fa-var-chart-bar, + "bar-chart": $fa-var-bar-chart, + "hands-bubbles": $fa-var-hands-bubbles, + "hands-wash": $fa-var-hands-wash, + "less-than-equal": $fa-var-less-than-equal, + "train": $fa-var-train, + "eye-low-vision": $fa-var-eye-low-vision, + "low-vision": $fa-var-low-vision, + "crow": $fa-var-crow, + "sailboat": $fa-var-sailboat, + "window-restore": $fa-var-window-restore, + "square-plus": $fa-var-square-plus, + "plus-square": $fa-var-plus-square, + "torii-gate": $fa-var-torii-gate, + "frog": $fa-var-frog, + "bucket": $fa-var-bucket, + "image": $fa-var-image, + "microphone": $fa-var-microphone, + "cow": $fa-var-cow, + "caret-up": $fa-var-caret-up, + "screwdriver": $fa-var-screwdriver, + "folder-closed": $fa-var-folder-closed, + "house-tsunami": $fa-var-house-tsunami, + "square-nfi": $fa-var-square-nfi, + "arrow-up-from-ground-water": $fa-var-arrow-up-from-ground-water, + "martini-glass": $fa-var-martini-glass, + "glass-martini-alt": $fa-var-glass-martini-alt, + "square-binary": $fa-var-square-binary, + "rotate-left": $fa-var-rotate-left, + "rotate-back": $fa-var-rotate-back, + "rotate-backward": $fa-var-rotate-backward, + "undo-alt": $fa-var-undo-alt, + "table-columns": $fa-var-table-columns, + "columns": $fa-var-columns, + "lemon": $fa-var-lemon, + "head-side-mask": $fa-var-head-side-mask, + "handshake": $fa-var-handshake, + "gem": $fa-var-gem, + "dolly": $fa-var-dolly, + "dolly-box": $fa-var-dolly-box, + "smoking": $fa-var-smoking, + "minimize": $fa-var-minimize, + "compress-arrows-alt": $fa-var-compress-arrows-alt, + "monument": $fa-var-monument, + "snowplow": $fa-var-snowplow, + "angles-right": $fa-var-angles-right, + "angle-double-right": $fa-var-angle-double-right, + "cannabis": $fa-var-cannabis, + "circle-play": $fa-var-circle-play, + "play-circle": $fa-var-play-circle, + "tablets": $fa-var-tablets, + "ethernet": $fa-var-ethernet, + "euro-sign": $fa-var-euro-sign, + "eur": $fa-var-eur, + "euro": $fa-var-euro, + "chair": $fa-var-chair, + "circle-check": $fa-var-circle-check, + "check-circle": $fa-var-check-circle, + "circle-stop": $fa-var-circle-stop, + "stop-circle": $fa-var-stop-circle, + "compass-drafting": $fa-var-compass-drafting, + "drafting-compass": $fa-var-drafting-compass, + "plate-wheat": $fa-var-plate-wheat, + "icicles": $fa-var-icicles, + "person-shelter": $fa-var-person-shelter, + "neuter": $fa-var-neuter, + "id-badge": $fa-var-id-badge, + "marker": $fa-var-marker, + "face-laugh-beam": $fa-var-face-laugh-beam, + "laugh-beam": $fa-var-laugh-beam, + "helicopter-symbol": $fa-var-helicopter-symbol, + "universal-access": $fa-var-universal-access, + "circle-chevron-up": $fa-var-circle-chevron-up, + "chevron-circle-up": $fa-var-chevron-circle-up, + "lari-sign": $fa-var-lari-sign, + "volcano": $fa-var-volcano, + "person-walking-dashed-line-arrow-right": $fa-var-person-walking-dashed-line-arrow-right, + "sterling-sign": $fa-var-sterling-sign, + "gbp": $fa-var-gbp, + "pound-sign": $fa-var-pound-sign, + "viruses": $fa-var-viruses, + "square-person-confined": $fa-var-square-person-confined, + "user-tie": $fa-var-user-tie, + "arrow-down-long": $fa-var-arrow-down-long, + "long-arrow-down": $fa-var-long-arrow-down, + "tent-arrow-down-to-line": $fa-var-tent-arrow-down-to-line, + "certificate": $fa-var-certificate, + "reply-all": $fa-var-reply-all, + "mail-reply-all": $fa-var-mail-reply-all, + "suitcase": $fa-var-suitcase, + "person-skating": $fa-var-person-skating, + "skating": $fa-var-skating, + "filter-circle-dollar": $fa-var-filter-circle-dollar, + "funnel-dollar": $fa-var-funnel-dollar, + "camera-retro": $fa-var-camera-retro, + "circle-arrow-down": $fa-var-circle-arrow-down, + "arrow-circle-down": $fa-var-arrow-circle-down, + "file-import": $fa-var-file-import, + "arrow-right-to-file": $fa-var-arrow-right-to-file, + "square-arrow-up-right": $fa-var-square-arrow-up-right, + "external-link-square": $fa-var-external-link-square, + "box-open": $fa-var-box-open, + "scroll": $fa-var-scroll, + "spa": $fa-var-spa, + "location-pin-lock": $fa-var-location-pin-lock, + "pause": $fa-var-pause, + "hill-avalanche": $fa-var-hill-avalanche, + "temperature-empty": $fa-var-temperature-empty, + "temperature-0": $fa-var-temperature-0, + "thermometer-0": $fa-var-thermometer-0, + "thermometer-empty": $fa-var-thermometer-empty, + "bomb": $fa-var-bomb, + "registered": $fa-var-registered, + "address-card": $fa-var-address-card, + "contact-card": $fa-var-contact-card, + "vcard": $fa-var-vcard, + "scale-unbalanced-flip": $fa-var-scale-unbalanced-flip, + "balance-scale-right": $fa-var-balance-scale-right, + "subscript": $fa-var-subscript, + "diamond-turn-right": $fa-var-diamond-turn-right, + "directions": $fa-var-directions, + "burst": $fa-var-burst, + "house-laptop": $fa-var-house-laptop, + "laptop-house": $fa-var-laptop-house, + "face-tired": $fa-var-face-tired, + "tired": $fa-var-tired, + "money-bills": $fa-var-money-bills, + "smog": $fa-var-smog, + "crutch": $fa-var-crutch, + "cloud-arrow-up": $fa-var-cloud-arrow-up, + "cloud-upload": $fa-var-cloud-upload, + "cloud-upload-alt": $fa-var-cloud-upload-alt, + "palette": $fa-var-palette, + "arrows-turn-right": $fa-var-arrows-turn-right, + "vest": $fa-var-vest, + "ferry": $fa-var-ferry, + "arrows-down-to-people": $fa-var-arrows-down-to-people, + "seedling": $fa-var-seedling, + "sprout": $fa-var-sprout, + "left-right": $fa-var-left-right, + "arrows-alt-h": $fa-var-arrows-alt-h, + "boxes-packing": $fa-var-boxes-packing, + "circle-arrow-left": $fa-var-circle-arrow-left, + "arrow-circle-left": $fa-var-arrow-circle-left, + "group-arrows-rotate": $fa-var-group-arrows-rotate, + "bowl-food": $fa-var-bowl-food, + "candy-cane": $fa-var-candy-cane, + "arrow-down-wide-short": $fa-var-arrow-down-wide-short, + "sort-amount-asc": $fa-var-sort-amount-asc, + "sort-amount-down": $fa-var-sort-amount-down, + "cloud-bolt": $fa-var-cloud-bolt, + "thunderstorm": $fa-var-thunderstorm, + "text-slash": $fa-var-text-slash, + "remove-format": $fa-var-remove-format, + "face-smile-wink": $fa-var-face-smile-wink, + "smile-wink": $fa-var-smile-wink, + "file-word": $fa-var-file-word, + "file-powerpoint": $fa-var-file-powerpoint, + "arrows-left-right": $fa-var-arrows-left-right, + "arrows-h": $fa-var-arrows-h, + "house-lock": $fa-var-house-lock, + "cloud-arrow-down": $fa-var-cloud-arrow-down, + "cloud-download": $fa-var-cloud-download, + "cloud-download-alt": $fa-var-cloud-download-alt, + "children": $fa-var-children, + "chalkboard": $fa-var-chalkboard, + "blackboard": $fa-var-blackboard, + "user-large-slash": $fa-var-user-large-slash, + "user-alt-slash": $fa-var-user-alt-slash, + "envelope-open": $fa-var-envelope-open, + "handshake-simple-slash": $fa-var-handshake-simple-slash, + "handshake-alt-slash": $fa-var-handshake-alt-slash, + "mattress-pillow": $fa-var-mattress-pillow, + "guarani-sign": $fa-var-guarani-sign, + "arrows-rotate": $fa-var-arrows-rotate, + "refresh": $fa-var-refresh, + "sync": $fa-var-sync, + "fire-extinguisher": $fa-var-fire-extinguisher, + "cruzeiro-sign": $fa-var-cruzeiro-sign, + "greater-than-equal": $fa-var-greater-than-equal, + "shield-halved": $fa-var-shield-halved, + "shield-alt": $fa-var-shield-alt, + "book-atlas": $fa-var-book-atlas, + "atlas": $fa-var-atlas, + "virus": $fa-var-virus, + "envelope-circle-check": $fa-var-envelope-circle-check, + "layer-group": $fa-var-layer-group, + "arrows-to-dot": $fa-var-arrows-to-dot, + "archway": $fa-var-archway, + "heart-circle-check": $fa-var-heart-circle-check, + "house-chimney-crack": $fa-var-house-chimney-crack, + "house-damage": $fa-var-house-damage, + "file-zipper": $fa-var-file-zipper, + "file-archive": $fa-var-file-archive, + "square": $fa-var-square, + "martini-glass-empty": $fa-var-martini-glass-empty, + "glass-martini": $fa-var-glass-martini, + "couch": $fa-var-couch, + "cedi-sign": $fa-var-cedi-sign, + "italic": $fa-var-italic, + "table-cells-column-lock": $fa-var-table-cells-column-lock, + "church": $fa-var-church, + "comments-dollar": $fa-var-comments-dollar, + "democrat": $fa-var-democrat, + "z": $fa-var-z, + "person-skiing": $fa-var-person-skiing, + "skiing": $fa-var-skiing, + "road-lock": $fa-var-road-lock, + "a": $fa-var-a, + "temperature-arrow-down": $fa-var-temperature-arrow-down, + "temperature-down": $fa-var-temperature-down, + "feather-pointed": $fa-var-feather-pointed, + "feather-alt": $fa-var-feather-alt, + "p": $fa-var-p, + "snowflake": $fa-var-snowflake, + "newspaper": $fa-var-newspaper, + "rectangle-ad": $fa-var-rectangle-ad, + "ad": $fa-var-ad, + "circle-arrow-right": $fa-var-circle-arrow-right, + "arrow-circle-right": $fa-var-arrow-circle-right, + "filter-circle-xmark": $fa-var-filter-circle-xmark, + "locust": $fa-var-locust, + "sort": $fa-var-sort, + "unsorted": $fa-var-unsorted, + "list-ol": $fa-var-list-ol, + "list-1-2": $fa-var-list-1-2, + "list-numeric": $fa-var-list-numeric, + "person-dress-burst": $fa-var-person-dress-burst, + "money-check-dollar": $fa-var-money-check-dollar, + "money-check-alt": $fa-var-money-check-alt, + "vector-square": $fa-var-vector-square, + "bread-slice": $fa-var-bread-slice, + "language": $fa-var-language, + "face-kiss-wink-heart": $fa-var-face-kiss-wink-heart, + "kiss-wink-heart": $fa-var-kiss-wink-heart, + "filter": $fa-var-filter, + "question": $fa-var-question, + "file-signature": $fa-var-file-signature, + "up-down-left-right": $fa-var-up-down-left-right, + "arrows-alt": $fa-var-arrows-alt, + "house-chimney-user": $fa-var-house-chimney-user, + "hand-holding-heart": $fa-var-hand-holding-heart, + "puzzle-piece": $fa-var-puzzle-piece, + "money-check": $fa-var-money-check, + "star-half-stroke": $fa-var-star-half-stroke, + "star-half-alt": $fa-var-star-half-alt, + "code": $fa-var-code, + "whiskey-glass": $fa-var-whiskey-glass, + "glass-whiskey": $fa-var-glass-whiskey, + "building-circle-exclamation": $fa-var-building-circle-exclamation, + "magnifying-glass-chart": $fa-var-magnifying-glass-chart, + "arrow-up-right-from-square": $fa-var-arrow-up-right-from-square, + "external-link": $fa-var-external-link, + "cubes-stacked": $fa-var-cubes-stacked, + "won-sign": $fa-var-won-sign, + "krw": $fa-var-krw, + "won": $fa-var-won, + "virus-covid": $fa-var-virus-covid, + "austral-sign": $fa-var-austral-sign, + "f": $fa-var-f, + "leaf": $fa-var-leaf, + "road": $fa-var-road, + "taxi": $fa-var-taxi, + "cab": $fa-var-cab, + "person-circle-plus": $fa-var-person-circle-plus, + "chart-pie": $fa-var-chart-pie, + "pie-chart": $fa-var-pie-chart, + "bolt-lightning": $fa-var-bolt-lightning, + "sack-xmark": $fa-var-sack-xmark, + "file-excel": $fa-var-file-excel, + "file-contract": $fa-var-file-contract, + "fish-fins": $fa-var-fish-fins, + "building-flag": $fa-var-building-flag, + "face-grin-beam": $fa-var-face-grin-beam, + "grin-beam": $fa-var-grin-beam, + "object-ungroup": $fa-var-object-ungroup, + "poop": $fa-var-poop, + "location-pin": $fa-var-location-pin, + "map-marker": $fa-var-map-marker, + "kaaba": $fa-var-kaaba, + "toilet-paper": $fa-var-toilet-paper, + "helmet-safety": $fa-var-helmet-safety, + "hard-hat": $fa-var-hard-hat, + "hat-hard": $fa-var-hat-hard, + "eject": $fa-var-eject, + "circle-right": $fa-var-circle-right, + "arrow-alt-circle-right": $fa-var-arrow-alt-circle-right, + "plane-circle-check": $fa-var-plane-circle-check, + "face-rolling-eyes": $fa-var-face-rolling-eyes, + "meh-rolling-eyes": $fa-var-meh-rolling-eyes, + "object-group": $fa-var-object-group, + "chart-line": $fa-var-chart-line, + "line-chart": $fa-var-line-chart, + "mask-ventilator": $fa-var-mask-ventilator, + "arrow-right": $fa-var-arrow-right, + "signs-post": $fa-var-signs-post, + "map-signs": $fa-var-map-signs, + "cash-register": $fa-var-cash-register, + "person-circle-question": $fa-var-person-circle-question, + "h": $fa-var-h, + "tarp": $fa-var-tarp, + "screwdriver-wrench": $fa-var-screwdriver-wrench, + "tools": $fa-var-tools, + "arrows-to-eye": $fa-var-arrows-to-eye, + "plug-circle-bolt": $fa-var-plug-circle-bolt, + "heart": $fa-var-heart, + "mars-and-venus": $fa-var-mars-and-venus, + "house-user": $fa-var-house-user, + "home-user": $fa-var-home-user, + "dumpster-fire": $fa-var-dumpster-fire, + "house-crack": $fa-var-house-crack, + "martini-glass-citrus": $fa-var-martini-glass-citrus, + "cocktail": $fa-var-cocktail, + "face-surprise": $fa-var-face-surprise, + "surprise": $fa-var-surprise, + "bottle-water": $fa-var-bottle-water, + "circle-pause": $fa-var-circle-pause, + "pause-circle": $fa-var-pause-circle, + "toilet-paper-slash": $fa-var-toilet-paper-slash, + "apple-whole": $fa-var-apple-whole, + "apple-alt": $fa-var-apple-alt, + "kitchen-set": $fa-var-kitchen-set, + "r": $fa-var-r, + "temperature-quarter": $fa-var-temperature-quarter, + "temperature-1": $fa-var-temperature-1, + "thermometer-1": $fa-var-thermometer-1, + "thermometer-quarter": $fa-var-thermometer-quarter, + "cube": $fa-var-cube, + "bitcoin-sign": $fa-var-bitcoin-sign, + "shield-dog": $fa-var-shield-dog, + "solar-panel": $fa-var-solar-panel, + "lock-open": $fa-var-lock-open, + "elevator": $fa-var-elevator, + "money-bill-transfer": $fa-var-money-bill-transfer, + "money-bill-trend-up": $fa-var-money-bill-trend-up, + "house-flood-water-circle-arrow-right": $fa-var-house-flood-water-circle-arrow-right, + "square-poll-horizontal": $fa-var-square-poll-horizontal, + "poll-h": $fa-var-poll-h, + "circle": $fa-var-circle, + "backward-fast": $fa-var-backward-fast, + "fast-backward": $fa-var-fast-backward, + "recycle": $fa-var-recycle, + "user-astronaut": $fa-var-user-astronaut, + "plane-slash": $fa-var-plane-slash, + "trademark": $fa-var-trademark, + "basketball": $fa-var-basketball, + "basketball-ball": $fa-var-basketball-ball, + "satellite-dish": $fa-var-satellite-dish, + "circle-up": $fa-var-circle-up, + "arrow-alt-circle-up": $fa-var-arrow-alt-circle-up, + "mobile-screen-button": $fa-var-mobile-screen-button, + "mobile-alt": $fa-var-mobile-alt, + "volume-high": $fa-var-volume-high, + "volume-up": $fa-var-volume-up, + "users-rays": $fa-var-users-rays, + "wallet": $fa-var-wallet, + "clipboard-check": $fa-var-clipboard-check, + "file-audio": $fa-var-file-audio, + "burger": $fa-var-burger, + "hamburger": $fa-var-hamburger, + "wrench": $fa-var-wrench, + "bugs": $fa-var-bugs, + "rupee-sign": $fa-var-rupee-sign, + "rupee": $fa-var-rupee, + "file-image": $fa-var-file-image, + "circle-question": $fa-var-circle-question, + "question-circle": $fa-var-question-circle, + "plane-departure": $fa-var-plane-departure, + "handshake-slash": $fa-var-handshake-slash, + "book-bookmark": $fa-var-book-bookmark, + "code-branch": $fa-var-code-branch, + "hat-cowboy": $fa-var-hat-cowboy, + "bridge": $fa-var-bridge, + "phone-flip": $fa-var-phone-flip, + "phone-alt": $fa-var-phone-alt, + "truck-front": $fa-var-truck-front, + "cat": $fa-var-cat, + "anchor-circle-exclamation": $fa-var-anchor-circle-exclamation, + "truck-field": $fa-var-truck-field, + "route": $fa-var-route, + "clipboard-question": $fa-var-clipboard-question, + "panorama": $fa-var-panorama, + "comment-medical": $fa-var-comment-medical, + "teeth-open": $fa-var-teeth-open, + "file-circle-minus": $fa-var-file-circle-minus, + "tags": $fa-var-tags, + "wine-glass": $fa-var-wine-glass, + "forward-fast": $fa-var-forward-fast, + "fast-forward": $fa-var-fast-forward, + "face-meh-blank": $fa-var-face-meh-blank, + "meh-blank": $fa-var-meh-blank, + "square-parking": $fa-var-square-parking, + "parking": $fa-var-parking, + "house-signal": $fa-var-house-signal, + "bars-progress": $fa-var-bars-progress, + "tasks-alt": $fa-var-tasks-alt, + "faucet-drip": $fa-var-faucet-drip, + "cart-flatbed": $fa-var-cart-flatbed, + "dolly-flatbed": $fa-var-dolly-flatbed, + "ban-smoking": $fa-var-ban-smoking, + "smoking-ban": $fa-var-smoking-ban, + "terminal": $fa-var-terminal, + "mobile-button": $fa-var-mobile-button, + "house-medical-flag": $fa-var-house-medical-flag, + "basket-shopping": $fa-var-basket-shopping, + "shopping-basket": $fa-var-shopping-basket, + "tape": $fa-var-tape, + "bus-simple": $fa-var-bus-simple, + "bus-alt": $fa-var-bus-alt, + "eye": $fa-var-eye, + "face-sad-cry": $fa-var-face-sad-cry, + "sad-cry": $fa-var-sad-cry, + "audio-description": $fa-var-audio-description, + "person-military-to-person": $fa-var-person-military-to-person, + "file-shield": $fa-var-file-shield, + "user-slash": $fa-var-user-slash, + "pen": $fa-var-pen, + "tower-observation": $fa-var-tower-observation, + "file-code": $fa-var-file-code, + "signal": $fa-var-signal, + "signal-5": $fa-var-signal-5, + "signal-perfect": $fa-var-signal-perfect, + "bus": $fa-var-bus, + "heart-circle-xmark": $fa-var-heart-circle-xmark, + "house-chimney": $fa-var-house-chimney, + "home-lg": $fa-var-home-lg, + "window-maximize": $fa-var-window-maximize, + "face-frown": $fa-var-face-frown, + "frown": $fa-var-frown, + "prescription": $fa-var-prescription, + "shop": $fa-var-shop, + "store-alt": $fa-var-store-alt, + "floppy-disk": $fa-var-floppy-disk, + "save": $fa-var-save, + "vihara": $fa-var-vihara, + "scale-unbalanced": $fa-var-scale-unbalanced, + "balance-scale-left": $fa-var-balance-scale-left, + "sort-up": $fa-var-sort-up, + "sort-asc": $fa-var-sort-asc, + "comment-dots": $fa-var-comment-dots, + "commenting": $fa-var-commenting, + "plant-wilt": $fa-var-plant-wilt, + "diamond": $fa-var-diamond, + "face-grin-squint": $fa-var-face-grin-squint, + "grin-squint": $fa-var-grin-squint, + "hand-holding-dollar": $fa-var-hand-holding-dollar, + "hand-holding-usd": $fa-var-hand-holding-usd, + "chart-diagram": $fa-var-chart-diagram, + "bacterium": $fa-var-bacterium, + "hand-pointer": $fa-var-hand-pointer, + "drum-steelpan": $fa-var-drum-steelpan, + "hand-scissors": $fa-var-hand-scissors, + "hands-praying": $fa-var-hands-praying, + "praying-hands": $fa-var-praying-hands, + "arrow-rotate-right": $fa-var-arrow-rotate-right, + "arrow-right-rotate": $fa-var-arrow-right-rotate, + "arrow-rotate-forward": $fa-var-arrow-rotate-forward, + "redo": $fa-var-redo, + "biohazard": $fa-var-biohazard, + "location-crosshairs": $fa-var-location-crosshairs, + "location": $fa-var-location, + "mars-double": $fa-var-mars-double, + "child-dress": $fa-var-child-dress, + "users-between-lines": $fa-var-users-between-lines, + "lungs-virus": $fa-var-lungs-virus, + "face-grin-tears": $fa-var-face-grin-tears, + "grin-tears": $fa-var-grin-tears, + "phone": $fa-var-phone, + "calendar-xmark": $fa-var-calendar-xmark, + "calendar-times": $fa-var-calendar-times, + "child-reaching": $fa-var-child-reaching, + "head-side-virus": $fa-var-head-side-virus, + "user-gear": $fa-var-user-gear, + "user-cog": $fa-var-user-cog, + "arrow-up-1-9": $fa-var-arrow-up-1-9, + "sort-numeric-up": $fa-var-sort-numeric-up, + "door-closed": $fa-var-door-closed, + "shield-virus": $fa-var-shield-virus, + "dice-six": $fa-var-dice-six, + "mosquito-net": $fa-var-mosquito-net, + "file-fragment": $fa-var-file-fragment, + "bridge-water": $fa-var-bridge-water, + "person-booth": $fa-var-person-booth, + "text-width": $fa-var-text-width, + "hat-wizard": $fa-var-hat-wizard, + "pen-fancy": $fa-var-pen-fancy, + "person-digging": $fa-var-person-digging, + "digging": $fa-var-digging, + "trash": $fa-var-trash, + "gauge-simple": $fa-var-gauge-simple, + "gauge-simple-med": $fa-var-gauge-simple-med, + "tachometer-average": $fa-var-tachometer-average, + "book-medical": $fa-var-book-medical, + "poo": $fa-var-poo, + "quote-right": $fa-var-quote-right, + "quote-right-alt": $fa-var-quote-right-alt, + "shirt": $fa-var-shirt, + "t-shirt": $fa-var-t-shirt, + "tshirt": $fa-var-tshirt, + "cubes": $fa-var-cubes, + "divide": $fa-var-divide, + "tenge-sign": $fa-var-tenge-sign, + "tenge": $fa-var-tenge, + "headphones": $fa-var-headphones, + "hands-holding": $fa-var-hands-holding, + "hands-clapping": $fa-var-hands-clapping, + "republican": $fa-var-republican, + "arrow-left": $fa-var-arrow-left, + "person-circle-xmark": $fa-var-person-circle-xmark, + "ruler": $fa-var-ruler, + "align-left": $fa-var-align-left, + "dice-d6": $fa-var-dice-d6, + "restroom": $fa-var-restroom, + "j": $fa-var-j, + "users-viewfinder": $fa-var-users-viewfinder, + "file-video": $fa-var-file-video, + "up-right-from-square": $fa-var-up-right-from-square, + "external-link-alt": $fa-var-external-link-alt, + "table-cells": $fa-var-table-cells, + "th": $fa-var-th, + "file-pdf": $fa-var-file-pdf, + "book-bible": $fa-var-book-bible, + "bible": $fa-var-bible, + "o": $fa-var-o, + "suitcase-medical": $fa-var-suitcase-medical, + "medkit": $fa-var-medkit, + "user-secret": $fa-var-user-secret, + "otter": $fa-var-otter, + "person-dress": $fa-var-person-dress, + "female": $fa-var-female, + "comment-dollar": $fa-var-comment-dollar, + "business-time": $fa-var-business-time, + "briefcase-clock": $fa-var-briefcase-clock, + "table-cells-large": $fa-var-table-cells-large, + "th-large": $fa-var-th-large, + "book-tanakh": $fa-var-book-tanakh, + "tanakh": $fa-var-tanakh, + "phone-volume": $fa-var-phone-volume, + "volume-control-phone": $fa-var-volume-control-phone, + "hat-cowboy-side": $fa-var-hat-cowboy-side, + "clipboard-user": $fa-var-clipboard-user, + "child": $fa-var-child, + "lira-sign": $fa-var-lira-sign, + "satellite": $fa-var-satellite, + "plane-lock": $fa-var-plane-lock, + "tag": $fa-var-tag, + "comment": $fa-var-comment, + "cake-candles": $fa-var-cake-candles, + "birthday-cake": $fa-var-birthday-cake, + "cake": $fa-var-cake, + "envelope": $fa-var-envelope, + "angles-up": $fa-var-angles-up, + "angle-double-up": $fa-var-angle-double-up, + "paperclip": $fa-var-paperclip, + "arrow-right-to-city": $fa-var-arrow-right-to-city, + "ribbon": $fa-var-ribbon, + "lungs": $fa-var-lungs, + "arrow-up-9-1": $fa-var-arrow-up-9-1, + "sort-numeric-up-alt": $fa-var-sort-numeric-up-alt, + "litecoin-sign": $fa-var-litecoin-sign, + "border-none": $fa-var-border-none, + "circle-nodes": $fa-var-circle-nodes, + "parachute-box": $fa-var-parachute-box, + "indent": $fa-var-indent, + "truck-field-un": $fa-var-truck-field-un, + "hourglass": $fa-var-hourglass, + "hourglass-empty": $fa-var-hourglass-empty, + "mountain": $fa-var-mountain, + "user-doctor": $fa-var-user-doctor, + "user-md": $fa-var-user-md, + "circle-info": $fa-var-circle-info, + "info-circle": $fa-var-info-circle, + "cloud-meatball": $fa-var-cloud-meatball, + "camera": $fa-var-camera, + "camera-alt": $fa-var-camera-alt, + "square-virus": $fa-var-square-virus, + "meteor": $fa-var-meteor, + "car-on": $fa-var-car-on, + "sleigh": $fa-var-sleigh, + "arrow-down-1-9": $fa-var-arrow-down-1-9, + "sort-numeric-asc": $fa-var-sort-numeric-asc, + "sort-numeric-down": $fa-var-sort-numeric-down, + "hand-holding-droplet": $fa-var-hand-holding-droplet, + "hand-holding-water": $fa-var-hand-holding-water, + "water": $fa-var-water, + "calendar-check": $fa-var-calendar-check, + "braille": $fa-var-braille, + "prescription-bottle-medical": $fa-var-prescription-bottle-medical, + "prescription-bottle-alt": $fa-var-prescription-bottle-alt, + "landmark": $fa-var-landmark, + "truck": $fa-var-truck, + "crosshairs": $fa-var-crosshairs, + "person-cane": $fa-var-person-cane, + "tent": $fa-var-tent, + "vest-patches": $fa-var-vest-patches, + "check-double": $fa-var-check-double, + "arrow-down-a-z": $fa-var-arrow-down-a-z, + "sort-alpha-asc": $fa-var-sort-alpha-asc, + "sort-alpha-down": $fa-var-sort-alpha-down, + "money-bill-wheat": $fa-var-money-bill-wheat, + "cookie": $fa-var-cookie, + "arrow-rotate-left": $fa-var-arrow-rotate-left, + "arrow-left-rotate": $fa-var-arrow-left-rotate, + "arrow-rotate-back": $fa-var-arrow-rotate-back, + "arrow-rotate-backward": $fa-var-arrow-rotate-backward, + "undo": $fa-var-undo, + "hard-drive": $fa-var-hard-drive, + "hdd": $fa-var-hdd, + "face-grin-squint-tears": $fa-var-face-grin-squint-tears, + "grin-squint-tears": $fa-var-grin-squint-tears, + "dumbbell": $fa-var-dumbbell, + "rectangle-list": $fa-var-rectangle-list, + "list-alt": $fa-var-list-alt, + "tarp-droplet": $fa-var-tarp-droplet, + "house-medical-circle-check": $fa-var-house-medical-circle-check, + "person-skiing-nordic": $fa-var-person-skiing-nordic, + "skiing-nordic": $fa-var-skiing-nordic, + "calendar-plus": $fa-var-calendar-plus, + "plane-arrival": $fa-var-plane-arrival, + "circle-left": $fa-var-circle-left, + "arrow-alt-circle-left": $fa-var-arrow-alt-circle-left, + "train-subway": $fa-var-train-subway, + "subway": $fa-var-subway, + "chart-gantt": $fa-var-chart-gantt, + "indian-rupee-sign": $fa-var-indian-rupee-sign, + "indian-rupee": $fa-var-indian-rupee, + "inr": $fa-var-inr, + "crop-simple": $fa-var-crop-simple, + "crop-alt": $fa-var-crop-alt, + "money-bill-1": $fa-var-money-bill-1, + "money-bill-alt": $fa-var-money-bill-alt, + "left-long": $fa-var-left-long, + "long-arrow-alt-left": $fa-var-long-arrow-alt-left, + "dna": $fa-var-dna, + "virus-slash": $fa-var-virus-slash, + "minus": $fa-var-minus, + "subtract": $fa-var-subtract, + "chess": $fa-var-chess, + "arrow-left-long": $fa-var-arrow-left-long, + "long-arrow-left": $fa-var-long-arrow-left, + "plug-circle-check": $fa-var-plug-circle-check, + "street-view": $fa-var-street-view, + "franc-sign": $fa-var-franc-sign, + "volume-off": $fa-var-volume-off, + "hands-asl-interpreting": $fa-var-hands-asl-interpreting, + "american-sign-language-interpreting": $fa-var-american-sign-language-interpreting, + "asl-interpreting": $fa-var-asl-interpreting, + "hands-american-sign-language-interpreting": $fa-var-hands-american-sign-language-interpreting, + "gear": $fa-var-gear, + "cog": $fa-var-cog, + "droplet-slash": $fa-var-droplet-slash, + "tint-slash": $fa-var-tint-slash, + "mosque": $fa-var-mosque, + "mosquito": $fa-var-mosquito, + "star-of-david": $fa-var-star-of-david, + "person-military-rifle": $fa-var-person-military-rifle, + "cart-shopping": $fa-var-cart-shopping, + "shopping-cart": $fa-var-shopping-cart, + "vials": $fa-var-vials, + "plug-circle-plus": $fa-var-plug-circle-plus, + "place-of-worship": $fa-var-place-of-worship, + "grip-vertical": $fa-var-grip-vertical, + "hexagon-nodes": $fa-var-hexagon-nodes, + "arrow-turn-up": $fa-var-arrow-turn-up, + "level-up": $fa-var-level-up, + "u": $fa-var-u, + "square-root-variable": $fa-var-square-root-variable, + "square-root-alt": $fa-var-square-root-alt, + "clock": $fa-var-clock, + "clock-four": $fa-var-clock-four, + "backward-step": $fa-var-backward-step, + "step-backward": $fa-var-step-backward, + "pallet": $fa-var-pallet, + "faucet": $fa-var-faucet, + "baseball-bat-ball": $fa-var-baseball-bat-ball, + "s": $fa-var-s, + "timeline": $fa-var-timeline, + "keyboard": $fa-var-keyboard, + "caret-down": $fa-var-caret-down, + "house-chimney-medical": $fa-var-house-chimney-medical, + "clinic-medical": $fa-var-clinic-medical, + "temperature-three-quarters": $fa-var-temperature-three-quarters, + "temperature-3": $fa-var-temperature-3, + "thermometer-3": $fa-var-thermometer-3, + "thermometer-three-quarters": $fa-var-thermometer-three-quarters, + "mobile-screen": $fa-var-mobile-screen, + "mobile-android-alt": $fa-var-mobile-android-alt, + "plane-up": $fa-var-plane-up, + "piggy-bank": $fa-var-piggy-bank, + "battery-half": $fa-var-battery-half, + "battery-3": $fa-var-battery-3, + "mountain-city": $fa-var-mountain-city, + "coins": $fa-var-coins, + "khanda": $fa-var-khanda, + "sliders": $fa-var-sliders, + "sliders-h": $fa-var-sliders-h, + "folder-tree": $fa-var-folder-tree, + "network-wired": $fa-var-network-wired, + "map-pin": $fa-var-map-pin, + "hamsa": $fa-var-hamsa, + "cent-sign": $fa-var-cent-sign, + "flask": $fa-var-flask, + "person-pregnant": $fa-var-person-pregnant, + "wand-sparkles": $fa-var-wand-sparkles, + "ellipsis-vertical": $fa-var-ellipsis-vertical, + "ellipsis-v": $fa-var-ellipsis-v, + "ticket": $fa-var-ticket, + "power-off": $fa-var-power-off, + "right-long": $fa-var-right-long, + "long-arrow-alt-right": $fa-var-long-arrow-alt-right, + "flag-usa": $fa-var-flag-usa, + "laptop-file": $fa-var-laptop-file, + "tty": $fa-var-tty, + "teletype": $fa-var-teletype, + "diagram-next": $fa-var-diagram-next, + "person-rifle": $fa-var-person-rifle, + "house-medical-circle-exclamation": $fa-var-house-medical-circle-exclamation, + "closed-captioning": $fa-var-closed-captioning, + "person-hiking": $fa-var-person-hiking, + "hiking": $fa-var-hiking, + "venus-double": $fa-var-venus-double, + "images": $fa-var-images, + "calculator": $fa-var-calculator, + "people-pulling": $fa-var-people-pulling, + "n": $fa-var-n, + "cable-car": $fa-var-cable-car, + "tram": $fa-var-tram, + "cloud-rain": $fa-var-cloud-rain, + "building-circle-xmark": $fa-var-building-circle-xmark, + "ship": $fa-var-ship, + "arrows-down-to-line": $fa-var-arrows-down-to-line, + "download": $fa-var-download, + "face-grin": $fa-var-face-grin, + "grin": $fa-var-grin, + "delete-left": $fa-var-delete-left, + "backspace": $fa-var-backspace, + "eye-dropper": $fa-var-eye-dropper, + "eye-dropper-empty": $fa-var-eye-dropper-empty, + "eyedropper": $fa-var-eyedropper, + "file-circle-check": $fa-var-file-circle-check, + "forward": $fa-var-forward, + "mobile": $fa-var-mobile, + "mobile-android": $fa-var-mobile-android, + "mobile-phone": $fa-var-mobile-phone, + "face-meh": $fa-var-face-meh, + "meh": $fa-var-meh, + "align-center": $fa-var-align-center, + "book-skull": $fa-var-book-skull, + "book-dead": $fa-var-book-dead, + "id-card": $fa-var-id-card, + "drivers-license": $fa-var-drivers-license, + "outdent": $fa-var-outdent, + "dedent": $fa-var-dedent, + "heart-circle-exclamation": $fa-var-heart-circle-exclamation, + "house": $fa-var-house, + "home": $fa-var-home, + "home-alt": $fa-var-home-alt, + "home-lg-alt": $fa-var-home-lg-alt, + "calendar-week": $fa-var-calendar-week, + "laptop-medical": $fa-var-laptop-medical, + "b": $fa-var-b, + "file-medical": $fa-var-file-medical, + "dice-one": $fa-var-dice-one, + "kiwi-bird": $fa-var-kiwi-bird, + "arrow-right-arrow-left": $fa-var-arrow-right-arrow-left, + "exchange": $fa-var-exchange, + "rotate-right": $fa-var-rotate-right, + "redo-alt": $fa-var-redo-alt, + "rotate-forward": $fa-var-rotate-forward, + "utensils": $fa-var-utensils, + "cutlery": $fa-var-cutlery, + "arrow-up-wide-short": $fa-var-arrow-up-wide-short, + "sort-amount-up": $fa-var-sort-amount-up, + "mill-sign": $fa-var-mill-sign, + "bowl-rice": $fa-var-bowl-rice, + "skull": $fa-var-skull, + "tower-broadcast": $fa-var-tower-broadcast, + "broadcast-tower": $fa-var-broadcast-tower, + "truck-pickup": $fa-var-truck-pickup, + "up-long": $fa-var-up-long, + "long-arrow-alt-up": $fa-var-long-arrow-alt-up, + "stop": $fa-var-stop, + "code-merge": $fa-var-code-merge, + "upload": $fa-var-upload, + "hurricane": $fa-var-hurricane, + "mound": $fa-var-mound, + "toilet-portable": $fa-var-toilet-portable, + "compact-disc": $fa-var-compact-disc, + "file-arrow-down": $fa-var-file-arrow-down, + "file-download": $fa-var-file-download, + "caravan": $fa-var-caravan, + "shield-cat": $fa-var-shield-cat, + "bolt": $fa-var-bolt, + "zap": $fa-var-zap, + "glass-water": $fa-var-glass-water, + "oil-well": $fa-var-oil-well, + "vault": $fa-var-vault, + "mars": $fa-var-mars, + "toilet": $fa-var-toilet, + "plane-circle-xmark": $fa-var-plane-circle-xmark, + "yen-sign": $fa-var-yen-sign, + "cny": $fa-var-cny, + "jpy": $fa-var-jpy, + "rmb": $fa-var-rmb, + "yen": $fa-var-yen, + "ruble-sign": $fa-var-ruble-sign, + "rouble": $fa-var-rouble, + "rub": $fa-var-rub, + "ruble": $fa-var-ruble, + "sun": $fa-var-sun, + "guitar": $fa-var-guitar, + "face-laugh-wink": $fa-var-face-laugh-wink, + "laugh-wink": $fa-var-laugh-wink, + "horse-head": $fa-var-horse-head, + "bore-hole": $fa-var-bore-hole, + "industry": $fa-var-industry, + "circle-down": $fa-var-circle-down, + "arrow-alt-circle-down": $fa-var-arrow-alt-circle-down, + "arrows-turn-to-dots": $fa-var-arrows-turn-to-dots, + "florin-sign": $fa-var-florin-sign, + "arrow-down-short-wide": $fa-var-arrow-down-short-wide, + "sort-amount-desc": $fa-var-sort-amount-desc, + "sort-amount-down-alt": $fa-var-sort-amount-down-alt, + "less-than": $fa-var-less-than, + "angle-down": $fa-var-angle-down, + "car-tunnel": $fa-var-car-tunnel, + "head-side-cough": $fa-var-head-side-cough, + "grip-lines": $fa-var-grip-lines, + "thumbs-down": $fa-var-thumbs-down, + "user-lock": $fa-var-user-lock, + "arrow-right-long": $fa-var-arrow-right-long, + "long-arrow-right": $fa-var-long-arrow-right, + "anchor-circle-xmark": $fa-var-anchor-circle-xmark, + "ellipsis": $fa-var-ellipsis, + "ellipsis-h": $fa-var-ellipsis-h, + "chess-pawn": $fa-var-chess-pawn, + "kit-medical": $fa-var-kit-medical, + "first-aid": $fa-var-first-aid, + "person-through-window": $fa-var-person-through-window, + "toolbox": $fa-var-toolbox, + "hands-holding-circle": $fa-var-hands-holding-circle, + "bug": $fa-var-bug, + "credit-card": $fa-var-credit-card, + "credit-card-alt": $fa-var-credit-card-alt, + "car": $fa-var-car, + "automobile": $fa-var-automobile, + "hand-holding-hand": $fa-var-hand-holding-hand, + "book-open-reader": $fa-var-book-open-reader, + "book-reader": $fa-var-book-reader, + "mountain-sun": $fa-var-mountain-sun, + "arrows-left-right-to-line": $fa-var-arrows-left-right-to-line, + "dice-d20": $fa-var-dice-d20, + "truck-droplet": $fa-var-truck-droplet, + "file-circle-xmark": $fa-var-file-circle-xmark, + "temperature-arrow-up": $fa-var-temperature-arrow-up, + "temperature-up": $fa-var-temperature-up, + "medal": $fa-var-medal, + "bed": $fa-var-bed, + "square-h": $fa-var-square-h, + "h-square": $fa-var-h-square, + "podcast": $fa-var-podcast, + "temperature-full": $fa-var-temperature-full, + "temperature-4": $fa-var-temperature-4, + "thermometer-4": $fa-var-thermometer-4, + "thermometer-full": $fa-var-thermometer-full, + "bell": $fa-var-bell, + "superscript": $fa-var-superscript, + "plug-circle-xmark": $fa-var-plug-circle-xmark, + "star-of-life": $fa-var-star-of-life, + "phone-slash": $fa-var-phone-slash, + "paint-roller": $fa-var-paint-roller, + "handshake-angle": $fa-var-handshake-angle, + "hands-helping": $fa-var-hands-helping, + "location-dot": $fa-var-location-dot, + "map-marker-alt": $fa-var-map-marker-alt, + "file": $fa-var-file, + "greater-than": $fa-var-greater-than, + "person-swimming": $fa-var-person-swimming, + "swimmer": $fa-var-swimmer, + "arrow-down": $fa-var-arrow-down, + "droplet": $fa-var-droplet, + "tint": $fa-var-tint, + "eraser": $fa-var-eraser, + "earth-americas": $fa-var-earth-americas, + "earth": $fa-var-earth, + "earth-america": $fa-var-earth-america, + "globe-americas": $fa-var-globe-americas, + "person-burst": $fa-var-person-burst, + "dove": $fa-var-dove, + "battery-empty": $fa-var-battery-empty, + "battery-0": $fa-var-battery-0, + "socks": $fa-var-socks, + "inbox": $fa-var-inbox, + "section": $fa-var-section, + "gauge-high": $fa-var-gauge-high, + "tachometer-alt": $fa-var-tachometer-alt, + "tachometer-alt-fast": $fa-var-tachometer-alt-fast, + "envelope-open-text": $fa-var-envelope-open-text, + "hospital": $fa-var-hospital, + "hospital-alt": $fa-var-hospital-alt, + "hospital-wide": $fa-var-hospital-wide, + "wine-bottle": $fa-var-wine-bottle, + "chess-rook": $fa-var-chess-rook, + "bars-staggered": $fa-var-bars-staggered, + "reorder": $fa-var-reorder, + "stream": $fa-var-stream, + "dharmachakra": $fa-var-dharmachakra, + "hotdog": $fa-var-hotdog, + "person-walking-with-cane": $fa-var-person-walking-with-cane, + "blind": $fa-var-blind, + "drum": $fa-var-drum, + "ice-cream": $fa-var-ice-cream, + "heart-circle-bolt": $fa-var-heart-circle-bolt, + "fax": $fa-var-fax, + "paragraph": $fa-var-paragraph, + "check-to-slot": $fa-var-check-to-slot, + "vote-yea": $fa-var-vote-yea, + "star-half": $fa-var-star-half, + "boxes-stacked": $fa-var-boxes-stacked, + "boxes": $fa-var-boxes, + "boxes-alt": $fa-var-boxes-alt, + "link": $fa-var-link, + "chain": $fa-var-chain, + "ear-listen": $fa-var-ear-listen, + "assistive-listening-systems": $fa-var-assistive-listening-systems, + "tree-city": $fa-var-tree-city, + "play": $fa-var-play, + "font": $fa-var-font, + "table-cells-row-lock": $fa-var-table-cells-row-lock, + "rupiah-sign": $fa-var-rupiah-sign, + "magnifying-glass": $fa-var-magnifying-glass, + "search": $fa-var-search, + "table-tennis-paddle-ball": $fa-var-table-tennis-paddle-ball, + "ping-pong-paddle-ball": $fa-var-ping-pong-paddle-ball, + "table-tennis": $fa-var-table-tennis, + "person-dots-from-line": $fa-var-person-dots-from-line, + "diagnoses": $fa-var-diagnoses, + "trash-can-arrow-up": $fa-var-trash-can-arrow-up, + "trash-restore-alt": $fa-var-trash-restore-alt, + "naira-sign": $fa-var-naira-sign, + "cart-arrow-down": $fa-var-cart-arrow-down, + "walkie-talkie": $fa-var-walkie-talkie, + "file-pen": $fa-var-file-pen, + "file-edit": $fa-var-file-edit, + "receipt": $fa-var-receipt, + "square-pen": $fa-var-square-pen, + "pen-square": $fa-var-pen-square, + "pencil-square": $fa-var-pencil-square, + "suitcase-rolling": $fa-var-suitcase-rolling, + "person-circle-exclamation": $fa-var-person-circle-exclamation, + "chevron-down": $fa-var-chevron-down, + "battery-full": $fa-var-battery-full, + "battery": $fa-var-battery, + "battery-5": $fa-var-battery-5, + "skull-crossbones": $fa-var-skull-crossbones, + "code-compare": $fa-var-code-compare, + "list-ul": $fa-var-list-ul, + "list-dots": $fa-var-list-dots, + "school-lock": $fa-var-school-lock, + "tower-cell": $fa-var-tower-cell, + "down-long": $fa-var-down-long, + "long-arrow-alt-down": $fa-var-long-arrow-alt-down, + "ranking-star": $fa-var-ranking-star, + "chess-king": $fa-var-chess-king, + "person-harassing": $fa-var-person-harassing, + "brazilian-real-sign": $fa-var-brazilian-real-sign, + "landmark-dome": $fa-var-landmark-dome, + "landmark-alt": $fa-var-landmark-alt, + "arrow-up": $fa-var-arrow-up, + "tv": $fa-var-tv, + "television": $fa-var-television, + "tv-alt": $fa-var-tv-alt, + "shrimp": $fa-var-shrimp, + "list-check": $fa-var-list-check, + "tasks": $fa-var-tasks, + "jug-detergent": $fa-var-jug-detergent, + "circle-user": $fa-var-circle-user, + "user-circle": $fa-var-user-circle, + "user-shield": $fa-var-user-shield, + "wind": $fa-var-wind, + "car-burst": $fa-var-car-burst, + "car-crash": $fa-var-car-crash, + "y": $fa-var-y, + "person-snowboarding": $fa-var-person-snowboarding, + "snowboarding": $fa-var-snowboarding, + "truck-fast": $fa-var-truck-fast, + "shipping-fast": $fa-var-shipping-fast, + "fish": $fa-var-fish, + "user-graduate": $fa-var-user-graduate, + "circle-half-stroke": $fa-var-circle-half-stroke, + "adjust": $fa-var-adjust, + "clapperboard": $fa-var-clapperboard, + "circle-radiation": $fa-var-circle-radiation, + "radiation-alt": $fa-var-radiation-alt, + "baseball": $fa-var-baseball, + "baseball-ball": $fa-var-baseball-ball, + "jet-fighter-up": $fa-var-jet-fighter-up, + "diagram-project": $fa-var-diagram-project, + "project-diagram": $fa-var-project-diagram, + "copy": $fa-var-copy, + "volume-xmark": $fa-var-volume-xmark, + "volume-mute": $fa-var-volume-mute, + "volume-times": $fa-var-volume-times, + "hand-sparkles": $fa-var-hand-sparkles, + "grip": $fa-var-grip, + "grip-horizontal": $fa-var-grip-horizontal, + "share-from-square": $fa-var-share-from-square, + "share-square": $fa-var-share-square, + "child-combatant": $fa-var-child-combatant, + "child-rifle": $fa-var-child-rifle, + "gun": $fa-var-gun, + "square-phone": $fa-var-square-phone, + "phone-square": $fa-var-phone-square, + "plus": $fa-var-plus, + "add": $fa-var-add, + "expand": $fa-var-expand, + "computer": $fa-var-computer, + "xmark": $fa-var-xmark, + "close": $fa-var-close, + "multiply": $fa-var-multiply, + "remove": $fa-var-remove, + "times": $fa-var-times, + "arrows-up-down-left-right": $fa-var-arrows-up-down-left-right, + "arrows": $fa-var-arrows, + "chalkboard-user": $fa-var-chalkboard-user, + "chalkboard-teacher": $fa-var-chalkboard-teacher, + "peso-sign": $fa-var-peso-sign, + "building-shield": $fa-var-building-shield, + "baby": $fa-var-baby, + "users-line": $fa-var-users-line, + "quote-left": $fa-var-quote-left, + "quote-left-alt": $fa-var-quote-left-alt, + "tractor": $fa-var-tractor, + "trash-arrow-up": $fa-var-trash-arrow-up, + "trash-restore": $fa-var-trash-restore, + "arrow-down-up-lock": $fa-var-arrow-down-up-lock, + "lines-leaning": $fa-var-lines-leaning, + "ruler-combined": $fa-var-ruler-combined, + "copyright": $fa-var-copyright, + "equals": $fa-var-equals, + "blender": $fa-var-blender, + "teeth": $fa-var-teeth, + "shekel-sign": $fa-var-shekel-sign, + "ils": $fa-var-ils, + "shekel": $fa-var-shekel, + "sheqel": $fa-var-sheqel, + "sheqel-sign": $fa-var-sheqel-sign, + "map": $fa-var-map, + "rocket": $fa-var-rocket, + "photo-film": $fa-var-photo-film, + "photo-video": $fa-var-photo-video, + "folder-minus": $fa-var-folder-minus, + "hexagon-nodes-bolt": $fa-var-hexagon-nodes-bolt, + "store": $fa-var-store, + "arrow-trend-up": $fa-var-arrow-trend-up, + "plug-circle-minus": $fa-var-plug-circle-minus, + "sign-hanging": $fa-var-sign-hanging, + "sign": $fa-var-sign, + "bezier-curve": $fa-var-bezier-curve, + "bell-slash": $fa-var-bell-slash, + "tablet": $fa-var-tablet, + "tablet-android": $fa-var-tablet-android, + "school-flag": $fa-var-school-flag, + "fill": $fa-var-fill, + "angle-up": $fa-var-angle-up, + "drumstick-bite": $fa-var-drumstick-bite, + "holly-berry": $fa-var-holly-berry, + "chevron-left": $fa-var-chevron-left, + "bacteria": $fa-var-bacteria, + "hand-lizard": $fa-var-hand-lizard, + "notdef": $fa-var-notdef, + "disease": $fa-var-disease, + "briefcase-medical": $fa-var-briefcase-medical, + "genderless": $fa-var-genderless, + "chevron-right": $fa-var-chevron-right, + "retweet": $fa-var-retweet, + "car-rear": $fa-var-car-rear, + "car-alt": $fa-var-car-alt, + "pump-soap": $fa-var-pump-soap, + "video-slash": $fa-var-video-slash, + "battery-quarter": $fa-var-battery-quarter, + "battery-2": $fa-var-battery-2, + "radio": $fa-var-radio, + "baby-carriage": $fa-var-baby-carriage, + "carriage-baby": $fa-var-carriage-baby, + "traffic-light": $fa-var-traffic-light, + "thermometer": $fa-var-thermometer, + "vr-cardboard": $fa-var-vr-cardboard, + "hand-middle-finger": $fa-var-hand-middle-finger, + "percent": $fa-var-percent, + "percentage": $fa-var-percentage, + "truck-moving": $fa-var-truck-moving, + "glass-water-droplet": $fa-var-glass-water-droplet, + "display": $fa-var-display, + "face-smile": $fa-var-face-smile, + "smile": $fa-var-smile, + "thumbtack": $fa-var-thumbtack, + "thumb-tack": $fa-var-thumb-tack, + "trophy": $fa-var-trophy, + "person-praying": $fa-var-person-praying, + "pray": $fa-var-pray, + "hammer": $fa-var-hammer, + "hand-peace": $fa-var-hand-peace, + "rotate": $fa-var-rotate, + "sync-alt": $fa-var-sync-alt, + "spinner": $fa-var-spinner, + "robot": $fa-var-robot, + "peace": $fa-var-peace, + "gears": $fa-var-gears, + "cogs": $fa-var-cogs, + "warehouse": $fa-var-warehouse, + "arrow-up-right-dots": $fa-var-arrow-up-right-dots, + "splotch": $fa-var-splotch, + "face-grin-hearts": $fa-var-face-grin-hearts, + "grin-hearts": $fa-var-grin-hearts, + "dice-four": $fa-var-dice-four, + "sim-card": $fa-var-sim-card, + "transgender": $fa-var-transgender, + "transgender-alt": $fa-var-transgender-alt, + "mercury": $fa-var-mercury, + "arrow-turn-down": $fa-var-arrow-turn-down, + "level-down": $fa-var-level-down, + "person-falling-burst": $fa-var-person-falling-burst, + "award": $fa-var-award, + "ticket-simple": $fa-var-ticket-simple, + "ticket-alt": $fa-var-ticket-alt, + "building": $fa-var-building, + "angles-left": $fa-var-angles-left, + "angle-double-left": $fa-var-angle-double-left, + "qrcode": $fa-var-qrcode, + "clock-rotate-left": $fa-var-clock-rotate-left, + "history": $fa-var-history, + "face-grin-beam-sweat": $fa-var-face-grin-beam-sweat, + "grin-beam-sweat": $fa-var-grin-beam-sweat, + "file-export": $fa-var-file-export, + "arrow-right-from-file": $fa-var-arrow-right-from-file, + "shield": $fa-var-shield, + "shield-blank": $fa-var-shield-blank, + "arrow-up-short-wide": $fa-var-arrow-up-short-wide, + "sort-amount-up-alt": $fa-var-sort-amount-up-alt, + "comment-nodes": $fa-var-comment-nodes, + "house-medical": $fa-var-house-medical, + "golf-ball-tee": $fa-var-golf-ball-tee, + "golf-ball": $fa-var-golf-ball, + "circle-chevron-left": $fa-var-circle-chevron-left, + "chevron-circle-left": $fa-var-chevron-circle-left, + "house-chimney-window": $fa-var-house-chimney-window, + "pen-nib": $fa-var-pen-nib, + "tent-arrow-turn-left": $fa-var-tent-arrow-turn-left, + "tents": $fa-var-tents, + "wand-magic": $fa-var-wand-magic, + "magic": $fa-var-magic, + "dog": $fa-var-dog, + "carrot": $fa-var-carrot, + "moon": $fa-var-moon, + "wine-glass-empty": $fa-var-wine-glass-empty, + "wine-glass-alt": $fa-var-wine-glass-alt, + "cheese": $fa-var-cheese, + "yin-yang": $fa-var-yin-yang, + "music": $fa-var-music, + "code-commit": $fa-var-code-commit, + "temperature-low": $fa-var-temperature-low, + "person-biking": $fa-var-person-biking, + "biking": $fa-var-biking, + "broom": $fa-var-broom, + "shield-heart": $fa-var-shield-heart, + "gopuram": $fa-var-gopuram, + "earth-oceania": $fa-var-earth-oceania, + "globe-oceania": $fa-var-globe-oceania, + "square-xmark": $fa-var-square-xmark, + "times-square": $fa-var-times-square, + "xmark-square": $fa-var-xmark-square, + "hashtag": $fa-var-hashtag, + "up-right-and-down-left-from-center": $fa-var-up-right-and-down-left-from-center, + "expand-alt": $fa-var-expand-alt, + "oil-can": $fa-var-oil-can, + "t": $fa-var-t, + "hippo": $fa-var-hippo, + "chart-column": $fa-var-chart-column, + "infinity": $fa-var-infinity, + "vial-circle-check": $fa-var-vial-circle-check, + "person-arrow-down-to-line": $fa-var-person-arrow-down-to-line, + "voicemail": $fa-var-voicemail, + "fan": $fa-var-fan, + "person-walking-luggage": $fa-var-person-walking-luggage, + "up-down": $fa-var-up-down, + "arrows-alt-v": $fa-var-arrows-alt-v, + "cloud-moon-rain": $fa-var-cloud-moon-rain, + "calendar": $fa-var-calendar, + "trailer": $fa-var-trailer, + "bahai": $fa-var-bahai, + "haykal": $fa-var-haykal, + "sd-card": $fa-var-sd-card, + "dragon": $fa-var-dragon, + "shoe-prints": $fa-var-shoe-prints, + "circle-plus": $fa-var-circle-plus, + "plus-circle": $fa-var-plus-circle, + "face-grin-tongue-wink": $fa-var-face-grin-tongue-wink, + "grin-tongue-wink": $fa-var-grin-tongue-wink, + "hand-holding": $fa-var-hand-holding, + "plug-circle-exclamation": $fa-var-plug-circle-exclamation, + "link-slash": $fa-var-link-slash, + "chain-broken": $fa-var-chain-broken, + "chain-slash": $fa-var-chain-slash, + "unlink": $fa-var-unlink, + "clone": $fa-var-clone, + "person-walking-arrow-loop-left": $fa-var-person-walking-arrow-loop-left, + "arrow-up-z-a": $fa-var-arrow-up-z-a, + "sort-alpha-up-alt": $fa-var-sort-alpha-up-alt, + "fire-flame-curved": $fa-var-fire-flame-curved, + "fire-alt": $fa-var-fire-alt, + "tornado": $fa-var-tornado, + "file-circle-plus": $fa-var-file-circle-plus, + "book-quran": $fa-var-book-quran, + "quran": $fa-var-quran, + "anchor": $fa-var-anchor, + "border-all": $fa-var-border-all, + "face-angry": $fa-var-face-angry, + "angry": $fa-var-angry, + "cookie-bite": $fa-var-cookie-bite, + "arrow-trend-down": $fa-var-arrow-trend-down, + "rss": $fa-var-rss, + "feed": $fa-var-feed, + "draw-polygon": $fa-var-draw-polygon, + "scale-balanced": $fa-var-scale-balanced, + "balance-scale": $fa-var-balance-scale, + "gauge-simple-high": $fa-var-gauge-simple-high, + "tachometer": $fa-var-tachometer, + "tachometer-fast": $fa-var-tachometer-fast, + "shower": $fa-var-shower, + "desktop": $fa-var-desktop, + "desktop-alt": $fa-var-desktop-alt, + "m": $fa-var-m, + "table-list": $fa-var-table-list, + "th-list": $fa-var-th-list, + "comment-sms": $fa-var-comment-sms, + "sms": $fa-var-sms, + "book": $fa-var-book, + "user-plus": $fa-var-user-plus, + "check": $fa-var-check, + "battery-three-quarters": $fa-var-battery-three-quarters, + "battery-4": $fa-var-battery-4, + "house-circle-check": $fa-var-house-circle-check, + "angle-left": $fa-var-angle-left, + "diagram-successor": $fa-var-diagram-successor, + "truck-arrow-right": $fa-var-truck-arrow-right, + "arrows-split-up-and-left": $fa-var-arrows-split-up-and-left, + "hand-fist": $fa-var-hand-fist, + "fist-raised": $fa-var-fist-raised, + "cloud-moon": $fa-var-cloud-moon, + "briefcase": $fa-var-briefcase, + "person-falling": $fa-var-person-falling, + "image-portrait": $fa-var-image-portrait, + "portrait": $fa-var-portrait, + "user-tag": $fa-var-user-tag, + "rug": $fa-var-rug, + "earth-europe": $fa-var-earth-europe, + "globe-europe": $fa-var-globe-europe, + "cart-flatbed-suitcase": $fa-var-cart-flatbed-suitcase, + "luggage-cart": $fa-var-luggage-cart, + "rectangle-xmark": $fa-var-rectangle-xmark, + "rectangle-times": $fa-var-rectangle-times, + "times-rectangle": $fa-var-times-rectangle, + "window-close": $fa-var-window-close, + "baht-sign": $fa-var-baht-sign, + "book-open": $fa-var-book-open, + "book-journal-whills": $fa-var-book-journal-whills, + "journal-whills": $fa-var-journal-whills, + "handcuffs": $fa-var-handcuffs, + "triangle-exclamation": $fa-var-triangle-exclamation, + "exclamation-triangle": $fa-var-exclamation-triangle, + "warning": $fa-var-warning, + "database": $fa-var-database, + "share": $fa-var-share, + "mail-forward": $fa-var-mail-forward, + "bottle-droplet": $fa-var-bottle-droplet, + "mask-face": $fa-var-mask-face, + "hill-rockslide": $fa-var-hill-rockslide, + "right-left": $fa-var-right-left, + "exchange-alt": $fa-var-exchange-alt, + "paper-plane": $fa-var-paper-plane, + "road-circle-exclamation": $fa-var-road-circle-exclamation, + "dungeon": $fa-var-dungeon, + "align-right": $fa-var-align-right, + "money-bill-1-wave": $fa-var-money-bill-1-wave, + "money-bill-wave-alt": $fa-var-money-bill-wave-alt, + "life-ring": $fa-var-life-ring, + "hands": $fa-var-hands, + "sign-language": $fa-var-sign-language, + "signing": $fa-var-signing, + "calendar-day": $fa-var-calendar-day, + "water-ladder": $fa-var-water-ladder, + "ladder-water": $fa-var-ladder-water, + "swimming-pool": $fa-var-swimming-pool, + "arrows-up-down": $fa-var-arrows-up-down, + "arrows-v": $fa-var-arrows-v, + "face-grimace": $fa-var-face-grimace, + "grimace": $fa-var-grimace, + "wheelchair-move": $fa-var-wheelchair-move, + "wheelchair-alt": $fa-var-wheelchair-alt, + "turn-down": $fa-var-turn-down, + "level-down-alt": $fa-var-level-down-alt, + "person-walking-arrow-right": $fa-var-person-walking-arrow-right, + "square-envelope": $fa-var-square-envelope, + "envelope-square": $fa-var-envelope-square, + "dice": $fa-var-dice, + "bowling-ball": $fa-var-bowling-ball, + "brain": $fa-var-brain, + "bandage": $fa-var-bandage, + "band-aid": $fa-var-band-aid, + "calendar-minus": $fa-var-calendar-minus, + "circle-xmark": $fa-var-circle-xmark, + "times-circle": $fa-var-times-circle, + "xmark-circle": $fa-var-xmark-circle, + "gifts": $fa-var-gifts, + "hotel": $fa-var-hotel, + "earth-asia": $fa-var-earth-asia, + "globe-asia": $fa-var-globe-asia, + "id-card-clip": $fa-var-id-card-clip, + "id-card-alt": $fa-var-id-card-alt, + "magnifying-glass-plus": $fa-var-magnifying-glass-plus, + "search-plus": $fa-var-search-plus, + "thumbs-up": $fa-var-thumbs-up, + "user-clock": $fa-var-user-clock, + "hand-dots": $fa-var-hand-dots, + "allergies": $fa-var-allergies, + "file-invoice": $fa-var-file-invoice, + "window-minimize": $fa-var-window-minimize, + "mug-saucer": $fa-var-mug-saucer, + "coffee": $fa-var-coffee, + "brush": $fa-var-brush, + "file-half-dashed": $fa-var-file-half-dashed, + "mask": $fa-var-mask, + "magnifying-glass-minus": $fa-var-magnifying-glass-minus, + "search-minus": $fa-var-search-minus, + "ruler-vertical": $fa-var-ruler-vertical, + "user-large": $fa-var-user-large, + "user-alt": $fa-var-user-alt, + "train-tram": $fa-var-train-tram, + "user-nurse": $fa-var-user-nurse, + "syringe": $fa-var-syringe, + "cloud-sun": $fa-var-cloud-sun, + "stopwatch-20": $fa-var-stopwatch-20, + "square-full": $fa-var-square-full, + "magnet": $fa-var-magnet, + "jar": $fa-var-jar, + "note-sticky": $fa-var-note-sticky, + "sticky-note": $fa-var-sticky-note, + "bug-slash": $fa-var-bug-slash, + "arrow-up-from-water-pump": $fa-var-arrow-up-from-water-pump, + "bone": $fa-var-bone, + "table-cells-row-unlock": $fa-var-table-cells-row-unlock, + "user-injured": $fa-var-user-injured, + "face-sad-tear": $fa-var-face-sad-tear, + "sad-tear": $fa-var-sad-tear, + "plane": $fa-var-plane, + "tent-arrows-down": $fa-var-tent-arrows-down, + "exclamation": $fa-var-exclamation, + "arrows-spin": $fa-var-arrows-spin, + "print": $fa-var-print, + "turkish-lira-sign": $fa-var-turkish-lira-sign, + "try": $fa-var-try, + "turkish-lira": $fa-var-turkish-lira, + "dollar-sign": $fa-var-dollar-sign, + "dollar": $fa-var-dollar, + "usd": $fa-var-usd, + "x": $fa-var-x, + "magnifying-glass-dollar": $fa-var-magnifying-glass-dollar, + "search-dollar": $fa-var-search-dollar, + "users-gear": $fa-var-users-gear, + "users-cog": $fa-var-users-cog, + "person-military-pointing": $fa-var-person-military-pointing, + "building-columns": $fa-var-building-columns, + "bank": $fa-var-bank, + "institution": $fa-var-institution, + "museum": $fa-var-museum, + "university": $fa-var-university, + "umbrella": $fa-var-umbrella, + "trowel": $fa-var-trowel, + "d": $fa-var-d, + "stapler": $fa-var-stapler, + "masks-theater": $fa-var-masks-theater, + "theater-masks": $fa-var-theater-masks, + "kip-sign": $fa-var-kip-sign, + "hand-point-left": $fa-var-hand-point-left, + "handshake-simple": $fa-var-handshake-simple, + "handshake-alt": $fa-var-handshake-alt, + "jet-fighter": $fa-var-jet-fighter, + "fighter-jet": $fa-var-fighter-jet, + "square-share-nodes": $fa-var-square-share-nodes, + "share-alt-square": $fa-var-share-alt-square, + "barcode": $fa-var-barcode, + "plus-minus": $fa-var-plus-minus, + "video": $fa-var-video, + "video-camera": $fa-var-video-camera, + "graduation-cap": $fa-var-graduation-cap, + "mortar-board": $fa-var-mortar-board, + "hand-holding-medical": $fa-var-hand-holding-medical, + "person-circle-check": $fa-var-person-circle-check, + "turn-up": $fa-var-turn-up, + "level-up-alt": $fa-var-level-up-alt, +); + +$fa-brand-icons: ( + "monero": $fa-var-monero, + "hooli": $fa-var-hooli, + "yelp": $fa-var-yelp, + "cc-visa": $fa-var-cc-visa, + "lastfm": $fa-var-lastfm, + "shopware": $fa-var-shopware, + "creative-commons-nc": $fa-var-creative-commons-nc, + "aws": $fa-var-aws, + "redhat": $fa-var-redhat, + "yoast": $fa-var-yoast, + "cloudflare": $fa-var-cloudflare, + "ups": $fa-var-ups, + "pixiv": $fa-var-pixiv, + "wpexplorer": $fa-var-wpexplorer, + "dyalog": $fa-var-dyalog, + "bity": $fa-var-bity, + "stackpath": $fa-var-stackpath, + "buysellads": $fa-var-buysellads, + "first-order": $fa-var-first-order, + "modx": $fa-var-modx, + "guilded": $fa-var-guilded, + "vnv": $fa-var-vnv, + "square-js": $fa-var-square-js, + "js-square": $fa-var-js-square, + "microsoft": $fa-var-microsoft, + "qq": $fa-var-qq, + "orcid": $fa-var-orcid, + "java": $fa-var-java, + "invision": $fa-var-invision, + "creative-commons-pd-alt": $fa-var-creative-commons-pd-alt, + "centercode": $fa-var-centercode, + "glide-g": $fa-var-glide-g, + "drupal": $fa-var-drupal, + "jxl": $fa-var-jxl, + "dart-lang": $fa-var-dart-lang, + "hire-a-helper": $fa-var-hire-a-helper, + "creative-commons-by": $fa-var-creative-commons-by, + "unity": $fa-var-unity, + "whmcs": $fa-var-whmcs, + "rocketchat": $fa-var-rocketchat, + "vk": $fa-var-vk, + "untappd": $fa-var-untappd, + "mailchimp": $fa-var-mailchimp, + "css3-alt": $fa-var-css3-alt, + "square-reddit": $fa-var-square-reddit, + "reddit-square": $fa-var-reddit-square, + "vimeo-v": $fa-var-vimeo-v, + "contao": $fa-var-contao, + "square-font-awesome": $fa-var-square-font-awesome, + "deskpro": $fa-var-deskpro, + "brave": $fa-var-brave, + "sistrix": $fa-var-sistrix, + "square-instagram": $fa-var-square-instagram, + "instagram-square": $fa-var-instagram-square, + "battle-net": $fa-var-battle-net, + "the-red-yeti": $fa-var-the-red-yeti, + "square-hacker-news": $fa-var-square-hacker-news, + "hacker-news-square": $fa-var-hacker-news-square, + "edge": $fa-var-edge, + "threads": $fa-var-threads, + "napster": $fa-var-napster, + "square-snapchat": $fa-var-square-snapchat, + "snapchat-square": $fa-var-snapchat-square, + "google-plus-g": $fa-var-google-plus-g, + "artstation": $fa-var-artstation, + "markdown": $fa-var-markdown, + "sourcetree": $fa-var-sourcetree, + "google-plus": $fa-var-google-plus, + "diaspora": $fa-var-diaspora, + "foursquare": $fa-var-foursquare, + "stack-overflow": $fa-var-stack-overflow, + "github-alt": $fa-var-github-alt, + "phoenix-squadron": $fa-var-phoenix-squadron, + "pagelines": $fa-var-pagelines, + "algolia": $fa-var-algolia, + "red-river": $fa-var-red-river, + "creative-commons-sa": $fa-var-creative-commons-sa, + "safari": $fa-var-safari, + "google": $fa-var-google, + "square-font-awesome-stroke": $fa-var-square-font-awesome-stroke, + "font-awesome-alt": $fa-var-font-awesome-alt, + "atlassian": $fa-var-atlassian, + "linkedin-in": $fa-var-linkedin-in, + "digital-ocean": $fa-var-digital-ocean, + "nimblr": $fa-var-nimblr, + "chromecast": $fa-var-chromecast, + "evernote": $fa-var-evernote, + "hacker-news": $fa-var-hacker-news, + "creative-commons-sampling": $fa-var-creative-commons-sampling, + "adversal": $fa-var-adversal, + "creative-commons": $fa-var-creative-commons, + "watchman-monitoring": $fa-var-watchman-monitoring, + "fonticons": $fa-var-fonticons, + "weixin": $fa-var-weixin, + "shirtsinbulk": $fa-var-shirtsinbulk, + "codepen": $fa-var-codepen, + "git-alt": $fa-var-git-alt, + "lyft": $fa-var-lyft, + "rev": $fa-var-rev, + "windows": $fa-var-windows, + "wizards-of-the-coast": $fa-var-wizards-of-the-coast, + "square-viadeo": $fa-var-square-viadeo, + "viadeo-square": $fa-var-viadeo-square, + "meetup": $fa-var-meetup, + "centos": $fa-var-centos, + "adn": $fa-var-adn, + "cloudsmith": $fa-var-cloudsmith, + "opensuse": $fa-var-opensuse, + "pied-piper-alt": $fa-var-pied-piper-alt, + "square-dribbble": $fa-var-square-dribbble, + "dribbble-square": $fa-var-dribbble-square, + "codiepie": $fa-var-codiepie, + "node": $fa-var-node, + "mix": $fa-var-mix, + "steam": $fa-var-steam, + "cc-apple-pay": $fa-var-cc-apple-pay, + "scribd": $fa-var-scribd, + "debian": $fa-var-debian, + "openid": $fa-var-openid, + "instalod": $fa-var-instalod, + "files-pinwheel": $fa-var-files-pinwheel, + "expeditedssl": $fa-var-expeditedssl, + "sellcast": $fa-var-sellcast, + "square-twitter": $fa-var-square-twitter, + "twitter-square": $fa-var-twitter-square, + "r-project": $fa-var-r-project, + "delicious": $fa-var-delicious, + "freebsd": $fa-var-freebsd, + "vuejs": $fa-var-vuejs, + "accusoft": $fa-var-accusoft, + "ioxhost": $fa-var-ioxhost, + "fonticons-fi": $fa-var-fonticons-fi, + "app-store": $fa-var-app-store, + "cc-mastercard": $fa-var-cc-mastercard, + "itunes-note": $fa-var-itunes-note, + "golang": $fa-var-golang, + "kickstarter": $fa-var-kickstarter, + "square-kickstarter": $fa-var-square-kickstarter, + "grav": $fa-var-grav, + "weibo": $fa-var-weibo, + "uncharted": $fa-var-uncharted, + "firstdraft": $fa-var-firstdraft, + "square-youtube": $fa-var-square-youtube, + "youtube-square": $fa-var-youtube-square, + "wikipedia-w": $fa-var-wikipedia-w, + "wpressr": $fa-var-wpressr, + "rendact": $fa-var-rendact, + "angellist": $fa-var-angellist, + "galactic-republic": $fa-var-galactic-republic, + "nfc-directional": $fa-var-nfc-directional, + "skype": $fa-var-skype, + "joget": $fa-var-joget, + "fedora": $fa-var-fedora, + "stripe-s": $fa-var-stripe-s, + "meta": $fa-var-meta, + "laravel": $fa-var-laravel, + "hotjar": $fa-var-hotjar, + "bluetooth-b": $fa-var-bluetooth-b, + "square-letterboxd": $fa-var-square-letterboxd, + "sticker-mule": $fa-var-sticker-mule, + "creative-commons-zero": $fa-var-creative-commons-zero, + "hips": $fa-var-hips, + "css": $fa-var-css, + "behance": $fa-var-behance, + "reddit": $fa-var-reddit, + "discord": $fa-var-discord, + "chrome": $fa-var-chrome, + "app-store-ios": $fa-var-app-store-ios, + "cc-discover": $fa-var-cc-discover, + "wpbeginner": $fa-var-wpbeginner, + "confluence": $fa-var-confluence, + "shoelace": $fa-var-shoelace, + "mdb": $fa-var-mdb, + "dochub": $fa-var-dochub, + "accessible-icon": $fa-var-accessible-icon, + "ebay": $fa-var-ebay, + "amazon": $fa-var-amazon, + "unsplash": $fa-var-unsplash, + "yarn": $fa-var-yarn, + "square-steam": $fa-var-square-steam, + "steam-square": $fa-var-steam-square, + "500px": $fa-var-500px, + "square-vimeo": $fa-var-square-vimeo, + "vimeo-square": $fa-var-vimeo-square, + "asymmetrik": $fa-var-asymmetrik, + "font-awesome": $fa-var-font-awesome, + "font-awesome-flag": $fa-var-font-awesome-flag, + "font-awesome-logo-full": $fa-var-font-awesome-logo-full, + "gratipay": $fa-var-gratipay, + "apple": $fa-var-apple, + "hive": $fa-var-hive, + "gitkraken": $fa-var-gitkraken, + "keybase": $fa-var-keybase, + "apple-pay": $fa-var-apple-pay, + "padlet": $fa-var-padlet, + "amazon-pay": $fa-var-amazon-pay, + "square-github": $fa-var-square-github, + "github-square": $fa-var-github-square, + "stumbleupon": $fa-var-stumbleupon, + "fedex": $fa-var-fedex, + "phoenix-framework": $fa-var-phoenix-framework, + "shopify": $fa-var-shopify, + "neos": $fa-var-neos, + "square-threads": $fa-var-square-threads, + "hackerrank": $fa-var-hackerrank, + "researchgate": $fa-var-researchgate, + "swift": $fa-var-swift, + "angular": $fa-var-angular, + "speakap": $fa-var-speakap, + "angrycreative": $fa-var-angrycreative, + "y-combinator": $fa-var-y-combinator, + "empire": $fa-var-empire, + "envira": $fa-var-envira, + "google-scholar": $fa-var-google-scholar, + "square-gitlab": $fa-var-square-gitlab, + "gitlab-square": $fa-var-gitlab-square, + "studiovinari": $fa-var-studiovinari, + "pied-piper": $fa-var-pied-piper, + "wordpress": $fa-var-wordpress, + "product-hunt": $fa-var-product-hunt, + "firefox": $fa-var-firefox, + "linode": $fa-var-linode, + "goodreads": $fa-var-goodreads, + "square-odnoklassniki": $fa-var-square-odnoklassniki, + "odnoklassniki-square": $fa-var-odnoklassniki-square, + "jsfiddle": $fa-var-jsfiddle, + "sith": $fa-var-sith, + "themeisle": $fa-var-themeisle, + "page4": $fa-var-page4, + "hashnode": $fa-var-hashnode, + "react": $fa-var-react, + "cc-paypal": $fa-var-cc-paypal, + "squarespace": $fa-var-squarespace, + "cc-stripe": $fa-var-cc-stripe, + "creative-commons-share": $fa-var-creative-commons-share, + "bitcoin": $fa-var-bitcoin, + "keycdn": $fa-var-keycdn, + "opera": $fa-var-opera, + "itch-io": $fa-var-itch-io, + "umbraco": $fa-var-umbraco, + "galactic-senate": $fa-var-galactic-senate, + "ubuntu": $fa-var-ubuntu, + "draft2digital": $fa-var-draft2digital, + "stripe": $fa-var-stripe, + "houzz": $fa-var-houzz, + "gg": $fa-var-gg, + "dhl": $fa-var-dhl, + "square-pinterest": $fa-var-square-pinterest, + "pinterest-square": $fa-var-pinterest-square, + "xing": $fa-var-xing, + "blackberry": $fa-var-blackberry, + "creative-commons-pd": $fa-var-creative-commons-pd, + "playstation": $fa-var-playstation, + "quinscape": $fa-var-quinscape, + "less": $fa-var-less, + "blogger-b": $fa-var-blogger-b, + "opencart": $fa-var-opencart, + "vine": $fa-var-vine, + "signal-messenger": $fa-var-signal-messenger, + "paypal": $fa-var-paypal, + "gitlab": $fa-var-gitlab, + "typo3": $fa-var-typo3, + "reddit-alien": $fa-var-reddit-alien, + "yahoo": $fa-var-yahoo, + "dailymotion": $fa-var-dailymotion, + "affiliatetheme": $fa-var-affiliatetheme, + "pied-piper-pp": $fa-var-pied-piper-pp, + "bootstrap": $fa-var-bootstrap, + "odnoklassniki": $fa-var-odnoklassniki, + "nfc-symbol": $fa-var-nfc-symbol, + "mintbit": $fa-var-mintbit, + "ethereum": $fa-var-ethereum, + "speaker-deck": $fa-var-speaker-deck, + "creative-commons-nc-eu": $fa-var-creative-commons-nc-eu, + "patreon": $fa-var-patreon, + "avianex": $fa-var-avianex, + "ello": $fa-var-ello, + "gofore": $fa-var-gofore, + "bimobject": $fa-var-bimobject, + "brave-reverse": $fa-var-brave-reverse, + "facebook-f": $fa-var-facebook-f, + "square-google-plus": $fa-var-square-google-plus, + "google-plus-square": $fa-var-google-plus-square, + "web-awesome": $fa-var-web-awesome, + "mandalorian": $fa-var-mandalorian, + "first-order-alt": $fa-var-first-order-alt, + "osi": $fa-var-osi, + "google-wallet": $fa-var-google-wallet, + "d-and-d-beyond": $fa-var-d-and-d-beyond, + "periscope": $fa-var-periscope, + "fulcrum": $fa-var-fulcrum, + "cloudscale": $fa-var-cloudscale, + "forumbee": $fa-var-forumbee, + "mizuni": $fa-var-mizuni, + "schlix": $fa-var-schlix, + "square-xing": $fa-var-square-xing, + "xing-square": $fa-var-xing-square, + "bandcamp": $fa-var-bandcamp, + "wpforms": $fa-var-wpforms, + "cloudversify": $fa-var-cloudversify, + "usps": $fa-var-usps, + "megaport": $fa-var-megaport, + "magento": $fa-var-magento, + "spotify": $fa-var-spotify, + "optin-monster": $fa-var-optin-monster, + "fly": $fa-var-fly, + "square-bluesky": $fa-var-square-bluesky, + "aviato": $fa-var-aviato, + "itunes": $fa-var-itunes, + "cuttlefish": $fa-var-cuttlefish, + "blogger": $fa-var-blogger, + "flickr": $fa-var-flickr, + "viber": $fa-var-viber, + "soundcloud": $fa-var-soundcloud, + "digg": $fa-var-digg, + "tencent-weibo": $fa-var-tencent-weibo, + "letterboxd": $fa-var-letterboxd, + "symfony": $fa-var-symfony, + "maxcdn": $fa-var-maxcdn, + "etsy": $fa-var-etsy, + "facebook-messenger": $fa-var-facebook-messenger, + "audible": $fa-var-audible, + "think-peaks": $fa-var-think-peaks, + "bilibili": $fa-var-bilibili, + "erlang": $fa-var-erlang, + "x-twitter": $fa-var-x-twitter, + "cotton-bureau": $fa-var-cotton-bureau, + "dashcube": $fa-var-dashcube, + "42-group": $fa-var-42-group, + "innosoft": $fa-var-innosoft, + "stack-exchange": $fa-var-stack-exchange, + "elementor": $fa-var-elementor, + "square-pied-piper": $fa-var-square-pied-piper, + "pied-piper-square": $fa-var-pied-piper-square, + "creative-commons-nd": $fa-var-creative-commons-nd, + "palfed": $fa-var-palfed, + "superpowers": $fa-var-superpowers, + "resolving": $fa-var-resolving, + "xbox": $fa-var-xbox, + "square-web-awesome-stroke": $fa-var-square-web-awesome-stroke, + "searchengin": $fa-var-searchengin, + "tiktok": $fa-var-tiktok, + "square-facebook": $fa-var-square-facebook, + "facebook-square": $fa-var-facebook-square, + "renren": $fa-var-renren, + "linux": $fa-var-linux, + "glide": $fa-var-glide, + "linkedin": $fa-var-linkedin, + "hubspot": $fa-var-hubspot, + "deploydog": $fa-var-deploydog, + "twitch": $fa-var-twitch, + "flutter": $fa-var-flutter, + "ravelry": $fa-var-ravelry, + "mixer": $fa-var-mixer, + "square-lastfm": $fa-var-square-lastfm, + "lastfm-square": $fa-var-lastfm-square, + "vimeo": $fa-var-vimeo, + "mendeley": $fa-var-mendeley, + "uniregistry": $fa-var-uniregistry, + "figma": $fa-var-figma, + "creative-commons-remix": $fa-var-creative-commons-remix, + "cc-amazon-pay": $fa-var-cc-amazon-pay, + "dropbox": $fa-var-dropbox, + "instagram": $fa-var-instagram, + "cmplid": $fa-var-cmplid, + "upwork": $fa-var-upwork, + "facebook": $fa-var-facebook, + "gripfire": $fa-var-gripfire, + "jedi-order": $fa-var-jedi-order, + "uikit": $fa-var-uikit, + "fort-awesome-alt": $fa-var-fort-awesome-alt, + "phabricator": $fa-var-phabricator, + "ussunnah": $fa-var-ussunnah, + "earlybirds": $fa-var-earlybirds, + "trade-federation": $fa-var-trade-federation, + "autoprefixer": $fa-var-autoprefixer, + "whatsapp": $fa-var-whatsapp, + "square-upwork": $fa-var-square-upwork, + "slideshare": $fa-var-slideshare, + "google-play": $fa-var-google-play, + "viadeo": $fa-var-viadeo, + "line": $fa-var-line, + "google-drive": $fa-var-google-drive, + "servicestack": $fa-var-servicestack, + "simplybuilt": $fa-var-simplybuilt, + "bitbucket": $fa-var-bitbucket, + "imdb": $fa-var-imdb, + "deezer": $fa-var-deezer, + "raspberry-pi": $fa-var-raspberry-pi, + "jira": $fa-var-jira, + "docker": $fa-var-docker, + "screenpal": $fa-var-screenpal, + "bluetooth": $fa-var-bluetooth, + "gitter": $fa-var-gitter, + "d-and-d": $fa-var-d-and-d, + "microblog": $fa-var-microblog, + "cc-diners-club": $fa-var-cc-diners-club, + "gg-circle": $fa-var-gg-circle, + "pied-piper-hat": $fa-var-pied-piper-hat, + "kickstarter-k": $fa-var-kickstarter-k, + "yandex": $fa-var-yandex, + "readme": $fa-var-readme, + "html5": $fa-var-html5, + "sellsy": $fa-var-sellsy, + "square-web-awesome": $fa-var-square-web-awesome, + "sass": $fa-var-sass, + "wirsindhandwerk": $fa-var-wirsindhandwerk, + "wsh": $fa-var-wsh, + "buromobelexperte": $fa-var-buromobelexperte, + "salesforce": $fa-var-salesforce, + "octopus-deploy": $fa-var-octopus-deploy, + "medapps": $fa-var-medapps, + "ns8": $fa-var-ns8, + "pinterest-p": $fa-var-pinterest-p, + "apper": $fa-var-apper, + "fort-awesome": $fa-var-fort-awesome, + "waze": $fa-var-waze, + "bluesky": $fa-var-bluesky, + "cc-jcb": $fa-var-cc-jcb, + "snapchat": $fa-var-snapchat, + "snapchat-ghost": $fa-var-snapchat-ghost, + "fantasy-flight-games": $fa-var-fantasy-flight-games, + "rust": $fa-var-rust, + "wix": $fa-var-wix, + "square-behance": $fa-var-square-behance, + "behance-square": $fa-var-behance-square, + "supple": $fa-var-supple, + "webflow": $fa-var-webflow, + "rebel": $fa-var-rebel, + "css3": $fa-var-css3, + "staylinked": $fa-var-staylinked, + "kaggle": $fa-var-kaggle, + "space-awesome": $fa-var-space-awesome, + "deviantart": $fa-var-deviantart, + "cpanel": $fa-var-cpanel, + "goodreads-g": $fa-var-goodreads-g, + "square-git": $fa-var-square-git, + "git-square": $fa-var-git-square, + "square-tumblr": $fa-var-square-tumblr, + "tumblr-square": $fa-var-tumblr-square, + "trello": $fa-var-trello, + "creative-commons-nc-jp": $fa-var-creative-commons-nc-jp, + "get-pocket": $fa-var-get-pocket, + "perbyte": $fa-var-perbyte, + "grunt": $fa-var-grunt, + "weebly": $fa-var-weebly, + "connectdevelop": $fa-var-connectdevelop, + "leanpub": $fa-var-leanpub, + "black-tie": $fa-var-black-tie, + "themeco": $fa-var-themeco, + "python": $fa-var-python, + "android": $fa-var-android, + "bots": $fa-var-bots, + "free-code-camp": $fa-var-free-code-camp, + "hornbill": $fa-var-hornbill, + "js": $fa-var-js, + "ideal": $fa-var-ideal, + "git": $fa-var-git, + "dev": $fa-var-dev, + "sketch": $fa-var-sketch, + "yandex-international": $fa-var-yandex-international, + "cc-amex": $fa-var-cc-amex, + "uber": $fa-var-uber, + "github": $fa-var-github, + "php": $fa-var-php, + "alipay": $fa-var-alipay, + "youtube": $fa-var-youtube, + "skyatlas": $fa-var-skyatlas, + "firefox-browser": $fa-var-firefox-browser, + "replyd": $fa-var-replyd, + "suse": $fa-var-suse, + "jenkins": $fa-var-jenkins, + "twitter": $fa-var-twitter, + "rockrms": $fa-var-rockrms, + "pinterest": $fa-var-pinterest, + "buffer": $fa-var-buffer, + "npm": $fa-var-npm, + "yammer": $fa-var-yammer, + "btc": $fa-var-btc, + "dribbble": $fa-var-dribbble, + "stumbleupon-circle": $fa-var-stumbleupon-circle, + "internet-explorer": $fa-var-internet-explorer, + "stubber": $fa-var-stubber, + "telegram": $fa-var-telegram, + "telegram-plane": $fa-var-telegram-plane, + "old-republic": $fa-var-old-republic, + "odysee": $fa-var-odysee, + "square-whatsapp": $fa-var-square-whatsapp, + "whatsapp-square": $fa-var-whatsapp-square, + "node-js": $fa-var-node-js, + "edge-legacy": $fa-var-edge-legacy, + "slack": $fa-var-slack, + "slack-hash": $fa-var-slack-hash, + "medrt": $fa-var-medrt, + "usb": $fa-var-usb, + "tumblr": $fa-var-tumblr, + "vaadin": $fa-var-vaadin, + "quora": $fa-var-quora, + "square-x-twitter": $fa-var-square-x-twitter, + "reacteurope": $fa-var-reacteurope, + "medium": $fa-var-medium, + "medium-m": $fa-var-medium-m, + "amilia": $fa-var-amilia, + "mixcloud": $fa-var-mixcloud, + "flipboard": $fa-var-flipboard, + "viacoin": $fa-var-viacoin, + "critical-role": $fa-var-critical-role, + "sitrox": $fa-var-sitrox, + "discourse": $fa-var-discourse, + "joomla": $fa-var-joomla, + "mastodon": $fa-var-mastodon, + "airbnb": $fa-var-airbnb, + "wolf-pack-battalion": $fa-var-wolf-pack-battalion, + "buy-n-large": $fa-var-buy-n-large, + "gulp": $fa-var-gulp, + "creative-commons-sampling-plus": $fa-var-creative-commons-sampling-plus, + "strava": $fa-var-strava, + "ember": $fa-var-ember, + "canadian-maple-leaf": $fa-var-canadian-maple-leaf, + "teamspeak": $fa-var-teamspeak, + "pushed": $fa-var-pushed, + "wordpress-simple": $fa-var-wordpress-simple, + "nutritionix": $fa-var-nutritionix, + "wodu": $fa-var-wodu, + "google-pay": $fa-var-google-pay, + "intercom": $fa-var-intercom, + "zhihu": $fa-var-zhihu, + "korvue": $fa-var-korvue, + "pix": $fa-var-pix, + "steam-symbol": $fa-var-steam-symbol, +); diff --git a/src/sass/fontawesome/brands.scss b/src/sass/fontawesome/brands.scss new file mode 100644 index 0000000..f05b3fc --- /dev/null +++ b/src/sass/fontawesome/brands.scss @@ -0,0 +1,30 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +@import 'functions'; +@import 'variables'; + +:root, :host { + --#{$fa-css-prefix}-style-family-brands: 'Font Awesome 6 Brands'; + --#{$fa-css-prefix}-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; +} + +@font-face { + font-family: 'Font Awesome 6 Brands'; + font-style: normal; + font-weight: 400; + font-display: $fa-font-display; + src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'), + url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'); +} + +.fab, +.#{$fa-css-prefix}-brands { + font-weight: 400; +} + +@each $name, $icon in $fa-brand-icons { + .#{$fa-css-prefix}-#{$name} { #{$fa-icon-property}: unquote("\"#{ $icon }\""); } +} diff --git a/src/sass/fontawesome/fontawesome.scss b/src/sass/fontawesome/fontawesome.scss new file mode 100644 index 0000000..2a5b40a --- /dev/null +++ b/src/sass/fontawesome/fontawesome.scss @@ -0,0 +1,21 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +// Font Awesome core compile (Web Fonts-based) +// ------------------------- + +@import 'functions'; +@import 'variables'; +@import 'mixins'; +@import 'core'; +@import 'sizing'; +@import 'fixed-width'; +@import 'list'; +@import 'bordered-pulled'; +@import 'animated'; +@import 'rotated-flipped'; +@import 'stacked'; +@import 'icons'; +@import 'screen-reader'; diff --git a/src/sass/fontawesome/regular.scss b/src/sass/fontawesome/regular.scss new file mode 100644 index 0000000..4a15596 --- /dev/null +++ b/src/sass/fontawesome/regular.scss @@ -0,0 +1,27 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +@import 'functions'; +@import 'variables'; + +:root, :host { + --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; + --#{$fa-css-prefix}-font-regular: normal 400 1em/1 '#{ $fa-style-family }'; +} + + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 400; + font-display: $fa-font-display; + src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'), + url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'); +} + +.far, +.#{$fa-css-prefix}-regular { + font-weight: 400; +} diff --git a/src/sass/fontawesome/solid.scss b/src/sass/fontawesome/solid.scss new file mode 100644 index 0000000..f673892 --- /dev/null +++ b/src/sass/fontawesome/solid.scss @@ -0,0 +1,27 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +@import 'functions'; +@import 'variables'; + +:root, :host { + --#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }'; + --#{$fa-css-prefix}-font-solid: normal 900 1em/1 '#{ $fa-style-family }'; +} + + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 900; + font-display: $fa-font-display; + src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'), + url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'); +} + +.fas, +.#{$fa-css-prefix}-solid { + font-weight: 900; +} diff --git a/src/sass/fontawesome/v4-shims.scss b/src/sass/fontawesome/v4-shims.scss new file mode 100644 index 0000000..448e25a --- /dev/null +++ b/src/sass/fontawesome/v4-shims.scss @@ -0,0 +1,11 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +// V4 shims compile (Web Fonts-based) +// ------------------------- + +@import 'functions'; +@import 'variables'; +@import 'shims'; diff --git a/src/sass/fonts/Inter-Regular.woff2 b/src/sass/fonts/Inter-Regular.woff2 deleted file mode 100644 index 6c2b689..0000000 Binary files a/src/sass/fonts/Inter-Regular.woff2 and /dev/null differ diff --git a/src/sass/fonts/Inter-SemiBold.woff2 b/src/sass/fonts/Inter-SemiBold.woff2 deleted file mode 100644 index 611e90c..0000000 Binary files a/src/sass/fonts/Inter-SemiBold.woff2 and /dev/null differ diff --git a/src/sass/magicuserjs.scss b/src/sass/magicuserjs.scss index 326e3de..ec6651f 100644 --- a/src/sass/magicuserjs.scss +++ b/src/sass/magicuserjs.scss @@ -1,3 +1,3 @@ @charset 'UTF-8'; -@import 'main'; +@use 'main'; diff --git a/src/sass/web-ext.scss b/src/sass/web-ext.scss index cae6385..0dbb6da 100644 --- a/src/sass/web-ext.scss +++ b/src/sass/web-ext.scss @@ -1,13 +1,22 @@ +// importing core styling file +@use './fontawesome/fontawesome.scss'; + +// our project needs Solid + Brands +@use './fontawesome/solid.scss'; +@use './fontawesome/brands.scss'; + body.webext-page { - // overflow-x: hidden; + overflow: hidden; padding: 0; margin: 0; - background: #495060; - color: #ffffff; + background: var(--mujs-background-color, hsl(222, 14%, 33%)); + color: var(--mujs-txt-color, hsl(0, 0%, 100%)); border: 1px solid rgba(0, 0, 0, 0); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; font-size: 14px; + min-width: 720px; + .mujs-cfg { display: flex; gap: 10px; @@ -20,11 +29,7 @@ body.webext-page { } } mujs-main { - // max-width: 100%; - min-width: 480px; - // max-height: 100%; - // min-height: 200px; - // min-width: 50em; - min-height: 40em; + min-width: 25em; + height: 492px; } -} \ No newline at end of file +} diff --git a/src/svg/bug-svgrepo-com.svg b/src/svg/bug-svgrepo-com.svg deleted file mode 100644 index 32b7852..0000000 --- a/src/svg/bug-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/svg/checkmark-seal-svgrepo-com.svg b/src/svg/checkmark-seal-svgrepo-com.svg deleted file mode 100644 index 8a5b4a3..0000000 --- a/src/svg/checkmark-seal-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/close-svgrepo-com.svg b/src/svg/close-svgrepo-com.svg deleted file mode 100644 index a4a1e54..0000000 --- a/src/svg/close-svgrepo-com.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/src/svg/download-svgrepo-com.svg b/src/svg/download-svgrepo-com.svg deleted file mode 100644 index 9b8cf09..0000000 --- a/src/svg/download-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/expand-full-fullscreen-svgrepo-com.svg b/src/svg/expand-full-fullscreen-svgrepo-com.svg deleted file mode 100644 index 53218ff..0000000 --- a/src/svg/expand-full-fullscreen-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/filter-svgrepo-com.svg b/src/svg/filter-svgrepo-com.svg deleted file mode 100644 index 6bedf72..0000000 --- a/src/svg/filter-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/fork-svgrepo-com.svg b/src/svg/fork-svgrepo-com.svg deleted file mode 100644 index 8087aa8..0000000 --- a/src/svg/fork-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/fullscreen-svgrepo-com.svg b/src/svg/fullscreen-svgrepo-com.svg deleted file mode 100644 index 15379af..0000000 --- a/src/svg/fullscreen-svgrepo-com.svg +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/svg/github-svgrepo-com.svg b/src/svg/github-svgrepo-com.svg deleted file mode 100644 index eaac757..0000000 --- a/src/svg/github-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/minus-svgrepo-com.svg b/src/svg/minus-svgrepo-com.svg deleted file mode 100644 index 4f655c0..0000000 --- a/src/svg/minus-svgrepo-com.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/svg/navigation-svgrepo-com.svg b/src/svg/navigation-svgrepo-com.svg deleted file mode 100644 index 92569ad..0000000 --- a/src/svg/navigation-svgrepo-com.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/svg/plus-svgrepo-com.svg b/src/svg/plus-svgrepo-com.svg deleted file mode 100644 index 8410ef7..0000000 --- a/src/svg/plus-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/search-svgrepo-com.svg b/src/svg/search-svgrepo-com.svg deleted file mode 100644 index 8369115..0000000 --- a/src/svg/search-svgrepo-com.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/svg/settings-svgrepo-com.svg b/src/svg/settings-svgrepo-com.svg deleted file mode 100644 index eaeef67..0000000 --- a/src/svg/settings-svgrepo-com.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/typings/UserJS.d.ts b/src/typings/UserJS.d.ts index 1584f08..a3a0cc9 100644 --- a/src/typings/UserJS.d.ts +++ b/src/typings/UserJS.d.ts @@ -1,24 +1,116 @@ // import GM from '@types/greasemonkey'; // import '@types/tampermonkey'; import '@violentmonkey/types'; -import { type GSForkQuery } from './types'; +import { config, FilterLayout, UserJSEngine, type GSForkQuery } from './types'; import './scheduler'; +/** [i18n directory](https://github.com/magicoflolis/Userscript-Plus/tree/master/src/_locales) */ +export const translations: { + [i18n: string]: { + [key: string]: string; + }; +}; +/** [source code](https://github.com/magicoflolis/Userscript-Plus/blob/master/src/sass/_main.scss) */ +export const main_css: string; + export interface safeHandles { - // trustedTypes: { - // createPolicy(): void; - // }; XMLHttpRequest: typeof XMLHttpRequest; + CustomEvent: typeof CustomEvent; + customElements: typeof customElements; createElement: typeof document.createElement; createElementNS: typeof document.createElementNS; createTextNode: typeof document.createTextNode; setTimeout: typeof setTimeout; clearTimeout: typeof clearTimeout; navigator: typeof navigator; + // customElements: typeof customElements; /** Taken from [scheduler-polyfill](https://github.com/GoogleChromeLabs/scheduler-polyfill) */ scheduler: typeof scheduler; + /** + * Groups members of an iterable according to the return value of the passed callback. + * @param items An iterable. + * @param keySelector A callback which will be invoked for each item in items. + */ + groupBy( + items: Iterable, + keySelector: (item: T, index: number) => K + ): Partial>; +} + +export class Safe { + public constructor(); + public _self: safeHandles | null; +} + +export interface Translations { + createdby: string; + name: string; + daily_installs: string; + close: string; + filterA: string; + max: string; + min: string; + search: string; + search_placeholder: string; + install: string; + issue: string; + version_number: string; + updated: string; + total_installs: string; + ratings: string; + good: string; + ok: string; + bad: string; + created_date: string; + redirect: string; + filter: string; + dtime: string; + save: string; + reset: string; + preview_code: string; + saveFile: string; + newTab: string; + applies_to: string; + license: string; + no_license: string; + antifeatures: string; + userjs_fullscreen: string; + listing_none: string; + export_config: string; + export_theme: string; + import_config: string; + import_theme: string; + code_size: string; + prmpt_css: string; + userjs_inject: string; + userjs_close: string; + userjs_sync: string; + userjs_autoinject: string; + auto_fetch: string; +} + +export interface LanguageTranslations { + ar: Translations; + de: Translations; + en: Translations; + en_GB: Translations; + es: Translations; + fr: Translations; + ja: Translations; + nl: Translations; + pl: Translations; + ru: Translations; + zh: Translations; + zh_CN: Translations; + zh_TW: Translations; } +// export declare const translations: LanguageTranslations; + +export declare function i18n$( + key: string +): Translations[K] | 'Unknown'; + /** * Some sites will alter or remove document functions * To get around this we bind them to the `userjs` object @@ -47,15 +139,9 @@ export declare function observe( */ export declare function openTab(url: string | URL): WindowProxy | null; -/** - * Get information about the current userscript. - * - * [ViolentMonkey Reference](https://violentmonkey.github.io/api/gm/#gm_info) - */ -export declare function getGMInfo(): typeof GM_info; - export interface StorageSystem { prefix: string; + events: Set; /** * Alias of `window.localStorage.getItem` */ @@ -73,6 +159,10 @@ export interface StorageSystem { */ remove(key: K): void; + addListener(name: string, callback: VMScriptGMValueChangeCallback): number | void; + + attach(): void; + /** * Set value - Saves key to either GM managed storage or `window.localStorage` * @@ -118,35 +208,163 @@ export interface Network { ): Promise>; bscStr(str: S, lowerCase: boolean): S; } - -declare class Timeout { +//#region Testing crap +export class Timeout { + public constructor(); public ids: number[]; - public set(delay: number, reason: any): Promise; + public set(delay: number, reason?: R): Promise; public clear(...ids: number[]): void; } +export class Tabs { + public constructor(root: HTMLElement); + public pool: Set; + public blank: 'about:blank'; + public protocal: 'mujs:'; + public protoReg: RegExp; + public el: { + add: HTMLElement; + head: HTMLElement; + root: HTMLElement; + }; + public custom: () => void; + public getTab(hostname: S): HTMLElement | undefined; + public getActive(): HTMLElement | undefined; + public intFN(hostname: S): void; + public active(tab: T, build?: boolean): void; + public close(tab: T): void; + public create(hostname?: string): HTMLElement | undefined; +} + +export interface mujsName extends HTMLTableCellElement { + _mujs: { + fmore: HTMLElement; + fBtns: HTMLElement; + codeArea: HTMLTextAreaElement; + } +} + +export interface cfgpage extends HTMLElement { + _mujs: { + base: { + text: string; + tag: string; + value: string; + type: HTMLInputElement['type']; + attrs: object; + default: string | boolean | number | UserJSEngine | FilterLayout; + cache: string | boolean | number | UserJSEngine | FilterLayout; + elem: HTMLInputElement | HTMLSelectElement; + elemUrl?: HTMLInputElement; + elemToken?: HTMLInputElement; + }[]; + sections: Set; + } +} + +export class Container { + public static prompts: HTMLElement[]; + public webpage: URL; + public host: string; + public domain: string; + public ready: boolean; + public injected: boolean; + public shadowRoot?: ShadowRoot; + public shadowSupport: boolean; + public frame: HTMLElement; + public hostCache?: Map; + public userjsCache: Map; + public root: HTMLElement; + public unsaved: boolean; + public isBlacklisted: boolean; + public rebuild: boolean; + public opacityMin: string; + public opacityMax: string; + public elementsReady?: boolean; + public timeouts?: { + frame: Timeout; + mouse: Timeout; + }; + public Tabs?: Tabs; + public mainframe?: HTMLElement; + public countframe?: HTMLElement; + public mainbtn?: HTMLElement; + public urlBar?: HTMLInputElement; + public rateContainer?: HTMLElement; + public footer?: HTMLElement; + public tabbody?: HTMLElement; + public promptElem?: HTMLElement; + public toolbar?: HTMLElement; + public table?: HTMLTableElement; + public tabhead?: HTMLTableSectionElement; + public header?: HTMLElement; + public tbody?: HTMLTableSectionElement; + public cfgpage: cfgpage; + public main?: HTMLElement; + public urlContainer?: HTMLElement; + public btnframe?: HTMLElement; + public btnHandles?: HTMLElement; + public btnHide?: HTMLElement; + public btnfullscreen?: HTMLElement; + public closebtn?: HTMLElement; + public btncfg?: HTMLElement; + public btnhome?: HTMLElement; + public btnissue?: HTMLElement; + public btngreasy?: HTMLElement; + public btnnav?: HTMLElement; + public injFN?: () => void; + public inject(callback: (this: this, shadowRoot: this['shadowRoot']) => any, doc?: Document): void; + public initFn(): void; + public init(): boolean; + public remove(): void; + public save(): Promise; + /** + * @param css - CSS to inject + * @param name - Name of stylesheet + * @return Style element + */ + public loadCSS(css: C, name?: N): HTMLStyleElement | undefined; + public checkBlacklist(str: S): boolean; + public setTheme(): void; + public makePrompt(txt: S, dataset?: {}, usePrompt?: boolean): HTMLElement; + public showError(...ex: E[]): void; + public refresh(): void; + /** + * Redirects sleazyfork userscripts from greasyfork.org to sleazyfork.org + * + * Taken from: https://greasyfork.org/scripts/23840 + */ + public redirect(): void; + public timeoutFrame(time?: N): Promise; + public toElem(): HTMLElement[]; + public [Symbol.iterator](): Generator; +} + +export class List { + public constructor(hostname?: string); + private intEngines: UserJSEngine[]; + private intHost: string; + public dispatch(ujs: GSForkQuery): void; + public get engines(): UserJSEngine[]; + public get host(): string; + public setEngines(engines?: E[]): E[]; + public setHost(hostname: S): S; + public getDomain(str?: S): S; + public build(): void; + public sortRecords(): void; + public groupBy(): Record; + public [Symbol.iterator](): Generator; +} +//#endregion -export interface Container { - webpage: URL; - host: string; - domain: string; - ready: boolean; - injected: boolean; - shadowRoot?: ShadowRoot | HTMLElement; - supported: boolean; - frame: HTMLElement | HTMLIFrameElement; - cache: Map; - userjsCache: Map; - root: HTMLElement; - unsaved: boolean; - isBlacklisted: boolean; - rebuild: boolean; - counters: { - total: number; - [engine: string]: number; +declare global { + let translations: { + [i18n: string]: string; + }; + let userjs: { + /** + * this should always be `true` otherwise the script won't execute + */ + UserJS: boolean; + isMobile?: boolean; }; - opacityMin: string; - opacityMax: string; - Timeout: new () => Timeout; } diff --git a/src/typings/types.d.ts b/src/typings/types.d.ts index 893ca62..2ff990c 100644 --- a/src/typings/types.d.ts +++ b/src/typings/types.d.ts @@ -48,6 +48,10 @@ export type GSForkQuery = { description: string; url: string; code_url: string; + code_urls: { + name: string; + code_url: string; + }[]; license: string; version: string; locale: string; @@ -58,14 +62,22 @@ export type GSForkQuery = { host?: string; }; code: { - antifeatures: string[]; - data?: string; + meta: UserJSMeta | null; + request(translate: boolean, code_url?: string): Promise; + code_size?: string[]; translated?: boolean; - grant: string[]; - match: string[]; - meta: UserJSMeta | null; - request(): Promise; + code?: string; + data_meta_block?: string; + data_code_block?: string; + data_meta?: { + [meta: string]: string | string[] | { [resource: string]: string }; + }; + data_names?: { + text: string; + domain: boolean; + tld_extra: boolean; + }[]; }; root?: HTMLTableRowElement; }; @@ -94,24 +106,54 @@ export type GSFork = { export type UserJSEngine = { enabled: boolean; name: string; - url: string; + url?: string; token?: string; - query?: string; + query: string; }; +export interface FilterLayout { + enabled: boolean; + name: string; + flag?: string; + regExp: string; +} + +export interface Filters extends FilterLayout { + reg: RegExp; + keyReg: RegExp; + valueReg: RegExp; +} + export type config = { + /** + * List sorting on load + */ + autoSort: string; + /** + * Fetch from engines on load + */ autofetch: boolean; + /** + * Inject list on load + */ autoinject: boolean; /** - * Sync config with UserScript manager + * Clear cache on tab close + */ + clearTabCache: boolean; + /** + * `UserScript:` Sync config with UserScript manager */ cache?: boolean; /** * Fetch all UserScript code on load */ - codePreview: boolean; + preview: { + code: boolean; + metadata: boolean; + }; /** - * Fullscreen list on load + * `UserScript:` Fullscreen list on load */ autoexpand?: boolean; /** @@ -123,7 +165,7 @@ export type config = { */ sleazyredirect: boolean; /** - * Miliseconds before list closes + * `UserScript:` Miliseconds before list closes */ time?: number; /** @@ -177,82 +219,68 @@ export type config = { author: boolean; others: boolean; }; + /** + * Taken from https://greasyfork.org/scripts/12179 + */ filters: { - [name: string ]: { - enabled: boolean; - name: string; - flag?: string; - regExp: string; - }; - // 'Non-ASCII': { - // enabled: boolean; - // regExp: string; - // }; - // 'Non-Latin': { - // enabled: boolean; - // regExp: string; - // }; - // Games: { - // enabled: boolean; - // regExp: string; - // }; - // 'Social Networks': { - // enabled: boolean; - // regExp: string; - // }; - // Clutter: { - // enabled: boolean; - // regExp: string; - // }; + ASCII: FilterLayout; + Latin: FilterLayout; + Games: FilterLayout; + SocialNetworks: FilterLayout; + Clutter: FilterLayout; }; }; -export declare function objToStr(obj: O): string; - -export declare function strToURL(str: S): URL; - +/** + * Object to `[object *]` + */ +export declare function objToStr(obj: any): string; /** * Object is typeof `RegExp` */ -export declare function isRegExp(obj: O): boolean; - +export declare function isRegExp(obj: any): obj is RegExp; +/** + * Object is typeof `HTMLElement` + */ +export declare function isHTML(obj: any): obj is HTMLElement; /** * Object is typeof `Element` */ -export declare function isElem(obj: O): boolean; +export declare function isElem(obj: any): obj is Element; /** * Object is typeof `object` / JSON Object */ -export declare function isObj(obj: O): boolean; +export declare function isObj(obj: any): obj is Object; /** * Object is typeof `Function` */ -export declare function isFN(obj: O): boolean; +export declare function isFN(obj: any): obj is Function; /** * Object is `null` or `undefined` */ -export declare function isNull(obj: O): boolean; +export declare function isNull(obj: any): obj is null; +export declare function isNull(obj: any): obj is undefined; /** - * Object is Blank + * Object is blank */ -export declare function isBlank(obj: O): boolean; +export declare function isBlank(obj: O): boolean; /** - * Object is Empty + * Object is empty */ -export declare function isEmpty(obj: O): boolean; +export declare function isEmpty(obj: O): boolean; /** * Type is not 100% accurate */ export declare function normalizeTarget( target: T, - toQuery: boolean, - root: Element | Document + toQuery?: boolean, + root?: Document | Element ): T[]; export declare function halt(evt: Event): void; @@ -292,26 +320,12 @@ declare global { } /** - * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. - * - * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. - * - * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. - * - * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. - * - * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. - * - * If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted. - * - * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. - * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) */ export declare function ael( el: E, type: K, - listener: (this: E, ev: HTMLElementEventMap[K]) => any, + listener: (this: E, ev: HTMLElementEventMap[K]) => any | EventListenerOrEventListenerObject, options?: AddEventListenerOptions | boolean ): void; @@ -334,8 +348,10 @@ export declare function qsA( /** * Set attributes for an element. + * @param elem HTML element + * @param attr Set attributes for the element */ -export declare function formAttrs(elem: E, attr: keyof E): E; +export declare function formAttrs(elem: E, attr?: E[keyof E]): E; /** * Creates an instance of the element for the specified tag. @@ -350,9 +366,14 @@ export declare function make< A extends keyof HTMLElementTagNameMap[K] >( tagName: K, - cname?: string, + cname?: + | string + | string[] + | { + [key in A]: Record; + }, attrs?: { - [key in A]: unknown; + [key in A]: Record; } ): HTMLElementTagNameMap[K]; @@ -373,9 +394,10 @@ export interface dom { value?: V ): V | undefined; text(target: T, text?: V): string | null | undefined; + remove(target: T): void; cl: { - add(target: T, token: string | string[]): void; - remove(target: T, token: string | string[]): void; + add(target: T, token: string | string[]): boolean; + remove(target: T, token: string | string[]): boolean; toggle(target: T, token: string | string[], force?: boolean): boolean; has(target: T, token: string | string[]): boolean; }; diff --git a/src/webfonts/fa-brands-400.ttf b/src/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000..0f82a83 Binary files /dev/null and b/src/webfonts/fa-brands-400.ttf differ diff --git a/src/webfonts/fa-brands-400.woff2 b/src/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000..3c5cf97 Binary files /dev/null and b/src/webfonts/fa-brands-400.woff2 differ diff --git a/src/webfonts/fa-regular-400.ttf b/src/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000..9ee1919 Binary files /dev/null and b/src/webfonts/fa-regular-400.ttf differ diff --git a/src/webfonts/fa-regular-400.woff2 b/src/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000..57d9179 Binary files /dev/null and b/src/webfonts/fa-regular-400.woff2 differ diff --git a/src/webfonts/fa-solid-900.ttf b/src/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000..1c10972 Binary files /dev/null and b/src/webfonts/fa-solid-900.ttf differ diff --git a/src/webfonts/fa-solid-900.woff2 b/src/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000..1672102 Binary files /dev/null and b/src/webfonts/fa-solid-900.woff2 differ diff --git a/src/webfonts/fa-v4compatibility.ttf b/src/webfonts/fa-v4compatibility.ttf new file mode 100644 index 0000000..3bcb67f Binary files /dev/null and b/src/webfonts/fa-v4compatibility.ttf differ diff --git a/src/webfonts/fa-v4compatibility.woff2 b/src/webfonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000..fbafb22 Binary files /dev/null and b/src/webfonts/fa-v4compatibility.woff2 differ diff --git a/tools/languageLoader.js b/tools/languageLoader.js deleted file mode 100644 index 50e8861..0000000 --- a/tools/languageLoader.js +++ /dev/null @@ -1,54 +0,0 @@ -import { readdir, readFile, stat } from 'node:fs/promises'; -import { URL, fileURLToPath } from 'node:url'; - -/** - * Loads all the structures in the provided directory. - * - * @template T - * @param {import('node:fs').PathLike} dir - The directory to load the structures from - * @param {boolean} recursive - Whether to recursively load the structures in the directory - * @returns {Promise} - */ -export async function loadLanguages(dir, recursive = true) { - // Get the stats of the directory - const statDir = await stat(dir); - - // If the provided directory path is not a directory, throw an error - if (!statDir.isDirectory()) { - throw new Error(`The directory '${dir}' is not a directory.`); - } - - // Get all the files in the directory - const files = await readdir(dir); - - // Create an empty array to store the structures - /** @type {T[]} */ - const structures = []; - - // Loop through all the files in the directory - for (const file of files) { - // Get the stats of the file - const statFile = await stat(new URL(`${dir}/${file}`)); - - // If the file is a directory and recursive is true, recursively load the structures in the directory - if (statFile.isDirectory() && recursive) { - structures.push(...(await loadLanguages(new URL(`${dir}/${file}`), recursive))); - continue; - } - - if (!file.endsWith('.json')) { - continue; - } - - const filePath = fileURLToPath(`${dir}/${file}`); - const reg = /_locales\\(.*?)\\messages\.json/g.exec(filePath); - if (reg) { - const structure = await readFile(filePath, 'utf-8'); - structures.push({ - [reg[1]]: JSON.parse(structure.toString('utf-8')) - }); - } - } - - return structures; -} diff --git a/tools/userscript.js b/tools/userscript.js index 2bb3a73..c7c6d0d 100644 --- a/tools/userscript.js +++ b/tools/userscript.js @@ -1,398 +1,4 @@ -import path from 'path'; -import { URL } from 'node:url'; -import fs from 'node:fs'; -import dotenv from 'dotenv'; -import Watchpack from 'watchpack'; -import { loadLanguages } from './languageLoader.js'; +'use strict'; +import build from 'user.js'; -/** - * @typedef { import('../package.json') } CFG - */ - -const replaceTemplate = /\[\[(.*?)\]\]/g; -const metaStr = '[[metadata]]'; - -const log = (...msg) => { - console.log('[LOG]', ...msg); -}; -const err = (...msg) => { - console.error('[ERROR]', ...msg); -}; -/** - * Object is typeof `object` / JSON Object - * @template O - * @param { O } obj - * @returns { boolean } - */ -const isObj = (obj) => { - /** @type { string } */ - const s = Object.prototype.toString.call(obj); - return s.includes('Object'); -}; -/** - * Object is `null` or `undefined` - * @template O - * @param { O } obj - * @returns { boolean } - */ -const isNull = (obj) => { - return Object.is(obj, null) || Object.is(obj, undefined); -}; -/** - * Object is Blank - * @template O - * @param { O } obj - * @returns { boolean } - */ -const isBlank = (obj) => { - return ( - (typeof obj === 'string' && Object.is(obj.trim(), '')) || - ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || - (Array.isArray(obj) && Object.is(obj.length, 0)) || - (isObj(obj) && Object.is(Object.keys(obj).length, 0)) - ); -}; -/** - * Object is Empty - * @template O - * @param { O } obj - * @returns { boolean } - */ -const isEmpty = (obj) => { - return isNull(obj) || isBlank(obj); -}; -/** - * @template { string | { msg: string; } } T - * @template D - * @param { T } template - * @param { D } data - */ -const nano = (template, data) => { - if (typeof template === 'string') { - return template.replace(replaceTemplate, (_match, key) => { - const keys = key.split('.'); - let v = data[keys.shift()]; - for (const i in keys.length) v = v[keys[i]]; - return isEmpty(v) ? '' : v; - }); - } - return ''; -}; -/** - * @param {import('node:fs').PathLike} filePath - * @param {string} encoding - */ -const canAccess = async (filePath, encoding = 'utf-8') => { - const testAccess = await fs.promises.access( - filePath, - fs.promises.constants.R_OK | fs.promises.constants.W_OK - ); - if (isNull(testAccess)) { - const data = await fs.promises.readFile(filePath, encoding); - return data.toString(encoding); - } - return { - msg: `Cannot access provided filePath: ${filePath}` - }; -}; -/** - * @param {import('node:fs').PathLike} filePath - * @param {string} encoding - */ -const fileToJSON = async (filePath, encoding = 'utf-8') => { - const testAccess = await canAccess(filePath, encoding); - if (isObj(testAccess)) { - throw new Error(testAccess.msg); - } - return JSON.parse(testAccess); -}; -/** - * @param {import('node:fs').PathLike} destinationFilePath - * @param data - */ -const writeUserJS = async (destinationFilePath, data) => { - return await fs.promises.writeFile(destinationFilePath, data); -}; -const toTime = () => { - return new Intl.DateTimeFormat('default', { - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - fractionalSecondDigits: 3 - }).format(new Date()); -}; -/** - * @type { Map } - */ -const dataMap = new Map(); -const compareArrays = (a, b) => - a.length === b.length && a.every((element, index) => element === b[index]); -/** - * @template {string} K - * @template V - * @param {K} key - * @param {...V} values - */ -const addTo = (key, ...values) => { - if (values.length === 0) { - return ''; - } - if (dataMap.has(key)) { - if (compareArrays(dataMap.get(key), values)) { - return dataMap.get(key); - } - } - dataMap.set(key, values); - return dataMap.get(key); -}; -const filterData = [ - 'name', - 'description', - 'author', - 'icon', - 'version', - 'url', - 'homepage', - 'bugs', - 'license' -]; - -(async () => { - /** @type { dotenv.DotenvConfigOutput } */ - let result = {}; - try { - /** - * @type { CFG } - */ - const jsonData = await fileToJSON('./package.json'); - if (!isObj(jsonData)) { - throw new Error('"jsonData" must be an object.'); - } - if (!jsonData.userJS) { - throw new Error('Missing "userJS" key'); - } - const userJS = jsonData.userJS; - const { build } = userJS; - - result = dotenv.config({ - path: isEmpty(process.env.JS_ENV) ? build.paths.dev.env : build.paths.public.env - }); - if (result.error) { - throw result.error; - } - if (isNull(result.parsed.JS_ENV)) { - dotenv.populate( - result.parsed, - { - JS_ENV: 'development' - }, - { override: true, debug: true } - ); - } - const env = result.parsed; - const js_env = env.JS_ENV === 'development'; - const dp = js_env ? 'dev' : 'public'; - const compileMetadata = () => { - const metaData = []; - for (const [key, value] of Object.entries(userJS.metadata)) { - if (Array.isArray(value)) { - for (const v of value) { - metaData.push(`// @${key} ${v}`); - } - } else if (isObj(value)) { - for (const [k, v] of Object.entries(value)) { - metaData.push(`// @${key} ${k} ${v}`); - } - } else if (typeof value === 'boolean') { - if (value === true) { - metaData.push(`// @${key}`); - } - } else { - metaData.push(`// @${key} ${value}`); - } - } - return metaData.join('\n'); - }; - const buildUserJS = async () => { - try { - const lngList = await loadLanguages(new URL('../src/_locales', import.meta.url)); - const transformLanguages = () => { - const resp = {}; - for (const obj of lngList) { - for (const [k, v] of Object.entries(obj)) { - const o = {}; - for (const [key, value] of Object.entries(v)) { - if (key.startsWith('ext')) { - continue; - } - if (/userjs_(name|description)/i.test(key)) { - continue; - } - if (isEmpty(value.message)) { - continue; - } - o[key] = value.message; - } - resp[k] = o; - } - } - return JSON.stringify(resp, null, ' '); - }; - const compileLanguage = (type = 'userjs_name') => { - const resp = []; - for (const obj of lngList) { - for (const [k, v] of Object.entries(obj)) { - if (v[type]) { - if (isEmpty(v[type].message)) { - continue; - } - if (k.startsWith('en')) { - continue; - } - const t = type.toLowerCase().replace('userjs_', ''); - if (type === 'userjs_name') { - resp.push( - `// @${t}:${k.replace('_', '-')} ${js_env ? '[Dev] ' : ''}${v[type].message}` - ); - } else { - resp.push(`// @${t}:${k.replace('_', '-')} ${v[type].message}`); - } - } - } - } - return resp; - }; - const getData = () => { - for (const [k, v] of Object.entries(userJS)) { - if (typeof v !== 'string') { - continue; - } - if (k === 'name') { - addTo( - k, - `// @${k} ${js_env ? '[Dev] ' : ''}${v}`, - ...compileLanguage('userjs_name') - ); - } else if (k === 'description') { - addTo(k, `// @${k} ${v}`, ...compileLanguage('userjs_description')); - } else if (k === 'author') { - addTo(k, `// @${k} ${v}`); - } else if (k === 'icon') { - const buff = new Buffer.from(fs.readFileSync(v)); - const base64data = buff.toString('base64'); - if (v.endsWith('.png')) { - addTo(k, `// @${k} data:image/png;base64,${base64data}`); - } else if (v.endsWith('.svg')) { - addTo(k, `// @${k} data:image/svg+xml;base64,${base64data}`); - } - } else if (k === 'downloadURL') { - addTo(k, `// @downloadURL ${v}`); - } else if (k === 'updateURL') { - addTo(k, `// @updateURL ${v}`); - } else if (k === 'url') { - addTo(k, `// @downloadURL ${v}`, `// @updateURL ${v}`); - } else if (k === 'version') { - addTo(k, `// @${k} ${js_env ? +new Date() : v}`); - } else if (k === 'homepage') { - addTo(k, `// @namespace ${v}`, `// @homepageURL ${v}`); - } else if (k === 'bugs') { - addTo(k, `// @supportURL ${v}`); - } else if (k === 'license') { - addTo(k, `// @${k} ${v}`); - } else { - addTo(k, v); - } - } - for (const [k, v] of Object.entries(jsonData)) { - if (typeof v !== 'string') { - continue; - } - if (!filterData.includes(k)) { - continue; - } - if (dataMap.has(k)) { - continue; - } - if (k === 'license') { - addTo(k, `// @${k} ${v}`); - } else if (k === 'author') { - addTo(k, `// @${k} ${v}`); - } - } - if (userJS.metadata) { - addTo('metaData', compileMetadata()); - } - return [...dataMap.values()].flat().join('\n'); - }; - const userJSHeader = `// ==UserScript==\n${getData()}\n// ==/UserScript==`; - const headerFile = await canAccess(build.source.head); - const mainFile = await canAccess(build.source.body); - const nanoCFG = { - metadata: userJSHeader, - languageList: transformLanguages(), - code: mainFile - }; - if (build.source.extras) { - for (const [k, v] of Object.entries(build.source.extras)) { - const extraFile = await canAccess(v); - if (typeof extraFile === 'string') { - nanoCFG[k] = extraFile; - } - } - } - const outFile = `${build.paths[dp].dir}/${build.paths[dp].fileName}.user.js`; - await writeUserJS(outFile, nano(headerFile, nanoCFG)); - log('UserJS Build:', { - path: outFile, - time: toTime() - }); - if (!js_env) { - const outMeta = `${build.paths[dp].dir}/${build.paths[dp].fileName}.meta.js`; - await writeUserJS(outMeta, nano(metaStr, { metadata: userJSHeader })); - log('UserJS Metadata:', { - path: outMeta, - time: toTime() - }); - } - } catch (ex) { - err(ex); - } - }; - //#region Start Process - log(`Node ENV: ${env.JS_ENV}`); - - if (js_env) { - const wp = new Watchpack(); - let changed = new Set(); - wp.watch(build.watch.files, build.watch.dirs); - wp.on('change', (changedFile, mtime) => { - if (mtime === null) { - changed.delete(changedFile); - } else { - changed.add(changedFile); - } - }); - wp.on('aggregated', async () => { - // Filter out files that start with a dot from detected changes - // (as they are hidden files or temp files created by an editor). - const changes = Array.from(changed).filter((filePath) => { - return !path.basename(filePath).startsWith('.'); - }); - changed = new Set(); - - if (changes.length === 0) { - return; - } - - await buildUserJS(); - }); - return; - } - await buildUserJS(); - - process.exit(0); - //#endregion - } catch (ex) { - err(ex); - } -})(); +build(); diff --git a/tools/web-ext.js b/tools/web-ext.mjs similarity index 57% rename from tools/web-ext.js rename to tools/web-ext.mjs index a36f36e..8d38668 100644 --- a/tools/web-ext.js +++ b/tools/web-ext.mjs @@ -1,20 +1,21 @@ -module.exports = { +export default { + verbose: false, artifactsDir: './build', sourceDir: './build/firefox', - verbose: true, + ignoreFiles: [ + '*.web-extension-id', + 'web-ext-artifacts' + ], build: { asNeeded: false, overwriteDest: true, }, run: { - args: ['-devtools'], - firefox: 'firefox', - firefoxProfile: 'debug', + devtools: true, + firefox: 'deved', + firefoxProfile: 'UserJS', + target: ['firefox-desktop'], startUrl: ['https://www.google.com'], - watchFile: ['./build/firefox/*'] - }, - ignoreFiles: [ - '*.web-extension-id', - 'web-ext-artifacts' - ], + watchFile: ['./build/firefox'] + } }; diff --git a/tools/webpack.config.js b/tools/webpack.config.js index c88799a..b4e49a6 100644 --- a/tools/webpack.config.js +++ b/tools/webpack.config.js @@ -85,6 +85,11 @@ export default async (env, args) => { to: file(`${webExtDir}/img`), globOptions }, + { + from: file(`${webExtSrc}/webfonts`), + to: file(`${webExtDir}/webfonts`), + globOptions + }, // { // from: file(`${webExtSrc}/web_accessible_resources`), // to: file(`${webExtDir}/web_accessible_resources`), @@ -105,7 +110,6 @@ export default async (env, args) => { const Config = { context: file(webExtSrc), entry: { - // start: './js/start.js' mu: './js/mu.js' }, output: { diff --git a/utils/builder/README.md b/utils/builder/README.md new file mode 100644 index 0000000..b69b879 --- /dev/null +++ b/utils/builder/README.md @@ -0,0 +1 @@ +# UserScript Builder diff --git a/utils/builder/package.json b/utils/builder/package.json new file mode 100644 index 0000000..e5c81dc --- /dev/null +++ b/utils/builder/package.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@userjs/builder", + "description": "Create a UserScript", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "main": "./src/index.js", + "types": "./typings/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./typings/index.d.ts", + "default": "./src/index.js" + } + } + }, + "directories": { + "lib": "src" + }, + "files": [ + "src", + "typings" + ], + "dependencies": { + "@userjs/i18n": "workspace:^", + "minimist": "^1.2.8", + "sass-embedded": "^1.85.1", + "watchpack": "^2.4.2" + } +} diff --git a/utils/builder/src/index.d.ts b/utils/builder/src/index.d.ts new file mode 100644 index 0000000..1ca7e43 --- /dev/null +++ b/utils/builder/src/index.d.ts @@ -0,0 +1,56 @@ +import { PathLike } from 'node:fs'; + +/** + * Loads all the structures in the provided directory. + * + * @param dir - The directory to load the structures from + * @param recursive - Whether to recursively load the structures in the directory + */ +export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise; +export declare function build(): Promise; +export interface UserJS { + name: string; + description: string; + version: string; + license?: string; + bugs?: URL; + homepage?: URL; + icon?: PathLike; + downloadURL?: URL; + updateURL?: URL; + url_source?: URL; + url?: URL; + build: { + source: { + languageList: string; + [source: string]: PathLike; + }; + watch: { + files: string[]; + dirs: PathLike[]; + }; + paths: { + fileName: string; + dir: PathLike; + dev?: { + fileName?: string; + dir?: PathLike; + }; + }; + }; + metadata: { + compatible: string[]; + connect: string[]; + grant: string[]; + exclude: string[]; + include: string[]; + 'exclude-match': string[]; + match: string[]; + noframes: boolean; + resource: { + [name: string]: string; + }; + require: string[]; + 'run-at': 'document-start' | 'document-body' | 'document-end' | 'document-idle'; + }; +} diff --git a/utils/builder/src/index.js b/utils/builder/src/index.js new file mode 100644 index 0000000..07195d3 --- /dev/null +++ b/utils/builder/src/index.js @@ -0,0 +1,400 @@ +'use strict'; +import path from 'path'; +import { URL } from 'node:url'; +import fs from 'node:fs'; +import Watchpack from 'watchpack'; +import minimist from 'minimist'; +import { loadLanguages } from '@userjs/i18n'; +import { compile } from 'sass-embedded'; + +/** + * @typedef { import('../typings/index.d.ts').UserJS } CFG + */ + +const replaceTemplate = /\[\[(.*?)\]\]/g; +const metaStr = '[[metadata]]'; + +const log = (...msg) => { + console.log('[LOG]', ...msg); +}; +const err = (...msg) => { + console.error('[ERROR]', ...msg); +}; +/** + * Object is typeof `object` / JSON Object + * @template O + * @param { O } obj + * @returns { boolean } + */ +const isObj = (obj) => { + /** @type { string } */ + const s = Object.prototype.toString.call(obj); + return s.includes('Object'); +}; +/** + * Object is `null` or `undefined` + * @template O + * @param { O } obj + * @returns { boolean } + */ +const isNull = (obj) => { + return Object.is(obj, null) || Object.is(obj, undefined); +}; +/** + * Object is Blank + * @template O + * @param { O } obj + * @returns { boolean } + */ +const isBlank = (obj) => { + return ( + (typeof obj === 'string' && Object.is(obj.trim(), '')) || + ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) || + (Array.isArray(obj) && Object.is(obj.length, 0)) || + (isObj(obj) && Object.is(Object.keys(obj).length, 0)) + ); +}; +/** + * Object is Empty + * @template O + * @param { O } obj + * @returns { boolean } + */ +const isEmpty = (obj) => { + return isNull(obj) || isBlank(obj); +}; +/** + * @template { string | { msg: string; } } T + * @template D + * @param { T } template + * @param { D } data + */ +const nano = (template, data) => { + if (typeof template === 'string') { + return template.replace(replaceTemplate, (_match, key) => { + const keys = key.split('.'); + let v = data[keys.shift()]; + for (const i in keys.length) v = v[keys[i]]; + return isEmpty(v) ? '' : v; + }); + } + return ''; +}; +/** + * @param {import('node:fs').PathLike} filePath + * @param {string} encoding + */ +const canAccess = async (filePath, encoding = 'utf-8') => { + const testAccess = await fs.promises.access( + filePath, + fs.promises.constants.R_OK | fs.promises.constants.W_OK + ); + if (isNull(testAccess)) { + const data = await fs.promises.readFile(filePath, encoding); + return data.toString(encoding); + } + return { + msg: `Cannot access provided filePath: ${filePath}` + }; +}; +/** + * @param {import('node:fs').PathLike} filePath + * @param {string} encoding + */ +const fileToJSON = async (filePath, encoding = 'utf-8') => { + const testAccess = await canAccess(filePath, encoding); + if (isObj(testAccess)) { + throw new Error(testAccess.msg); + } + return JSON.parse(testAccess); +}; +/** + * @param {import('node:fs').PathLike} destinationFilePath + * @param data + */ +const writeUserJS = async (destinationFilePath, data) => { + return await fs.promises.writeFile(destinationFilePath, data); +}; +const toTime = () => { + return new Intl.DateTimeFormat('default', { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + fractionalSecondDigits: 3 + }).format(new Date()); +}; +/** + * @type { Map } + */ +const dataMap = new Map(); +const compareArrays = (a, b) => + a.length === b.length && a.every((element, index) => element === b[index]); +/** + * @template {string} K + * @template V + * @param {K} key + * @param {...V} values + */ +const addTo = (key, ...values) => { + if (values.length === 0) { + return ''; + } + if (dataMap.has(key)) { + if (compareArrays(dataMap.get(key), values)) { + return dataMap.get(key); + } + } + dataMap.set(key, values); + return dataMap.get(key); +}; + +async function build() { + try { + const getEnv = () => { + const argv = minimist(process.argv.slice(2)); + if (argv.JS_ROOT && argv.JS_ENV) { + return { + JS_ROOT: argv.JS_ROOT, + JS_ENV: argv.JS_ENV, + JS_i18n: `${argv.JS_ROOT}src/_locales` + }; + } + return process.env; + }; + const { JS_ENV, JS_i18n } = getEnv(); + const jsonRecords = await Promise.all([ + // fileToJSON('./package.json').then(({ userJS }) => { + // return userJS ?? {}; + // }), + fileToJSON('./UserJS.json') + .then((userJS) => { + return userJS ?? {}; + }) + .catch(() => { + return {}; + }) + ]); + if (isEmpty(jsonRecords)) + throw new Error('"UserJS.json" not found + no "userJS" key in package.json'); + /** + * @type { CFG } + */ + const userJS = {}; + for (const r of jsonRecords) Object.assign(userJS, r); + const isDev = isEmpty(JS_ENV) || JS_ENV === 'development'; + const buildUserJS = async () => { + try { + const i18n = userJS.build.paths.i18n; + const i18nList = await loadLanguages(new URL(i18n.dir ?? JS_i18n, import.meta.url)); + const compileLanguage = (type = 'userjs_name') => { + const resp = []; + for (const [key, obj] of i18nList.entries()) { + const value = obj[type]; + if (!value || isEmpty(value) || key.startsWith(i18n.default)) { + continue; + } + const t = type.toLowerCase().replace('userjs_', ''); + if (type === 'userjs_name') { + resp.push( + `// @${t}:${key.replace('_', '-')} ${isDev ? '[Dev] ' : ''}${value}` + ); + } else { + resp.push(`// @${t}:${key.replace('_', '-')} ${value}`); + } + } + return resp; + }; + const compileData = () => { + if (isDev && !isEmpty(dataMap)) { + addTo('version', `// @version ${+new Date()}`); + return `// ==UserScript==\n${[...dataMap.values()].flat().join('\n')}\n// ==/UserScript==`; + } + for (const [k, v] of Object.entries(userJS)) { + if (k === 'metadata') { + const metaData = []; + for (const [key, value] of Object.entries(v)) { + if (Array.isArray(value)) { + for (const v of value) { + metaData.push(`// @${key} ${v}`); + } + } else if (isObj(value)) { + for (const [k, v] of Object.entries(value)) { + metaData.push(`// @${key} ${k} ${v}`); + } + } else if (typeof value === 'boolean') { + if (value === true) { + metaData.push(`// @${key}`); + } + } else { + metaData.push(`// @${key} ${value}`); + } + } + addTo('metaData', metaData.join('\n')); + } + if (typeof v !== 'string') { + continue; + } + if (k === 'name') { + addTo( + k, + `// @${k} ${isDev ? '[Dev] ' : ''}${v}`, + ...compileLanguage('userjs_name') + ); + } else if (k === 'description') { + addTo(k, `// @${k} ${v}`, ...compileLanguage('userjs_description')); + } else if (k === 'author') { + addTo(k, `// @${k} ${v}`); + } else if (k === 'icon') { + if (v.startsWith('.') || v.startsWith('/')) { + const buff = new Buffer.from(fs.readFileSync(v)); + const base64data = buff.toString('base64'); + if (v.endsWith('.png')) { + addTo(k, `// @${k} data:image/png;base64,${base64data}`); + } else if (v.endsWith('.svg')) { + addTo(k, `// @${k} data:image/svg+xml;base64,${base64data}`); + } + } else { + addTo(k, `// @${k} ${v}`); + } + } else if (k === 'downloadURL') { + addTo(k, `// @downloadURL ${v}`); + } else if (k === 'updateURL') { + addTo(k, `// @updateURL ${v}`); + } else if (k === 'url_source') { + addTo(k, `// @url_source ${v}`); + } else if (k === 'url') { + addTo(k, `// @downloadURL ${v}`, `// @updateURL ${v}`); + } else if (k === 'version') { + addTo(k, `// @${k} ${isDev ? +new Date() : v}`); + } else if (k === 'homepage') { + addTo(k, `// @namespace ${v}`, `// @homepageURL ${v}`); + } else if (k === 'bugs') { + addTo(k, `// @supportURL ${v}`); + } else if (k === 'license') { + addTo(k, `// @${k} ${v}`); + } else { + addTo(k, v); + } + } + return `// ==UserScript==\n${[...dataMap.values()].flat().join('\n')}\n// ==/UserScript==`; + }; + const cfg = { + nano: { + languageList: {}, + metadata: compileData() + }, + path: { + fileName: 'main-userjs', + dir: './dist' + }, + file: '', + meta: '', + metaPath: '' + }; + for (const [k, v] of Object.entries(userJS.build.source)) { + if (/\.s[ac]ss$/i.test(v)) { + cfg.nano[k] = compile(v, { + sourceMap: false, + style: 'expanded' + }).css; + continue; + } + const f = await canAccess(v); + if (typeof f !== 'string') continue; + if (k === 'metadata') { + cfg.meta = f; + } else { + cfg.nano[k] = f; + } + } + if (!isEmpty(i18nList)) { + for (const [k, obj] of i18nList.entries()) { + const o = {}; + for (const [key, value] of Object.entries(obj)) { + if (isEmpty(value)) { + continue; + } + if (/^ext[A-Z_]|^userjs_(name|description)/.test(key)) { + continue; + } + o[key] = value; + } + cfg.nano.languageList[k] = o; + } + } + for (const [k, v] of Object.entries(userJS.build.paths)) { + if (isEmpty(v)) continue; + if (isDev && /dev/i.test(k)) { + for (const [key, value] of Object.entries(v)) { + cfg.path[key] = value; + } + } else { + cfg.path[k] = v; + } + } + cfg.file = `${cfg.path.dir}/${cfg.path.fileName}.user.js`; + cfg.metaPath = `${cfg.path.dir}/${cfg.path.fileName}.meta.js`; + cfg.nano.languageList = JSON.stringify(cfg.nano.languageList, null, ' '); + + await writeUserJS(cfg.file, nano(cfg.meta, cfg.nano)); + log('UserJS File:', { + path: cfg.file, + time: toTime() + }); + if (!isDev) { + await writeUserJS( + cfg.metaPath, + nano(metaStr, { + metadata: cfg.nano.metadata + }) + ); + log('UserJS Metadata:', { + path: cfg.metaPath, + time: toTime() + }); + } + } catch (ex) { + err(ex); + } + }; + log(`Node ENV: ${JS_ENV}`); + await buildUserJS(); + if (isDev) { + const wp = new Watchpack(); + let changed = new Set(); + if (isEmpty(userJS.build.watch.files)) { + wp.watch(userJS.build.watch.dirs); + } else { + wp.watch(userJS.build.watch.files, userJS.build.watch.dirs); + } + wp.on('change', (changedFile, mtime) => { + if (mtime === null) { + changed.delete(changedFile); + } else { + changed.add(changedFile); + } + }); + wp.on('aggregated', async () => { + // Filter out files that start with a dot from detected changes + // (as they are hidden files or temp files created by an editor). + const changes = Array.from(changed).filter((filePath) => { + return !path.basename(filePath).startsWith('.'); + }); + changed = new Set(); + + if (changes.length === 0) { + return; + } + + await buildUserJS(); + }); + } else { + process.exit(0); + } + } catch (ex) { + err(ex); + } +} + +export { build }; +export default build; diff --git a/utils/builder/typings/index.d.ts b/utils/builder/typings/index.d.ts new file mode 100644 index 0000000..b60fd30 --- /dev/null +++ b/utils/builder/typings/index.d.ts @@ -0,0 +1,54 @@ +import { PathLike } from 'node:fs'; + +export interface UserJS { + name: string; + description: string; + version: string; + license?: string; + bugs?: URL; + homepage?: URL; + icon?: PathLike; + downloadURL?: URL; + updateURL?: URL; + url_source?: URL; + url?: URL; + build: { + source: { + languageList: string; + [source: string]: PathLike; + }; + watch: { + files: string[]; + dirs: PathLike[]; + }; + paths: { + fileName: string; + dir: PathLike; + i18n?: { + default: string; + dir: PathLike; + }; + dev?: { + fileName?: string; + dir?: PathLike; + }; + }; + }; + metadata: { + compatible: string[]; + connect: string[]; + grant: string[]; + exclude: string[]; + include: string[]; + 'exclude-match': string[]; + match: string[]; + noframes: boolean; + resource: { + [name: string]: string; + }; + require: string[]; + 'run-at': 'document-start' | 'document-body' | 'document-end' | 'document-idle'; + }; +} + +export declare function build(): Promise; diff --git a/utils/i18n/README.md b/utils/i18n/README.md new file mode 100644 index 0000000..90c84e3 --- /dev/null +++ b/utils/i18n/README.md @@ -0,0 +1 @@ +# UserScript i18n diff --git a/utils/i18n/package.json b/utils/i18n/package.json new file mode 100644 index 0000000..65555d5 --- /dev/null +++ b/utils/i18n/package.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@userjs/i18n", + "description": "Loads languages", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "main": "./src/index.js", + "types": "./typings/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./typings/index.d.ts", + "default": "./src/index.js" + } + } + }, + "directories": { + "lib": "src" + }, + "files": [ + "src", + "typings" + ] +} diff --git a/utils/i18n/src/index.js b/utils/i18n/src/index.js new file mode 100644 index 0000000..ba664f6 --- /dev/null +++ b/utils/i18n/src/index.js @@ -0,0 +1,54 @@ +'use strict'; +import { readdir, readFile, stat } from 'node:fs/promises'; +import { URL, fileURLToPath } from 'node:url'; + +/** + * + * @type { import('../typings/index.d.ts').loadLanguages } + */ +async function loadLanguages(dir, recursive = true, mapper = new Map()) { + try { + // Get the stats of the directory + const statDir = await stat(dir); + + // If the provided directory path is not a directory, throw an error + if (!statDir.isDirectory()) { + throw new Error(`The directory '${dir}' is not a directory.`); + } + + // Get all the files in the directory + const files = await readdir(dir); + + // Loop through all the files in the directory + for (const file of files) { + // Get the stats of the file + const statFile = await stat(new URL(`${dir}/${file}`)); + + // If the file is a directory and recursive is true, recursively load the structures in the directory + if (statFile.isDirectory() && recursive) { + await loadLanguages(new URL(`${dir}/${file}`), recursive, mapper); + continue; + } + + if (!file.endsWith('.json')) { + continue; + } + const filePath = fileURLToPath(`${dir}/${file}`); + const reg = /_locales\\(.*?)\\messages\.json/g.exec(filePath); + if (reg) { + const structure = await readFile(filePath, 'utf-8'); + const obj = {}; + for (const [k, v] of Object.entries(JSON.parse(structure.toString('utf-8')))) + obj[k] = v.message ?? ''; + mapper.set(reg[1], obj); + } + } + } catch (ex) { + console.error(ex); + } + + return mapper; +} + +export { loadLanguages }; +export default loadLanguages; diff --git a/utils/i18n/typings/index.d.ts b/utils/i18n/typings/index.d.ts new file mode 100644 index 0000000..046f1f2 --- /dev/null +++ b/utils/i18n/typings/index.d.ts @@ -0,0 +1,9 @@ +import { PathLike } from 'node:fs'; + +/** + * Loads all the structures in the provided directory. + * + * @param dir - The directory to load the structures from + */ +export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise>; +// export declare function loadLanguages(dir: PathLike, recursive?: boolean): Promise; diff --git a/utils/user.js/README.md b/utils/user.js/README.md new file mode 100644 index 0000000..b69b879 --- /dev/null +++ b/utils/user.js/README.md @@ -0,0 +1 @@ +# UserScript Builder diff --git a/utils/user.js/package.json b/utils/user.js/package.json new file mode 100644 index 0000000..47fb61a --- /dev/null +++ b/utils/user.js/package.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "user.js", + "description": "Create a UserScript", + "version": "1.0.0", + "license": "MIT", + "type": "module", + "main": "./src/index.js", + "types": "./typings/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./typings/index.d.ts", + "default": "./src/index.js" + } + } + }, + "directories": { + "lib": "src" + }, + "files": [ + "src", + "typings" + ], + "dependencies": { + "@userjs/builder": "workspace:^", + "@userjs/i18n": "workspace:^" + } +} diff --git a/utils/user.js/src/index.js b/utils/user.js/src/index.js new file mode 100644 index 0000000..f8f2a95 --- /dev/null +++ b/utils/user.js/src/index.js @@ -0,0 +1,6 @@ +'use strict'; +import { build } from '@userjs/builder'; +import { loadLanguages } from '@userjs/i18n'; + +export { build, loadLanguages }; +export default build; diff --git a/utils/user.js/typings/index.d.ts b/utils/user.js/typings/index.d.ts new file mode 100644 index 0000000..f8f2a95 --- /dev/null +++ b/utils/user.js/typings/index.d.ts @@ -0,0 +1,6 @@ +'use strict'; +import { build } from '@userjs/builder'; +import { loadLanguages } from '@userjs/i18n'; + +export { build, loadLanguages }; +export default build; diff --git a/wiki/Build.md b/wiki/Build.md new file mode 100644 index 0000000..4d1ef16 --- /dev/null +++ b/wiki/Build.md @@ -0,0 +1,84 @@ +# Build + +Install [Node.js](https://nodejs.org/) and [pnpm](https://pnpm.io/) - *the version of Node.js should match or be greater than the `"node"` key in `package.json`.* + +```sh +# Install dependencies +$ pnpm i +``` + +**UserScript:** + +Before you begin, rename `.env.example` to `.env` + +**Recommened Testing Environments:** + +- **Desktop:** any browser with [ViolentMonkey](https://violentmonkey.github.io/), see [How to edit scripts with your favorite editor?](https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/) +- **Mobile:** + - Desktop: use "Responsive Design Mode (Ctrl+Shift+M)" on [FireFox Developer Edition](https://www.mozilla.org/firefox/developer/) or [FireFox](https://www.mozilla.org/firefox/) + - Android: any browser with UserScript support or support for installing a UserScript manager, I use [Cromite](https://github.com/uazo/cromite) + - IOS: any browser with UserScript support or support for installing a UserScript manager + +```sh +# Watch and build to local HTTP server +$ pnpm run dev:UserJS + +# Build UserScript to "./dist" +$ pnpm run pub:UserJS +``` + +--- + +**WebExtension:** + +```sh +# Build WebExtension (developer version) for "Chromium" to "./build/chrome" +$ pnpm run dev:Cr +# Build WebExtension (developer version) for "Firefox" to "./build/firefox" +$ pnpm run dev:FF +# Build WebExtension (public version) for "Chromium" to "./build/chrome" +$ pnpm run webpack:Cr +# Build WebExtension (public version) for "Firefox" to "./build/firefox" +$ pnpm run webpack:FF + +# [ Testing ] +# Browsers can be launched via "Run and Debug" in VSCode. +# Edit runtimeExecutable in ".vscode/launch.json" to your Chrome executable. +# Recommended to create an additional profile "about:profiles" for Firefox. +``` + +## Contribution Rules + +**General:** + +Follow [Semantic Versioning](https://semver.org/) standards, if your unsure you don't need to update the version + +--- + +**Translations:** + +Translations follow [Web Extension Internationalization](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/Internationalization) standards + +--- + +**UserScript:** + +Do not edit the UserScript found in the `./dist` directory, the file is auto generated from `dev:UserJS` command. Instead edit it from `./src/UserJS` + +## Contribution Guide + +Fork this repo, make your changes, and open a [pull request](https://github.com/magicoflolis/Userscript-Plus/pulls) + +**Translations:** + +Translations are located in `./src/_locales` + +For versioning translations are a PATCH + +**Adding:** + +Recommend using `./src/_locales/en/messages.json` as a template + +**Updating:** + +Update any key from `messages.json` file diff --git a/wiki/README.md b/wiki/README.md index c4a70f4..daf8727 100644 --- a/wiki/README.md +++ b/wiki/README.md @@ -1,6 +1,27 @@ -# Wiki Page (Under Construction) +# Wiki Page -## New Tab +- [Wiki Page](#wiki-page) + - [GitHub Detection](#github-detection) + - [Shortcuts](#shortcuts) + +## GitHub Detection + +For this Web Extension / UserScript to detect your UserScript or UserStyle: + +- Your repository must have the following: + - UserScripts **must** be in `.user.js` format + - UserStyles **must** be in `.user.css` format + - In the Topics section of your repository: + - `domain` or `hostname`: + - example: you create a userscript for `google.com` then put `google` in the Topics section + - if you match all sites: `*` or `http*://*` then put `all-sites` in the Topics section + - put `userscript` or `userstyle` in the Topics section + +_if you need to create a build environment, you can use my workspace and tools as a template._ + +## Shortcuts + +**New Tab:** Syntax: `*` - Shortcut for userscipts that apply to `All sites` @@ -25,7 +46,7 @@ mujs:settings --- -## Search Bar +**Search Bar:** By default it will search by name and description, you can refine your searches using the examples listed below.