diff --git a/.editorconfig b/.editorconfig index 459f3d62..60bfc63b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,11 @@ +# https://editorconfig.org/ +# https://prettier.io/docs/en/configuration#editorconfig + root = true [*] charset = utf-8 end_of_line = lf -indent_size = 4 -indent_style = space +indent_style = tab insert_final_newline = true max_line_length = 80 -trim_trailing_whitespace = true diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index d51e455f..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "env": { - "browser": true, - "node": true, - "es2021": true, - "es2022": true, - "webextensions": true - }, - "extends": ["eslint:recommended", "airbnb-base"], - "globals": { - "_browser": "writable", - "browser": "readonly" - }, - "ignorePatterns": [ - "/etc", - "build", - "dist", - "node_modules" - ], - "overrides": [ - { - "files": ["*.svelte"], - "processor": "svelte3/svelte3" - } - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "plugins": ["svelte3"], - "rules": { - "array-callback-return": "off", - "arrow-body-style": "off", - "arrow-parens": ["error", "as-needed"], - "comma-dangle": ["error", "never"], - "consistent-return": "off", - "default-case": "off", - "eqeqeq": ["error", "smart"], - "global-require": "off", - "guard-for-in": "warn", - "import/extensions": "off", - "import/first": "off", - "import/no-mutable-exports": "off", - "import/no-extraneous-dependencies": ["error", { - "devDependencies": true - }], - "import/prefer-default-export": "off", - "import/no-unresolved": "off", - "indent": ["error", 4, {"SwitchCase": 1}], - "max-len": "off", - "no-alert":"off", - "no-await-in-loop":"warn", - "no-confusing-arrow": ["warn", { - "allowParens": true, - "onlyOneSimpleParam": true - }], - "no-console": ["warn", {"allow": ["info", "warn", "error"]}], - "no-continue": "off", - "no-else-return": ["error", {"allowElseIf": true}], - "no-nested-ternary": "off", - "no-param-reassign": ["warn", { "props": false }], - "no-plusplus": "off", - "no-restricted-syntax": "off", - "no-return-assign": "off", - "no-underscore-dangle":"off", - "no-unused-expressions": "off", - "no-use-before-define": ["error", { - "functions": false, - "classes": true, - "variables": true, - "allowNamedExports": false - }], - "object-curly-newline": ["error", {"consistent": true}], - "object-curly-spacing": ["error", "never"], - "one-var": "off", - "one-var-declaration-per-line": "off", - "prefer-destructuring": "off", - "prefer-object-spread": "warn", - "quotes": ["error", "double"], - "import/no-namespace": "warn" - } -} diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..cc156834 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,9 @@ +# https://git-scm.com/docs/git-blame +# https://github.blog/changelog/2022-03-24-ignore-commits-in-the-blame-view-beta/ +# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view + +# Prettier 3.0.3 +acc35fbec38d72968b735826c6807793a2054aed + +# Xcode swift indent replace spaces to tabs +962c4d81879e85362a5f878889d3aa869f4e6961 diff --git a/.gitattributes b/.gitattributes index 5c4dc3ba..b4a7b25c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,7 @@ -pnpm-lock.yaml linguist-generated -xcode/iOS-App/Base.lproj/Main.html linguist-generated \ No newline at end of file +# https://git-scm.com/docs/gitattributes +# https://git-scm.com/book/en/Customizing-Git-Git-Attributes +* text=auto + +# https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github +# https://github.com/github-linguist/linguist/blob/master/docs/overrides.md +public linguist-generated \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..cab762b7 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ +### Please note: the project is currently temporarily suspended from receiving direct code contributions. + +Please do not submit any new pull requests, they will be close immediately. + +If you have any ideas and suggestions, please submit them to us through [`Issues`](https://github.com/quoid/userscripts/issues) or [`Discussions`](https://github.com/quoid/userscripts/discussions). + +We apologize for the inconvenience and thank you for your understanding. diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml new file mode 100644 index 00000000..f9b2e529 --- /dev/null +++ b/.github/workflows/deployment.yaml @@ -0,0 +1,70 @@ +# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions + +name: Userscripts deployment +on: + release: + types: [published] # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release +jobs: + Deployment: + strategy: + matrix: # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + platform: [mac, ios] + runs-on: macos-26 # https://github.com/actions/runner-images + steps: + - uses: actions/checkout@v4 # https://github.com/actions/checkout + with: + fetch-depth: 0 + fetch-tags: true # https://github.com/actions/checkout/issues/1781 + - uses: actions/setup-node@v4 # https://github.com/actions/setup-node + with: + node-version: latest + - id: semver # Parse semver from github ref + uses: userscriptsup/actions/semver-parser@main + - name: Verify release type + run: | + if ${{ github.event.release.prerelease }}; then + ${{ steps.semver.outputs.beta }} || exit 11 + else + ${{ steps.semver.outputs.alpha }} && exit 12 + ${{ steps.semver.outputs.beta }} && exit 13 + [ -n "${{ steps.semver.outputs.prerelease }}" ] && exit 14 + exit 0 + fi + - run: npm ci + - run: npm run lint:js + - run: npm run lint:css + - run: BETA=1 npm run build:${{ matrix.platform }}-safari-15 + if: ${{ github.event.release.prerelease }} + - run: npm run build:${{ matrix.platform }}-safari-15 + if: ${{ ! github.event.release.prerelease }} + # - name: Set xcode version + # run: sudo xcode-select -s "/Applications/Xcode_26.1.1.app" # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md#xcode + # - run: xcodebuild -downloadPlatform iOS # Temporary fix for Xcode_26_beta + # if: matrix.platform == 'ios' + - name: Run fastlane + id: fastlane + working-directory: ./fastlane + env: + FASTLANE_OPT_OUT_USAGE: "YES" + MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} + MATCH_GIT_PRIVATE_KEY: "fastlane/${{ secrets.GIT_PRIVATE_KEY_FILE }}" + GIT_PRIVATE_KEY_FILE: ${{ secrets.GIT_PRIVATE_KEY_FILE }} + GIT_PRIVATE_KEY_BASE64: ${{ secrets.GIT_PRIVATE_KEY_BASE64 }} # https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#storing-base64-binary-blobs-as-secrets + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + GYM_INSTALLER_CERT_NAME: ${{ secrets.GYM_INSTALLER_CERT_NAME }} # required for mac (unable to auto-detect) + APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} + APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} + MARKETING_VERSION: ${{ steps.semver.outputs.core }} + IS_PRERELEASE: ${{ github.event.release.prerelease }} + run: | + echo $GIT_PRIVATE_KEY_BASE64 | base64 --decode > $GIT_PRIVATE_KEY_FILE + chmod 600 $GIT_PRIVATE_KEY_FILE + bundle install + $IS_PRERELEASE && bundle exec fastlane ${{ matrix.platform }} beta + $IS_PRERELEASE || bundle exec fastlane ${{ matrix.platform }} release + - name: Artifact dSYM + uses: actions/upload-artifact@v4 # https://github.com/actions/upload-artifact + with: + name: Userscripts_${{ matrix.platform }}_${{ steps.fastlane.outputs.ver || github.ref_name }}.app.dSYM + path: build/*.dSYM.zip diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 41bd0485..45e462ed 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,10 +5,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install modules - run: npm install - - name: Run ESLint - run: npm run lint-js - - name: Run Stylelint - run: npm run lint-css + - uses: actions/checkout@v2 + - name: Install modules + run: npm install + - name: Run ESLint + run: npm run lint:js + - name: Run Stylelint + run: npm run lint:css diff --git a/.gitignore b/.gitignore index 73b92ed1..6e9c9304 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,19 @@ +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Temp/Dist/Built +temp +dist +dist-ssr +build +built + # Logs logs *.log @@ -7,11 +23,11 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* +# npm node_modules -temp -dist -dist-ssr -*.local +#package-lock.json +yarn.lock +pnpm-lock.yaml # Editor directories and files .vscode/* @@ -25,20 +41,18 @@ dist-ssr *.sln *.sw? -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - # xcode xcuserdata/ *.xcuserstate *.dev.xcconfig XCBuildData -xcode/**/build/ -xcode/**/dist/ -# xcode/Userscripts-iOS/Base.lproj/Main.html \ No newline at end of file +xcode/Ext-Safari/Resources/ + +# directories and files +local +*.local +/scripts/*.local.js + +# fastlane +/fastlane/report.xml +/fastlane/**/*.local.rb \ No newline at end of file diff --git a/.postcssrc.json b/.postcssrc.json index cc24b151..5c893e80 100644 --- a/.postcssrc.json +++ b/.postcssrc.json @@ -1,5 +1,5 @@ { - "plugins": { - "autoprefixer": {"overrideBrowserslist": ["safari >= 13"]} - } -} \ No newline at end of file + "plugins": { + "autoprefixer": { "overrideBrowserslist": ["safari >= 15"] } + } +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..5c1a0da1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,26 @@ +# env +.env +.env.* +!.env.example +.DS_Store + +# root +/package +/public/**/vendor +/etc + +# dir +node_modules +xcode +build +built +dist +temp + +# files +*.svg + +# Ignore files for PNPM, NPM and YARN +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..a651d67a --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "plugins": ["prettier-plugin-svelte"], + "useTabs": true +} diff --git a/.stylelintrc.json b/.stylelintrc.json index ea60b077..707413f5 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,29 +1,25 @@ { - "extends": [ - "stylelint-config-recommended", - "stylelint-config-standard", - "stylelint-config-html/svelte" - ], - "ignoreFiles": [ - "**/build/**", - "**/dist/**", - "**/etc/**", - "**/node_modules/**", - "**/reset.css" - ], - "rules": { - "alpha-value-notation": "number", - "comment-empty-line-before": ["always",{ - "ignore": ["after-comment", "stylelint-commands"] - }], - "custom-property-empty-line-before": null, - "indentation": 4, - "max-empty-lines": 1, - "no-descending-specificity": null, - "property-no-vendor-prefix": null, - "selector-class-pattern": null, - "selector-pseudo-class-no-unknown": [true, { - "ignorePseudoClasses": ["global"] - }] - } + "extends": ["stylelint-config-standard", "stylelint-config-html"], + "ignoreFiles": [ + "**/etc/**", + "**/dist/**", + "**/build/**", + "**/node_modules/**", + "**/reset.css", + "public/**", + "xcode/**" + ], + "rules": { + "alpha-value-notation": "number", + "custom-property-empty-line-before": null, + "no-descending-specificity": null, + "property-no-vendor-prefix": null, + "selector-class-pattern": null, + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["global"] + } + ] + } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 728e8859..02ec4445 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ +// https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions { - // See https://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "svelte.svelte-vscode", - "dbaeumer.vscode-eslint", - "stylelint.vscode-stylelint" - ] -} \ No newline at end of file + "recommendations": [ + "svelte.svelte-vscode", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "stylelint.vscode-stylelint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index ffec34dc..892bfd4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,24 +1,21 @@ +// https://code.visualstudio.com/docs/getstarted/settings#_workspace-settings { - "editor.formatOnSave": false, - "files.associations": { - ".htmlhintrc": "json" - }, - "eslint.validate": [ - "svelte" - ], - "stylelint.validate": [ - "css", - "postcss", - "svelte" - ], - "svelte.plugin.svelte.compilerWarnings": { - "a11y-missing-attribute": "error" - }, - "svelte.plugin.svelte.rename.enable": false, - "[svelte]": { - "editor.defaultFormatter": "svelte.svelte-vscode" - }, - "[css]": { - "editor.defaultFormatter": "stylelint.vscode-stylelint" - } + "files.associations": { + ".git-blame-ignore-revs": "ignore" + }, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[svelte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + // https://github.com/microsoft/vscode-eslint#settings-options + "eslint.validate": ["javascript", "svelte"], + // https://github.com/stylelint/vscode-stylelint#stylelintvalidate + "stylelint.validate": ["css", "postcss", "svelte"] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d53a574f..c967bcab 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,63 +1,142 @@ +// https://code.visualstudio.com/docs/editor/tasks { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "pnpm: install", - "detail": "Install dev dependencies", - "type": "shell", - "command": "pnpm install", - "group": "build", - "options": { - "cwd": "./" - } - }, - { - "label": "pnpm: dev", - "detail": "Run dev server", - "type": "shell", - "command": "pnpm dev", - "group": "build", - "options": { - "cwd": "./" - } - }, - { - "label": "pnpm: build", - "detail": "Build to xcode dist path", - "type": "shell", - "command": "pnpm build", - "group": "build", - "options": { - "cwd": "./" - } - }, - { - "label": "xcode: build", - "detail": "xcodebuild", - "type": "shell", - "command": "xcodebuild -scheme Userscripts build", - "group": "build", - "options": { - "cwd": "./xcode/" - } - }, - { - "label": "xcode: rebuild", - "detail": "pnpm build + xcodebuild", - "presentation": { - "reveal": "silent" - }, - "dependsOrder": "sequence", - "dependsOn": [ - "pnpm: build", - "xcode: build" - ], - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "label": "npm: install", + "detail": "Install dev dependencies", + "type": "shell", + "command": "npm install", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "npm: dev", + "detail": "Run dev server", + "type": "shell", + "command": "npm run dev", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "npm: dev ext-safari-mac", + "detail": "Run dev server and build dev resource to xcode dist", + "type": "shell", + "command": "npm run dev:ext-safari-mac", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "npm: dev ext-safari-ios", + "detail": "Run dev server and build dev resource to xcode dist", + "type": "shell", + "command": "npm run dev:ext-safari-ios", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "npm: build mac", + "detail": "Build to xcode dist path", + "type": "shell", + "command": "npm run build:mac", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "npm: build ios", + "detail": "Build to xcode dist path", + "type": "shell", + "command": "npm run build:ios", + "group": "build", + "options": { + "cwd": "./" + } + }, + { + "label": "xcodebuild: mac-debug", + "detail": "xcodebuild mac debug", + "type": "shell", + "command": "xcodebuild -scheme Mac -configuration Debug", + "group": "build", + "options": { + "cwd": "./xcode/" + } + }, + { + "label": "xcodebuild: mac-vite", + "detail": "xcodebuild mac vite", + "type": "shell", + "command": "xcodebuild -scheme Mac-Vite-Dev -configuration Vite", + "group": "build", + "options": { + "cwd": "./xcode/" + } + }, + { + "label": "dev: ext-safari-mac", + "detail": "npm: dev ext-safari-mac + xcodebuild: mac-vite", + "presentation": { + "reveal": "silent" + }, + "dependsOrder": "sequence", + "dependsOn": ["npm: dev ext-safari-mac", "xcodebuild: mac-vite"], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "build: mac debug", + "detail": "npm: build mac + xcodebuild: mac-debug", + "presentation": { + "reveal": "silent" + }, + "dependsOrder": "sequence", + "dependsOn": ["npm: build mac", "xcodebuild: mac-debug"], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "lint: js", + "detail": "lint js with eslint", + "type": "shell", + "command": "npm run lint:js", + "group": "test", + "options": { + "cwd": "./" + } + }, + { + "label": "lint: css", + "detail": "lint css with stylelint", + "type": "shell", + "command": "npm run lint:css", + "group": "test", + "options": { + "cwd": "./" + } + }, + { + "label": "prettier", + "detail": "applying prettier formatting for all supported files", + "type": "shell", + "command": "npm run prettier", + "options": { + "cwd": "./" + }, + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index 1c9e2470..09cdc78a 100644 --- a/README.md +++ b/README.md @@ -5,25 +5,26 @@ An open-source userscript editor for Safari ![Userscripts Safari](/etc/screenshot.png) ## Table of Contents -* [Installation](#installation) -* [Usage](#usage) -* [UI Overview](#ui-overview) - * [Browser Page](#browser-page) - * [Settings Modal](#settings-modal) - * [Popover](#popover) -* [Metadata](#metadata) -* [API](#api) -* [Scripts Directory / Save Location](#scripts-directory) -* [Getting Help](#getting-help) -* [FAQs](#faqs) -* [Contributing](#contributing) -* [Support](#support) -* [Privacy Policy](#privacy-policy) -* [License](#license) + +- [Installation](#installation) +- [Usage](#usage) +- [UI Overview](#ui-overview) + - [Browser Page](#browser-page) + - [Settings Modal](#settings-modal) + - [Popover](#popover) +- [Metadata](#metadata) +- [API](#api) +- [Scripts Directory / Save Location](#scripts-directory) +- [Getting Help](#getting-help) +- [FAQs](#faqs) +- [Contributing](#contributing) +- [Support](#support) +- [Privacy Policy](#privacy-policy) +- [License](#license) ## Installation -Userscripts is available for iOS (+ipadOS) and macOS. For all versions, installation is done through [Apple's App Store](https://itunes.apple.com/us/app/userscripts/id1463298887). On macOS, versions prior to `4.x` were made available to download and install directly from the repository, but due to [changes in the way Apple allows developers to distribute apps built with the WebExtension API](https://github.com/quoid/userscripts/issues/154), that is no longer an option. +Userscripts is available for iOS (iPadOS) and macOS. For all versions, installation is done through [Apple's App Store](https://itunes.apple.com/us/app/userscripts/id1463298887). On macOS, versions prior to `4.x` were made available to download and install directly from the repository, but due to [changes in the way Apple allows developers to distribute apps built with the WebExtension API](https://github.com/quoid/userscripts/issues/154), that is no longer an option. To run Userscripts on iOS you should be on iOS 15.1 or higher. @@ -39,22 +40,42 @@ It's recommend to read this documentation and, if you have time, watch the follo Once the app is downloaded and installed the following steps should be taken: -**iOS** +### **iOS (iPadOS)** + +After installing the iOS App, you need two main steps to make the extension work: + +- **Open the App and set a directory** (For saving and loading userscripts) + - After Userscripts for ios v1.5.0, a local default directory will be set automatically + - In earlier versions please click the `Set Userscripts Directory` button and select the directory +- **Enable the extension in Safari** (And grant permissions) + + - Manage extensions from Settings App (Settings > Safari > Extensions) or Safari App (`AA` button on iPhone, `extension-icon` on iPad, also where you open the extension's `popup` interface) + - For optimal experience it's recommended that you `Always Allow` Userscripts for `All Websites` + +> [!NOTE] +> +> The iOS App cannot detect whether you have enabled the extension in Safari, therefore, the App prompt will not change after you enable the extension. Currently the App interface is only used to set or change the userscripts directory. +> +> You could select an iCloud folder for syncing scripts between macOS and iOS, but please note that there may be delays in synchronization, and you may encounter files be evictioned due to iCloud optimization, please refer to [#424](https://github.com/quoid/userscripts/issues/424). (**Since macOS 15 / iOS 18, please set "keep downloaded" for the folder to avoid eviction**) + +There are two main ways to install a user script from the iOS version: + +- Visit a `.user.js` URL in Safari, then open the extension `popup` and you will see an installation prompt + - The URL should end with `.user.js` in `/PATH` part, not the `?QUERY` or `#HASH` parts +- You could also save files with the extension `.user.js` directly to the Userscripts directory you set above -- Go to `Settings > Extensions > Userscripts` -- Turn Userscripts `on` -- For optimal experience it's recommended that you allow Userscripts for `All Websites` -- Once the above is complete **open the containing app** -- Click the "Set Userscripts Directory" button and select the directory, *within the Files.app*, where your userscripts are located and where you wish newly installed userscripts to be placed - - **Tip:** for optimal cross platform experience it's a good idea to use an iCloud folder for syncing between macOS and iOS - - **Note:** syncing between macOS and iOS is not immediate, it is sometimes necessary to open Files.app in order to applying changes made in macOS to be reflected in iOS - that includes userscript deletions, additions and edits -- Once the directory is set you can close the containing app and open Safari.app -- It **may be necessary** to apply further permissions and it's to `Always Allow` Userscripts for `All Websites` +> [!TIP] +> +> Both of the above work equally well in the macOS version. +> +> The iOS version does not include the script editor provided in the macOS version, but you can always edit script files in the directory you set directly on iOS. (use any third-party code editor apps, support in-place opening and editing) -**macOS** +### **macOS** After installing Userscripts on macOS, you **do not** need to select a userscripts directory if you do not plan on syncing your userscripts between multiple devices. Instead you can choose to use the default directory, which is located at `~/User/Library/Containers/Userscripts/Data/Documents/scripts` - again, this is default (and automatic) behavior. You only need to select a new location if you want to store your userscripts elsewhere, which is especially useful if you are using an external code editor such as Sublime Text or VSCode. +Refer to Apple's official guide page: [Use Safari extensions on your Mac](https://support.apple.com/102343) + [**Here's a short clip showing how to easily create/add a userscript in Safari using this extension on macOS**](https://youtu.be/x1r3-L7pdYQ?t=14) ## UI Overview @@ -64,34 +85,34 @@ After installing Userscripts on macOS, you **do not** need to select a userscrip ![Userscripts Safari Main Application Window](/etc/ui01.png) 1. **Extension button** - click this button to open the extension interface -1. **Filter bar** - use this input to filter items in the sidebar, by *name* +1. **Filter bar** - use this input to filter items in the sidebar, by _name_ 1. **Sort button** - changes the order of the items in the sidebar by name or modified time -1. **Sidebar buttons** - *described left to right* - - The `settings` button (represented by a [cog](https://wikipedia.org/wiki/Gear)) displays the settings modal (discussed below) - - The `plus` button allows users to add new items - - `New CSS` is a "userscript" that expects [CSS](https://www.w3schools.com/css/) code - - `New Javascript` is a prototypical userscript that expects [Javascript](https://www.w3schools.com/js/DEFAULT.asp) code - - `New Remote` allows the user to add a remote hosted userscript (or style) by inputting the web address (*ex:* `https://www.k21p.com/example.user.js`) +1. **Sidebar buttons** - _described left to right_ + - The `settings` button (represented by a [cog](https://wikipedia.org/wiki/Gear)) displays the settings modal (discussed below) + - The `plus` button allows users to add new items + - `New CSS` is a "userscript" that expects [CSS](https://www.w3schools.com/css/) code + - `New Javascript` is a prototypical userscript that expects [Javascript](https://www.w3schools.com/js/DEFAULT.asp) code + - `New Remote` allows the user to add a remote hosted userscript (or style) by inputting the web address (_ex:_ `https://www.k21p.com/example.user.js`) 1. **Item toggle** - this toggle enables or disables an item -1. **Item** - this is the userscript (or style), clicking on it will load it's contents into the editor - *you can hide descriptions in the settings area!* -1. **Editor buttons (top)** - *described left to right* - - **Update button** - this button allows you to update userscripts that meet the following conditions - - metadata contains `@version` tag - - metadata contains `@updateURL` tag - - **Download button** - click this button to download a copy of your userscript - - *Note:* every userscript that is displayed in the interface is already present on your local machine, at your save location - the download button offers a quick way to retrieve a copy of that file, without needing to click the settings button, and then the save location link within the settings modal - - **Trash button** - moves the currently loaded userscript to the trash bin - it will subsequently be removed from the interface and save location +1. **Item** - this is the userscript (or style), clicking on it will load it's contents into the editor - _you can hide descriptions in the settings area!_ +1. **Editor buttons (top)** - _described left to right_ + - **Update button** - this button allows you to update userscripts that meet the following conditions + - metadata contains `@version` tag + - metadata contains `@updateURL` tag + - **Download button** - click this button to download a copy of your userscript + - _Note:_ every userscript that is displayed in the interface is already present on your local machine, at your save location - the download button offers a quick way to retrieve a copy of that file, without needing to click the settings button, and then the save location link within the settings modal + - **Trash button** - moves the currently loaded userscript to the trash bin - it will subsequently be removed from the interface and save location 1. **Editor buttons (bottom)** - - `Discard` - while editing, reverts any unsaved changes you've made to a userscript - - `Save` - while editing, saves all changes you've made to a userscript - - `Command + S` is the keyboard shortcut for the action + - `Discard` - while editing, reverts any unsaved changes you've made to a userscript + - `Save` - while editing, saves all changes you've made to a userscript + - `Command + S` is the keyboard shortcut for the action ### Settings Modal: ![Userscripts Safari Settings Window](/etc/settings.png) - **Auto Close Brackets** - toggles on/off auto closing of brackets in the editor - - this affects the following characters: `() [] {} "" ''` + - this affects the following characters: `() [] {} "" ''` - **Auto Hint** - automatically shows completion hints while editing - **Hide Descriptions** - hides the item descriptions in the sidebar - **Show Invisibles** - toggles the display of invisible characters in the editor @@ -101,7 +122,7 @@ After installing Userscripts on macOS, you **do not** need to select a userscrip - **Show Toolbar Count** - displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on - **Save Location** - where your file are currently located and being saved to (click the blue text to open location) - **Change Save Location (cogs icon)** - this button, located directly to the right of the save location, is a shortcut for opening the host app, which will allow you to change the save location -- **Global Blacklist** - this input accepts a comma separated list of [`@match` patterns](https://developer.chrome.com/docs/extensions/mv3/match_patterns/), a page url that matches against a pattern in this list will be ignored for script injection +- **Global Blacklist** - this input accepts a comma separated list of `@match` patterns ([Match pattern structure](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#match_pattern_structure)), a page url that matches against a pattern in this list will be ignored for script injection ### Popup: @@ -109,7 +130,7 @@ After installing Userscripts on macOS, you **do not** need to select a userscrip -10. **Open Page Link** - *macOS only*, opens the extension browser page +10. **Open Page Link** - _macOS only_, opens the extension browser page 11. **Enable Injection toggle** - turns on/off page script injection (on/off switch) 12. **Refresh View** - refreshes the popup view 13. **Available Updates View** - the extension periodically checks all userscripts in your save location for updates and when an update is found, it is shown in this view @@ -121,149 +142,189 @@ After installing Userscripts on macOS, you **do not** need to select a userscrip Userscripts Safari currently supports the following userscript metadata: -- `@name` - This will be the name that displays in the sidebar and be used as the filename - you can *not* use the same name for multiple files of the same type +- `@name` - This will be the name that displays in the sidebar and be used as the filename - you can _not_ use the same name for multiple files of the same type - `@description`- Use this to describe what your userscript does - this will be displayed in the sidebar - there is a setting to hide descriptions - `@icon` - This doesn't have a function with this userscript manager, but the **first value** provided in the metadata will be accessible in the `GM_/GM.info` object -- `@match` - Domain match patterns - you can use several instances of this field if you'd like multiple domain matches - view [this article for more information on constructing patterns](https://developer.chrome.com/extensions/match_patterns) - - **Note:** this extension only supports `http/s` -- `@exclude-match` - Domain patterns where you do *not* want the script to run +- `@match` - Domain match patterns - you can use several instances of this field if you'd like multiple domain matches - please refer to: [Match pattern structure](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#match_pattern_structure) + - **Note:** this extension only supports `http/s` +- `@exclude-match` - Domain patterns where you do _not_ want the script to run - `@include` - Used to match against urls for injection, globs and regular expressions are allowed, [read more here](https://wiki.greasespot.net/Include_and_exclude_rules) - `@exclude` - Functions in a similar way as `@include` but rather than injecting, a match against this key's value will prevent injection - `@inject-into` - allows the user to choose which context to inject the script into - - allows the user to choose which context to inject the script into - - values: `auto` (default), `content`, `page` - - `GM` apis are only available when using `content` - - works like [violentmonkey](https://violentmonkey.github.io/api/metadata-block/#inject-into) + - allows the user to choose which context to inject the script into + - values: `auto` (default), `content`, `page` + - `GM` apis are only available when using `content` + - works like [violentmonkey](https://violentmonkey.github.io/api/metadata-block/#inject-into) - `@run-at` - - allows the user to choose the injection timing - - document-start, document-end (default), document-idle - - works like [violentmonkey](https://violentmonkey.github.io/api/metadata-block/#run-at) - - **JS Only** + - allows the user to choose the injection timing + - document-start, document-end (default), document-idle + - works like [violentmonkey](https://violentmonkey.github.io/api/metadata-block/#run-at) + - **JS Only** - `@weight` - - allows the user to further adjust script injection timing - - can be used to ensure one script injects before another - - ONLY accepts integers (floats, strings and everything else will be ignored) - - min value = 1, max value = 999, higher numbers (“heavier”) execute earlier + - allows the user to further adjust script injection timing + - can be used to ensure one script injects before another + - ONLY accepts integers (floats, strings and everything else will be ignored) + - min value = 1, max value = 999, higher numbers (“heavier”) execute earlier - `@require` - - allows users to include remote resources in their scripts - - the value must be a valid url, currently no local file support - - must require a resource of the same file type (JS for JS, CSS for CSS) - - when a resource is required, it is downloaded and saved locally - - the resources is downloaded once at save and never checked for updates or parsed in anyway - - if you want to update the require resources, and the url does not change, you must remove the resources, save, then re-input it - - **require remote resources at your own risk, the extension never validates remote resource code in any way and be aware that using remote resources from untrusted sources can jeopardize your personal security** + - allows users to include remote resources in their scripts + - the value must be a valid url, currently no local file support + - must require a resource of the same file type (JS for JS, CSS for CSS) + - when a resource is required, it is downloaded and saved locally + - the resources is downloaded once at save and never checked for updates or parsed in anyway + - if you want to update the require resources, and the url does not change, you must remove the resources, save, then re-input it + - **require remote resources at your own risk, the extension never validates remote resource code in any way and be aware that using remote resources from untrusted sources can jeopardize your personal security** - `@version` - - used to determine the current version of a userscript - - when paired with `@updateURL`, this will allow the user to update a userscript from a remote source, if the version on their machine is `<` version at the update URL - - `@version` does nothing by itself, it needs to be paired with` @updateURL` for remote updating to function properly + - used to determine the current version of a userscript + - when paired with `@updateURL`, this will allow the user to update a userscript from a remote source, if the version on their machine is `<` version at the update URL + - `@version` does nothing by itself, it needs to be paired with` @updateURL` for remote updating to function properly - `@updateURL` - - the remote url to check version against - - if the version of the file located at the update URL is `>` the version on the local machine, the file will be updated - - `@updateURL` does nothing by itself, it needs to be paired with `@version` for remote updating to function properly + - the remote url to check version against, the url `/PATH` should end with `.meta.js` and contains the Metadata block + - if the version of the file located at the update URL is `>` the version on the local machine, the file will be updated + - `@updateURL` does nothing by itself, it needs to be paired with `@version` for remote updating to function properly + - Read this [link](https://stackoverflow.com/questions/38023717/) for more details + - _Note: The extension does not correctly implement the entire update process, tracking in issue [#248](https://github.com/quoid/userscripts/issues/248)_ - `@downloadURL` - - optional download location for a remotely updateable file (*i.e. a file that has both `@version` and `@updateURL`) - - when paired with `@version` and `@updateURL`, if the local version is `<` the version of the file that `@updateURL` points to, the extension will attempt to update the file's code with the contents of the file located at the `@downloadURL` - - `@downloadURL` does nothing by itself, it needs `@version` and `@updateURL` to present in order to function properly + - the url `/PATH` should end with `.user.js`, that is, it cannot be in `?QUERY` or `#HASH` parts + - optional download location for a remotely updateable file (\*i.e. a file that has both `@version` and `@updateURL`) + - when paired with `@version` and `@updateURL`, if the local version is `<` the version of the file that `@updateURL` points to, the extension will attempt to update the file's code with the contents of the file located at the `@downloadURL` + - `@downloadURL` does nothing by itself, it needs `@version` and `@updateURL` to present in order to function properly - `@noframes` - - this key takes no value - - prevents code from being injected into nested frames - + - this key takes no value + - prevents code from being injected into nested frames +- `@grant` + - Imperative controls which special [`APIs`](#api) (if any) your script uses, one on each `@grant` line, only those API methods will be provided. + - If no `@grant` values are provided, `none` will be assumed. + - If you specify `none` and something else, `none` takes precedence. + **All userscripts need at least 1 `@match` or `@include` to run!** ## API Userscripts currently supports the following api methods. All methods are asynchronous unless otherwise noted. Users must `@grant` these methods in order to use them in a userscript. When using API methods, it's only possible to inject into the content script scope due to security concerns. +> [!NOTE] +> +> The following API description applies to the latest development branch, you may need to check the documentation for the corresponding version. Please switch to the version you want to check via `Branches` or `Tags` at the top. For example: +> +> For the current version of the App Store: +> https://github.com/quoid/userscripts/tree/release/4.x.x#api +> +> For the v4.5.x and earlier versions: +> https://github.com/quoid/userscripts/tree/v4.5.4#api + +For API type definitions, please refer to: [`types.d.ts`](https://github.com/userscriptsup/testscripts/blob/f2fcde4b556fa436fe806a44a89afb9eb5dccd0b/userscripts/types.d.ts) + - `GM.addStyle(css)` - - `css: String` - - on success returns a promise resolved with the css string argument provided + - `css: String` + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved if succeeds, rejected with error message if fails - `GM.setValue(key, value)` - - `key: String`, `value: Any` - - on success returns a promise resolved with an object indicating success + - `key: String` + - `value: Any` - any can be JSON-serialized + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved if succeeds, rejected with error message if fails - `GM.getValue(key, defaultValue)` - - `key: String`, `defaultValue: Any` - - on success returns a promise resolved with the value that was set or default value provided + - `key: String` + - `defaultValue: Any` - optional + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved with the `value` that was set or `defaultValue` provided or `undefined` if succeeds, rejected with error message if fails - `GM.deleteValue(key)` - - `key: String` - - on success returns a promise resolved with an object indicating success + - `key: String` + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved if succeeds, rejected with error message if fails - `GM.listValues()` - - on success returns a promise resolved with an array of the key names of **presently set** values + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved with an array of the key names of **presently set** values if succeeds, rejected with error message if fails - `GM.getTab()` - - on success returns a promise resolved with `Any` data that is persistent as long as this tab is open + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved with `Any` data that is persistent as long as this tab is open if succeeds, rejected with error message if fails - `GM.saveTab(tabObj)` - - `tabObj: Any` - - on success returns a promise resolved with an object indicating success + - `tabObj: Any` - any can be JSON-serialized + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved if succeeds, rejected with error message if fails - `GM.openInTab(url, openInBackground)` - - `url: String`, `openInBackground: Bool` - - on success returns a promise resolved with the [tab data](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab) for the tab just opened + - `url: String` + - `openInBackground: Bool` - optional, `false` by default + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved with [tab data](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/Tab) for the tab just opened if succeeds, rejected with error message if fails - `GM.closeTab(tabId)` - - `tabId: Int` - - `tabId` is **optional** and if omitted the tab that called `US.closeTab` will be closed - - on success returns a promise resolved with an object indicating success + - `tabId: Int` - optional, the `caller tab` by default + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved if succeeds, rejected with error message if fails - `GM.setClipboard(data, type)` - - `data: String` - **required** - - `type: String` - **optional** and defaults to `text/plain` - - [read more here](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData) - - on success returns a promise resolved with a `Bool` indicating success + - `data: String` + - `type: String` - optional, `text/plain` by default + - [read more here](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent/clipboardData) + - returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), resolved with a `Bool` value indicating succeeds or fails, rejected with error message if fails - `GM.info` && `GM_info` - - is available without needing to add it to `@grant` - - an object containing information about the running userscript - - `scriptHandler: String` - returns `Userscripts` - - `version: String` - the version of Userscripts app - - `scriptMetaStr: String` - the metablock for the currently running script - - `script: Object` - contains data about the currently running script - - `description: String` - - `exclude-match: [String]` - - `excludes: [String]` - - `grant: [String]` - - `includes: [String]` - - `inject-into: String` - - `matches: [String]` - - `name: String` - - `namespace: String` - - `noframes: Bool` - - `require: [String]` - - `resources: [String]` - *currently not implemented* - - `run-at: String` - - `version: String` - *the userscript version value* + - is available without needing to add it to `@grant` + - an object containing information about the running userscript + - `scriptHandler: String` - returns `Userscripts` + - `version: String` - the version of Userscripts app + - `scriptMetaStr: String` - the metablock for the currently running script + - `script: Object` - contains data about the currently running script + - `description: String` + - `exclude-match: [String]` + - `excludes: [String]` + - `grant: [String]` + - `includes: [String]` + - `inject-into: String` + - `matches: [String]` + - `name: String` + - `namespace: String` + - `noframes: Bool` + - `require: [String]` + - `resources: [String]` - _currently not implemented_ + - `run-at: String` + - `version: String` - _the userscript version value_ - `GM.xmlHttpRequest(details)` - - `details: Object` - - the `details` object accepts the following properties - - `url` - `String` - **required** - - `method` - `String` - defaults to `GET` - - `user` - `String` - - `password` - `String` - - `headers` - `Object` - - `overrideMimeType` - - `timeout` - `Int` - - `binary` - `Bool` - - `data` - `String` - - `responseType` - `String` - - read more about [XMLHttpRequests here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) - - event handlers: - - `onabort` - `function` - - `onerror` - `function` - - `onload` - `function` - - `onloadend` - `function` - - `onloadstart` - `function` - - `onprogress` - `function` - - `onreadystatechange` - `function` - - `ontimeout` - `function` - - the response object passed to the event handlers has the following properties: - - `readyState` - - `response` - - `responseHeaders` - - `responseType` - - `responseURL` - - `status` - - `statusText` - - `timeout` - - `withCredentials` - - `responseText` (when `responseType` is `text`) - - returns an object with a single property, `abort`, which is a `function` - - usage: `const foo = GM.xmlHttpRequest({...});` ... `foo.abort()` to abort the request + - `details: Object` + - the `details` object accepts the following properties + - `url: String` - required + - `method: String` - optional, `GET` by default + - `user: String` - optional + - `password: String` - optional + - `headers: Object` - optional + - `overrideMimeType: String` - optional + - `timeout: Int` - optional + - `binary: Bool` - optional (Deprecated, use binary data objects such as `Blob`, `ArrayBuffer`, `TypedArray`, etc. instead.) + - `data: String | Blob | ArrayBuffer | TypedArray | DataView | FormData | URLSearchParams` - optional + - `responseType: String` - optional + - refer to [`XMLHttpRequests`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) + - upload event handlers: + - `upload: Object` - optional + - `onabort: Function` - optional + - `onerror: Function` - optional + - `onload: Function` - optional + - `onloadend: Function` - optional + - `onloadstart: Function` - optional + - `onprogress: Function` - optional + - `ontimeout: Function` - optional + - the progress object passed to the event handlers has the following properties: + - `lengthComputable` + - `loaded` + - `total` + - event handlers: + - `onabort: Function` - optional + - `onerror: Function` - optional + - `onload: Function` - optional + - `onloadend: Function` - optional + - `onloadstart: Function` - optional + - `onprogress: Function` - optional + - `onreadystatechange: Function` - optional + - `ontimeout: Function` - optional + - the response object passed to the event handlers has the following properties: + - `readyState` + - `response` + - `responseHeaders` + - `responseType` + - `responseURL` + - `status` + - `statusText` + - `timeout` + - `responseText` (when `responseType` is `text`) + - returns a custom [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) contains an additional property `abort`, resolved with the response object. + - usage: + - `const xhr = GM.xmlHttpRequest({...});` + - `xhr.abort();` to abort the request + - `const response = await xhr;` + - or just: + - `const response = await GM.xmlHttpRequest({...});` - `GM_xmlhttpRequest(details)` - - an alias for `GM.xmlHttpRequest`, works exactly the same + - Basically the same as `GM.xmlHttpRequest(details)`, except: + - returns an object with a single property, `abort`, which is a `Function` + - usage: `const foo = GM.xmlHttpRequest({...});` ... `foo.abort();` to abort the request ## Scripts Directory @@ -277,7 +338,7 @@ This is the directory where the app/extension will read from and write to. This ## Getting Help -If you encounter a problem while using this app/extension or are in need of some assistance, please open an issue here in the repository. When doing so, please provide as much detail as possible. This includes listing system specs and what website and script you are trying to execute. *Please follow the issue template!* +If you encounter a problem while using this app/extension or are in need of some assistance, please open an issue here in the repository. When doing so, please provide as much detail as possible. This includes listing system specs and what website and script you are trying to execute. _Please follow the issue template!_ ## FAQs @@ -299,35 +360,32 @@ If you encounter a problem while using this app/extension or are in need of some **When I use `@require`, where are the required files stored?** -> All required files are saved *as Javascript files* in the extension container folder in macOS 11.x. That folder is located in the default save location, at: `~/Library/Containers/Userscripts/Data/Documents/require/`. +> All required files are saved _as Javascript files_ in the extension container folder in macOS 11.x. That folder is located in the default save location, at: `~/Library/Containers/Userscripts/Data/Documents/require/`. > > If you move files from the require folder or manually edit the `manifest.json` file, you will likely break app/extension functionality. ## Contributing -Code level contributions are welcome. *I prefer to collaborate directly with contributors rather than receiving spontaneous pull requests*. If you feel you can improve the project in some way, please reach out to me by email or by opening an issue with your improvement/feature request. -Further, any issue marked "help wanted" is actively seeking assistance. Please respond to those issues with feedback, guidance or offers of coding assistance. +Code level contributions please refer to [contributing.md](docs/contributing.md) -Notes: -- use [semantic commit messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) -- under most circumstances, you should fork the most current version of the `develop` branch for your contributions +Further, any issue marked "help wanted" is actively seeking assistance. Please respond to those issues with feedback, guidance or offers of coding assistance. -**Please ensure your contributions align with the project's license before committing anything.** +Participating and interacting with any existing [`Issues`](https://github.com/quoid/userscripts/issues) or [`Discussions`](https://github.com/quoid/userscripts/discussions) would be a great help to the project and open source communities. Thank you for your contributions. ## Support + The quickest and easiest way to support the project is by [leaving a positive review on the App Store](https://apps.apple.com/us/app/userscripts/id1463298887) if you enjoy the extension and want to see future improvements. Seeing these reviews let me know I am doing something right, or wrong, and motivates me to continue working on the project. The second best way to help out is to sign up to beta test new versions of the app. Since this extension values your privacy, and **does not collect any data from users**, it is difficult to gauge how the extension is being used. By signing up to be a beta tester it not only allows you to test upcoming features, but also gives me the opportunity to elicit direct feedback from real users. -**[iOS Beta Sign Up Form](https://forms.gle/QB46uYQHVyCxULue9)** - -**[macOS Beta Sign Up Form](https://forms.gle/cUDtKg1ip4Vc9Xhc7)** +**Please join and test the corresponding beta version in [releases](https://github.com/quoid/userscripts/releases) via the TestFlight public link.** ## Privacy Policy + Userscripts does not collect any data from its users nor monitor activities or actions you perform within the application and extension. This means everything that you do with the application and extension is private to you and is never shared with the developers or third parties. Since there is no data collection, there is no data retention of any kind. ## License -Copyright (c) 2022 Justin Wasack +Copyright © 2018-2025 Justin Wasack Licensed under the [GNU General Public License v3.0](/LICENSE) license for all open source applications. A commercial license is required for all other applications. diff --git a/Userscripts.code-workspace b/Userscripts.code-workspace new file mode 100644 index 00000000..11c4d094 --- /dev/null +++ b/Userscripts.code-workspace @@ -0,0 +1,14 @@ +// https://code.visualstudio.com/docs/editor/workspaces +{ + "folders": [ + { + "name": "Userscripts-JS", + "path": ".", + }, + { + "name": "Userscripts-Xcode", + "path": "xcode", + }, + ], + "settings": {}, +} diff --git a/docs/contributing.md b/docs/contributing.md index 4f9de8ad..3cb92e9d 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,14 +1,33 @@ # Welcome to Userscripts contributing guide +### Please note: the project is currently temporarily suspended from receiving direct code contributions. + +Please do not submit any new pull requests, they will be close immediately. + +If you have any ideas and suggestions, please submit them to us through [`Issues`](https://github.com/quoid/userscripts/issues) or [`Discussions`](https://github.com/quoid/userscripts/discussions). + +We apologize for the inconvenience and thank you for your understanding. + +### The following is the original guide for reference only. + Thank you for investing your time in contributing to this open source project! We hope this guide will helps you understand how to contribute to this project. For development and build environments please refer to [development guide](dev.md). +> [!NOTE] +> Portions of this guide are outdated. Some processes are still being tweaked and optimized, we will update them in due course. + +> [!IMPORTANT] +> Please ensure your contributions align with the project's license before committing anything. +> +> Any pull requests must first go through code formatting and linters, otherwise you will fail the CI check. (Refer to the `scripts` in [`package.json`](../package.json)) + # Branches -### `master`: default branch +### `main`: default branch + - corresponding to development channel - all development work around this branch - protected branches not accept commits @@ -16,6 +35,7 @@ For development and build environments please refer to [development guide](dev.m - all sub-branches will be merged via PRs ### `beta`: latest test version + - generated from the master branch - corresponding to TestFlight channel - only accept version number commits @@ -23,6 +43,7 @@ For development and build environments please refer to [development guide](dev.m - never merge back into master branch ### `release`: latest public version + - generated from the beta branch - generate GitHub Releases and Tags - corresponding to App Store channel @@ -30,6 +51,7 @@ For development and build environments please refer to [development guide](dev.m - always merge back into master branch # Commits + ### Please use semantic commit messages Format: `(): ` @@ -70,6 +92,7 @@ We will improve the guidelines for creating PRs in the future, like create [`pul If you are not a member of the project, first you need to fork the master branch of the repository. ### General development + - create a new branch from the master branch - suggested branch naming format like: - `issue/333` @@ -93,4 +116,5 @@ If you are not a member of the project, first you need to fork the master branch - Despite various tests, we may still find bugs through user feedback in the public version, and we will fix those minor bugs through hotfixes. # About -[Userscripts](https://github.com/quoid/userscripts) @ 2018-2023 \ No newline at end of file + +[Userscripts](https://github.com/quoid/userscripts) diff --git a/docs/dev.md b/docs/dev.md index 861fd35d..262aac05 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -1,52 +1,82 @@ +# Environment + The `root` directory is a typical multi-page app of Vite with Svelte JavaScript project. The `xcode` directory is the root of the Xcode project where the Safari extension app is built. -# Environment -- [`Node.js`](https://nodejs.dev/en/learn/how-to-install-nodejs/) -- [`pnpm`](https://pnpm.io/installation) (optional) -- [`Xcode`](https://geo.itunes.apple.com/app/xcode/id497799835) +For other directory structure instructions, please refer to [structure.md](structure.md). + +### Recommended code editor + +[Visual Studio Code](https://github.com/Microsoft/vscode) with extensions: [/.vscode/extensions.json](../.vscode/extensions.json) + +# Template + +The project is currently based on [`template-svelte`](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-svelte) from [`create-vite`](https://github.com/vitejs/vite/tree/main/packages/create-vite). + +Additional reference templates are from [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). + +Reviewing the template will help you understand the composition of the project. + +# Requirement + +- [`Node.js`](https://nodejs.org/) +- [`Xcode`](https://geo.itunes.apple.com/app/id497799835) +- `Developer Team ID` (i.e. Apple Account, see [Xcode section](#xcode)) # Dev -- `pnpm install` -- `pnpm dev` + +- `npm install` [^1] +- `npm run dev` [^1] # Build -- `pnpm build` + +> [!NOTE] +> Before building the app with Xcode make sure you create the `.dev.xcconfig` files [below](#xcconfig) and fill in your `Developer Team ID`. +> Otherwise `App groups` related functions will not work properly. + +- `npm run build:mac` [^1][^2] - `cd ./xcode` -- `xcodebuild` or build with Xcode App +- `xcodebuild -scheme Mac` [^1][^2][^3] or build with `Xcode` App + +[^1]: These commands can also be executed directly through the vscode tasks. Please refer to: [/.vscode/tasks.json](../.vscode/tasks.json) + +[^2]: Select the corresponding target and platform to build. Please refer to: [/package.json](../package.json) and [xcode-schemes](../xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/) + +[^3]: Local setup may be required. Please refer to: [Building from the Command Line with Xcode FAQ](https://developer.apple.com/library/archive/technotes/tn2339/_index.html) # Xcode +Please note that a developer account is required, which can be a free Apple Account that has agreed to the Apple Developer Agreement. This is required to obtain a `Team ID` and use the `App groups` capability. + ### Configurations -The Xcode project contains two configurations, which have independent `xcconfig` configuration files, and can run on your local at the same time without conflicts. -- `Debug`: No developer account is required, that is `Sign to Run Locally` by default, which will speed up the build during development. This is convenient for developers without an account, and contributors who only need to do simple local debugging. +The Xcode project contains several configurations, which have independent `xcconfig` configuration files, and can run on your local at the same time without conflicts. -- `Release`: A developer account is required, which means it can be used for distribution as well as running on real iOS/iPadOS devices. You will need to override your developer account information in `xcconfig` to complete the build. +- `Vite`: for vite real-time development. -### xcconfig -All `xcconfig` files are in the `./xcode/xcconfig/` directory. Each `.xcconfig` file can be overridden by `.dev.xcconfig` in the same path, they will be ignored by `git`, so you can override any build settings locally. +- `Debug`: for development and debugging. -For example, you can create an `Userscripts-Release.dev.xcconfig` file to override `Userscripts-Release.xcconfig` and fill in your own developer account information there: +- `Release`: for building and distributing. -`Userscripts-Release.dev.xcconfig` -``` -DEVELOPMENT_TEAM = XXXXXXXXXX -``` +### xcconfig + +All `xcconfig` files are in the [`/xcode/xcconfig/`](../xcode/xcconfig) directory. Each `.xcconfig` file can be overridden by `.dev.xcconfig` in the same path, they will be ignored by `git`, so you can override any build settings locally. -Another example, if you want `Debug` builds to be also signed, so instead of enabling `Allow Unsigned Extensions` every time in Safari, you can create: +For example, you can create an `Userscripts-Debug.dev.xcconfig` file to override [`Userscripts-Debug.xcconfig`](../xcode/xcconfig/Userscripts-Debug.xcconfig) and fill in your own developer account `Team ID` there: `Userscripts-Debug.dev.xcconfig` + ``` -CODE_SIGN_IDENTITY = Apple Development DEVELOPMENT_TEAM = XXXXXXXXXX ``` Note that all existing `.xcconfig` files already include `.dev.xcconfig` files of the same name, so they will be applied automatically once you create them. You only need to fill in the setting items that need to be overridden or added. # Contribute + [Contributing guide](contributing.md) # About -[Userscripts](https://github.com/quoid/userscripts) @ 2018-2023 \ No newline at end of file + +[Userscripts](https://github.com/quoid/userscripts) diff --git a/docs/structure.md b/docs/structure.md new file mode 100644 index 00000000..1a0cd320 --- /dev/null +++ b/docs/structure.md @@ -0,0 +1,41 @@ +# Project directory structure + +For development and build environments please refer to [development guide](dev.md). + +## `docs` + +Store documents and project description files, etc. + +## `etc` + +Readme references and project history assets + +## `public` + +Project Static Asset +https://vitejs.dev/guide/assets.html#the-public-directory + +### `./public/extension-page/jshint.min.js` + +CodeMirror 5 `codemirror/addon/lint/javascript-lint.js` depends on `jshint.js` +https://codemirror.net/5/doc/manual.html#addon_lint +https://github.com/jshint/jshint/blob/main/dist/jshint.js + +## `scripts` + +Custom build scripts ref in `package.json` +Used to build the source code in `src` below + +## `src` + +Project Web App/Ext Source Code +Build the project from here to xcode resources + +## `xcode` + +Project Bundled App Source Code +Build the project from here to extension bundled app + +# About + +[Userscripts](https://github.com/quoid/userscripts) diff --git a/docs/template.md b/docs/template.md deleted file mode 100644 index 69c2ac55..00000000 --- a/docs/template.md +++ /dev/null @@ -1,47 +0,0 @@ -# Svelte + Vite - -This template should help get you started developing with Svelte in Vite. - -## Recommended IDE Setup - -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). - -## Need an official Svelte framework? - -Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. - -## Technical considerations - -**Why use this over SvelteKit?** - -- It brings its own routing solution which might not be preferable for some users. -- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. - -This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. - -Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. - -**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** - -Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. - -**Why include `.vscode/extensions.json`?** - -Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. - -**Why enable `checkJs` in the JS template?** - -It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration. - -**Why is HMR not preserving my local component state?** - -HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). - -If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. - -```js -// store.js -// An extremely simple external store -import { writable } from 'svelte/store' -export default writable(0) -``` diff --git a/entry-app-webview.html b/entry-app-webview.html new file mode 100644 index 00000000..d8789222 --- /dev/null +++ b/entry-app-webview.html @@ -0,0 +1,17 @@ + + + + + + + + Userscripts App + + +
+ + + diff --git a/entry-ext-action-popup.html b/entry-ext-action-popup.html new file mode 100644 index 00000000..8d65c1ca --- /dev/null +++ b/entry-ext-action-popup.html @@ -0,0 +1,14 @@ + + + + + + + Userscripts popup + + + +
+ + + diff --git a/entry-ext-background.html b/entry-ext-background.html new file mode 100644 index 00000000..e3ee5d4c --- /dev/null +++ b/entry-ext-background.html @@ -0,0 +1,9 @@ + + + + Userscripts background + + + + + diff --git a/entry-ext-extension-page.html b/entry-ext-extension-page.html new file mode 100644 index 00000000..24b226cb --- /dev/null +++ b/entry-ext-extension-page.html @@ -0,0 +1,15 @@ + + + + + + + Userscripts page + + + +
+ + + + diff --git a/entry-page.html b/entry-page.html deleted file mode 100644 index 2e585fc0..00000000 --- a/entry-page.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Userscripts page - - - -
- - - diff --git a/entry-popup.html b/entry-popup.html deleted file mode 100644 index 2694df66..00000000 --- a/entry-popup.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Userscripts popup - - - -
- - - diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..e89b2946 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,68 @@ +/** + * @file ESLint project configuration (New flat config) + * @see {@link https://eslint.org/docs/latest/use/configure/configuration-files-new} + * @see {@link https://eslint.org/docs/latest/use/configure/migration-guide} + * @see {@link https://eslint.org/docs/latest/extend/plugin-migration-flat-config} + * @see {@link https://eslint.org/blog/2022/08/new-config-system-part-1/} + * @see {@link https://eslint.org/blog/2022/08/new-config-system-part-2/} + */ + +import js from "@eslint/js"; +import globals from "globals"; +import eslintPluginSvelte from "eslint-plugin-svelte"; +import eslintConfigPrettier from "eslint-config-prettier"; + +/** @type {import("eslint").Linter.Config[]} */ +export default [ + /** + * @see {@link https://eslint.org/docs/latest/use/configure/configuration-files-new#using-predefined-configurations} + * @see {@link https://github.com/eslint/eslint/tree/main/packages/js} + */ + js.configs.recommended, + + /** + * @see {@link https://github.com/sveltejs/eslint-plugin-svelte} + */ + ...eslintPluginSvelte.configs.recommended, + ...eslintPluginSvelte.configs.prettier, + + /** + * @see {@link https://github.com/prettier/eslint-config-prettier} + * turns off the conflict rules, put it last + */ + eslintConfigPrettier, + + /** custom config objects */ + + /** @see {@link https://eslint.org/docs/latest/use/configure/configuration-files-new#globally-ignoring-files-with-ignores} */ + { + ignores: ["**/dist/", "**/build/", "etc/", "xcode/", "public/"], + }, + + /** @see {@link https://eslint.org/docs/latest/use/configure/migration-guide#configuring-language-options} */ + { + files: ["*.js", "scripts/**/*.js"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + { + files: ["src/{app,dev,shared}/**/*.{js,svelte}"], + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, + { + files: ["src/ext/**/*.{js,svelte}"], + languageOptions: { + globals: { + ...globals.browser, + ...globals.webextensions, + }, + }, + }, +]; diff --git a/etc/App-iOS-WebView.zip b/etc/App-iOS-WebView.zip deleted file mode 100644 index 49863444..00000000 Binary files a/etc/App-iOS-WebView.zip and /dev/null differ diff --git a/etc/uilayout.sketch b/etc/uilayout.sketch index 3c513380..f86be3ba 100644 Binary files a/etc/uilayout.sketch and b/etc/uilayout.sketch differ diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000..86b5e78c --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,4 @@ +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile + +app_identifier("com.userscripts.macos") diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000..dfbdaa49 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,89 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools + +LOCAL_FASTFILE = "local/Fastfile.local.rb" +if File.file?(LOCAL_FASTFILE) + import(LOCAL_FASTFILE) +end + +PROJECT_PATH = "xcode/Userscripts.xcodeproj" + +# https://docs.fastlane.tools/actions/is_ci/ +if is_ci + setup_ci + IS_CI = true +else + IS_CI = false +end + +# https://docs.fastlane.tools/actions/app_store_connect_api_key/ +app_store_connect_api_key( + key_id: ENV['APP_STORE_CONNECT_API_KEY_KEY_ID'], + issuer_id: ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'], + key_content: ENV['APP_STORE_CONNECT_API_KEY_KEY'], + key_filepath: ENV['APP_STORE_CONNECT_API_KEY_KEY_FILEPATH'], +) + +default_platform(:ios) + +platform :ios do + lane :beta do + match + BUILD_NUMBER = latest_testflight_build_number(platform: "ios") + 1 + File.open(ENV['GITHUB_OUTPUT'], 'a') do |file| + file.puts "ver=v#{ENV['MARKETING_VERSION']}(#{BUILD_NUMBER})" + end + build_app( + project: PROJECT_PATH, + scheme: "iOS", + xcargs: { + CURRENT_PROJECT_VERSION: BUILD_NUMBER.to_s, + MARKETING_VERSION: ENV['MARKETING_VERSION'], + }, + output_directory: "build", + output_name: "Userscripts-iOS-Beta", + silent: IS_CI, + ) + upload_to_testflight( + changelog: "#{ENV['GITHUB_REF_NAME']}", + distribute_external: true, + groups: ["QA"], + ) + end + + lane :release do + puts "ios release #{ENV['MARKETING_VERSION']}" + beta + end +end + +platform :mac do + lane :beta do + match + BUILD_NUMBER = latest_testflight_build_number(platform: "osx") + 1 + File.open(ENV['GITHUB_OUTPUT'], 'a') do |file| + file.puts "ver=v#{ENV['MARKETING_VERSION']}(#{BUILD_NUMBER})" + end + build_mac_app( + project: PROJECT_PATH, + scheme: "Mac", + xcargs: { + CURRENT_PROJECT_VERSION: BUILD_NUMBER.to_s, + MARKETING_VERSION: ENV['MARKETING_VERSION'], + }, + output_directory: "build", + output_name: "Userscripts-Mac-Beta", + silent: IS_CI, + ) + upload_to_testflight( + changelog: "#{ENV['GITHUB_REF_NAME']}", + distribute_external: true, + groups: ["QA"], + ) + end + + lane :release do + puts "mac release #{ENV['MARKETING_VERSION']}" + beta + end +end \ No newline at end of file diff --git a/fastlane/Gemfile b/fastlane/Gemfile new file mode 100644 index 00000000..a4353219 --- /dev/null +++ b/fastlane/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "fastlane" +# gem "rubocop" diff --git a/fastlane/Gemfile.lock b/fastlane/Gemfile.lock new file mode 100644 index 00000000..c6013a88 --- /dev/null +++ b/fastlane/Gemfile.lock @@ -0,0 +1,233 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1188.0) + aws-sdk-core (3.239.2) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.205.0) + aws-sdk-core (~> 3, >= 3.234.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + bigdecimal (3.3.1) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.5) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.1) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.229.1) + CFPropertyList (>= 2.3, < 4.0.0) + abbrev (~> 0.1.2) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2.0) + bundler (>= 1.12.0, < 3.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3.0) + naturally (~> 2.2) + nkf (~> 0.2.0) + optparse (>= 0.1.1, < 1.0.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + jmespath (1.6.2) + json (2.16.0) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.17.0) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + optparse (0.8.0) + os (1.1.4) + plist (3.7.2) + public_suffix (7.0.0) + rake (13.3.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + arm64-darwin-25 + ruby + +DEPENDENCIES + fastlane + +BUNDLED WITH + 2.7.2 diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 00000000..3cbce194 --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1,12 @@ +# https://docs.fastlane.tools/actions/match + +readonly(true) +type("appstore") +app_identifier([ + "com.userscripts.macos", + "com.userscripts.macos.Userscripts-Extension", +]) + +for_platform :mac do + additional_cert_types(["mac_installer_distribution"]) +end \ No newline at end of file diff --git a/index.html b/index.html index b97bad98..215948a7 100644 --- a/index.html +++ b/index.html @@ -1,26 +1,28 @@ - + - - - - Userscripts dev - - - -
- - - + + + + Userscripts dev + + + +
+ + + diff --git a/jsconfig.json b/jsconfig.json index a8284bdf..8f85baf3 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,33 +1,26 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + { - "compilerOptions": { - "moduleResolution": "Node", - "target": "ESNext", - "module": "ESNext", - /** - * svelte-preprocess cannot figure out whether you have - * a value or a type, so tell TypeScript to enforce using - * `import type` instead of `import` for Types. - */ - "importsNotUsedAsValues": "error", - "isolatedModules": true, - "resolveJsonModule": true, - /** - * To have warnings / errors of the Svelte compiler at the - * correct position, enable source maps by default. - */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable this if you'd like to use dynamic types. - */ - // "checkJs": true - }, - /** - * Use global.d.ts instead of compilerOptions.types - * to avoid limiting type declarations. - */ - "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true + }, + "include": ["*.d.ts", "*.js"] } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..d4f33dbf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4194 @@ +{ + "name": "userscripts", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "userscripts", + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@types/webextension-polyfill": "^0.12.3", + "autoprefixer": "^10.4.21", + "cm-show-invisibles": "^3.1.0", + "codemirror": "^5.65.19", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-svelte": "^3.9.0", + "globals": "^16.1.0", + "marked": "^15.0.12", + "prettier": "3.5.3", + "prettier-plugin-svelte": "3.4.0", + "stylelint": "^16.19.1", + "stylelint-config-html": "^1.1.0", + "stylelint-config-standard": "^38.0.0", + "svelte": "^5.33.0", + "vite": "^6.3.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", + "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.0", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.15", + "vitefu": "^1.0.4" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/webextension-polyfill": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.12.3.tgz", + "integrity": "sha512-F58aDVSeN/MjUGazXo/cPsmR76EvqQhQ1v4x23hFjUX0cfAJYE+JBWwiOGW36/VJGGxoH74sVlRIF3z7SJCKyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cacheable": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.9.0.tgz", + "integrity": "sha512-8D5htMCxPDUULux9gFzv30f04Xo3wCnik0oOxKoRTPIBoqA7HtOcJ87uBhQTs3jCfZZTrUBGsYIZOgE0ZRgMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.8.2", + "keyv": "^5.3.3" + } + }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", + "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cm-show-invisibles": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cm-show-invisibles/-/cm-show-invisibles-3.1.0.tgz", + "integrity": "sha512-dfWd4QmuSMoSgONkCv4+REfPrxp3R3OE8jnxR3u2IjkLPolHArGOhMetBLo6gTeiL8tA4ff5v+VBZ1u1AZx+4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/codemirror": { + "version": "5.65.19", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.19.tgz", + "integrity": "sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-functions-list": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12 || >=16" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.155", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.155.tgz", + "integrity": "sha512-ps5KcGGmwL8VaeJlvlDlu4fORQpv3+GIcF5I3f9tUKUlJ/wsysh6HU8P5L1XWRYeXfA0oJd4PyM8ds8zTFf6Ng==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", + "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-3.9.0.tgz", + "integrity": "sha512-nvIUNyyPGbr5922Kd1p/jXe+FfNdVPXsxLyrrXpwfSbZZEFdAYva9O/gm2lObC/wXkQo/AUmQkAihfmNJYeCjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.6.1", + "@jridgewell/sourcemap-codec": "^1.5.0", + "esutils": "^2.0.3", + "globals": "^16.0.0", + "known-css-properties": "^0.36.0", + "postcss": "^8.4.49", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^7.0.0", + "semver": "^7.6.3", + "svelte-eslint-parser": "^1.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^8.57.1 || ^9.0.0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz", + "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", + "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hookified": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.0.tgz", + "integrity": "sha512-2yEEGqphImtKIe1NXWEhu6yD3hlFR4Mxk4Mtp3XEyScpSt4pQ4ymmXA1zzxZpj99QkFK+nN0nzjeb2+RUi/6CQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", + "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-html": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-1.8.0.tgz", + "integrity": "sha512-5mMeb1TgLWoRKxZ0Xh9RZDfwUUIqRrcxO2uXO+Ezl1N5lqpCiSU5Gk6+1kZediBfBHFtPCdopr2UZ2SgUsKcgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "htmlparser2": "^8.0.0", + "js-tokens": "^9.0.0", + "postcss": "^8.5.0", + "postcss-safe-parser": "^6.0.0" + }, + "engines": { + "node": "^12 || >=14" + } + }, + "node_modules/postcss-html/node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", + "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint": { + "version": "16.19.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.19.1.tgz", + "integrity": "sha512-C1SlPZNMKl+d/C867ZdCRthrS+6KuZ3AoGW113RZCOL0M8xOGpgx7G70wq7lFvqvm4dcfdGFVLB/mNaLFChRKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.1.0", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^10.0.8", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.3", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.36.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-html": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-html/-/stylelint-config-html-1.1.0.tgz", + "integrity": "sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12 || >=14" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "postcss-html": "^1.0.0", + "stylelint": ">=14.0.0" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.16.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "38.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-38.0.0.tgz", + "integrity": "sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "stylelint-config-recommended": "^16.0.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.18.0" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.0.tgz", + "integrity": "sha512-Et/ex6smi3wOOB+n5mek+Grf7P2AxZR5ueqRUvAAn4qkyatXi3cUC1cuQXVkX0VlzBVsN4BkWJFmY/fYiRTdww==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.9" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.9.tgz", + "integrity": "sha512-DUqiKkTlAfhtl7g78IuwqYM+YqvT+as0mY+EVk6mfimy19U79pJCzDZQsnqk3Ou/T6hFXWLGbwbADzD/c8Tydg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^1.9.0", + "flatted": "^3.3.3", + "hookified": "^1.8.2" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.4.tgz", + "integrity": "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/svelte": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.0.tgz", + "integrity": "sha512-COPJa+cxefgDShgTr3vcLKXdyBfuE96Z65D/EsuxDcU3fwqBxAN6bVjZ0LoVYyAYAShu4K96/k9BxxlAVO6fpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "esm-env": "^1.2.1", + "esrap": "^1.4.6", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.2.0.tgz", + "integrity": "sha512-mbPtajIeuiyU80BEyGvwAktBeTX7KCr5/0l+uRGLq1dafwRNrjfM5kHGJScEBlPG3ipu6dJqfW/k0/fujvIEVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.0", + "postcss": "^8.4.49", + "postcss-scss": "^4.0.9", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitefu": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index b6e5e482..6f9ef87f 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,37 @@ { - "name": "userscripts", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "lint-js": "eslint ./", - "lint-css": "stylelint '**/*.{css,svelte}'" - }, - "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^2.0.3", - "autoprefixer": "^10.4.13", - "cm-show-invisibles": "^3.1.0", - "codemirror": "^5.65.12", - "eslint": "^8.35.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-svelte3": "^4.0.0", - "postcss-html": "^1.5.0", - "stylelint": "^15.2.0", - "stylelint-config-html": "^1.1.0", - "stylelint-config-recommended": "^10.0.1", - "stylelint-config-standard": "^30.0.1", - "svelte": "^3.55.1", - "vite": "^4.1.4" - } + "name": "userscripts", + "private": true, + "type": "module", + "sideEffects": false, + "scripts": { + "dev": "vite", + "dev:mac-safari": "SAFARI_PLATFORM=mac node scripts/dev-safari.js", + "dev:ios-safari": "SAFARI_PLATFORM=ios node scripts/dev-safari.js", + "build:mac-safari-15": "SAFARI_PLATFORM=mac node scripts/build-safari-15.js", + "build:ios-safari-15": "SAFARI_PLATFORM=ios node scripts/build-safari-15.js", + "build:mac-safari-16.4": "SAFARI_PLATFORM=mac node scripts/build-safari-16.4.js", + "build:ios-safari-16.4": "SAFARI_PLATFORM=ios node scripts/build-safari-16.4.js", + "lint:js": "eslint . && prettier --check . --plugin prettier-plugin-svelte", + "lint:css": "stylelint '**/*.{css,html,svelte}'", + "prettier": "prettier --write . --plugin prettier-plugin-svelte" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@types/webextension-polyfill": "^0.12.3", + "autoprefixer": "^10.4.21", + "cm-show-invisibles": "^3.1.0", + "codemirror": "^5.65.19", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-svelte": "^3.9.0", + "globals": "^16.1.0", + "marked": "^15.0.12", + "prettier": "3.5.3", + "prettier-plugin-svelte": "3.4.0", + "stylelint": "^16.19.1", + "stylelint-config-html": "^1.1.0", + "stylelint-config-standard": "^38.0.0", + "svelte": "^5.33.0", + "vite": "^6.3.5" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index f5b48ea0..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,2600 +0,0 @@ -lockfileVersion: 5.4 - -specifiers: - '@sveltejs/vite-plugin-svelte': ^2.0.3 - autoprefixer: ^10.4.13 - cm-show-invisibles: ^3.1.0 - codemirror: ^5.65.12 - eslint: ^8.35.0 - eslint-config-airbnb-base: ^15.0.0 - eslint-plugin-import: ^2.27.5 - eslint-plugin-svelte3: ^4.0.0 - postcss-html: ^1.5.0 - stylelint: ^15.2.0 - stylelint-config-html: ^1.1.0 - stylelint-config-recommended: ^10.0.1 - stylelint-config-standard: ^30.0.1 - svelte: ^3.55.1 - vite: ^4.1.4 - -devDependencies: - '@sveltejs/vite-plugin-svelte': 2.0.3_svelte@3.55.1+vite@4.1.4 - autoprefixer: 10.4.13 - cm-show-invisibles: 3.1.0 - codemirror: 5.65.12 - eslint: 8.35.0 - eslint-config-airbnb-base: 15.0.0_yckic57kx266ph64dhq6ozvb54 - eslint-plugin-import: 2.27.5_eslint@8.35.0 - eslint-plugin-svelte3: 4.0.0_n4ieifq2d7jq3sqoe474cgqlim - postcss-html: 1.5.0 - stylelint: 15.2.0 - stylelint-config-html: 1.1.0_x2qzl5lwlwnkkekx7bj2y4o6y4 - stylelint-config-recommended: 10.0.1_stylelint@15.2.0 - stylelint-config-standard: 30.0.1_stylelint@15.2.0 - svelte: 3.55.1 - vite: 4.1.4 - -packages: - - /@babel/code-frame/7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - dev: true - - /@babel/helper-validator-identifier/7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/highlight/7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - - /@csstools/css-parser-algorithms/2.0.1_5vzy4lghjvuzkedkkk4tqwjftm: - resolution: {integrity: sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-tokenizer': ^2.0.0 - dependencies: - '@csstools/css-tokenizer': 2.1.0 - dev: true - - /@csstools/css-tokenizer/2.1.0: - resolution: {integrity: sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A==} - engines: {node: ^14 || ^16 || >=18} - dev: true - - /@csstools/media-query-list-parser/2.0.1_ppok7cytzjc65mcyxmtit3wdyi: - resolution: {integrity: sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-parser-algorithms': ^2.0.0 - '@csstools/css-tokenizer': ^2.0.0 - dependencies: - '@csstools/css-parser-algorithms': 2.0.1_5vzy4lghjvuzkedkkk4tqwjftm - '@csstools/css-tokenizer': 2.1.0 - dev: true - - /@csstools/selector-specificity/2.1.1_wajs5nedgkikc5pcuwett7legi: - resolution: {integrity: sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss: ^8.4 - postcss-selector-parser: ^6.0.10 - dependencies: - postcss: 8.4.21 - postcss-selector-parser: 6.0.11 - dev: true - - /@esbuild/android-arm/0.16.17: - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm64/0.16.17: - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64/0.16.17: - resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64/0.16.17: - resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64/0.16.17: - resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64/0.16.17: - resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64/0.16.17: - resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm/0.16.17: - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64/0.16.17: - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32/0.16.17: - resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64/0.16.17: - resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el/0.16.17: - resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64/0.16.17: - resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64/0.16.17: - resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x/0.16.17: - resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64/0.16.17: - resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64/0.16.17: - resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64/0.16.17: - resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64/0.16.17: - resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64/0.16.17: - resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32/0.16.17: - resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64/0.16.17: - resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@eslint/eslintrc/2.0.0: - resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.4.1 - globals: 13.20.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js/8.35.0: - resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@humanwhocodes/config-array/0.11.8: - resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer/1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema/1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - - /@jridgewell/sourcemap-codec/1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: true - - /@nodelib/fs.scandir/2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat/2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk/1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - - /@sveltejs/vite-plugin-svelte/2.0.3_svelte@3.55.1+vite@4.1.4: - resolution: {integrity: sha512-o+cguBFdwIGtRbNkYOyqTM7KvRUffxh5bfK4oJsWKG2obu+v/cbpT03tJrGl58C7tRXo/aEC0/axN5FVHBj0nA==} - engines: {node: ^14.18.0 || >= 16} - peerDependencies: - svelte: ^3.54.0 - vite: ^4.0.0 - dependencies: - debug: 4.3.4 - deepmerge: 4.3.0 - kleur: 4.1.5 - magic-string: 0.29.0 - svelte: 3.55.1 - svelte-hmr: 0.15.1_svelte@3.55.1 - vite: 4.1.4 - vitefu: 0.2.4_vite@4.1.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@types/json5/0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true - - /@types/minimist/1.2.2: - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - dev: true - - /@types/normalize-package-data/2.4.1: - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} - dev: true - - /acorn-jsx/5.3.2_acorn@8.8.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.8.2 - dev: true - - /acorn/8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv/6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ajv/8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: true - - /ansi-regex/5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: true - - /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /argparse/2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /array-includes/3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - get-intrinsic: 1.2.0 - is-string: 1.0.7 - dev: true - - /array-union/2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /array.prototype.flat/1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - es-shim-unscopables: 1.0.0 - dev: true - - /array.prototype.flatmap/1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - es-shim-unscopables: 1.0.0 - dev: true - - /arrify/1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true - - /astral-regex/2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - - /autoprefixer/10.4.13: - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.5 - caniuse-lite: 1.0.30001458 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss-value-parser: 4.2.0 - dev: true - - /available-typed-arrays/1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true - - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /balanced-match/2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - dev: true - - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /browserslist/4.21.5: - resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001458 - electron-to-chromium: 1.4.313 - node-releases: 2.0.10 - update-browserslist-db: 1.0.10_browserslist@4.21.5 - dev: true - - /call-bind/1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.0 - dev: true - - /callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /camelcase-keys/6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - dev: true - - /camelcase/5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true - - /caniuse-lite/1.0.30001458: - resolution: {integrity: sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==} - dev: true - - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true - - /chalk/4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /cm-show-invisibles/3.1.0: - resolution: {integrity: sha512-dfWd4QmuSMoSgONkCv4+REfPrxp3R3OE8jnxR3u2IjkLPolHArGOhMetBLo6gTeiL8tA4ff5v+VBZ1u1AZx+4A==} - dev: true - - /codemirror/5.65.12: - resolution: {integrity: sha512-z2jlHBocElRnPYysN2HAuhXbO3DNB0bcSKmNz3hcWR2Js2Dkhc1bEOxG93Z3DeUrnm+qx56XOY5wQmbP5KY0sw==} - dev: true - - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - - /color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name/1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true - - /color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /colord/2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true - - /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - - /confusing-browser-globals/1.0.11: - resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - dev: true - - /cosmiconfig/8.1.0: - resolution: {integrity: sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==} - engines: {node: '>=14'} - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - dev: true - - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /css-functions-list/3.1.0: - resolution: {integrity: sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==} - engines: {node: '>=12.22'} - dev: true - - /css-tree/2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.0.2 - dev: true - - /cssesc/3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /debug/3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: true - - /debug/4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - - /decamelize-keys/1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - dev: true - - /decamelize/1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - dev: true - - /deep-is/0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /deepmerge/4.3.0: - resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} - engines: {node: '>=0.10.0'} - dev: true - - /define-properties/1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - dev: true - - /dir-glob/3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - - /doctrine/2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /doctrine/3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /dom-serializer/2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.4.0 - dev: true - - /domelementtype/2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true - - /domhandler/5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - dependencies: - domelementtype: 2.3.0 - dev: true - - /domutils/3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dev: true - - /electron-to-chromium/1.4.313: - resolution: {integrity: sha512-QckB9OVqr2oybjIrbMI99uF+b9+iTja5weFe0ePbqLb5BHqXOJUO1SG6kDj/1WtWPRIBr51N153AEq8m7HuIaA==} - dev: true - - /emoji-regex/8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true - - /entities/4.4.0: - resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} - engines: {node: '>=0.12'} - dev: true - - /error-ex/1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - dev: true - - /es-abstract/1.21.1: - resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - es-set-tostringtag: 2.0.1 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.2.0 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.1 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.10 - is-weakref: 1.0.2 - object-inspect: 1.12.3 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.4.3 - safe-regex-test: 1.0.0 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.9 - dev: true - - /es-set-tostringtag/2.0.1: - resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.0 - has: 1.0.3 - has-tostringtag: 1.0.0 - dev: true - - /es-shim-unscopables/1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - dependencies: - has: 1.0.3 - dev: true - - /es-to-primitive/1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: true - - /esbuild/0.16.17: - resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.16.17 - '@esbuild/android-arm64': 0.16.17 - '@esbuild/android-x64': 0.16.17 - '@esbuild/darwin-arm64': 0.16.17 - '@esbuild/darwin-x64': 0.16.17 - '@esbuild/freebsd-arm64': 0.16.17 - '@esbuild/freebsd-x64': 0.16.17 - '@esbuild/linux-arm': 0.16.17 - '@esbuild/linux-arm64': 0.16.17 - '@esbuild/linux-ia32': 0.16.17 - '@esbuild/linux-loong64': 0.16.17 - '@esbuild/linux-mips64el': 0.16.17 - '@esbuild/linux-ppc64': 0.16.17 - '@esbuild/linux-riscv64': 0.16.17 - '@esbuild/linux-s390x': 0.16.17 - '@esbuild/linux-x64': 0.16.17 - '@esbuild/netbsd-x64': 0.16.17 - '@esbuild/openbsd-x64': 0.16.17 - '@esbuild/sunos-x64': 0.16.17 - '@esbuild/win32-arm64': 0.16.17 - '@esbuild/win32-ia32': 0.16.17 - '@esbuild/win32-x64': 0.16.17 - dev: true - - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: true - - /escape-string-regexp/1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: true - - /escape-string-regexp/4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-config-airbnb-base/15.0.0_yckic57kx266ph64dhq6ozvb54: - resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} - engines: {node: ^10.12.0 || >=12.0.0} - peerDependencies: - eslint: ^7.32.0 || ^8.2.0 - eslint-plugin-import: ^2.25.2 - dependencies: - confusing-browser-globals: 1.0.11 - eslint: 8.35.0 - eslint-plugin-import: 2.27.5_eslint@8.35.0 - object.assign: 4.1.4 - object.entries: 1.1.6 - semver: 6.3.0 - dev: true - - /eslint-import-resolver-node/0.3.7: - resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} - dependencies: - debug: 3.2.7 - is-core-module: 2.11.0 - resolve: 1.22.1 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-module-utils/2.7.4_noxuapdo33xou3o5tg3un5cxoy: - resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - debug: 3.2.7 - eslint: 8.35.0 - eslint-import-resolver-node: 0.3.7 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-plugin-import/2.27.5_eslint@8.35.0: - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - array-includes: 3.1.6 - array.prototype.flat: 1.3.1 - array.prototype.flatmap: 1.3.1 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.35.0 - eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4_noxuapdo33xou3o5tg3un5cxoy - has: 1.0.3 - is-core-module: 2.11.0 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.values: 1.1.6 - resolve: 1.22.1 - semver: 6.3.0 - tsconfig-paths: 3.14.2 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - - /eslint-plugin-svelte3/4.0.0_n4ieifq2d7jq3sqoe474cgqlim: - resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} - peerDependencies: - eslint: '>=8.0.0' - svelte: ^3.2.0 - dependencies: - eslint: 8.35.0 - svelte: 3.55.1 - dev: true - - /eslint-scope/7.1.1: - resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-utils/3.0.0_eslint@8.35.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.35.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys/2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - - /eslint-visitor-keys/3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint/8.35.0: - resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint/eslintrc': 2.0.0 - '@eslint/js': 8.35.0 - '@humanwhocodes/config-array': 0.11.8 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.35.0 - eslint-visitor-keys: 3.3.0 - espree: 9.4.1 - esquery: 1.4.2 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.20.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.4 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-sdsl: 4.3.0 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - regexpp: 3.2.0 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree/9.4.1: - resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2_acorn@8.8.2 - eslint-visitor-keys: 3.3.0 - dev: true - - /esquery/1.4.2: - resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse/4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse/5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /fast-deep-equal/3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-glob/3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - - /fast-json-stable-stringify/2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein/2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastest-levenshtein/1.0.16: - resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} - engines: {node: '>= 4.9.1'} - dev: true - - /fastq/1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true - - /file-entry-cache/6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: true - - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /find-up/4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - dev: true - - /find-up/5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache/3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 - dev: true - - /flatted/3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: true - - /for-each/0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: true - - /fraction.js/4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - dev: true - - /fs.realpath/1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /function.prototype.name/1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - functions-have-names: 1.2.3 - dev: true - - /functions-have-names/1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true - - /get-intrinsic/1.2.0: - resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - dev: true - - /get-symbol-description/1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - dev: true - - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob-parent/6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob/7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /global-modules/2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} - dependencies: - global-prefix: 3.0.0 - dev: true - - /global-prefix/3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} - dependencies: - ini: 1.3.8 - kind-of: 6.0.3 - which: 1.3.1 - dev: true - - /globals/13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /globalthis/1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.0 - dev: true - - /globby/11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - - /globjoin/0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - dev: true - - /gopd/1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.0 - dev: true - - /grapheme-splitter/1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true - - /hard-rejection/2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true - - /has-bigints/1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true - - /has-flag/3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: true - - /has-flag/4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /has-property-descriptors/1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - dependencies: - get-intrinsic: 1.2.0 - dev: true - - /has-proto/1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: true - - /has-symbols/1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: true - - /has-tostringtag/1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /hosted-git-info/2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - dev: true - - /hosted-git-info/4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - dependencies: - lru-cache: 6.0.0 - dev: true - - /html-tags/3.2.0: - resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==} - engines: {node: '>=8'} - dev: true - - /htmlparser2/8.0.1: - resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.0.1 - entities: 4.4.0 - dev: true - - /ignore/5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: true - - /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /import-lazy/4.0.0: - resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} - engines: {node: '>=8'} - dev: true - - /imurmurhash/0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /indent-string/4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - dev: true - - /inflight/1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /ini/1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true - - /internal-slot/1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.0 - has: 1.0.3 - side-channel: 1.0.4 - dev: true - - /is-array-buffer/3.0.1: - resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - is-typed-array: 1.1.10 - dev: true - - /is-arrayish/0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true - - /is-bigint/1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: true - - /is-boolean-object/1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true - - /is-callable/1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true - - /is-core-module/2.11.0: - resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} - dependencies: - has: 1.0.3 - dev: true - - /is-date-object/1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-fullwidth-code-point/3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - dev: true - - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-negative-zero/2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true - - /is-number-object/1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-path-inside/3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /is-plain-obj/1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - dev: true - - /is-plain-object/5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: true - - /is-regex/1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true - - /is-shared-array-buffer/1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - dependencies: - call-bind: 1.0.2 - dev: true - - /is-string/1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-symbol/1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /is-typed-array/1.1.10: - resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: true - - /is-weakref/1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.2 - dev: true - - /isexe/2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /js-sdsl/4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} - dev: true - - /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true - - /js-tokens/8.0.1: - resolution: {integrity: sha512-3AGrZT6tuMm1ZWWn9mLXh7XMfi2YtiLNPALCVxBCiUVq0LD1OQMxV/AdS/s7rLJU5o9i/jBZw/N4vXXL5dm29A==} - dev: true - - /js-yaml/4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /json-parse-even-better-errors/2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true - - /json-schema-traverse/0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true - - /json-stable-stringify-without-jsonify/1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /json5/1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: true - - /kind-of/6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true - - /kleur/4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - dev: true - - /known-css-properties/0.26.0: - resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==} - dev: true - - /levn/0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /lines-and-columns/1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - - /locate-path/5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - dev: true - - /locate-path/6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.merge/4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash.truncate/4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - dev: true - - /lru-cache/6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true - - /magic-string/0.29.0: - resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 - dev: true - - /map-obj/1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: true - - /map-obj/4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: true - - /mathml-tag-names/2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - dev: true - - /mdn-data/2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true - - /meow/9.0.0: - resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} - engines: {node: '>=10'} - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - dev: true - - /merge2/1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true - - /micromatch/4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - dev: true - - /min-indent/1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - dev: true - - /minimatch/3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /minimist-options/4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true - - /minimist/1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /ms/2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /nanoid/3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /natural-compare/1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /node-releases/2.0.10: - resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} - dev: true - - /normalize-package-data/2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.1 - semver: 5.7.1 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-package-data/3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.11.0 - semver: 7.3.8 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - - /normalize-range/0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: true - - /object-inspect/1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: true - - /object-keys/1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: true - - /object.assign/4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - - /object.entries/1.1.6: - resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - dev: true - - /object.values/1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - dev: true - - /once/1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator/0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: true - - /p-limit/2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - dev: true - - /p-limit/3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate/4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - dev: true - - /p-locate/5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /p-try/2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - dev: true - - /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /parse-json/5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.18.6 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - dev: true - - /path-exists/4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute/1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /path-parse/1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - - /path-type/4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true - - /picocolors/1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /picomatch/2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - dev: true - - /postcss-html/1.5.0: - resolution: {integrity: sha512-kCMRWJRHKicpA166kc2lAVUGxDZL324bkj/pVOb6RhjB0Z5Krl7mN0AsVkBhVIRZZirY0lyQXG38HCVaoKVNoA==} - engines: {node: ^12 || >=14} - dependencies: - htmlparser2: 8.0.1 - js-tokens: 8.0.1 - postcss: 8.4.21 - postcss-safe-parser: 6.0.0_postcss@8.4.21 - dev: true - - /postcss-media-query-parser/0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - dev: true - - /postcss-resolve-nested-selector/0.1.1: - resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} - dev: true - - /postcss-safe-parser/6.0.0_postcss@8.4.21: - resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.3.3 - dependencies: - postcss: 8.4.21 - dev: true - - /postcss-selector-parser/6.0.11: - resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - - /postcss-value-parser/4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true - - /postcss/8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /prelude-ls/1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /punycode/2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - - /queue-microtask/1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /quick-lru/4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - dev: true - - /read-pkg-up/7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - dev: true - - /read-pkg/5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - dev: true - - /redent/3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - dev: true - - /regexp.prototype.flags/1.4.3: - resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - functions-have-names: 1.2.3 - dev: true - - /regexpp/3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - - /require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: true - - /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /resolve-from/5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true - - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true - dependencies: - is-core-module: 2.11.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /reusify/1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf/3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rollup/3.17.3: - resolution: {integrity: sha512-p5LaCXiiOL/wrOkj8djsIDFmyU9ysUxcyW+EKRLHb6TKldJzXpImjcRSR+vgo09DBdofGcOoLOsRyxxG2n5/qQ==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /run-parallel/1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /safe-regex-test/1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - is-regex: 1.1.4 - dev: true - - /semver/5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - dev: true - - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - - /semver/7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /side-channel/1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - object-inspect: 1.12.3 - dev: true - - /signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true - - /slash/3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true - - /slice-ansi/4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - - /source-map-js/1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - dev: true - - /spdx-correct/3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.12 - dev: true - - /spdx-exceptions/2.3.0: - resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} - dev: true - - /spdx-expression-parse/3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.12 - dev: true - - /spdx-license-ids/3.0.12: - resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} - dev: true - - /string-width/4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - dev: true - - /string.prototype.trimend/1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - dev: true - - /string.prototype.trimstart/1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.1 - dev: true - - /strip-ansi/6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-bom/3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true - - /strip-indent/3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - dependencies: - min-indent: 1.0.1 - dev: true - - /strip-json-comments/3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /style-search/0.1.0: - resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - dev: true - - /stylelint-config-html/1.1.0_x2qzl5lwlwnkkekx7bj2y4o6y4: - resolution: {integrity: sha512-IZv4IVESjKLumUGi+HWeb7skgO6/g4VMuAYrJdlqQFndgbj6WJAXPhaysvBiXefX79upBdQVumgYcdd17gCpjQ==} - engines: {node: ^12 || >=14} - peerDependencies: - postcss-html: ^1.0.0 - stylelint: '>=14.0.0' - dependencies: - postcss-html: 1.5.0 - stylelint: 15.2.0 - dev: true - - /stylelint-config-recommended/10.0.1_stylelint@15.2.0: - resolution: {integrity: sha512-TQ4xQ48tW4QSlODcti7pgSRqBZcUaBzuh0jPpfiMhwJKBPkqzTIAU+IrSWL/7BgXlOM90DjB7YaNgFpx8QWhuA==} - peerDependencies: - stylelint: ^15.0.0 - dependencies: - stylelint: 15.2.0 - dev: true - - /stylelint-config-standard/30.0.1_stylelint@15.2.0: - resolution: {integrity: sha512-NbeHOmpRQhjZh5XB1B/S4MLRWvz4xxAxeDBjzl0tY2xEcayNhLbaRGF0ZQzq+DQZLCcPpOHeS2Ru1ydbkhkmLg==} - peerDependencies: - stylelint: ^15.0.0 - dependencies: - stylelint: 15.2.0 - stylelint-config-recommended: 10.0.1_stylelint@15.2.0 - dev: true - - /stylelint/15.2.0: - resolution: {integrity: sha512-wjg5OLn8zQwjlj5cYUgyQpMWKzct42AG5dYlqkHRJQJqsystFFn3onqEc263KH4xfEI0W3lZCnlIhFfS64uwSA==} - engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true - dependencies: - '@csstools/css-parser-algorithms': 2.0.1_5vzy4lghjvuzkedkkk4tqwjftm - '@csstools/css-tokenizer': 2.1.0 - '@csstools/media-query-list-parser': 2.0.1_ppok7cytzjc65mcyxmtit3wdyi - '@csstools/selector-specificity': 2.1.1_wajs5nedgkikc5pcuwett7legi - balanced-match: 2.0.0 - colord: 2.9.3 - cosmiconfig: 8.1.0 - css-functions-list: 3.1.0 - css-tree: 2.3.1 - debug: 4.3.4 - fast-glob: 3.2.12 - fastest-levenshtein: 1.0.16 - file-entry-cache: 6.0.1 - global-modules: 2.0.0 - globby: 11.1.0 - globjoin: 0.1.4 - html-tags: 3.2.0 - ignore: 5.2.4 - import-lazy: 4.0.0 - imurmurhash: 0.1.4 - is-plain-object: 5.0.0 - known-css-properties: 0.26.0 - mathml-tag-names: 2.1.3 - meow: 9.0.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-media-query-parser: 0.2.3 - postcss-resolve-nested-selector: 0.1.1 - postcss-safe-parser: 6.0.0_postcss@8.4.21 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - resolve-from: 5.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - style-search: 0.1.0 - supports-hyperlinks: 2.3.0 - svg-tags: 1.0.0 - table: 6.8.1 - v8-compile-cache: 2.3.0 - write-file-atomic: 5.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: true - - /supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /supports-hyperlinks/2.3.0: - resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - dev: true - - /supports-preserve-symlinks-flag/1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - - /svelte-hmr/0.15.1_svelte@3.55.1: - resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} - engines: {node: ^12.20 || ^14.13.1 || >= 16} - peerDependencies: - svelte: '>=3.19.0' - dependencies: - svelte: 3.55.1 - dev: true - - /svelte/3.55.1: - resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} - engines: {node: '>= 8'} - dev: true - - /svg-tags/1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - dev: true - - /table/6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.12.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /text-table/0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /trim-newlines/3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - dev: true - - /tsconfig-paths/3.14.2: - resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - dev: true - - /type-check/0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-fest/0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - dev: true - - /type-fest/0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /type-fest/0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - dev: true - - /type-fest/0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - dev: true - - /typed-array-length/1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} - dependencies: - call-bind: 1.0.2 - for-each: 0.3.3 - is-typed-array: 1.1.10 - dev: true - - /unbox-primitive/1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.2 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true - - /update-browserslist-db/1.0.10_browserslist@4.21.5: - resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.5 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: true - - /uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: true - - /util-deprecate/1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true - - /v8-compile-cache/2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - dev: true - - /validate-npm-package-license/3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - dependencies: - spdx-correct: 3.1.1 - spdx-expression-parse: 3.0.1 - dev: true - - /vite/4.1.4: - resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - esbuild: 0.16.17 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 3.17.3 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /vitefu/0.2.4_vite@4.1.4: - resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 - peerDependenciesMeta: - vite: - optional: true - dependencies: - vite: 4.1.4 - dev: true - - /which-boxed-primitive/1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: true - - /which-typed-array/1.1.9: - resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - is-typed-array: 1.1.10 - dev: true - - /which/1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /word-wrap/1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - - /wrappy/1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /write-file-atomic/5.0.0: - resolution: {integrity: sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - dev: true - - /yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true - - /yargs-parser/20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true - - /yocto-queue/0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true diff --git a/public/ext/safari-15/images/icon-128.png b/public/ext/safari-15/images/icon-128.png new file mode 100644 index 00000000..f0030ddd Binary files /dev/null and b/public/ext/safari-15/images/icon-128.png differ diff --git a/public/ext/safari-15/images/icon-256.png b/public/ext/safari-15/images/icon-256.png new file mode 100644 index 00000000..dea9320e Binary files /dev/null and b/public/ext/safari-15/images/icon-256.png differ diff --git a/public/ext/safari-15/images/icon-48.png b/public/ext/safari-15/images/icon-48.png new file mode 100644 index 00000000..3955fd6b Binary files /dev/null and b/public/ext/safari-15/images/icon-48.png differ diff --git a/public/ext/safari-15/images/icon-512.png b/public/ext/safari-15/images/icon-512.png new file mode 100644 index 00000000..3d0c5854 Binary files /dev/null and b/public/ext/safari-15/images/icon-512.png differ diff --git a/public/ext/safari-15/images/icon-64.png b/public/ext/safari-15/images/icon-64.png new file mode 100644 index 00000000..d1ffa334 Binary files /dev/null and b/public/ext/safari-15/images/icon-64.png differ diff --git a/public/ext/safari-15/images/icon-96.png b/public/ext/safari-15/images/icon-96.png new file mode 100644 index 00000000..411a520a Binary files /dev/null and b/public/ext/safari-15/images/icon-96.png differ diff --git a/public/ext/safari-15/images/toolbar-icon-16.png b/public/ext/safari-15/images/toolbar-icon-16.png new file mode 100644 index 00000000..cee9abb0 Binary files /dev/null and b/public/ext/safari-15/images/toolbar-icon-16.png differ diff --git a/public/ext/safari-15/images/toolbar-icon-32.png b/public/ext/safari-15/images/toolbar-icon-32.png new file mode 100644 index 00000000..1de18ba5 Binary files /dev/null and b/public/ext/safari-15/images/toolbar-icon-32.png differ diff --git a/public/ext/safari-15/images/toolbar-icon-48.png b/public/ext/safari-15/images/toolbar-icon-48.png new file mode 100644 index 00000000..e1367b2b Binary files /dev/null and b/public/ext/safari-15/images/toolbar-icon-48.png differ diff --git a/public/ext/safari-15/manifest.json b/public/ext/safari-15/manifest.json new file mode 100644 index 00000000..1f64988a --- /dev/null +++ b/public/ext/safari-15/manifest.json @@ -0,0 +1,62 @@ +{ + "manifest_version": 2, + "default_locale": "en", + "name": "__MSG_extension_name__", + "description": "__MSG_extension_description__", + "version": "_VERSION_", + "icons": { + "48": "images/icon-48.png", + "64": "images/icon-64.png", + "96": "images/icon-96.png", + "128": "images/icon-128.png", + "256": "images/icon-256.png", + "512": "images/icon-512.png" + }, + "background": { + "scripts": ["dist/background.js"], + "persistent": false + }, + "browser_action": { + "default_popup": "dist/entry-ext-action-popup.html", + "default_icon": { + "16": "images/toolbar-icon-16.png", + "32": "images/toolbar-icon-32.png", + "48": "images/toolbar-icon-48.png" + } + }, + "options_ui": { + "page": "dist/entry-ext-extension-page.html#state=settings" + }, + "content_scripts": [ + { + "js": ["dist/content-scripts/userscripts.js"], + "matches": [""], + "run_at": "document_start", + "all_frames": true + }, + { + "js": ["dist/content-scripts/dot-user-js.js"], + "matches": ["*://*/*.user.js", "*://*/*.user.js?*"], + "run_at": "document_start" + }, + { + "js": ["dist/content-scripts/script-market.js"], + "matches": ["*://*.greasyfork.org/*"], + "exclude_matches": ["*://*/*.user.js", "*://*/*.user.js?*"], + "run_at": "document_end" + } + ], + "permissions": [ + "", + "clipboardWrite", + "contextMenus", + "declarativeNetRequest", + "declarativeNetRequestWithHostAccess", + "menus", + "nativeMessaging", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation" + ] +} diff --git a/public/ext/safari-16.4/images/128.svg b/public/ext/safari-16.4/images/128.svg new file mode 100644 index 00000000..c6ec62ad --- /dev/null +++ b/public/ext/safari-16.4/images/128.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/ext/safari-16.4/images/action.svg b/public/ext/safari-16.4/images/action.svg new file mode 100644 index 00000000..ad16f09d --- /dev/null +++ b/public/ext/safari-16.4/images/action.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/ext/safari-16.4/manifest.json b/public/ext/safari-16.4/manifest.json new file mode 100644 index 00000000..ab445895 --- /dev/null +++ b/public/ext/safari-16.4/manifest.json @@ -0,0 +1,51 @@ +{ + "manifest_version": 2, + "default_locale": "en", + "name": "__MSG_extension_name__", + "description": "__MSG_extension_description__", + "version": "_VERSION_", + "icons": { + "48": "images/128.svg", + "96": "images/128.svg", + "128": "images/128.svg", + "256": "images/128.svg", + "512": "images/128.svg" + }, + "background": { + "scripts": ["dist/background.js"], + "persistent": false + }, + "browser_action": { + "default_popup": "dist/entry-ext-action-popup.html", + "default_icon": "images/action.svg" + }, + "options_ui": { + "page": "dist/entry-ext-extension-page.html#state=settings" + }, + "content_scripts": [ + { + "js": ["dist/content-scripts/userscripts.js"], + "matches": [""], + "run_at": "document_start", + "all_frames": true + } + ], + "permissions": [ + "", + "clipboardWrite", + "contextMenus", + "declarativeNetRequestWithHostAccess", + "menus", + "nativeMessaging", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation" + ], + "browser_specific_settings": { + "safari": { + "strict_min_version": "16.4" + } + } +} diff --git a/public/ext/safari-dev/manifest-ios.json b/public/ext/safari-dev/manifest-ios.json new file mode 100644 index 00000000..e1cac050 --- /dev/null +++ b/public/ext/safari-dev/manifest-ios.json @@ -0,0 +1,52 @@ +{ + "manifest_version": 2, + "default_locale": "en", + "name": "__MSG_extension_name__", + "description": "__MSG_extension_description__", + "version": "_VERSION_", + "icons": { + "48": "images/128.svg", + "96": "images/128.svg", + "128": "images/128.svg", + "256": "images/128.svg", + "512": "images/128.svg" + }, + "background": { + "page": "dist/entry-ext-background.html", + "persistent": false + }, + "browser_action": { + "default_popup": "dist/entry-ext-action-popup.html", + "default_icon": "images/action.svg" + }, + "options_ui": { + "page": "dist/entry-ext-extension-page.html#state=settings" + }, + "content_scripts": [ + { + "js": ["dist/content-scripts/userscripts.js"], + "matches": [""], + "run_at": "document_start", + "all_frames": true + } + ], + "permissions": [ + "", + "clipboardWrite", + "contextMenus", + "declarativeNetRequestWithHostAccess", + "menus", + "nativeMessaging", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation" + ], + "browser_specific_settings": { + "safari": { + "strict_min_version": "16.4" + } + }, + "content_security_policy": "script-src 'self' https://localhost:55173 https://userscripts.test:55173" +} diff --git a/public/ext/safari-dev/manifest-mac.json b/public/ext/safari-dev/manifest-mac.json new file mode 100644 index 00000000..e469aa66 --- /dev/null +++ b/public/ext/safari-dev/manifest-mac.json @@ -0,0 +1,52 @@ +{ + "manifest_version": 2, + "default_locale": "en", + "name": "__MSG_extension_name__", + "description": "__MSG_extension_description__", + "version": "_VERSION_", + "icons": { + "48": "images/128.svg", + "96": "images/128.svg", + "128": "images/128.svg", + "256": "images/128.svg", + "512": "images/128.svg" + }, + "background": { + "page": "dist/entry-ext-background.html", + "persistent": true + }, + "browser_action": { + "default_popup": "dist/entry-ext-action-popup.html", + "default_icon": "images/action.svg" + }, + "options_ui": { + "page": "dist/entry-ext-extension-page.html#state=settings" + }, + "content_scripts": [ + { + "js": ["dist/content-scripts/userscripts.js"], + "matches": [""], + "run_at": "document_start", + "all_frames": true + } + ], + "permissions": [ + "", + "clipboardWrite", + "contextMenus", + "declarativeNetRequestWithHostAccess", + "menus", + "nativeMessaging", + "scripting", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation" + ], + "browser_specific_settings": { + "safari": { + "strict_min_version": "16.4" + } + }, + "content_security_policy": "script-src 'self' https://localhost:55173 https://userscripts.test:55173" +} diff --git a/public/ext/shared-dev/images/128.svg b/public/ext/shared-dev/images/128.svg new file mode 100644 index 00000000..76189438 --- /dev/null +++ b/public/ext/shared-dev/images/128.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/ext/shared-dev/images/action.svg b/public/ext/shared-dev/images/action.svg new file mode 100644 index 00000000..3789dd89 --- /dev/null +++ b/public/ext/shared-dev/images/action.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/ext/shared/_locales/en/messages.json b/public/ext/shared/_locales/en/messages.json new file mode 100644 index 00000000..c17c6cad --- /dev/null +++ b/public/ext/shared/_locales/en/messages.json @@ -0,0 +1,268 @@ +{ + "extension_name": { + "message": "_NAME_", + "description": "The display name for the extension." + }, + "extension_description": { + "message": "Save and run javascript for the web pages you visit", + "description": "Description of what the extension does." + }, + "settings": { + "message": "Settings" + }, + "settings_section_editor": { + "message": "Editor Settings" + }, + "settings_section_general": { + "message": "General Settings" + }, + "settings_section_native": { + "message": "Native Settings" + }, + "settings_section_tools": { + "message": "Tools" + }, + "settings_section_about": { + "message": "About" + }, + "settings_editor_auto_hint": { + "message": "Auto Hint" + }, + "settings_editor_auto_hint_desc": { + "message": "Automatically shows completion hints while editing" + }, + "settings_editor_close_brackets": { + "message": "Auto Close Brackets" + }, + "settings_editor_close_brackets_desc": { + "message": "Toggles on/off auto closing of brackets in the editor, this affects the following characters: () [] {} \"\" ''" + }, + "settings_editor_javascript_lint": { + "message": "JavaScript Linter" + }, + "settings_editor_javascript_lint_desc": { + "message": "Toggles the basic javascript linting within the editor" + }, + "settings_editor_list_descriptions": { + "message": "Sidebar Item Descriptions" + }, + "settings_editor_list_descriptions_desc": { + "message": "Show or hides the item descriptions in the sidebar" + }, + "settings_editor_list_sort": { + "message": "Sidebar Item Sort Order" + }, + "settings_editor_list_sort_desc": { + "message": "Display order of items in the sidebar" + }, + "settings_editor_list_sort_nameAsc": { + "message": "Scripts Name: Asc" + }, + "settings_editor_list_sort_nameDesc": { + "message": "Scripts Name: Desc" + }, + "settings_editor_list_sort_lastModifiedAsc": { + "message": "Last Modified: Asc" + }, + "settings_editor_list_sort_lastModifiedDesc": { + "message": "Last Modified: Desc" + }, + "settings_editor_show_whitespace": { + "message": "Show Whitespace Characters" + }, + "settings_editor_show_whitespace_desc": { + "message": "Toggles the display of invisible characters in the editor" + }, + "settings_editor_tab_size": { + "message": "Tab Size" + }, + "settings_editor_tab_size_desc": { + "message": "Choose the number of spaces a tab is equal to when rendering code" + }, + "settings_global_active": { + "message": "Enable Injection" + }, + "settings_global_active_desc": { + "message": "Toggle on/off script injection for the pages you visit" + }, + "settings_theme_mode": { + "message": "Theme mode" + }, + "settings_theme_mode_desc": { + "message": "Toggle theme mode" + }, + "settings_theme_mode_auto": { + "message": "Auto (System)" + }, + "settings_theme_mode_dark": { + "message": "Dark" + }, + "settings_theme_mode_light": { + "message": "Light" + }, + "settings_theme_mode_custom": { + "message": "Custom" + }, + "settings_global_exclude_match": { + "message": "Global Exclude Match patterns" + }, + "settings_global_exclude_match_desc": { + "message": "This input accepts a whitespace (spaces, newlines etc.) separated list of @match patterns, a page url that matches against a pattern in this list will be ignored for script injection" + }, + "settings_global_exclude_match_done": { + "message": "Done" + }, + "settings_global_exclude_match_saving": { + "message": "Saving..." + }, + "settings_global_exclude_match_placeholder": { + "message": "list of @match patterns, for example: \n*://*/*foo.bar\n*://*/*foo.bar?*\n*://*.example.net/*\nhttps://example.net/*/foo/*/\nhttps://*.example.net/a/b/c/?foo=/" + }, + "settings_global_exclude_match_refer": { + "message": "Please refer to:" + }, + "settings_scripts_update_automation": { + "message": "Scripts Updates Automatically" + }, + "settings_scripts_update_automation_desc": { + "message": "Script silently auto-updates in the background, which is dangerous and may introduce unconfirmed malicious code" + }, + "settings_scripts_update_check_interval": { + "message": "Scripts Update Check Interval" + }, + "settings_scripts_update_check_interval_desc": { + "message": "Disable or set user scripts update check interval (days)" + }, + "settings_scripts_update_check_interval_0": { + "message": "Never" + }, + "settings_scripts_update_check_lasttime": { + "message": "Scripts Update Check Lasttime" + }, + "settings_scripts_update_check_lasttime_desc": { + "message": "The lasttime for script update check in background" + }, + "settings_settings_sync": { + "message": "Sync Settings" + }, + "settings_settings_sync_desc": { + "message": "Sync settings across devices" + }, + "settings_augmented_userjs_install": { + "message": "Enhanced installation prompts" + }, + "settings_augmented_userjs_install_desc": { + "message": "Automatically pop up the installation interface when opening a user script URL (.user.js), and takes over the install button of the user scripts market" + }, + "settings_toolbar_badge_count": { + "message": "Show Toolbar Count Badge" + }, + "settings_toolbar_badge_count_desc": { + "message": "Displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on" + }, + "settings_scripts_directory": { + "message": "Save Location" + }, + "settings_scripts_directory_desc": { + "message": "Path to the folder where user scripts are stored" + }, + "settings_set_scripts_directory": { + "message": "Change save location" + }, + "settings_section_tools_import": { + "message": "Import" + }, + "settings_section_tools_export": { + "message": "Export" + }, + "settings_section_tools_reset": { + "message": "Reset" + }, + "settings_section_tools_goback": { + "message": "Go Back" + }, + "settings_section_tools_reset_all": { + "message": "Reset All Settings" + }, + "settings_section_tools_reset_single": { + "message": "Reset" + }, + "settings_section_tools_reset_section": { + "message": "Reset Section" + }, + "settings_scripts_tools_desc": { + "message": "These tools can help you import and export \"settings data\". Reset single, section or all settings." + }, + "settings_about_text1": { + "message": "Get more information about this extension by visiting the open source project:" + }, + "settings_about_text2": { + "message": "If you enjoy using this extension, please consider leaving a review on the App Store or sign up to beta test new versions:" + }, + "settings_about_button_repo": { + "message": "Code repository" + }, + "settings_about_button_docs": { + "message": "Documentation" + }, + "settings_about_button_issues": { + "message": "Report bugs" + }, + "settings_about_button_store": { + "message": "Open in the App Store" + }, + "settings_about_button_beta": { + "message": "Sign up for beta testing" + }, + "utils_check_match_patterns_0": { + "message": "The match pattern will match all URLs" + }, + "utils_check_match_patterns_1": { + "message": "The scheme component should one of *, https, http" + }, + "utils_check_match_patterns_2": { + "message": "The scheme and host should separated by `://`" + }, + "utils_check_match_patterns_3": { + "message": "The match pattern has no path component" + }, + "utils_check_match_patterns_4": { + "message": "The `*.` should followed by part of the hostname" + }, + "utils_check_match_patterns_5": { + "message": "The host component length should be 1-255" + }, + "utils_check_match_patterns_6": { + "message": "The `*` in host component should be independent or `*.` at the start" + }, + "utils_check_match_patterns_7": { + "message": "The host component contains empty label(s)" + }, + "utils_check_match_patterns_8": { + "message": "The hostname label cannot start or end with `-` character" + }, + "utils_check_match_patterns_9": { + "message": "The maximum length of the hostname label cannot exceed 63" + }, + "utils_check_match_patterns_10": { + "message": "The host component contains invalid character(s): $1" + }, + "utils_check_match_patterns_11": { + "message": "The path component contains invalid character(s): $1" + }, + "msg_invalid_match_pattern": { + "message": "Invalid match pattern" + }, + "msg_invalid_backup_file": { + "message": "Not a valid backup file" + }, + "msg_backup_import_finish": { + "message": "Import completed! ✅" + }, + "msg_backup_import_failed": { + "message": "Import failed, please check the error log in the console" + }, + "msg_settings_reset_finish": { + "message": "Reset all settings completed! ✅" + } +} diff --git a/public/ext/shared/_locales/zh/messages.json b/public/ext/shared/_locales/zh/messages.json new file mode 100644 index 00000000..7338f30a --- /dev/null +++ b/public/ext/shared/_locales/zh/messages.json @@ -0,0 +1,246 @@ +{ + "extension_description": { + "message": "用户脚本和样式管理器", + "description": "Description of what the extension does." + }, + "settings": { + "message": "设置" + }, + "settings_section_editor": { + "message": "编辑器设置" + }, + "settings_section_general": { + "message": "通用设置" + }, + "settings_section_native": { + "message": "本地设置" + }, + "settings_section_tools": { + "message": "工具" + }, + "settings_section_about": { + "message": "关于" + }, + "settings_editor_auto_hint": { + "message": "自动提示(Hint)" + }, + "settings_editor_auto_hint_desc": { + "message": "编辑时自动显示完成提示" + }, + "settings_editor_close_brackets": { + "message": "自动关闭括号" + }, + "settings_editor_close_brackets_desc": { + "message": "在编辑器中启用自动关闭括号,这会影响以下字符:() [] {} \"\" ''" + }, + "settings_editor_javascript_lint": { + "message": "JavaScript Linter" + }, + "settings_editor_javascript_lint_desc": { + "message": "在编辑器中启用基本的 JavaScript Linting(代码检查)" + }, + "settings_editor_list_descriptions": { + "message": "侧栏项目描述" + }, + "settings_editor_list_descriptions_desc": { + "message": "显示或隐藏侧边栏中项目的描述" + }, + "settings_editor_list_sort": { + "message": "侧栏项目排序" + }, + "settings_editor_list_sort_desc": { + "message": "侧边栏中项目的显示顺序" + }, + "settings_editor_list_sort_nameAsc": { + "message": "项目名称: 升序" + }, + "settings_editor_list_sort_nameDesc": { + "message": "项目名称: 降序" + }, + "settings_editor_list_sort_lastModifiedAsc": { + "message": "最后修改: 升序" + }, + "settings_editor_list_sort_lastModifiedDesc": { + "message": "最后修改: 降序" + }, + "settings_editor_show_whitespace": { + "message": "显示空白字符" + }, + "settings_editor_show_whitespace_desc": { + "message": "切换编辑器中不可见字符的显示" + }, + "settings_editor_tab_size": { + "message": "制表符大小" + }, + "settings_editor_tab_size_desc": { + "message": "选择渲染代码时制表符等于的空格数" + }, + "settings_global_active": { + "message": "启用注入" + }, + "settings_global_active_desc": { + "message": "全局脚本注入的开启或关闭" + }, + "settings_global_exclude_match": { + "message": "全局排除匹配模式列表" + }, + "settings_global_exclude_match_desc": { + "message": "此输入接受以空白符(空格、换行等)分隔的 @match 模式列表,与此列表中的模式匹配的页面 URL 将在脚本注入时被忽略" + }, + "settings_global_exclude_match_done": { + "message": "完成" + }, + "settings_global_exclude_match_saving": { + "message": "保存中..." + }, + "settings_global_exclude_match_placeholder": { + "message": "@match 模式列表,例如:\n*://*/*foo.bar\n*://*/*foo.bar?*\n*://*.example.net/*\nhttps://example.net/*/foo/*/\nhttps://*.example.net/a/b/c/?foo=/" + }, + "settings_global_exclude_match_refer": { + "message": "匹配模式结构请参考:" + }, + "settings_scripts_update_automation": { + "message": "自动更新脚本" + }, + "settings_scripts_update_automation_desc": { + "message": "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码" + }, + "settings_scripts_update_check_interval": { + "message": "脚本更新检查间隔" + }, + "settings_scripts_update_check_interval_desc": { + "message": "禁用或设置用户脚本更新检查间隔(天)" + }, + "settings_scripts_update_check_interval_0": { + "message": "从不" + }, + "settings_scripts_update_check_lasttime": { + "message": "脚本更新上次检查时间" + }, + "settings_scripts_update_check_lasttime_desc": { + "message": "后台脚本更新检查上次时间" + }, + "settings_settings_sync": { + "message": "同步设置" + }, + "settings_settings_sync_desc": { + "message": "跨设备同步设置" + }, + "settings_augmented_userjs_install": { + "message": "增强的脚本安装提示" + }, + "settings_augmented_userjs_install_desc": { + "message": "当打开一个用户脚本 URL(.user.js)时自动弹出安装界面,并接管用户脚本市场的安装按钮" + }, + "settings_toolbar_badge_count": { + "message": "工具栏图标显示计数徽章" + }, + "settings_toolbar_badge_count_desc": { + "message": "在工具栏图标上显示一个徽章,其中的数字代表有多少个已启用的脚本与您所在页面的 URL 匹配" + }, + "settings_scripts_directory": { + "message": "保存位置" + }, + "settings_scripts_directory_desc": { + "message": "存储用户脚本的文件夹路径" + }, + "settings_set_scripts_directory": { + "message": "更改保存位置" + }, + "settings_section_tools_import": { + "message": "导入" + }, + "settings_section_tools_export": { + "message": "导出" + }, + "settings_section_tools_reset": { + "message": "还原" + }, + "settings_section_tools_goback": { + "message": "返回" + }, + "settings_section_tools_reset_all": { + "message": "还原所有设置" + }, + "settings_section_tools_reset_single": { + "message": "还原" + }, + "settings_section_tools_reset_section": { + "message": "还原该组" + }, + "settings_scripts_tools_desc": { + "message": "这些工具可以帮助您导入和导出“设置数据”。重置单个、一组或所有设置。" + }, + "settings_about_text1": { + "message": "获取有关此扩展的更多信息,请访问本开源项目:" + }, + "settings_about_text2": { + "message": "如果您喜欢使用此扩展,请考虑在 App Store 上留下您的评论或注册 Beta 测试新版本:" + }, + "settings_about_button_repo": { + "message": "代码库" + }, + "settings_about_button_docs": { + "message": "文档" + }, + "settings_about_button_issues": { + "message": "报告错误" + }, + "settings_about_button_store": { + "message": "在 App Store 中打开" + }, + "settings_about_button_beta": { + "message": "注册 Beta 测试版" + }, + "utils_check_match_patterns_0": { + "message": "此匹配模式将匹配所有 URL" + }, + "utils_check_match_patterns_1": { + "message": "这 scheme 部分应当为 *、https、http 之一" + }, + "utils_check_match_patterns_2": { + "message": "这 scheme 和 host 部分应当用 `://` 分隔" + }, + "utils_check_match_patterns_3": { + "message": "匹配模式缺少 path 部分(至少应当有`/`)" + }, + "utils_check_match_patterns_4": { + "message": "这 `*.` 后面应当跟随主机名的一部分" + }, + "utils_check_match_patterns_5": { + "message": "这 host 部分长度应当为 1-255" + }, + "utils_check_match_patterns_6": { + "message": "这 host 部分中的 `*` 应该是独立的或为 `*.` 在开头" + }, + "utils_check_match_patterns_7": { + "message": "这 host 部分包含一个或多个空标签" + }, + "utils_check_match_patterns_8": { + "message": "主机名标签不能以 `-` 字符开头或结尾" + }, + "utils_check_match_patterns_9": { + "message": "主机名标签最大长度不能超过 63" + }, + "utils_check_match_patterns_10": { + "message": "这 host 部分包含无效字符:$1" + }, + "utils_check_match_patterns_11": { + "message": "这 path 部分包含无效字符:$1" + }, + "msg_invalid_match_pattern": { + "message": "无效的匹配模式" + }, + "msg_invalid_backup_file": { + "message": "无效的备份文件" + }, + "msg_backup_import_finish": { + "message": "导入完成!✅" + }, + "msg_backup_import_failed": { + "message": "导入失败,请在控制台中检查错误日志" + }, + "msg_settings_reset_finish": { + "message": "还原所有设置完成!✅" + } +} diff --git a/public/ext/shared/_locales/zh_HK b/public/ext/shared/_locales/zh_HK new file mode 120000 index 00000000..dec9132e --- /dev/null +++ b/public/ext/shared/_locales/zh_HK @@ -0,0 +1 @@ +zh_TW \ No newline at end of file diff --git a/public/ext/shared/_locales/zh_MO b/public/ext/shared/_locales/zh_MO new file mode 120000 index 00000000..dec9132e --- /dev/null +++ b/public/ext/shared/_locales/zh_MO @@ -0,0 +1 @@ +zh_TW \ No newline at end of file diff --git a/public/ext/shared/_locales/zh_TW/messages.json b/public/ext/shared/_locales/zh_TW/messages.json new file mode 100644 index 00000000..07853cb3 --- /dev/null +++ b/public/ext/shared/_locales/zh_TW/messages.json @@ -0,0 +1,246 @@ +{ + "extension_description": { + "message": "使用者腳本和樣式管理器", + "description": "Description of what the extension does." + }, + "settings": { + "message": "設定" + }, + "settings_section_editor": { + "message": "編輯器設定" + }, + "settings_section_general": { + "message": "一般設定" + }, + "settings_section_native": { + "message": "本地設定" + }, + "settings_section_tools": { + "message": "工具" + }, + "settings_section_about": { + "message": "關於" + }, + "settings_editor_auto_hint": { + "message": "自動提示(Hint)" + }, + "settings_editor_auto_hint_desc": { + "message": "編輯時自動顯示完成提示" + }, + "settings_editor_close_brackets": { + "message": "自動關閉括號" + }, + "settings_editor_close_brackets_desc": { + "message": "在編輯器中啟用自動關閉括號,這會影響以下字元:() [] {} \"\" ''" + }, + "settings_editor_javascript_lint": { + "message": "JavaScript Linter" + }, + "settings_editor_javascript_lint_desc": { + "message": "在編輯器中啟用基本的 JavaScript Linting(程式碼檢查)" + }, + "settings_editor_list_descriptions": { + "message": "側欄項目描述" + }, + "settings_editor_list_descriptions_desc": { + "message": "顯示或隱藏側邊欄中項目的描述" + }, + "settings_editor_list_sort": { + "message": "側欄項目排序" + }, + "settings_editor_list_sort_desc": { + "message": "側邊欄中項目的顯示順序" + }, + "settings_editor_list_sort_nameAsc": { + "message": "項目名稱: 升序" + }, + "settings_editor_list_sort_nameDesc": { + "message": "項目名稱: 降序" + }, + "settings_editor_list_sort_lastModifiedAsc": { + "message": "最後修改: 升序" + }, + "settings_editor_list_sort_lastModifiedDesc": { + "message": "最後修改: 降序" + }, + "settings_editor_show_whitespace": { + "message": "顯示空白字元" + }, + "settings_editor_show_whitespace_desc": { + "message": "切換編輯器中不可見字元的顯示" + }, + "settings_editor_tab_size": { + "message": "製表符大小" + }, + "settings_editor_tab_size_desc": { + "message": "選擇渲染程式碼時製表符等於的空格數" + }, + "settings_global_active": { + "message": "啟用注入" + }, + "settings_global_active_desc": { + "message": "全域性腳本注入的開啟或關閉" + }, + "settings_global_exclude_match": { + "message": "全域性排除匹配模式列表" + }, + "settings_global_exclude_match_desc": { + "message": "此輸入接受以空白符(空格、換行等)分隔的 @match 模式列表,與此列表中的模式匹配的頁面 URL 將在腳本注入時被忽略" + }, + "settings_global_exclude_match_done": { + "message": "完成" + }, + "settings_global_exclude_match_saving": { + "message": "儲存中..." + }, + "settings_global_exclude_match_placeholder": { + "message": "@match 模式列表,例如:\n*://*/*foo.bar\n*://*/*foo.bar?*\n*://*.example.net/*\nhttps://example.net/*/foo/*/\nhttps://*.example.net/a/b/c/?foo=/" + }, + "settings_global_exclude_match_refer": { + "message": "匹配模式結構請參考:" + }, + "settings_scripts_update_automation": { + "message": "自動更新腳本" + }, + "settings_scripts_update_automation_desc": { + "message": "腳本在後臺靜默自動更新,這是危險的,可能引入未經確認的惡意程式碼" + }, + "settings_scripts_update_check_interval": { + "message": "腳本更新檢查間隔" + }, + "settings_scripts_update_check_interval_desc": { + "message": "禁用或設定使用者腳本更新檢查間隔(天)" + }, + "settings_scripts_update_check_interval_0": { + "message": "從不" + }, + "settings_scripts_update_check_lasttime": { + "message": "腳本更新上次檢查時間" + }, + "settings_scripts_update_check_lasttime_desc": { + "message": "後臺腳本更新檢查上次時間" + }, + "settings_settings_sync": { + "message": "同步設定" + }, + "settings_settings_sync_desc": { + "message": "跨裝置同步設定" + }, + "settings_augmented_userjs_install": { + "message": "增強的腳本安裝提示" + }, + "settings_augmented_userjs_install_desc": { + "message": "當開啟一個使用者腳本 URL(.user.js)時自動彈出安裝介面,並接管使用者腳本市場的安裝按鈕" + }, + "settings_toolbar_badge_count": { + "message": "工具欄圖示顯示計數徽章" + }, + "settings_toolbar_badge_count_desc": { + "message": "在工具欄圖示上顯示一個徽章,其中的數字代表有多少個已啟用的腳本與您所在頁面的 URL 匹配" + }, + "settings_scripts_directory": { + "message": "儲存位置" + }, + "settings_scripts_directory_desc": { + "message": "儲存使用者腳本的資料夾路徑" + }, + "settings_set_scripts_directory": { + "message": "更改儲存位置" + }, + "settings_section_tools_import": { + "message": "匯入" + }, + "settings_section_tools_export": { + "message": "匯出" + }, + "settings_section_tools_reset": { + "message": "還原" + }, + "settings_section_tools_goback": { + "message": "返回" + }, + "settings_section_tools_reset_all": { + "message": "還原所有設定" + }, + "settings_section_tools_reset_single": { + "message": "還原" + }, + "settings_section_tools_reset_section": { + "message": "還原該組" + }, + "settings_scripts_tools_desc": { + "message": "這些工具可以幫助您匯入和匯出“設定資料”。重置單個、一組或所有設定。" + }, + "settings_about_text1": { + "message": "獲取有關此延伸功能的更多資訊,請訪問本開源項目:" + }, + "settings_about_text2": { + "message": "如果您喜歡使用此延伸功能,請考慮在 App Store 上留下您的評論或註冊 Beta 測試新版本:" + }, + "settings_about_button_repo": { + "message": "代碼庫" + }, + "settings_about_button_docs": { + "message": "文件" + }, + "settings_about_button_issues": { + "message": "報告錯誤" + }, + "settings_about_button_store": { + "message": "在 App Store 中開啟" + }, + "settings_about_button_beta": { + "message": "註冊 Beta 測試版" + }, + "utils_check_match_patterns_0": { + "message": "此匹配模式將匹配所有 URL" + }, + "utils_check_match_patterns_1": { + "message": "這 scheme 部分應當為 *、https、http 之一" + }, + "utils_check_match_patterns_2": { + "message": "這 scheme 和 host 部分應當用 `://` 分隔" + }, + "utils_check_match_patterns_3": { + "message": "匹配模式缺少 path 部分(至少應當有`/`)" + }, + "utils_check_match_patterns_4": { + "message": "這 `*.` 後面應當跟隨主機名的一部分" + }, + "utils_check_match_patterns_5": { + "message": "這 host 部分長度應當為 1-255" + }, + "utils_check_match_patterns_6": { + "message": "這 host 部分中的 `*` 應該是獨立的或為 `*.` 在開頭" + }, + "utils_check_match_patterns_7": { + "message": "這 host 部分包含一個或多個空標籤" + }, + "utils_check_match_patterns_8": { + "message": "主機名標籤不能以 `-` 字元開頭或結尾" + }, + "utils_check_match_patterns_9": { + "message": "主機名標籤最大長度不能超過 63" + }, + "utils_check_match_patterns_10": { + "message": "這 host 部分包含無效字元:$1" + }, + "utils_check_match_patterns_11": { + "message": "這 path 部分包含無效字元:$1" + }, + "msg_invalid_match_pattern": { + "message": "無效的匹配模式" + }, + "msg_invalid_backup_file": { + "message": "無效的備份檔案" + }, + "msg_backup_import_finish": { + "message": "匯入完成!✅" + }, + "msg_backup_import_failed": { + "message": "匯入失敗,請在控制檯中檢查錯誤日誌" + }, + "msg_settings_reset_finish": { + "message": "還原所有設定完成!✅" + } +} diff --git a/public/ext/vendor/jshint.min.js b/public/ext/vendor/jshint.min.js new file mode 100644 index 00000000..4819821b --- /dev/null +++ b/public/ext/vendor/jshint.min.js @@ -0,0 +1,28 @@ +/*! 2.13.6 */ var JSHINT;"undefined"==typeof window&&(window={}),function(){var _;JSHINT=(_=function $(e,t,n){function r(u,o){if(!t[u]){if(!e[u]){var a="function"==typeof _&&_;if(!o&&a)return a(u,!0);if(i)return i(u,!0);var s=Error("Cannot find module '"+u+"'");throw s.code="MODULE_NOT_FOUND",s}var c=t[u]={exports:{}};e[u][0].call(c.exports,function(_){var $;return r(e[u][1][_]||_)},c,c.exports,$,e,t,n)}return t[u].exports}for(var i="function"==typeof _&&_,u=0;u=65&&n<=90||95===n||n>=97&&n<=122;for(var r=[],n=0;n<128;n++)r[n]=t[n]||n>=48&&n<=57;$.exports={asciiIdentifierStartTable:t,asciiIdentifierPartTable:r}},{}],2:[function(_,$,e){$.exports=/^(?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0621-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971\u0972\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC\u0EDD\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8B\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u2094\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA65F\uA662-\uA66E\uA67F-\uA697\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])(?:[\$0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u0525\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0621-\u065E\u0660-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0900-\u0939\u093C-\u094E\u0950-\u0955\u0958-\u0963\u0966-\u096F\u0971\u0972\u0979-\u097F\u0981-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C01-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C82\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC\u0EDD\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F8B\u0F90-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10D0-\u10FA\u10FC\u1100-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F0\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17B3\u17B6-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191C\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BAA\u1BAE-\u1BB9\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF2\u1D00-\u1DE6\u1DFD-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u2094\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF1\u2D00-\u2D25\u2D30-\u2D65\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31B7\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCB\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA65F\uA662-\uA66F\uA67C\uA67D\uA67F-\uA697\uA6A0-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B\uA78C\uA7FB-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7B\uAA80-\uAAC2\uAADB-\uAADD\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA2D\uFA30-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE26\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])*$/},{}],3:[function(_,$,e){var t="183,768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,903,1155,1156,1157,1158,1159,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1471,1473,1474,1476,1477,1479,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1648,1750,1751,1752,1753,1754,1755,1756,1759,1760,1761,1762,1763,1764,1767,1768,1770,1771,1772,1773,1776,1777,1778,1779,1780,1781,1782,1783,1784,1785,1809,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,2027,2028,2029,2030,2031,2032,2033,2034,2035,2045,2070,2071,2072,2073,2075,2076,2077,2078,2079,2080,2081,2082,2083,2085,2086,2087,2089,2090,2091,2092,2093,2137,2138,2139,2259,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2362,2363,2364,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2385,2386,2387,2388,2389,2390,2391,2402,2403,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2433,2434,2435,2492,2494,2495,2496,2497,2498,2499,2500,2503,2504,2507,2508,2509,2519,2530,2531,2534,2535,2536,2537,2538,2539,2540,2541,2542,2543,2558,2561,2562,2563,2620,2622,2623,2624,2625,2626,2631,2632,2635,2636,2637,2641,2662,2663,2664,2665,2666,2667,2668,2669,2670,2671,2672,2673,2677,2689,2690,2691,2748,2750,2751,2752,2753,2754,2755,2756,2757,2759,2760,2761,2763,2764,2765,2786,2787,2790,2791,2792,2793,2794,2795,2796,2797,2798,2799,2810,2811,2812,2813,2814,2815,2817,2818,2819,2876,2878,2879,2880,2881,2882,2883,2884,2887,2888,2891,2892,2893,2902,2903,2914,2915,2918,2919,2920,2921,2922,2923,2924,2925,2926,2927,2946,3006,3007,3008,3009,3010,3014,3015,3016,3018,3019,3020,3021,3031,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3072,3073,3074,3075,3076,3134,3135,3136,3137,3138,3139,3140,3142,3143,3144,3146,3147,3148,3149,3157,3158,3170,3171,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3201,3202,3203,3260,3262,3263,3264,3265,3266,3267,3268,3270,3271,3272,3274,3275,3276,3277,3285,3286,3298,3299,3302,3303,3304,3305,3306,3307,3308,3309,3310,3311,3328,3329,3330,3331,3387,3388,3390,3391,3392,3393,3394,3395,3396,3398,3399,3400,3402,3403,3404,3405,3415,3426,3427,3430,3431,3432,3433,3434,3435,3436,3437,3438,3439,3458,3459,3530,3535,3536,3537,3538,3539,3540,3542,3544,3545,3546,3547,3548,3549,3550,3551,3558,3559,3560,3561,3562,3563,3564,3565,3566,3567,3570,3571,3633,3636,3637,3638,3639,3640,3641,3642,3655,3656,3657,3658,3659,3660,3661,3662,3664,3665,3666,3667,3668,3669,3670,3671,3672,3673,3761,3764,3765,3766,3767,3768,3769,3771,3772,3784,3785,3786,3787,3788,3789,3792,3793,3794,3795,3796,3797,3798,3799,3800,3801,3864,3865,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3893,3895,3897,3902,3903,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3974,3975,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4038,4139,4140,4141,4142,4143,4144,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157,4158,4160,4161,4162,4163,4164,4165,4166,4167,4168,4169,4182,4183,4184,4185,4190,4191,4192,4194,4195,4196,4199,4200,4201,4202,4203,4204,4205,4209,4210,4211,4212,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4239,4240,4241,4242,4243,4244,4245,4246,4247,4248,4249,4250,4251,4252,4253,4957,4958,4959,4969,4970,4971,4972,4973,4974,4975,4976,4977,5906,5907,5908,5938,5939,5940,5970,5971,6002,6003,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080,6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096,6097,6098,6099,6109,6112,6113,6114,6115,6116,6117,6118,6119,6120,6121,6155,6156,6157,6160,6161,6162,6163,6164,6165,6166,6167,6168,6169,6313,6432,6433,6434,6435,6436,6437,6438,6439,6440,6441,6442,6443,6448,6449,6450,6451,6452,6453,6454,6455,6456,6457,6458,6459,6470,6471,6472,6473,6474,6475,6476,6477,6478,6479,6608,6609,6610,6611,6612,6613,6614,6615,6616,6617,6618,6679,6680,6681,6682,6683,6741,6742,6743,6744,6745,6746,6747,6748,6749,6750,6752,6753,6754,6755,6756,6757,6758,6759,6760,6761,6762,6763,6764,6765,6766,6767,6768,6769,6770,6771,6772,6773,6774,6775,6776,6777,6778,6779,6780,6783,6784,6785,6786,6787,6788,6789,6790,6791,6792,6793,6800,6801,6802,6803,6804,6805,6806,6807,6808,6809,6832,6833,6834,6835,6836,6837,6838,6839,6840,6841,6842,6843,6844,6845,6912,6913,6914,6915,6916,6964,6965,6966,6967,6968,6969,6970,6971,6972,6973,6974,6975,6976,6977,6978,6979,6980,6992,6993,6994,6995,6996,6997,6998,6999,7000,7001,7019,7020,7021,7022,7023,7024,7025,7026,7027,7040,7041,7042,7073,7074,7075,7076,7077,7078,7079,7080,7081,7082,7083,7084,7085,7088,7089,7090,7091,7092,7093,7094,7095,7096,7097,7142,7143,7144,7145,7146,7147,7148,7149,7150,7151,7152,7153,7154,7155,7204,7205,7206,7207,7208,7209,7210,7211,7212,7213,7214,7215,7216,7217,7218,7219,7220,7221,7222,7223,7232,7233,7234,7235,7236,7237,7238,7239,7240,7241,7248,7249,7250,7251,7252,7253,7254,7255,7256,7257,7376,7377,7378,7380,7381,7382,7383,7384,7385,7386,7387,7388,7389,7390,7391,7392,7393,7394,7395,7396,7397,7398,7399,7400,7405,7410,7411,7412,7415,7416,7417,7616,7617,7618,7619,7620,7621,7622,7623,7624,7625,7626,7627,7628,7629,7630,7631,7632,7633,7634,7635,7636,7637,7638,7639,7640,7641,7642,7643,7644,7645,7646,7647,7648,7649,7650,7651,7652,7653,7654,7655,7656,7657,7658,7659,7660,7661,7662,7663,7664,7665,7666,7667,7668,7669,7670,7671,7672,7673,7675,7676,7677,7678,7679,8204,8205,8255,8256,8276,8400,8401,8402,8403,8404,8405,8406,8407,8408,8409,8410,8411,8412,8417,8421,8422,8423,8424,8425,8426,8427,8428,8429,8430,8431,8432,11503,11504,11505,11647,11744,11745,11746,11747,11748,11749,11750,11751,11752,11753,11754,11755,11756,11757,11758,11759,11760,11761,11762,11763,11764,11765,11766,11767,11768,11769,11770,11771,11772,11773,11774,11775,12330,12331,12332,12333,12334,12335,12441,12442,42528,42529,42530,42531,42532,42533,42534,42535,42536,42537,42607,42612,42613,42614,42615,42616,42617,42618,42619,42620,42621,42654,42655,42736,42737,43010,43014,43019,43043,43044,43045,43046,43047,43136,43137,43188,43189,43190,43191,43192,43193,43194,43195,43196,43197,43198,43199,43200,43201,43202,43203,43204,43205,43216,43217,43218,43219,43220,43221,43222,43223,43224,43225,43232,43233,43234,43235,43236,43237,43238,43239,43240,43241,43242,43243,43244,43245,43246,43247,43248,43249,43263,43264,43265,43266,43267,43268,43269,43270,43271,43272,43273,43302,43303,43304,43305,43306,43307,43308,43309,43335,43336,43337,43338,43339,43340,43341,43342,43343,43344,43345,43346,43347,43392,43393,43394,43395,43443,43444,43445,43446,43447,43448,43449,43450,43451,43452,43453,43454,43455,43456,43472,43473,43474,43475,43476,43477,43478,43479,43480,43481,43493,43504,43505,43506,43507,43508,43509,43510,43511,43512,43513,43561,43562,43563,43564,43565,43566,43567,43568,43569,43570,43571,43572,43573,43574,43587,43596,43597,43600,43601,43602,43603,43604,43605,43606,43607,43608,43609,43643,43644,43645,43696,43698,43699,43700,43703,43704,43710,43711,43713,43755,43756,43757,43758,43759,43765,43766,44003,44004,44005,44006,44007,44008,44009,44010,44012,44013,44016,44017,44018,44019,44020,44021,44022,44023,44024,44025,64286,65024,65025,65026,65027,65028,65029,65030,65031,65032,65033,65034,65035,65036,65037,65038,65039,65056,65057,65058,65059,65060,65061,65062,65063,65064,65065,65066,65067,65068,65069,65070,65071,65075,65076,65101,65102,65103,65296,65297,65298,65299,65300,65301,65302,65303,65304,65305,65343".split(",").map(function(_){return parseInt(_,10)});$.exports=t},{}],4:[function(_,$,e){var t="170,181,186,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,663,664,665,666,667,668,669,670,671,672,673,674,675,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,710,711,712,713,714,715,716,717,718,719,720,721,736,737,738,739,740,748,750,880,881,882,883,884,886,887,890,891,892,893,895,902,904,905,906,908,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,931,932,933,934,935,936,937,938,939,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,957,958,959,960,961,962,963,964,965,966,967,968,969,970,971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257,1258,1259,1260,1261,1262,1263,1264,1265,1266,1267,1268,1269,1270,1271,1272,1273,1274,1275,1276,1277,1278,1279,1280,1281,1282,1283,1284,1285,1286,1287,1288,1289,1290,1291,1292,1293,1294,1295,1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307,1308,1309,1310,1311,1312,1313,1314,1315,1316,1317,1318,1319,1320,1321,1322,1323,1324,1325,1326,1327,1329,1330,1331,1332,1333,1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347,1348,1349,1350,1351,1352,1353,1354,1355,1356,1357,1358,1359,1360,1361,1362,1363,1364,1365,1366,1369,1376,1377,1378,1379,1380,1381,1382,1383,1384,1385,1386,1387,1388,1389,1390,1391,1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411,1412,1413,1414,1415,1416,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508,1509,1510,1511,1512,1513,1514,1519,1520,1521,1522,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1646,1647,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720,1721,1722,1723,1724,1725,1726,1727,1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1749,1765,1766,1774,1775,1786,1787,1788,1791,1808,1810,1811,1812,1813,1814,1815,1816,1817,1818,1819,1820,1821,1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839,1869,1870,1871,1872,1873,1874,1875,1876,1877,1878,1879,1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903,1904,1905,1906,1907,1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919,1920,1921,1922,1923,1924,1925,1926,1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951,1952,1953,1954,1955,1956,1957,1969,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2036,2037,2042,2048,2049,2050,2051,2052,2053,2054,2055,2056,2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067,2068,2069,2074,2084,2088,2112,2113,2114,2115,2116,2117,2118,2119,2120,2121,2122,2123,2124,2125,2126,2127,2128,2129,2130,2131,2132,2133,2134,2135,2136,2144,2145,2146,2147,2148,2149,2150,2151,2152,2153,2154,2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219,2220,2221,2222,2223,2224,2225,2226,2227,2228,2230,2231,2232,2233,2234,2235,2236,2237,2308,2309,2310,2311,2312,2313,2314,2315,2316,2317,2318,2319,2320,2321,2322,2323,2324,2325,2326,2327,2328,2329,2330,2331,2332,2333,2334,2335,2336,2337,2338,2339,2340,2341,2342,2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356,2357,2358,2359,2360,2361,2365,2384,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2437,2438,2439,2440,2441,2442,2443,2444,2447,2448,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460,2461,2462,2463,2464,2465,2466,2467,2468,2469,2470,2471,2472,2474,2475,2476,2477,2478,2479,2480,2482,2486,2487,2488,2489,2493,2510,2524,2525,2527,2528,2529,2544,2545,2556,2565,2566,2567,2568,2569,2570,2575,2576,2579,2580,2581,2582,2583,2584,2585,2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597,2598,2599,2600,2602,2603,2604,2605,2606,2607,2608,2610,2611,2613,2614,2616,2617,2649,2650,2651,2652,2654,2674,2675,2676,2693,2694,2695,2696,2697,2698,2699,2700,2701,2703,2704,2705,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716,2717,2718,2719,2720,2721,2722,2723,2724,2725,2726,2727,2728,2730,2731,2732,2733,2734,2735,2736,2738,2739,2741,2742,2743,2744,2745,2749,2768,2784,2785,2809,2821,2822,2823,2824,2825,2826,2827,2828,2831,2832,2835,2836,2837,2838,2839,2840,2841,2842,2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856,2858,2859,2860,2861,2862,2863,2864,2866,2867,2869,2870,2871,2872,2873,2877,2908,2909,2911,2912,2913,2929,2947,2949,2950,2951,2952,2953,2954,2958,2959,2960,2962,2963,2964,2965,2969,2970,2972,2974,2975,2979,2980,2984,2985,2986,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3024,3077,3078,3079,3080,3081,3082,3083,3084,3086,3087,3088,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3133,3160,3161,3162,3168,3169,3200,3205,3206,3207,3208,3209,3210,3211,3212,3214,3215,3216,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,3239,3240,3242,3243,3244,3245,3246,3247,3248,3249,3250,3251,3253,3254,3255,3256,3257,3261,3294,3296,3297,3313,3314,3333,3334,3335,3336,3337,3338,3339,3340,3342,3343,3344,3346,3347,3348,3349,3350,3351,3352,3353,3354,3355,3356,3357,3358,3359,3360,3361,3362,3363,3364,3365,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379,3380,3381,3382,3383,3384,3385,3386,3389,3406,3412,3413,3414,3423,3424,3425,3450,3451,3452,3453,3454,3455,3461,3462,3463,3464,3465,3466,3467,3468,3469,3470,3471,3472,3473,3474,3475,3476,3477,3478,3482,3483,3484,3485,3486,3487,3488,3489,3490,3491,3492,3493,3494,3495,3496,3497,3498,3499,3500,3501,3502,3503,3504,3505,3507,3508,3509,3510,3511,3512,3513,3514,3515,3517,3520,3521,3522,3523,3524,3525,3526,3585,3586,3587,3588,3589,3590,3591,3592,3593,3594,3595,3596,3597,3598,3599,3600,3601,3602,3603,3604,3605,3606,3607,3608,3609,3610,3611,3612,3613,3614,3615,3616,3617,3618,3619,3620,3621,3622,3623,3624,3625,3626,3627,3628,3629,3630,3631,3632,3634,3635,3648,3649,3650,3651,3652,3653,3654,3713,3714,3716,3719,3720,3722,3725,3732,3733,3734,3735,3737,3738,3739,3740,3741,3742,3743,3745,3746,3747,3749,3751,3754,3755,3757,3758,3759,3760,3762,3763,3773,3776,3777,3778,3779,3780,3782,3804,3805,3806,3807,3840,3904,3905,3906,3907,3908,3909,3910,3911,3913,3914,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3976,3977,3978,3979,3980,4096,4097,4098,4099,4100,4101,4102,4103,4104,4105,4106,4107,4108,4109,4110,4111,4112,4113,4114,4115,4116,4117,4118,4119,4120,4121,4122,4123,4124,4125,4126,4127,4128,4129,4130,4131,4132,4133,4134,4135,4136,4137,4138,4159,4176,4177,4178,4179,4180,4181,4186,4187,4188,4189,4193,4197,4198,4206,4207,4208,4213,4214,4215,4216,4217,4218,4219,4220,4221,4222,4223,4224,4225,4238,4256,4257,4258,4259,4260,4261,4262,4263,4264,4265,4266,4267,4268,4269,4270,4271,4272,4273,4274,4275,4276,4277,4278,4279,4280,4281,4282,4283,4284,4285,4286,4287,4288,4289,4290,4291,4292,4293,4295,4301,4304,4305,4306,4307,4308,4309,4310,4311,4312,4313,4314,4315,4316,4317,4318,4319,4320,4321,4322,4323,4324,4325,4326,4327,4328,4329,4330,4331,4332,4333,4334,4335,4336,4337,4338,4339,4340,4341,4342,4343,4344,4345,4346,4348,4349,4350,4351,4352,4353,4354,4355,4356,4357,4358,4359,4360,4361,4362,4363,4364,4365,4366,4367,4368,4369,4370,4371,4372,4373,4374,4375,4376,4377,4378,4379,4380,4381,4382,4383,4384,4385,4386,4387,4388,4389,4390,4391,4392,4393,4394,4395,4396,4397,4398,4399,4400,4401,4402,4403,4404,4405,4406,4407,4408,4409,4410,4411,4412,4413,4414,4415,4416,4417,4418,4419,4420,4421,4422,4423,4424,4425,4426,4427,4428,4429,4430,4431,4432,4433,4434,4435,4436,4437,4438,4439,4440,4441,4442,4443,4444,4445,4446,4447,4448,4449,4450,4451,4452,4453,4454,4455,4456,4457,4458,4459,4460,4461,4462,4463,4464,4465,4466,4467,4468,4469,4470,4471,4472,4473,4474,4475,4476,4477,4478,4479,4480,4481,4482,4483,4484,4485,4486,4487,4488,4489,4490,4491,4492,4493,4494,4495,4496,4497,4498,4499,4500,4501,4502,4503,4504,4505,4506,4507,4508,4509,4510,4511,4512,4513,4514,4515,4516,4517,4518,4519,4520,4521,4522,4523,4524,4525,4526,4527,4528,4529,4530,4531,4532,4533,4534,4535,4536,4537,4538,4539,4540,4541,4542,4543,4544,4545,4546,4547,4548,4549,4550,4551,4552,4553,4554,4555,4556,4557,4558,4559,4560,4561,4562,4563,4564,4565,4566,4567,4568,4569,4570,4571,4572,4573,4574,4575,4576,4577,4578,4579,4580,4581,4582,4583,4584,4585,4586,4587,4588,4589,4590,4591,4592,4593,4594,4595,4596,4597,4598,4599,4600,4601,4602,4603,4604,4605,4606,4607,4608,4609,4610,4611,4612,4613,4614,4615,4616,4617,4618,4619,4620,4621,4622,4623,4624,4625,4626,4627,4628,4629,4630,4631,4632,4633,4634,4635,4636,4637,4638,4639,4640,4641,4642,4643,4644,4645,4646,4647,4648,4649,4650,4651,4652,4653,4654,4655,4656,4657,4658,4659,4660,4661,4662,4663,4664,4665,4666,4667,4668,4669,4670,4671,4672,4673,4674,4675,4676,4677,4678,4679,4680,4682,4683,4684,4685,4688,4689,4690,4691,4692,4693,4694,4696,4698,4699,4700,4701,4704,4705,4706,4707,4708,4709,4710,4711,4712,4713,4714,4715,4716,4717,4718,4719,4720,4721,4722,4723,4724,4725,4726,4727,4728,4729,4730,4731,4732,4733,4734,4735,4736,4737,4738,4739,4740,4741,4742,4743,4744,4746,4747,4748,4749,4752,4753,4754,4755,4756,4757,4758,4759,4760,4761,4762,4763,4764,4765,4766,4767,4768,4769,4770,4771,4772,4773,4774,4775,4776,4777,4778,4779,4780,4781,4782,4783,4784,4786,4787,4788,4789,4792,4793,4794,4795,4796,4797,4798,4800,4802,4803,4804,4805,4808,4809,4810,4811,4812,4813,4814,4815,4816,4817,4818,4819,4820,4821,4822,4824,4825,4826,4827,4828,4829,4830,4831,4832,4833,4834,4835,4836,4837,4838,4839,4840,4841,4842,4843,4844,4845,4846,4847,4848,4849,4850,4851,4852,4853,4854,4855,4856,4857,4858,4859,4860,4861,4862,4863,4864,4865,4866,4867,4868,4869,4870,4871,4872,4873,4874,4875,4876,4877,4878,4879,4880,4882,4883,4884,4885,4888,4889,4890,4891,4892,4893,4894,4895,4896,4897,4898,4899,4900,4901,4902,4903,4904,4905,4906,4907,4908,4909,4910,4911,4912,4913,4914,4915,4916,4917,4918,4919,4920,4921,4922,4923,4924,4925,4926,4927,4928,4929,4930,4931,4932,4933,4934,4935,4936,4937,4938,4939,4940,4941,4942,4943,4944,4945,4946,4947,4948,4949,4950,4951,4952,4953,4954,4992,4993,4994,4995,4996,4997,4998,4999,5000,5001,5002,5003,5004,5005,5006,5007,5024,5025,5026,5027,5028,5029,5030,5031,5032,5033,5034,5035,5036,5037,5038,5039,5040,5041,5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,5056,5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103,5104,5105,5106,5107,5108,5109,5112,5113,5114,5115,5116,5117,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149,5150,5151,5152,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164,5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,5176,5177,5178,5179,5180,5181,5182,5183,5184,5185,5186,5187,5188,5189,5190,5191,5192,5193,5194,5195,5196,5197,5198,5199,5200,5201,5202,5203,5204,5205,5206,5207,5208,5209,5210,5211,5212,5213,5214,5215,5216,5217,5218,5219,5220,5221,5222,5223,5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,5237,5238,5239,5240,5241,5242,5243,5244,5245,5246,5247,5248,5249,5250,5251,5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267,5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283,5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299,5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315,5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331,5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347,5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363,5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379,5380,5381,5382,5383,5384,5385,5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408,5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424,5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440,5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456,5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472,5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488,5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504,5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520,5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552,5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568,5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584,5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600,5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616,5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632,5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648,5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664,5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680,5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696,5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712,5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728,5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5743,5744,5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776,5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5792,5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808,5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824,5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840,5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856,5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5870,5871,5872,5873,5874,5875,5876,5877,5878,5879,5880,5888,5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5902,5903,5904,5905,5920,5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936,5937,5952,5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968,5969,5984,5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5998,5999,6000,6016,6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032,6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048,6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064,6065,6066,6067,6103,6108,6176,6177,6178,6179,6180,6181,6182,6183,6184,6185,6186,6187,6188,6189,6190,6191,6192,6193,6194,6195,6196,6197,6198,6199,6200,6201,6202,6203,6204,6205,6206,6207,6208,6209,6210,6211,6212,6213,6214,6215,6216,6217,6218,6219,6220,6221,6222,6223,6224,6225,6226,6227,6228,6229,6230,6231,6232,6233,6234,6235,6236,6237,6238,6239,6240,6241,6242,6243,6244,6245,6246,6247,6248,6249,6250,6251,6252,6253,6254,6255,6256,6257,6258,6259,6260,6261,6262,6263,6264,6272,6273,6274,6275,6276,6277,6278,6279,6280,6281,6282,6283,6284,6285,6286,6287,6288,6289,6290,6291,6292,6293,6294,6295,6296,6297,6298,6299,6300,6301,6302,6303,6304,6305,6306,6307,6308,6309,6310,6311,6312,6314,6320,6321,6322,6323,6324,6325,6326,6327,6328,6329,6330,6331,6332,6333,6334,6335,6336,6337,6338,6339,6340,6341,6342,6343,6344,6345,6346,6347,6348,6349,6350,6351,6352,6353,6354,6355,6356,6357,6358,6359,6360,6361,6362,6363,6364,6365,6366,6367,6368,6369,6370,6371,6372,6373,6374,6375,6376,6377,6378,6379,6380,6381,6382,6383,6384,6385,6386,6387,6388,6389,6400,6401,6402,6403,6404,6405,6406,6407,6408,6409,6410,6411,6412,6413,6414,6415,6416,6417,6418,6419,6420,6421,6422,6423,6424,6425,6426,6427,6428,6429,6430,6480,6481,6482,6483,6484,6485,6486,6487,6488,6489,6490,6491,6492,6493,6494,6495,6496,6497,6498,6499,6500,6501,6502,6503,6504,6505,6506,6507,6508,6509,6512,6513,6514,6515,6516,6528,6529,6530,6531,6532,6533,6534,6535,6536,6537,6538,6539,6540,6541,6542,6543,6544,6545,6546,6547,6548,6549,6550,6551,6552,6553,6554,6555,6556,6557,6558,6559,6560,6561,6562,6563,6564,6565,6566,6567,6568,6569,6570,6571,6576,6577,6578,6579,6580,6581,6582,6583,6584,6585,6586,6587,6588,6589,6590,6591,6592,6593,6594,6595,6596,6597,6598,6599,6600,6601,6656,6657,6658,6659,6660,6661,6662,6663,6664,6665,6666,6667,6668,6669,6670,6671,6672,6673,6674,6675,6676,6677,6678,6688,6689,6690,6691,6692,6693,6694,6695,6696,6697,6698,6699,6700,6701,6702,6703,6704,6705,6706,6707,6708,6709,6710,6711,6712,6713,6714,6715,6716,6717,6718,6719,6720,6721,6722,6723,6724,6725,6726,6727,6728,6729,6730,6731,6732,6733,6734,6735,6736,6737,6738,6739,6740,6823,6917,6918,6919,6920,6921,6922,6923,6924,6925,6926,6927,6928,6929,6930,6931,6932,6933,6934,6935,6936,6937,6938,6939,6940,6941,6942,6943,6944,6945,6946,6947,6948,6949,6950,6951,6952,6953,6954,6955,6956,6957,6958,6959,6960,6961,6962,6963,6981,6982,6983,6984,6985,6986,6987,7043,7044,7045,7046,7047,7048,7049,7050,7051,7052,7053,7054,7055,7056,7057,7058,7059,7060,7061,7062,7063,7064,7065,7066,7067,7068,7069,7070,7071,7072,7086,7087,7098,7099,7100,7101,7102,7103,7104,7105,7106,7107,7108,7109,7110,7111,7112,7113,7114,7115,7116,7117,7118,7119,7120,7121,7122,7123,7124,7125,7126,7127,7128,7129,7130,7131,7132,7133,7134,7135,7136,7137,7138,7139,7140,7141,7168,7169,7170,7171,7172,7173,7174,7175,7176,7177,7178,7179,7180,7181,7182,7183,7184,7185,7186,7187,7188,7189,7190,7191,7192,7193,7194,7195,7196,7197,7198,7199,7200,7201,7202,7203,7245,7246,7247,7258,7259,7260,7261,7262,7263,7264,7265,7266,7267,7268,7269,7270,7271,7272,7273,7274,7275,7276,7277,7278,7279,7280,7281,7282,7283,7284,7285,7286,7287,7288,7289,7290,7291,7292,7293,7296,7297,7298,7299,7300,7301,7302,7303,7304,7312,7313,7314,7315,7316,7317,7318,7319,7320,7321,7322,7323,7324,7325,7326,7327,7328,7329,7330,7331,7332,7333,7334,7335,7336,7337,7338,7339,7340,7341,7342,7343,7344,7345,7346,7347,7348,7349,7350,7351,7352,7353,7354,7357,7358,7359,7401,7402,7403,7404,7406,7407,7408,7409,7413,7414,7424,7425,7426,7427,7428,7429,7430,7431,7432,7433,7434,7435,7436,7437,7438,7439,7440,7441,7442,7443,7444,7445,7446,7447,7448,7449,7450,7451,7452,7453,7454,7455,7456,7457,7458,7459,7460,7461,7462,7463,7464,7465,7466,7467,7468,7469,7470,7471,7472,7473,7474,7475,7476,7477,7478,7479,7480,7481,7482,7483,7484,7485,7486,7487,7488,7489,7490,7491,7492,7493,7494,7495,7496,7497,7498,7499,7500,7501,7502,7503,7504,7505,7506,7507,7508,7509,7510,7511,7512,7513,7514,7515,7516,7517,7518,7519,7520,7521,7522,7523,7524,7525,7526,7527,7528,7529,7530,7531,7532,7533,7534,7535,7536,7537,7538,7539,7540,7541,7542,7543,7544,7545,7546,7547,7548,7549,7550,7551,7552,7553,7554,7555,7556,7557,7558,7559,7560,7561,7562,7563,7564,7565,7566,7567,7568,7569,7570,7571,7572,7573,7574,7575,7576,7577,7578,7579,7580,7581,7582,7583,7584,7585,7586,7587,7588,7589,7590,7591,7592,7593,7594,7595,7596,7597,7598,7599,7600,7601,7602,7603,7604,7605,7606,7607,7608,7609,7610,7611,7612,7613,7614,7615,7680,7681,7682,7683,7684,7685,7686,7687,7688,7689,7690,7691,7692,7693,7694,7695,7696,7697,7698,7699,7700,7701,7702,7703,7704,7705,7706,7707,7708,7709,7710,7711,7712,7713,7714,7715,7716,7717,7718,7719,7720,7721,7722,7723,7724,7725,7726,7727,7728,7729,7730,7731,7732,7733,7734,7735,7736,7737,7738,7739,7740,7741,7742,7743,7744,7745,7746,7747,7748,7749,7750,7751,7752,7753,7754,7755,7756,7757,7758,7759,7760,7761,7762,7763,7764,7765,7766,7767,7768,7769,7770,7771,7772,7773,7774,7775,7776,7777,7778,7779,7780,7781,7782,7783,7784,7785,7786,7787,7788,7789,7790,7791,7792,7793,7794,7795,7796,7797,7798,7799,7800,7801,7802,7803,7804,7805,7806,7807,7808,7809,7810,7811,7812,7813,7814,7815,7816,7817,7818,7819,7820,7821,7822,7823,7824,7825,7826,7827,7828,7829,7830,7831,7832,7833,7834,7835,7836,7837,7838,7839,7840,7841,7842,7843,7844,7845,7846,7847,7848,7849,7850,7851,7852,7853,7854,7855,7856,7857,7858,7859,7860,7861,7862,7863,7864,7865,7866,7867,7868,7869,7870,7871,7872,7873,7874,7875,7876,7877,7878,7879,7880,7881,7882,7883,7884,7885,7886,7887,7888,7889,7890,7891,7892,7893,7894,7895,7896,7897,7898,7899,7900,7901,7902,7903,7904,7905,7906,7907,7908,7909,7910,7911,7912,7913,7914,7915,7916,7917,7918,7919,7920,7921,7922,7923,7924,7925,7926,7927,7928,7929,7930,7931,7932,7933,7934,7935,7936,7937,7938,7939,7940,7941,7942,7943,7944,7945,7946,7947,7948,7949,7950,7951,7952,7953,7954,7955,7956,7957,7960,7961,7962,7963,7964,7965,7968,7969,7970,7971,7972,7973,7974,7975,7976,7977,7978,7979,7980,7981,7982,7983,7984,7985,7986,7987,7988,7989,7990,7991,7992,7993,7994,7995,7996,7997,7998,7999,8000,8001,8002,8003,8004,8005,8008,8009,8010,8011,8012,8013,8016,8017,8018,8019,8020,8021,8022,8023,8025,8027,8029,8031,8032,8033,8034,8035,8036,8037,8038,8039,8040,8041,8042,8043,8044,8045,8046,8047,8048,8049,8050,8051,8052,8053,8054,8055,8056,8057,8058,8059,8060,8061,8064,8065,8066,8067,8068,8069,8070,8071,8072,8073,8074,8075,8076,8077,8078,8079,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8102,8103,8104,8105,8106,8107,8108,8109,8110,8111,8112,8113,8114,8115,8116,8118,8119,8120,8121,8122,8123,8124,8126,8130,8131,8132,8134,8135,8136,8137,8138,8139,8140,8144,8145,8146,8147,8150,8151,8152,8153,8154,8155,8160,8161,8162,8163,8164,8165,8166,8167,8168,8169,8170,8171,8172,8178,8179,8180,8182,8183,8184,8185,8186,8187,8188,8305,8319,8336,8337,8338,8339,8340,8341,8342,8343,8344,8345,8346,8347,8348,8450,8455,8458,8459,8460,8461,8462,8463,8464,8465,8466,8467,8469,8472,8473,8474,8475,8476,8477,8484,8486,8488,8490,8491,8492,8493,8494,8495,8496,8497,8498,8499,8500,8501,8502,8503,8504,8505,8508,8509,8510,8511,8517,8518,8519,8520,8521,8526,8544,8545,8546,8547,8548,8549,8550,8551,8552,8553,8554,8555,8556,8557,8558,8559,8560,8561,8562,8563,8564,8565,8566,8567,8568,8569,8570,8571,8572,8573,8574,8575,8576,8577,8578,8579,8580,8581,8582,8583,8584,11264,11265,11266,11267,11268,11269,11270,11271,11272,11273,11274,11275,11276,11277,11278,11279,11280,11281,11282,11283,11284,11285,11286,11287,11288,11289,11290,11291,11292,11293,11294,11295,11296,11297,11298,11299,11300,11301,11302,11303,11304,11305,11306,11307,11308,11309,11310,11312,11313,11314,11315,11316,11317,11318,11319,11320,11321,11322,11323,11324,11325,11326,11327,11328,11329,11330,11331,11332,11333,11334,11335,11336,11337,11338,11339,11340,11341,11342,11343,11344,11345,11346,11347,11348,11349,11350,11351,11352,11353,11354,11355,11356,11357,11358,11360,11361,11362,11363,11364,11365,11366,11367,11368,11369,11370,11371,11372,11373,11374,11375,11376,11377,11378,11379,11380,11381,11382,11383,11384,11385,11386,11387,11388,11389,11390,11391,11392,11393,11394,11395,11396,11397,11398,11399,11400,11401,11402,11403,11404,11405,11406,11407,11408,11409,11410,11411,11412,11413,11414,11415,11416,11417,11418,11419,11420,11421,11422,11423,11424,11425,11426,11427,11428,11429,11430,11431,11432,11433,11434,11435,11436,11437,11438,11439,11440,11441,11442,11443,11444,11445,11446,11447,11448,11449,11450,11451,11452,11453,11454,11455,11456,11457,11458,11459,11460,11461,11462,11463,11464,11465,11466,11467,11468,11469,11470,11471,11472,11473,11474,11475,11476,11477,11478,11479,11480,11481,11482,11483,11484,11485,11486,11487,11488,11489,11490,11491,11492,11499,11500,11501,11502,11506,11507,11520,11521,11522,11523,11524,11525,11526,11527,11528,11529,11530,11531,11532,11533,11534,11535,11536,11537,11538,11539,11540,11541,11542,11543,11544,11545,11546,11547,11548,11549,11550,11551,11552,11553,11554,11555,11556,11557,11559,11565,11568,11569,11570,11571,11572,11573,11574,11575,11576,11577,11578,11579,11580,11581,11582,11583,11584,11585,11586,11587,11588,11589,11590,11591,11592,11593,11594,11595,11596,11597,11598,11599,11600,11601,11602,11603,11604,11605,11606,11607,11608,11609,11610,11611,11612,11613,11614,11615,11616,11617,11618,11619,11620,11621,11622,11623,11631,11648,11649,11650,11651,11652,11653,11654,11655,11656,11657,11658,11659,11660,11661,11662,11663,11664,11665,11666,11667,11668,11669,11670,11680,11681,11682,11683,11684,11685,11686,11688,11689,11690,11691,11692,11693,11694,11696,11697,11698,11699,11700,11701,11702,11704,11705,11706,11707,11708,11709,11710,11712,11713,11714,11715,11716,11717,11718,11720,11721,11722,11723,11724,11725,11726,11728,11729,11730,11731,11732,11733,11734,11736,11737,11738,11739,11740,11741,11742,12293,12294,12295,12321,12322,12323,12324,12325,12326,12327,12328,12329,12337,12338,12339,12340,12341,12344,12345,12346,12347,12348,12353,12354,12355,12356,12357,12358,12359,12360,12361,12362,12363,12364,12365,12366,12367,12368,12369,12370,12371,12372,12373,12374,12375,12376,12377,12378,12379,12380,12381,12382,12383,12384,12385,12386,12387,12388,12389,12390,12391,12392,12393,12394,12395,12396,12397,12398,12399,12400,12401,12402,12403,12404,12405,12406,12407,12408,12409,12410,12411,12412,12413,12414,12415,12416,12417,12418,12419,12420,12421,12422,12423,12424,12425,12426,12427,12428,12429,12430,12431,12432,12433,12434,12435,12436,12437,12438,12443,12444,12445,12446,12447,12449,12450,12451,12452,12453,12454,12455,12456,12457,12458,12459,12460,12461,12462,12463,12464,12465,12466,12467,12468,12469,12470,12471,12472,12473,12474,12475,12476,12477,12478,12479,12480,12481,12482,12483,12484,12485,12486,12487,12488,12489,12490,12491,12492,12493,12494,12495,12496,12497,12498,12499,12500,12501,12502,12503,12504,12505,12506,12507,12508,12509,12510,12511,12512,12513,12514,12515,12516,12517,12518,12519,12520,12521,12522,12523,12524,12525,12526,12527,12528,12529,12530,12531,12532,12533,12534,12535,12536,12537,12538,12540,12541,12542,12543,12549,12550,12551,12552,12553,12554,12555,12556,12557,12558,12559,12560,12561,12562,12563,12564,12565,12566,12567,12568,12569,12570,12571,12572,12573,12574,12575,12576,12577,12578,12579,12580,12581,12582,12583,12584,12585,12586,12587,12588,12589,12590,12591,12593,12594,12595,12596,12597,12598,12599,12600,12601,12602,12603,12604,12605,12606,12607,12608,12609,12610,12611,12612,12613,12614,12615,12616,12617,12618,12619,12620,12621,12622,12623,12624,12625,12626,12627,12628,12629,12630,12631,12632,12633,12634,12635,12636,12637,12638,12639,12640,12641,12642,12643,12644,12645,12646,12647,12648,12649,12650,12651,12652,12653,12654,12655,12656,12657,12658,12659,12660,12661,12662,12663,12664,12665,12666,12667,12668,12669,12670,12671,12672,12673,12674,12675,12676,12677,12678,12679,12680,12681,12682,12683,12684,12685,12686,12704,12705,12706,12707,12708,12709,12710,12711,12712,12713,12714,12715,12716,12717,12718,12719,12720,12721,12722,12723,12724,12725,12726,12727,12728,12729,12730,12784,12785,12786,12787,12788,12789,12790,12791,12792,12793,12794,12795,12796,12797,12798,12799,13312,13313,13314,13315,13316,13317,13318,13319,13320,13321,13322,13323,13324,13325,13326,13327,13328,13329,13330,13331,13332,13333,13334,13335,13336,13337,13338,13339,13340,13341,13342,13343,13344,13345,13346,13347,13348,13349,13350,13351,13352,13353,13354,13355,13356,13357,13358,13359,13360,13361,13362,13363,13364,13365,13366,13367,13368,13369,13370,13371,13372,13373,13374,13375,13376,13377,13378,13379,13380,13381,13382,13383,13384,13385,13386,13387,13388,13389,13390,13391,13392,13393,13394,13395,13396,13397,13398,13399,13400,13401,13402,13403,13404,13405,13406,13407,13408,13409,13410,13411,13412,13413,13414,13415,13416,13417,13418,13419,13420,13421,13422,13423,13424,13425,13426,13427,13428,13429,13430,13431,13432,13433,13434,13435,13436,13437,13438,13439,13440,13441,13442,13443,13444,13445,13446,13447,13448,13449,13450,13451,13452,13453,13454,13455,13456,13457,13458,13459,13460,13461,13462,13463,13464,13465,13466,13467,13468,13469,13470,13471,13472,13473,13474,13475,13476,13477,13478,13479,13480,13481,13482,13483,13484,13485,13486,13487,13488,13489,13490,13491,13492,13493,13494,13495,13496,13497,13498,13499,13500,13501,13502,13503,13504,13505,13506,13507,13508,13509,13510,13511,13512,13513,13514,13515,13516,13517,13518,13519,13520,13521,13522,13523,13524,13525,13526,13527,13528,13529,13530,13531,13532,13533,13534,13535,13536,13537,13538,13539,13540,13541,13542,13543,13544,13545,13546,13547,13548,13549,13550,13551,13552,13553,13554,13555,13556,13557,13558,13559,13560,13561,13562,13563,13564,13565,13566,13567,13568,13569,13570,13571,13572,13573,13574,13575,13576,13577,13578,13579,13580,13581,13582,13583,13584,13585,13586,13587,13588,13589,13590,13591,13592,13593,13594,13595,13596,13597,13598,13599,13600,13601,13602,13603,13604,13605,13606,13607,13608,13609,13610,13611,13612,13613,13614,13615,13616,13617,13618,13619,13620,13621,13622,13623,13624,13625,13626,13627,13628,13629,13630,13631,13632,13633,13634,13635,13636,13637,13638,13639,13640,13641,13642,13643,13644,13645,13646,13647,13648,13649,13650,13651,13652,13653,13654,13655,13656,13657,13658,13659,13660,13661,13662,13663,13664,13665,13666,13667,13668,13669,13670,13671,13672,13673,13674,13675,13676,13677,13678,13679,13680,13681,13682,13683,13684,13685,13686,13687,13688,13689,13690,13691,13692,13693,13694,13695,13696,13697,13698,13699,13700,13701,13702,13703,13704,13705,13706,13707,13708,13709,13710,13711,13712,13713,13714,13715,13716,13717,13718,13719,13720,13721,13722,13723,13724,13725,13726,13727,13728,13729,13730,13731,13732,13733,13734,13735,13736,13737,13738,13739,13740,13741,13742,13743,13744,13745,13746,13747,13748,13749,13750,13751,13752,13753,13754,13755,13756,13757,13758,13759,13760,13761,13762,13763,13764,13765,13766,13767,13768,13769,13770,13771,13772,13773,13774,13775,13776,13777,13778,13779,13780,13781,13782,13783,13784,13785,13786,13787,13788,13789,13790,13791,13792,13793,13794,13795,13796,13797,13798,13799,13800,13801,13802,13803,13804,13805,13806,13807,13808,13809,13810,13811,13812,13813,13814,13815,13816,13817,13818,13819,13820,13821,13822,13823,13824,13825,13826,13827,13828,13829,13830,13831,13832,13833,13834,13835,13836,13837,13838,13839,13840,13841,13842,13843,13844,13845,13846,13847,13848,13849,13850,13851,13852,13853,13854,13855,13856,13857,13858,13859,13860,13861,13862,13863,13864,13865,13866,13867,13868,13869,13870,13871,13872,13873,13874,13875,13876,13877,13878,13879,13880,13881,13882,13883,13884,13885,13886,13887,13888,13889,13890,13891,13892,13893,13894,13895,13896,13897,13898,13899,13900,13901,13902,13903,13904,13905,13906,13907,13908,13909,13910,13911,13912,13913,13914,13915,13916,13917,13918,13919,13920,13921,13922,13923,13924,13925,13926,13927,13928,13929,13930,13931,13932,13933,13934,13935,13936,13937,13938,13939,13940,13941,13942,13943,13944,13945,13946,13947,13948,13949,13950,13951,13952,13953,13954,13955,13956,13957,13958,13959,13960,13961,13962,13963,13964,13965,13966,13967,13968,13969,13970,13971,13972,13973,13974,13975,13976,13977,13978,13979,13980,13981,13982,13983,13984,13985,13986,13987,13988,13989,13990,13991,13992,13993,13994,13995,13996,13997,13998,13999,14000,14001,14002,14003,14004,14005,14006,14007,14008,14009,14010,14011,14012,14013,14014,14015,14016,14017,14018,14019,14020,14021,14022,14023,14024,14025,14026,14027,14028,14029,14030,14031,14032,14033,14034,14035,14036,14037,14038,14039,14040,14041,14042,14043,14044,14045,14046,14047,14048,14049,14050,14051,14052,14053,14054,14055,14056,14057,14058,14059,14060,14061,14062,14063,14064,14065,14066,14067,14068,14069,14070,14071,14072,14073,14074,14075,14076,14077,14078,14079,14080,14081,14082,14083,14084,14085,14086,14087,14088,14089,14090,14091,14092,14093,14094,14095,14096,14097,14098,14099,14100,14101,14102,14103,14104,14105,14106,14107,14108,14109,14110,14111,14112,14113,14114,14115,14116,14117,14118,14119,14120,14121,14122,14123,14124,14125,14126,14127,14128,14129,14130,14131,14132,14133,14134,14135,14136,14137,14138,14139,14140,14141,14142,14143,14144,14145,14146,14147,14148,14149,14150,14151,14152,14153,14154,14155,14156,14157,14158,14159,14160,14161,14162,14163,14164,14165,14166,14167,14168,14169,14170,14171,14172,14173,14174,14175,14176,14177,14178,14179,14180,14181,14182,14183,14184,14185,14186,14187,14188,14189,14190,14191,14192,14193,14194,14195,14196,14197,14198,14199,14200,14201,14202,14203,14204,14205,14206,14207,14208,14209,14210,14211,14212,14213,14214,14215,14216,14217,14218,14219,14220,14221,14222,14223,14224,14225,14226,14227,14228,14229,14230,14231,14232,14233,14234,14235,14236,14237,14238,14239,14240,14241,14242,14243,14244,14245,14246,14247,14248,14249,14250,14251,14252,14253,14254,14255,14256,14257,14258,14259,14260,14261,14262,14263,14264,14265,14266,14267,14268,14269,14270,14271,14272,14273,14274,14275,14276,14277,14278,14279,14280,14281,14282,14283,14284,14285,14286,14287,14288,14289,14290,14291,14292,14293,14294,14295,14296,14297,14298,14299,14300,14301,14302,14303,14304,14305,14306,14307,14308,14309,14310,14311,14312,14313,14314,14315,14316,14317,14318,14319,14320,14321,14322,14323,14324,14325,14326,14327,14328,14329,14330,14331,14332,14333,14334,14335,14336,14337,14338,14339,14340,14341,14342,14343,14344,14345,14346,14347,14348,14349,14350,14351,14352,14353,14354,14355,14356,14357,14358,14359,14360,14361,14362,14363,14364,14365,14366,14367,14368,14369,14370,14371,14372,14373,14374,14375,14376,14377,14378,14379,14380,14381,14382,14383,14384,14385,14386,14387,14388,14389,14390,14391,14392,14393,14394,14395,14396,14397,14398,14399,14400,14401,14402,14403,14404,14405,14406,14407,14408,14409,14410,14411,14412,14413,14414,14415,14416,14417,14418,14419,14420,14421,14422,14423,14424,14425,14426,14427,14428,14429,14430,14431,14432,14433,14434,14435,14436,14437,14438,14439,14440,14441,14442,14443,14444,14445,14446,14447,14448,14449,14450,14451,14452,14453,14454,14455,14456,14457,14458,14459,14460,14461,14462,14463,14464,14465,14466,14467,14468,14469,14470,14471,14472,14473,14474,14475,14476,14477,14478,14479,14480,14481,14482,14483,14484,14485,14486,14487,14488,14489,14490,14491,14492,14493,14494,14495,14496,14497,14498,14499,14500,14501,14502,14503,14504,14505,14506,14507,14508,14509,14510,14511,14512,14513,14514,14515,14516,14517,14518,14519,14520,14521,14522,14523,14524,14525,14526,14527,14528,14529,14530,14531,14532,14533,14534,14535,14536,14537,14538,14539,14540,14541,14542,14543,14544,14545,14546,14547,14548,14549,14550,14551,14552,14553,14554,14555,14556,14557,14558,14559,14560,14561,14562,14563,14564,14565,14566,14567,14568,14569,14570,14571,14572,14573,14574,14575,14576,14577,14578,14579,14580,14581,14582,14583,14584,14585,14586,14587,14588,14589,14590,14591,14592,14593,14594,14595,14596,14597,14598,14599,14600,14601,14602,14603,14604,14605,14606,14607,14608,14609,14610,14611,14612,14613,14614,14615,14616,14617,14618,14619,14620,14621,14622,14623,14624,14625,14626,14627,14628,14629,14630,14631,14632,14633,14634,14635,14636,14637,14638,14639,14640,14641,14642,14643,14644,14645,14646,14647,14648,14649,14650,14651,14652,14653,14654,14655,14656,14657,14658,14659,14660,14661,14662,14663,14664,14665,14666,14667,14668,14669,14670,14671,14672,14673,14674,14675,14676,14677,14678,14679,14680,14681,14682,14683,14684,14685,14686,14687,14688,14689,14690,14691,14692,14693,14694,14695,14696,14697,14698,14699,14700,14701,14702,14703,14704,14705,14706,14707,14708,14709,14710,14711,14712,14713,14714,14715,14716,14717,14718,14719,14720,14721,14722,14723,14724,14725,14726,14727,14728,14729,14730,14731,14732,14733,14734,14735,14736,14737,14738,14739,14740,14741,14742,14743,14744,14745,14746,14747,14748,14749,14750,14751,14752,14753,14754,14755,14756,14757,14758,14759,14760,14761,14762,14763,14764,14765,14766,14767,14768,14769,14770,14771,14772,14773,14774,14775,14776,14777,14778,14779,14780,14781,14782,14783,14784,14785,14786,14787,14788,14789,14790,14791,14792,14793,14794,14795,14796,14797,14798,14799,14800,14801,14802,14803,14804,14805,14806,14807,14808,14809,14810,14811,14812,14813,14814,14815,14816,14817,14818,14819,14820,14821,14822,14823,14824,14825,14826,14827,14828,14829,14830,14831,14832,14833,14834,14835,14836,14837,14838,14839,14840,14841,14842,14843,14844,14845,14846,14847,14848,14849,14850,14851,14852,14853,14854,14855,14856,14857,14858,14859,14860,14861,14862,14863,14864,14865,14866,14867,14868,14869,14870,14871,14872,14873,14874,14875,14876,14877,14878,14879,14880,14881,14882,14883,14884,14885,14886,14887,14888,14889,14890,14891,14892,14893,14894,14895,14896,14897,14898,14899,14900,14901,14902,14903,14904,14905,14906,14907,14908,14909,14910,14911,14912,14913,14914,14915,14916,14917,14918,14919,14920,14921,14922,14923,14924,14925,14926,14927,14928,14929,14930,14931,14932,14933,14934,14935,14936,14937,14938,14939,14940,14941,14942,14943,14944,14945,14946,14947,14948,14949,14950,14951,14952,14953,14954,14955,14956,14957,14958,14959,14960,14961,14962,14963,14964,14965,14966,14967,14968,14969,14970,14971,14972,14973,14974,14975,14976,14977,14978,14979,14980,14981,14982,14983,14984,14985,14986,14987,14988,14989,14990,14991,14992,14993,14994,14995,14996,14997,14998,14999,15000,15001,15002,15003,15004,15005,15006,15007,15008,15009,15010,15011,15012,15013,15014,15015,15016,15017,15018,15019,15020,15021,15022,15023,15024,15025,15026,15027,15028,15029,15030,15031,15032,15033,15034,15035,15036,15037,15038,15039,15040,15041,15042,15043,15044,15045,15046,15047,15048,15049,15050,15051,15052,15053,15054,15055,15056,15057,15058,15059,15060,15061,15062,15063,15064,15065,15066,15067,15068,15069,15070,15071,15072,15073,15074,15075,15076,15077,15078,15079,15080,15081,15082,15083,15084,15085,15086,15087,15088,15089,15090,15091,15092,15093,15094,15095,15096,15097,15098,15099,15100,15101,15102,15103,15104,15105,15106,15107,15108,15109,15110,15111,15112,15113,15114,15115,15116,15117,15118,15119,15120,15121,15122,15123,15124,15125,15126,15127,15128,15129,15130,15131,15132,15133,15134,15135,15136,15137,15138,15139,15140,15141,15142,15143,15144,15145,15146,15147,15148,15149,15150,15151,15152,15153,15154,15155,15156,15157,15158,15159,15160,15161,15162,15163,15164,15165,15166,15167,15168,15169,15170,15171,15172,15173,15174,15175,15176,15177,15178,15179,15180,15181,15182,15183,15184,15185,15186,15187,15188,15189,15190,15191,15192,15193,15194,15195,15196,15197,15198,15199,15200,15201,15202,15203,15204,15205,15206,15207,15208,15209,15210,15211,15212,15213,15214,15215,15216,15217,15218,15219,15220,15221,15222,15223,15224,15225,15226,15227,15228,15229,15230,15231,15232,15233,15234,15235,15236,15237,15238,15239,15240,15241,15242,15243,15244,15245,15246,15247,15248,15249,15250,15251,15252,15253,15254,15255,15256,15257,15258,15259,15260,15261,15262,15263,15264,15265,15266,15267,15268,15269,15270,15271,15272,15273,15274,15275,15276,15277,15278,15279,15280,15281,15282,15283,15284,15285,15286,15287,15288,15289,15290,15291,15292,15293,15294,15295,15296,15297,15298,15299,15300,15301,15302,15303,15304,15305,15306,15307,15308,15309,15310,15311,15312,15313,15314,15315,15316,15317,15318,15319,15320,15321,15322,15323,15324,15325,15326,15327,15328,15329,15330,15331,15332,15333,15334,15335,15336,15337,15338,15339,15340,15341,15342,15343,15344,15345,15346,15347,15348,15349,15350,15351,15352,15353,15354,15355,15356,15357,15358,15359,15360,15361,15362,15363,15364,15365,15366,15367,15368,15369,15370,15371,15372,15373,15374,15375,15376,15377,15378,15379,15380,15381,15382,15383,15384,15385,15386,15387,15388,15389,15390,15391,15392,15393,15394,15395,15396,15397,15398,15399,15400,15401,15402,15403,15404,15405,15406,15407,15408,15409,15410,15411,15412,15413,15414,15415,15416,15417,15418,15419,15420,15421,15422,15423,15424,15425,15426,15427,15428,15429,15430,15431,15432,15433,15434,15435,15436,15437,15438,15439,15440,15441,15442,15443,15444,15445,15446,15447,15448,15449,15450,15451,15452,15453,15454,15455,15456,15457,15458,15459,15460,15461,15462,15463,15464,15465,15466,15467,15468,15469,15470,15471,15472,15473,15474,15475,15476,15477,15478,15479,15480,15481,15482,15483,15484,15485,15486,15487,15488,15489,15490,15491,15492,15493,15494,15495,15496,15497,15498,15499,15500,15501,15502,15503,15504,15505,15506,15507,15508,15509,15510,15511,15512,15513,15514,15515,15516,15517,15518,15519,15520,15521,15522,15523,15524,15525,15526,15527,15528,15529,15530,15531,15532,15533,15534,15535,15536,15537,15538,15539,15540,15541,15542,15543,15544,15545,15546,15547,15548,15549,15550,15551,15552,15553,15554,15555,15556,15557,15558,15559,15560,15561,15562,15563,15564,15565,15566,15567,15568,15569,15570,15571,15572,15573,15574,15575,15576,15577,15578,15579,15580,15581,15582,15583,15584,15585,15586,15587,15588,15589,15590,15591,15592,15593,15594,15595,15596,15597,15598,15599,15600,15601,15602,15603,15604,15605,15606,15607,15608,15609,15610,15611,15612,15613,15614,15615,15616,15617,15618,15619,15620,15621,15622,15623,15624,15625,15626,15627,15628,15629,15630,15631,15632,15633,15634,15635,15636,15637,15638,15639,15640,15641,15642,15643,15644,15645,15646,15647,15648,15649,15650,15651,15652,15653,15654,15655,15656,15657,15658,15659,15660,15661,15662,15663,15664,15665,15666,15667,15668,15669,15670,15671,15672,15673,15674,15675,15676,15677,15678,15679,15680,15681,15682,15683,15684,15685,15686,15687,15688,15689,15690,15691,15692,15693,15694,15695,15696,15697,15698,15699,15700,15701,15702,15703,15704,15705,15706,15707,15708,15709,15710,15711,15712,15713,15714,15715,15716,15717,15718,15719,15720,15721,15722,15723,15724,15725,15726,15727,15728,15729,15730,15731,15732,15733,15734,15735,15736,15737,15738,15739,15740,15741,15742,15743,15744,15745,15746,15747,15748,15749,15750,15751,15752,15753,15754,15755,15756,15757,15758,15759,15760,15761,15762,15763,15764,15765,15766,15767,15768,15769,15770,15771,15772,15773,15774,15775,15776,15777,15778,15779,15780,15781,15782,15783,15784,15785,15786,15787,15788,15789,15790,15791,15792,15793,15794,15795,15796,15797,15798,15799,15800,15801,15802,15803,15804,15805,15806,15807,15808,15809,15810,15811,15812,15813,15814,15815,15816,15817,15818,15819,15820,15821,15822,15823,15824,15825,15826,15827,15828,15829,15830,15831,15832,15833,15834,15835,15836,15837,15838,15839,15840,15841,15842,15843,15844,15845,15846,15847,15848,15849,15850,15851,15852,15853,15854,15855,15856,15857,15858,15859,15860,15861,15862,15863,15864,15865,15866,15867,15868,15869,15870,15871,15872,15873,15874,15875,15876,15877,15878,15879,15880,15881,15882,15883,15884,15885,15886,15887,15888,15889,15890,15891,15892,15893,15894,15895,15896,15897,15898,15899,15900,15901,15902,15903,15904,15905,15906,15907,15908,15909,15910,15911,15912,15913,15914,15915,15916,15917,15918,15919,15920,15921,15922,15923,15924,15925,15926,15927,15928,15929,15930,15931,15932,15933,15934,15935,15936,15937,15938,15939,15940,15941,15942,15943,15944,15945,15946,15947,15948,15949,15950,15951,15952,15953,15954,15955,15956,15957,15958,15959,15960,15961,15962,15963,15964,15965,15966,15967,15968,15969,15970,15971,15972,15973,15974,15975,15976,15977,15978,15979,15980,15981,15982,15983,15984,15985,15986,15987,15988,15989,15990,15991,15992,15993,15994,15995,15996,15997,15998,15999,16000,16001,16002,16003,16004,16005,16006,16007,16008,16009,16010,16011,16012,16013,16014,16015,16016,16017,16018,16019,16020,16021,16022,16023,16024,16025,16026,16027,16028,16029,16030,16031,16032,16033,16034,16035,16036,16037,16038,16039,16040,16041,16042,16043,16044,16045,16046,16047,16048,16049,16050,16051,16052,16053,16054,16055,16056,16057,16058,16059,16060,16061,16062,16063,16064,16065,16066,16067,16068,16069,16070,16071,16072,16073,16074,16075,16076,16077,16078,16079,16080,16081,16082,16083,16084,16085,16086,16087,16088,16089,16090,16091,16092,16093,16094,16095,16096,16097,16098,16099,16100,16101,16102,16103,16104,16105,16106,16107,16108,16109,16110,16111,16112,16113,16114,16115,16116,16117,16118,16119,16120,16121,16122,16123,16124,16125,16126,16127,16128,16129,16130,16131,16132,16133,16134,16135,16136,16137,16138,16139,16140,16141,16142,16143,16144,16145,16146,16147,16148,16149,16150,16151,16152,16153,16154,16155,16156,16157,16158,16159,16160,16161,16162,16163,16164,16165,16166,16167,16168,16169,16170,16171,16172,16173,16174,16175,16176,16177,16178,16179,16180,16181,16182,16183,16184,16185,16186,16187,16188,16189,16190,16191,16192,16193,16194,16195,16196,16197,16198,16199,16200,16201,16202,16203,16204,16205,16206,16207,16208,16209,16210,16211,16212,16213,16214,16215,16216,16217,16218,16219,16220,16221,16222,16223,16224,16225,16226,16227,16228,16229,16230,16231,16232,16233,16234,16235,16236,16237,16238,16239,16240,16241,16242,16243,16244,16245,16246,16247,16248,16249,16250,16251,16252,16253,16254,16255,16256,16257,16258,16259,16260,16261,16262,16263,16264,16265,16266,16267,16268,16269,16270,16271,16272,16273,16274,16275,16276,16277,16278,16279,16280,16281,16282,16283,16284,16285,16286,16287,16288,16289,16290,16291,16292,16293,16294,16295,16296,16297,16298,16299,16300,16301,16302,16303,16304,16305,16306,16307,16308,16309,16310,16311,16312,16313,16314,16315,16316,16317,16318,16319,16320,16321,16322,16323,16324,16325,16326,16327,16328,16329,16330,16331,16332,16333,16334,16335,16336,16337,16338,16339,16340,16341,16342,16343,16344,16345,16346,16347,16348,16349,16350,16351,16352,16353,16354,16355,16356,16357,16358,16359,16360,16361,16362,16363,16364,16365,16366,16367,16368,16369,16370,16371,16372,16373,16374,16375,16376,16377,16378,16379,16380,16381,16382,16383,16384,16385,16386,16387,16388,16389,16390,16391,16392,16393,16394,16395,16396,16397,16398,16399,16400,16401,16402,16403,16404,16405,16406,16407,16408,16409,16410,16411,16412,16413,16414,16415,16416,16417,16418,16419,16420,16421,16422,16423,16424,16425,16426,16427,16428,16429,16430,16431,16432,16433,16434,16435,16436,16437,16438,16439,16440,16441,16442,16443,16444,16445,16446,16447,16448,16449,16450,16451,16452,16453,16454,16455,16456,16457,16458,16459,16460,16461,16462,16463,16464,16465,16466,16467,16468,16469,16470,16471,16472,16473,16474,16475,16476,16477,16478,16479,16480,16481,16482,16483,16484,16485,16486,16487,16488,16489,16490,16491,16492,16493,16494,16495,16496,16497,16498,16499,16500,16501,16502,16503,16504,16505,16506,16507,16508,16509,16510,16511,16512,16513,16514,16515,16516,16517,16518,16519,16520,16521,16522,16523,16524,16525,16526,16527,16528,16529,16530,16531,16532,16533,16534,16535,16536,16537,16538,16539,16540,16541,16542,16543,16544,16545,16546,16547,16548,16549,16550,16551,16552,16553,16554,16555,16556,16557,16558,16559,16560,16561,16562,16563,16564,16565,16566,16567,16568,16569,16570,16571,16572,16573,16574,16575,16576,16577,16578,16579,16580,16581,16582,16583,16584,16585,16586,16587,16588,16589,16590,16591,16592,16593,16594,16595,16596,16597,16598,16599,16600,16601,16602,16603,16604,16605,16606,16607,16608,16609,16610,16611,16612,16613,16614,16615,16616,16617,16618,16619,16620,16621,16622,16623,16624,16625,16626,16627,16628,16629,16630,16631,16632,16633,16634,16635,16636,16637,16638,16639,16640,16641,16642,16643,16644,16645,16646,16647,16648,16649,16650,16651,16652,16653,16654,16655,16656,16657,16658,16659,16660,16661,16662,16663,16664,16665,16666,16667,16668,16669,16670,16671,16672,16673,16674,16675,16676,16677,16678,16679,16680,16681,16682,16683,16684,16685,16686,16687,16688,16689,16690,16691,16692,16693,16694,16695,16696,16697,16698,16699,16700,16701,16702,16703,16704,16705,16706,16707,16708,16709,16710,16711,16712,16713,16714,16715,16716,16717,16718,16719,16720,16721,16722,16723,16724,16725,16726,16727,16728,16729,16730,16731,16732,16733,16734,16735,16736,16737,16738,16739,16740,16741,16742,16743,16744,16745,16746,16747,16748,16749,16750,16751,16752,16753,16754,16755,16756,16757,16758,16759,16760,16761,16762,16763,16764,16765,16766,16767,16768,16769,16770,16771,16772,16773,16774,16775,16776,16777,16778,16779,16780,16781,16782,16783,16784,16785,16786,16787,16788,16789,16790,16791,16792,16793,16794,16795,16796,16797,16798,16799,16800,16801,16802,16803,16804,16805,16806,16807,16808,16809,16810,16811,16812,16813,16814,16815,16816,16817,16818,16819,16820,16821,16822,16823,16824,16825,16826,16827,16828,16829,16830,16831,16832,16833,16834,16835,16836,16837,16838,16839,16840,16841,16842,16843,16844,16845,16846,16847,16848,16849,16850,16851,16852,16853,16854,16855,16856,16857,16858,16859,16860,16861,16862,16863,16864,16865,16866,16867,16868,16869,16870,16871,16872,16873,16874,16875,16876,16877,16878,16879,16880,16881,16882,16883,16884,16885,16886,16887,16888,16889,16890,16891,16892,16893,16894,16895,16896,16897,16898,16899,16900,16901,16902,16903,16904,16905,16906,16907,16908,16909,16910,16911,16912,16913,16914,16915,16916,16917,16918,16919,16920,16921,16922,16923,16924,16925,16926,16927,16928,16929,16930,16931,16932,16933,16934,16935,16936,16937,16938,16939,16940,16941,16942,16943,16944,16945,16946,16947,16948,16949,16950,16951,16952,16953,16954,16955,16956,16957,16958,16959,16960,16961,16962,16963,16964,16965,16966,16967,16968,16969,16970,16971,16972,16973,16974,16975,16976,16977,16978,16979,16980,16981,16982,16983,16984,16985,16986,16987,16988,16989,16990,16991,16992,16993,16994,16995,16996,16997,16998,16999,17000,17001,17002,17003,17004,17005,17006,17007,17008,17009,17010,17011,17012,17013,17014,17015,17016,17017,17018,17019,17020,17021,17022,17023,17024,17025,17026,17027,17028,17029,17030,17031,17032,17033,17034,17035,17036,17037,17038,17039,17040,17041,17042,17043,17044,17045,17046,17047,17048,17049,17050,17051,17052,17053,17054,17055,17056,17057,17058,17059,17060,17061,17062,17063,17064,17065,17066,17067,17068,17069,17070,17071,17072,17073,17074,17075,17076,17077,17078,17079,17080,17081,17082,17083,17084,17085,17086,17087,17088,17089,17090,17091,17092,17093,17094,17095,17096,17097,17098,17099,17100,17101,17102,17103,17104,17105,17106,17107,17108,17109,17110,17111,17112,17113,17114,17115,17116,17117,17118,17119,17120,17121,17122,17123,17124,17125,17126,17127,17128,17129,17130,17131,17132,17133,17134,17135,17136,17137,17138,17139,17140,17141,17142,17143,17144,17145,17146,17147,17148,17149,17150,17151,17152,17153,17154,17155,17156,17157,17158,17159,17160,17161,17162,17163,17164,17165,17166,17167,17168,17169,17170,17171,17172,17173,17174,17175,17176,17177,17178,17179,17180,17181,17182,17183,17184,17185,17186,17187,17188,17189,17190,17191,17192,17193,17194,17195,17196,17197,17198,17199,17200,17201,17202,17203,17204,17205,17206,17207,17208,17209,17210,17211,17212,17213,17214,17215,17216,17217,17218,17219,17220,17221,17222,17223,17224,17225,17226,17227,17228,17229,17230,17231,17232,17233,17234,17235,17236,17237,17238,17239,17240,17241,17242,17243,17244,17245,17246,17247,17248,17249,17250,17251,17252,17253,17254,17255,17256,17257,17258,17259,17260,17261,17262,17263,17264,17265,17266,17267,17268,17269,17270,17271,17272,17273,17274,17275,17276,17277,17278,17279,17280,17281,17282,17283,17284,17285,17286,17287,17288,17289,17290,17291,17292,17293,17294,17295,17296,17297,17298,17299,17300,17301,17302,17303,17304,17305,17306,17307,17308,17309,17310,17311,17312,17313,17314,17315,17316,17317,17318,17319,17320,17321,17322,17323,17324,17325,17326,17327,17328,17329,17330,17331,17332,17333,17334,17335,17336,17337,17338,17339,17340,17341,17342,17343,17344,17345,17346,17347,17348,17349,17350,17351,17352,17353,17354,17355,17356,17357,17358,17359,17360,17361,17362,17363,17364,17365,17366,17367,17368,17369,17370,17371,17372,17373,17374,17375,17376,17377,17378,17379,17380,17381,17382,17383,17384,17385,17386,17387,17388,17389,17390,17391,17392,17393,17394,17395,17396,17397,17398,17399,17400,17401,17402,17403,17404,17405,17406,17407,17408,17409,17410,17411,17412,17413,17414,17415,17416,17417,17418,17419,17420,17421,17422,17423,17424,17425,17426,17427,17428,17429,17430,17431,17432,17433,17434,17435,17436,17437,17438,17439,17440,17441,17442,17443,17444,17445,17446,17447,17448,17449,17450,17451,17452,17453,17454,17455,17456,17457,17458,17459,17460,17461,17462,17463,17464,17465,17466,17467,17468,17469,17470,17471,17472,17473,17474,17475,17476,17477,17478,17479,17480,17481,17482,17483,17484,17485,17486,17487,17488,17489,17490,17491,17492,17493,17494,17495,17496,17497,17498,17499,17500,17501,17502,17503,17504,17505,17506,17507,17508,17509,17510,17511,17512,17513,17514,17515,17516,17517,17518,17519,17520,17521,17522,17523,17524,17525,17526,17527,17528,17529,17530,17531,17532,17533,17534,17535,17536,17537,17538,17539,17540,17541,17542,17543,17544,17545,17546,17547,17548,17549,17550,17551,17552,17553,17554,17555,17556,17557,17558,17559,17560,17561,17562,17563,17564,17565,17566,17567,17568,17569,17570,17571,17572,17573,17574,17575,17576,17577,17578,17579,17580,17581,17582,17583,17584,17585,17586,17587,17588,17589,17590,17591,17592,17593,17594,17595,17596,17597,17598,17599,17600,17601,17602,17603,17604,17605,17606,17607,17608,17609,17610,17611,17612,17613,17614,17615,17616,17617,17618,17619,17620,17621,17622,17623,17624,17625,17626,17627,17628,17629,17630,17631,17632,17633,17634,17635,17636,17637,17638,17639,17640,17641,17642,17643,17644,17645,17646,17647,17648,17649,17650,17651,17652,17653,17654,17655,17656,17657,17658,17659,17660,17661,17662,17663,17664,17665,17666,17667,17668,17669,17670,17671,17672,17673,17674,17675,17676,17677,17678,17679,17680,17681,17682,17683,17684,17685,17686,17687,17688,17689,17690,17691,17692,17693,17694,17695,17696,17697,17698,17699,17700,17701,17702,17703,17704,17705,17706,17707,17708,17709,17710,17711,17712,17713,17714,17715,17716,17717,17718,17719,17720,17721,17722,17723,17724,17725,17726,17727,17728,17729,17730,17731,17732,17733,17734,17735,17736,17737,17738,17739,17740,17741,17742,17743,17744,17745,17746,17747,17748,17749,17750,17751,17752,17753,17754,17755,17756,17757,17758,17759,17760,17761,17762,17763,17764,17765,17766,17767,17768,17769,17770,17771,17772,17773,17774,17775,17776,17777,17778,17779,17780,17781,17782,17783,17784,17785,17786,17787,17788,17789,17790,17791,17792,17793,17794,17795,17796,17797,17798,17799,17800,17801,17802,17803,17804,17805,17806,17807,17808,17809,17810,17811,17812,17813,17814,17815,17816,17817,17818,17819,17820,17821,17822,17823,17824,17825,17826,17827,17828,17829,17830,17831,17832,17833,17834,17835,17836,17837,17838,17839,17840,17841,17842,17843,17844,17845,17846,17847,17848,17849,17850,17851,17852,17853,17854,17855,17856,17857,17858,17859,17860,17861,17862,17863,17864,17865,17866,17867,17868,17869,17870,17871,17872,17873,17874,17875,17876,17877,17878,17879,17880,17881,17882,17883,17884,17885,17886,17887,17888,17889,17890,17891,17892,17893,17894,17895,17896,17897,17898,17899,17900,17901,17902,17903,17904,17905,17906,17907,17908,17909,17910,17911,17912,17913,17914,17915,17916,17917,17918,17919,17920,17921,17922,17923,17924,17925,17926,17927,17928,17929,17930,17931,17932,17933,17934,17935,17936,17937,17938,17939,17940,17941,17942,17943,17944,17945,17946,17947,17948,17949,17950,17951,17952,17953,17954,17955,17956,17957,17958,17959,17960,17961,17962,17963,17964,17965,17966,17967,17968,17969,17970,17971,17972,17973,17974,17975,17976,17977,17978,17979,17980,17981,17982,17983,17984,17985,17986,17987,17988,17989,17990,17991,17992,17993,17994,17995,17996,17997,17998,17999,18000,18001,18002,18003,18004,18005,18006,18007,18008,18009,18010,18011,18012,18013,18014,18015,18016,18017,18018,18019,18020,18021,18022,18023,18024,18025,18026,18027,18028,18029,18030,18031,18032,18033,18034,18035,18036,18037,18038,18039,18040,18041,18042,18043,18044,18045,18046,18047,18048,18049,18050,18051,18052,18053,18054,18055,18056,18057,18058,18059,18060,18061,18062,18063,18064,18065,18066,18067,18068,18069,18070,18071,18072,18073,18074,18075,18076,18077,18078,18079,18080,18081,18082,18083,18084,18085,18086,18087,18088,18089,18090,18091,18092,18093,18094,18095,18096,18097,18098,18099,18100,18101,18102,18103,18104,18105,18106,18107,18108,18109,18110,18111,18112,18113,18114,18115,18116,18117,18118,18119,18120,18121,18122,18123,18124,18125,18126,18127,18128,18129,18130,18131,18132,18133,18134,18135,18136,18137,18138,18139,18140,18141,18142,18143,18144,18145,18146,18147,18148,18149,18150,18151,18152,18153,18154,18155,18156,18157,18158,18159,18160,18161,18162,18163,18164,18165,18166,18167,18168,18169,18170,18171,18172,18173,18174,18175,18176,18177,18178,18179,18180,18181,18182,18183,18184,18185,18186,18187,18188,18189,18190,18191,18192,18193,18194,18195,18196,18197,18198,18199,18200,18201,18202,18203,18204,18205,18206,18207,18208,18209,18210,18211,18212,18213,18214,18215,18216,18217,18218,18219,18220,18221,18222,18223,18224,18225,18226,18227,18228,18229,18230,18231,18232,18233,18234,18235,18236,18237,18238,18239,18240,18241,18242,18243,18244,18245,18246,18247,18248,18249,18250,18251,18252,18253,18254,18255,18256,18257,18258,18259,18260,18261,18262,18263,18264,18265,18266,18267,18268,18269,18270,18271,18272,18273,18274,18275,18276,18277,18278,18279,18280,18281,18282,18283,18284,18285,18286,18287,18288,18289,18290,18291,18292,18293,18294,18295,18296,18297,18298,18299,18300,18301,18302,18303,18304,18305,18306,18307,18308,18309,18310,18311,18312,18313,18314,18315,18316,18317,18318,18319,18320,18321,18322,18323,18324,18325,18326,18327,18328,18329,18330,18331,18332,18333,18334,18335,18336,18337,18338,18339,18340,18341,18342,18343,18344,18345,18346,18347,18348,18349,18350,18351,18352,18353,18354,18355,18356,18357,18358,18359,18360,18361,18362,18363,18364,18365,18366,18367,18368,18369,18370,18371,18372,18373,18374,18375,18376,18377,18378,18379,18380,18381,18382,18383,18384,18385,18386,18387,18388,18389,18390,18391,18392,18393,18394,18395,18396,18397,18398,18399,18400,18401,18402,18403,18404,18405,18406,18407,18408,18409,18410,18411,18412,18413,18414,18415,18416,18417,18418,18419,18420,18421,18422,18423,18424,18425,18426,18427,18428,18429,18430,18431,18432,18433,18434,18435,18436,18437,18438,18439,18440,18441,18442,18443,18444,18445,18446,18447,18448,18449,18450,18451,18452,18453,18454,18455,18456,18457,18458,18459,18460,18461,18462,18463,18464,18465,18466,18467,18468,18469,18470,18471,18472,18473,18474,18475,18476,18477,18478,18479,18480,18481,18482,18483,18484,18485,18486,18487,18488,18489,18490,18491,18492,18493,18494,18495,18496,18497,18498,18499,18500,18501,18502,18503,18504,18505,18506,18507,18508,18509,18510,18511,18512,18513,18514,18515,18516,18517,18518,18519,18520,18521,18522,18523,18524,18525,18526,18527,18528,18529,18530,18531,18532,18533,18534,18535,18536,18537,18538,18539,18540,18541,18542,18543,18544,18545,18546,18547,18548,18549,18550,18551,18552,18553,18554,18555,18556,18557,18558,18559,18560,18561,18562,18563,18564,18565,18566,18567,18568,18569,18570,18571,18572,18573,18574,18575,18576,18577,18578,18579,18580,18581,18582,18583,18584,18585,18586,18587,18588,18589,18590,18591,18592,18593,18594,18595,18596,18597,18598,18599,18600,18601,18602,18603,18604,18605,18606,18607,18608,18609,18610,18611,18612,18613,18614,18615,18616,18617,18618,18619,18620,18621,18622,18623,18624,18625,18626,18627,18628,18629,18630,18631,18632,18633,18634,18635,18636,18637,18638,18639,18640,18641,18642,18643,18644,18645,18646,18647,18648,18649,18650,18651,18652,18653,18654,18655,18656,18657,18658,18659,18660,18661,18662,18663,18664,18665,18666,18667,18668,18669,18670,18671,18672,18673,18674,18675,18676,18677,18678,18679,18680,18681,18682,18683,18684,18685,18686,18687,18688,18689,18690,18691,18692,18693,18694,18695,18696,18697,18698,18699,18700,18701,18702,18703,18704,18705,18706,18707,18708,18709,18710,18711,18712,18713,18714,18715,18716,18717,18718,18719,18720,18721,18722,18723,18724,18725,18726,18727,18728,18729,18730,18731,18732,18733,18734,18735,18736,18737,18738,18739,18740,18741,18742,18743,18744,18745,18746,18747,18748,18749,18750,18751,18752,18753,18754,18755,18756,18757,18758,18759,18760,18761,18762,18763,18764,18765,18766,18767,18768,18769,18770,18771,18772,18773,18774,18775,18776,18777,18778,18779,18780,18781,18782,18783,18784,18785,18786,18787,18788,18789,18790,18791,18792,18793,18794,18795,18796,18797,18798,18799,18800,18801,18802,18803,18804,18805,18806,18807,18808,18809,18810,18811,18812,18813,18814,18815,18816,18817,18818,18819,18820,18821,18822,18823,18824,18825,18826,18827,18828,18829,18830,18831,18832,18833,18834,18835,18836,18837,18838,18839,18840,18841,18842,18843,18844,18845,18846,18847,18848,18849,18850,18851,18852,18853,18854,18855,18856,18857,18858,18859,18860,18861,18862,18863,18864,18865,18866,18867,18868,18869,18870,18871,18872,18873,18874,18875,18876,18877,18878,18879,18880,18881,18882,18883,18884,18885,18886,18887,18888,18889,18890,18891,18892,18893,18894,18895,18896,18897,18898,18899,18900,18901,18902,18903,18904,18905,18906,18907,18908,18909,18910,18911,18912,18913,18914,18915,18916,18917,18918,18919,18920,18921,18922,18923,18924,18925,18926,18927,18928,18929,18930,18931,18932,18933,18934,18935,18936,18937,18938,18939,18940,18941,18942,18943,18944,18945,18946,18947,18948,18949,18950,18951,18952,18953,18954,18955,18956,18957,18958,18959,18960,18961,18962,18963,18964,18965,18966,18967,18968,18969,18970,18971,18972,18973,18974,18975,18976,18977,18978,18979,18980,18981,18982,18983,18984,18985,18986,18987,18988,18989,18990,18991,18992,18993,18994,18995,18996,18997,18998,18999,19000,19001,19002,19003,19004,19005,19006,19007,19008,19009,19010,19011,19012,19013,19014,19015,19016,19017,19018,19019,19020,19021,19022,19023,19024,19025,19026,19027,19028,19029,19030,19031,19032,19033,19034,19035,19036,19037,19038,19039,19040,19041,19042,19043,19044,19045,19046,19047,19048,19049,19050,19051,19052,19053,19054,19055,19056,19057,19058,19059,19060,19061,19062,19063,19064,19065,19066,19067,19068,19069,19070,19071,19072,19073,19074,19075,19076,19077,19078,19079,19080,19081,19082,19083,19084,19085,19086,19087,19088,19089,19090,19091,19092,19093,19094,19095,19096,19097,19098,19099,19100,19101,19102,19103,19104,19105,19106,19107,19108,19109,19110,19111,19112,19113,19114,19115,19116,19117,19118,19119,19120,19121,19122,19123,19124,19125,19126,19127,19128,19129,19130,19131,19132,19133,19134,19135,19136,19137,19138,19139,19140,19141,19142,19143,19144,19145,19146,19147,19148,19149,19150,19151,19152,19153,19154,19155,19156,19157,19158,19159,19160,19161,19162,19163,19164,19165,19166,19167,19168,19169,19170,19171,19172,19173,19174,19175,19176,19177,19178,19179,19180,19181,19182,19183,19184,19185,19186,19187,19188,19189,19190,19191,19192,19193,19194,19195,19196,19197,19198,19199,19200,19201,19202,19203,19204,19205,19206,19207,19208,19209,19210,19211,19212,19213,19214,19215,19216,19217,19218,19219,19220,19221,19222,19223,19224,19225,19226,19227,19228,19229,19230,19231,19232,19233,19234,19235,19236,19237,19238,19239,19240,19241,19242,19243,19244,19245,19246,19247,19248,19249,19250,19251,19252,19253,19254,19255,19256,19257,19258,19259,19260,19261,19262,19263,19264,19265,19266,19267,19268,19269,19270,19271,19272,19273,19274,19275,19276,19277,19278,19279,19280,19281,19282,19283,19284,19285,19286,19287,19288,19289,19290,19291,19292,19293,19294,19295,19296,19297,19298,19299,19300,19301,19302,19303,19304,19305,19306,19307,19308,19309,19310,19311,19312,19313,19314,19315,19316,19317,19318,19319,19320,19321,19322,19323,19324,19325,19326,19327,19328,19329,19330,19331,19332,19333,19334,19335,19336,19337,19338,19339,19340,19341,19342,19343,19344,19345,19346,19347,19348,19349,19350,19351,19352,19353,19354,19355,19356,19357,19358,19359,19360,19361,19362,19363,19364,19365,19366,19367,19368,19369,19370,19371,19372,19373,19374,19375,19376,19377,19378,19379,19380,19381,19382,19383,19384,19385,19386,19387,19388,19389,19390,19391,19392,19393,19394,19395,19396,19397,19398,19399,19400,19401,19402,19403,19404,19405,19406,19407,19408,19409,19410,19411,19412,19413,19414,19415,19416,19417,19418,19419,19420,19421,19422,19423,19424,19425,19426,19427,19428,19429,19430,19431,19432,19433,19434,19435,19436,19437,19438,19439,19440,19441,19442,19443,19444,19445,19446,19447,19448,19449,19450,19451,19452,19453,19454,19455,19456,19457,19458,19459,19460,19461,19462,19463,19464,19465,19466,19467,19468,19469,19470,19471,19472,19473,19474,19475,19476,19477,19478,19479,19480,19481,19482,19483,19484,19485,19486,19487,19488,19489,19490,19491,19492,19493,19494,19495,19496,19497,19498,19499,19500,19501,19502,19503,19504,19505,19506,19507,19508,19509,19510,19511,19512,19513,19514,19515,19516,19517,19518,19519,19520,19521,19522,19523,19524,19525,19526,19527,19528,19529,19530,19531,19532,19533,19534,19535,19536,19537,19538,19539,19540,19541,19542,19543,19544,19545,19546,19547,19548,19549,19550,19551,19552,19553,19554,19555,19556,19557,19558,19559,19560,19561,19562,19563,19564,19565,19566,19567,19568,19569,19570,19571,19572,19573,19574,19575,19576,19577,19578,19579,19580,19581,19582,19583,19584,19585,19586,19587,19588,19589,19590,19591,19592,19593,19594,19595,19596,19597,19598,19599,19600,19601,19602,19603,19604,19605,19606,19607,19608,19609,19610,19611,19612,19613,19614,19615,19616,19617,19618,19619,19620,19621,19622,19623,19624,19625,19626,19627,19628,19629,19630,19631,19632,19633,19634,19635,19636,19637,19638,19639,19640,19641,19642,19643,19644,19645,19646,19647,19648,19649,19650,19651,19652,19653,19654,19655,19656,19657,19658,19659,19660,19661,19662,19663,19664,19665,19666,19667,19668,19669,19670,19671,19672,19673,19674,19675,19676,19677,19678,19679,19680,19681,19682,19683,19684,19685,19686,19687,19688,19689,19690,19691,19692,19693,19694,19695,19696,19697,19698,19699,19700,19701,19702,19703,19704,19705,19706,19707,19708,19709,19710,19711,19712,19713,19714,19715,19716,19717,19718,19719,19720,19721,19722,19723,19724,19725,19726,19727,19728,19729,19730,19731,19732,19733,19734,19735,19736,19737,19738,19739,19740,19741,19742,19743,19744,19745,19746,19747,19748,19749,19750,19751,19752,19753,19754,19755,19756,19757,19758,19759,19760,19761,19762,19763,19764,19765,19766,19767,19768,19769,19770,19771,19772,19773,19774,19775,19776,19777,19778,19779,19780,19781,19782,19783,19784,19785,19786,19787,19788,19789,19790,19791,19792,19793,19794,19795,19796,19797,19798,19799,19800,19801,19802,19803,19804,19805,19806,19807,19808,19809,19810,19811,19812,19813,19814,19815,19816,19817,19818,19819,19820,19821,19822,19823,19824,19825,19826,19827,19828,19829,19830,19831,19832,19833,19834,19835,19836,19837,19838,19839,19840,19841,19842,19843,19844,19845,19846,19847,19848,19849,19850,19851,19852,19853,19854,19855,19856,19857,19858,19859,19860,19861,19862,19863,19864,19865,19866,19867,19868,19869,19870,19871,19872,19873,19874,19875,19876,19877,19878,19879,19880,19881,19882,19883,19884,19885,19886,19887,19888,19889,19890,19891,19892,19893,19968,19969,19970,19971,19972,19973,19974,19975,19976,19977,19978,19979,19980,19981,19982,19983,19984,19985,19986,19987,19988,19989,19990,19991,19992,19993,19994,19995,19996,19997,19998,19999,20000,20001,20002,20003,20004,20005,20006,20007,20008,20009,20010,20011,20012,20013,20014,20015,20016,20017,20018,20019,20020,20021,20022,20023,20024,20025,20026,20027,20028,20029,20030,20031,20032,20033,20034,20035,20036,20037,20038,20039,20040,20041,20042,20043,20044,20045,20046,20047,20048,20049,20050,20051,20052,20053,20054,20055,20056,20057,20058,20059,20060,20061,20062,20063,20064,20065,20066,20067,20068,20069,20070,20071,20072,20073,20074,20075,20076,20077,20078,20079,20080,20081,20082,20083,20084,20085,20086,20087,20088,20089,20090,20091,20092,20093,20094,20095,20096,20097,20098,20099,20100,20101,20102,20103,20104,20105,20106,20107,20108,20109,20110,20111,20112,20113,20114,20115,20116,20117,20118,20119,20120,20121,20122,20123,20124,20125,20126,20127,20128,20129,20130,20131,20132,20133,20134,20135,20136,20137,20138,20139,20140,20141,20142,20143,20144,20145,20146,20147,20148,20149,20150,20151,20152,20153,20154,20155,20156,20157,20158,20159,20160,20161,20162,20163,20164,20165,20166,20167,20168,20169,20170,20171,20172,20173,20174,20175,20176,20177,20178,20179,20180,20181,20182,20183,20184,20185,20186,20187,20188,20189,20190,20191,20192,20193,20194,20195,20196,20197,20198,20199,20200,20201,20202,20203,20204,20205,20206,20207,20208,20209,20210,20211,20212,20213,20214,20215,20216,20217,20218,20219,20220,20221,20222,20223,20224,20225,20226,20227,20228,20229,20230,20231,20232,20233,20234,20235,20236,20237,20238,20239,20240,20241,20242,20243,20244,20245,20246,20247,20248,20249,20250,20251,20252,20253,20254,20255,20256,20257,20258,20259,20260,20261,20262,20263,20264,20265,20266,20267,20268,20269,20270,20271,20272,20273,20274,20275,20276,20277,20278,20279,20280,20281,20282,20283,20284,20285,20286,20287,20288,20289,20290,20291,20292,20293,20294,20295,20296,20297,20298,20299,20300,20301,20302,20303,20304,20305,20306,20307,20308,20309,20310,20311,20312,20313,20314,20315,20316,20317,20318,20319,20320,20321,20322,20323,20324,20325,20326,20327,20328,20329,20330,20331,20332,20333,20334,20335,20336,20337,20338,20339,20340,20341,20342,20343,20344,20345,20346,20347,20348,20349,20350,20351,20352,20353,20354,20355,20356,20357,20358,20359,20360,20361,20362,20363,20364,20365,20366,20367,20368,20369,20370,20371,20372,20373,20374,20375,20376,20377,20378,20379,20380,20381,20382,20383,20384,20385,20386,20387,20388,20389,20390,20391,20392,20393,20394,20395,20396,20397,20398,20399,20400,20401,20402,20403,20404,20405,20406,20407,20408,20409,20410,20411,20412,20413,20414,20415,20416,20417,20418,20419,20420,20421,20422,20423,20424,20425,20426,20427,20428,20429,20430,20431,20432,20433,20434,20435,20436,20437,20438,20439,20440,20441,20442,20443,20444,20445,20446,20447,20448,20449,20450,20451,20452,20453,20454,20455,20456,20457,20458,20459,20460,20461,20462,20463,20464,20465,20466,20467,20468,20469,20470,20471,20472,20473,20474,20475,20476,20477,20478,20479,20480,20481,20482,20483,20484,20485,20486,20487,20488,20489,20490,20491,20492,20493,20494,20495,20496,20497,20498,20499,20500,20501,20502,20503,20504,20505,20506,20507,20508,20509,20510,20511,20512,20513,20514,20515,20516,20517,20518,20519,20520,20521,20522,20523,20524,20525,20526,20527,20528,20529,20530,20531,20532,20533,20534,20535,20536,20537,20538,20539,20540,20541,20542,20543,20544,20545,20546,20547,20548,20549,20550,20551,20552,20553,20554,20555,20556,20557,20558,20559,20560,20561,20562,20563,20564,20565,20566,20567,20568,20569,20570,20571,20572,20573,20574,20575,20576,20577,20578,20579,20580,20581,20582,20583,20584,20585,20586,20587,20588,20589,20590,20591,20592,20593,20594,20595,20596,20597,20598,20599,20600,20601,20602,20603,20604,20605,20606,20607,20608,20609,20610,20611,20612,20613,20614,20615,20616,20617,20618,20619,20620,20621,20622,20623,20624,20625,20626,20627,20628,20629,20630,20631,20632,20633,20634,20635,20636,20637,20638,20639,20640,20641,20642,20643,20644,20645,20646,20647,20648,20649,20650,20651,20652,20653,20654,20655,20656,20657,20658,20659,20660,20661,20662,20663,20664,20665,20666,20667,20668,20669,20670,20671,20672,20673,20674,20675,20676,20677,20678,20679,20680,20681,20682,20683,20684,20685,20686,20687,20688,20689,20690,20691,20692,20693,20694,20695,20696,20697,20698,20699,20700,20701,20702,20703,20704,20705,20706,20707,20708,20709,20710,20711,20712,20713,20714,20715,20716,20717,20718,20719,20720,20721,20722,20723,20724,20725,20726,20727,20728,20729,20730,20731,20732,20733,20734,20735,20736,20737,20738,20739,20740,20741,20742,20743,20744,20745,20746,20747,20748,20749,20750,20751,20752,20753,20754,20755,20756,20757,20758,20759,20760,20761,20762,20763,20764,20765,20766,20767,20768,20769,20770,20771,20772,20773,20774,20775,20776,20777,20778,20779,20780,20781,20782,20783,20784,20785,20786,20787,20788,20789,20790,20791,20792,20793,20794,20795,20796,20797,20798,20799,20800,20801,20802,20803,20804,20805,20806,20807,20808,20809,20810,20811,20812,20813,20814,20815,20816,20817,20818,20819,20820,20821,20822,20823,20824,20825,20826,20827,20828,20829,20830,20831,20832,20833,20834,20835,20836,20837,20838,20839,20840,20841,20842,20843,20844,20845,20846,20847,20848,20849,20850,20851,20852,20853,20854,20855,20856,20857,20858,20859,20860,20861,20862,20863,20864,20865,20866,20867,20868,20869,20870,20871,20872,20873,20874,20875,20876,20877,20878,20879,20880,20881,20882,20883,20884,20885,20886,20887,20888,20889,20890,20891,20892,20893,20894,20895,20896,20897,20898,20899,20900,20901,20902,20903,20904,20905,20906,20907,20908,20909,20910,20911,20912,20913,20914,20915,20916,20917,20918,20919,20920,20921,20922,20923,20924,20925,20926,20927,20928,20929,20930,20931,20932,20933,20934,20935,20936,20937,20938,20939,20940,20941,20942,20943,20944,20945,20946,20947,20948,20949,20950,20951,20952,20953,20954,20955,20956,20957,20958,20959,20960,20961,20962,20963,20964,20965,20966,20967,20968,20969,20970,20971,20972,20973,20974,20975,20976,20977,20978,20979,20980,20981,20982,20983,20984,20985,20986,20987,20988,20989,20990,20991,20992,20993,20994,20995,20996,20997,20998,20999,21000,21001,21002,21003,21004,21005,21006,21007,21008,21009,21010,21011,21012,21013,21014,21015,21016,21017,21018,21019,21020,21021,21022,21023,21024,21025,21026,21027,21028,21029,21030,21031,21032,21033,21034,21035,21036,21037,21038,21039,21040,21041,21042,21043,21044,21045,21046,21047,21048,21049,21050,21051,21052,21053,21054,21055,21056,21057,21058,21059,21060,21061,21062,21063,21064,21065,21066,21067,21068,21069,21070,21071,21072,21073,21074,21075,21076,21077,21078,21079,21080,21081,21082,21083,21084,21085,21086,21087,21088,21089,21090,21091,21092,21093,21094,21095,21096,21097,21098,21099,21100,21101,21102,21103,21104,21105,21106,21107,21108,21109,21110,21111,21112,21113,21114,21115,21116,21117,21118,21119,21120,21121,21122,21123,21124,21125,21126,21127,21128,21129,21130,21131,21132,21133,21134,21135,21136,21137,21138,21139,21140,21141,21142,21143,21144,21145,21146,21147,21148,21149,21150,21151,21152,21153,21154,21155,21156,21157,21158,21159,21160,21161,21162,21163,21164,21165,21166,21167,21168,21169,21170,21171,21172,21173,21174,21175,21176,21177,21178,21179,21180,21181,21182,21183,21184,21185,21186,21187,21188,21189,21190,21191,21192,21193,21194,21195,21196,21197,21198,21199,21200,21201,21202,21203,21204,21205,21206,21207,21208,21209,21210,21211,21212,21213,21214,21215,21216,21217,21218,21219,21220,21221,21222,21223,21224,21225,21226,21227,21228,21229,21230,21231,21232,21233,21234,21235,21236,21237,21238,21239,21240,21241,21242,21243,21244,21245,21246,21247,21248,21249,21250,21251,21252,21253,21254,21255,21256,21257,21258,21259,21260,21261,21262,21263,21264,21265,21266,21267,21268,21269,21270,21271,21272,21273,21274,21275,21276,21277,21278,21279,21280,21281,21282,21283,21284,21285,21286,21287,21288,21289,21290,21291,21292,21293,21294,21295,21296,21297,21298,21299,21300,21301,21302,21303,21304,21305,21306,21307,21308,21309,21310,21311,21312,21313,21314,21315,21316,21317,21318,21319,21320,21321,21322,21323,21324,21325,21326,21327,21328,21329,21330,21331,21332,21333,21334,21335,21336,21337,21338,21339,21340,21341,21342,21343,21344,21345,21346,21347,21348,21349,21350,21351,21352,21353,21354,21355,21356,21357,21358,21359,21360,21361,21362,21363,21364,21365,21366,21367,21368,21369,21370,21371,21372,21373,21374,21375,21376,21377,21378,21379,21380,21381,21382,21383,21384,21385,21386,21387,21388,21389,21390,21391,21392,21393,21394,21395,21396,21397,21398,21399,21400,21401,21402,21403,21404,21405,21406,21407,21408,21409,21410,21411,21412,21413,21414,21415,21416,21417,21418,21419,21420,21421,21422,21423,21424,21425,21426,21427,21428,21429,21430,21431,21432,21433,21434,21435,21436,21437,21438,21439,21440,21441,21442,21443,21444,21445,21446,21447,21448,21449,21450,21451,21452,21453,21454,21455,21456,21457,21458,21459,21460,21461,21462,21463,21464,21465,21466,21467,21468,21469,21470,21471,21472,21473,21474,21475,21476,21477,21478,21479,21480,21481,21482,21483,21484,21485,21486,21487,21488,21489,21490,21491,21492,21493,21494,21495,21496,21497,21498,21499,21500,21501,21502,21503,21504,21505,21506,21507,21508,21509,21510,21511,21512,21513,21514,21515,21516,21517,21518,21519,21520,21521,21522,21523,21524,21525,21526,21527,21528,21529,21530,21531,21532,21533,21534,21535,21536,21537,21538,21539,21540,21541,21542,21543,21544,21545,21546,21547,21548,21549,21550,21551,21552,21553,21554,21555,21556,21557,21558,21559,21560,21561,21562,21563,21564,21565,21566,21567,21568,21569,21570,21571,21572,21573,21574,21575,21576,21577,21578,21579,21580,21581,21582,21583,21584,21585,21586,21587,21588,21589,21590,21591,21592,21593,21594,21595,21596,21597,21598,21599,21600,21601,21602,21603,21604,21605,21606,21607,21608,21609,21610,21611,21612,21613,21614,21615,21616,21617,21618,21619,21620,21621,21622,21623,21624,21625,21626,21627,21628,21629,21630,21631,21632,21633,21634,21635,21636,21637,21638,21639,21640,21641,21642,21643,21644,21645,21646,21647,21648,21649,21650,21651,21652,21653,21654,21655,21656,21657,21658,21659,21660,21661,21662,21663,21664,21665,21666,21667,21668,21669,21670,21671,21672,21673,21674,21675,21676,21677,21678,21679,21680,21681,21682,21683,21684,21685,21686,21687,21688,21689,21690,21691,21692,21693,21694,21695,21696,21697,21698,21699,21700,21701,21702,21703,21704,21705,21706,21707,21708,21709,21710,21711,21712,21713,21714,21715,21716,21717,21718,21719,21720,21721,21722,21723,21724,21725,21726,21727,21728,21729,21730,21731,21732,21733,21734,21735,21736,21737,21738,21739,21740,21741,21742,21743,21744,21745,21746,21747,21748,21749,21750,21751,21752,21753,21754,21755,21756,21757,21758,21759,21760,21761,21762,21763,21764,21765,21766,21767,21768,21769,21770,21771,21772,21773,21774,21775,21776,21777,21778,21779,21780,21781,21782,21783,21784,21785,21786,21787,21788,21789,21790,21791,21792,21793,21794,21795,21796,21797,21798,21799,21800,21801,21802,21803,21804,21805,21806,21807,21808,21809,21810,21811,21812,21813,21814,21815,21816,21817,21818,21819,21820,21821,21822,21823,21824,21825,21826,21827,21828,21829,21830,21831,21832,21833,21834,21835,21836,21837,21838,21839,21840,21841,21842,21843,21844,21845,21846,21847,21848,21849,21850,21851,21852,21853,21854,21855,21856,21857,21858,21859,21860,21861,21862,21863,21864,21865,21866,21867,21868,21869,21870,21871,21872,21873,21874,21875,21876,21877,21878,21879,21880,21881,21882,21883,21884,21885,21886,21887,21888,21889,21890,21891,21892,21893,21894,21895,21896,21897,21898,21899,21900,21901,21902,21903,21904,21905,21906,21907,21908,21909,21910,21911,21912,21913,21914,21915,21916,21917,21918,21919,21920,21921,21922,21923,21924,21925,21926,21927,21928,21929,21930,21931,21932,21933,21934,21935,21936,21937,21938,21939,21940,21941,21942,21943,21944,21945,21946,21947,21948,21949,21950,21951,21952,21953,21954,21955,21956,21957,21958,21959,21960,21961,21962,21963,21964,21965,21966,21967,21968,21969,21970,21971,21972,21973,21974,21975,21976,21977,21978,21979,21980,21981,21982,21983,21984,21985,21986,21987,21988,21989,21990,21991,21992,21993,21994,21995,21996,21997,21998,21999,22000,22001,22002,22003,22004,22005,22006,22007,22008,22009,22010,22011,22012,22013,22014,22015,22016,22017,22018,22019,22020,22021,22022,22023,22024,22025,22026,22027,22028,22029,22030,22031,22032,22033,22034,22035,22036,22037,22038,22039,22040,22041,22042,22043,22044,22045,22046,22047,22048,22049,22050,22051,22052,22053,22054,22055,22056,22057,22058,22059,22060,22061,22062,22063,22064,22065,22066,22067,22068,22069,22070,22071,22072,22073,22074,22075,22076,22077,22078,22079,22080,22081,22082,22083,22084,22085,22086,22087,22088,22089,22090,22091,22092,22093,22094,22095,22096,22097,22098,22099,22100,22101,22102,22103,22104,22105,22106,22107,22108,22109,22110,22111,22112,22113,22114,22115,22116,22117,22118,22119,22120,22121,22122,22123,22124,22125,22126,22127,22128,22129,22130,22131,22132,22133,22134,22135,22136,22137,22138,22139,22140,22141,22142,22143,22144,22145,22146,22147,22148,22149,22150,22151,22152,22153,22154,22155,22156,22157,22158,22159,22160,22161,22162,22163,22164,22165,22166,22167,22168,22169,22170,22171,22172,22173,22174,22175,22176,22177,22178,22179,22180,22181,22182,22183,22184,22185,22186,22187,22188,22189,22190,22191,22192,22193,22194,22195,22196,22197,22198,22199,22200,22201,22202,22203,22204,22205,22206,22207,22208,22209,22210,22211,22212,22213,22214,22215,22216,22217,22218,22219,22220,22221,22222,22223,22224,22225,22226,22227,22228,22229,22230,22231,22232,22233,22234,22235,22236,22237,22238,22239,22240,22241,22242,22243,22244,22245,22246,22247,22248,22249,22250,22251,22252,22253,22254,22255,22256,22257,22258,22259,22260,22261,22262,22263,22264,22265,22266,22267,22268,22269,22270,22271,22272,22273,22274,22275,22276,22277,22278,22279,22280,22281,22282,22283,22284,22285,22286,22287,22288,22289,22290,22291,22292,22293,22294,22295,22296,22297,22298,22299,22300,22301,22302,22303,22304,22305,22306,22307,22308,22309,22310,22311,22312,22313,22314,22315,22316,22317,22318,22319,22320,22321,22322,22323,22324,22325,22326,22327,22328,22329,22330,22331,22332,22333,22334,22335,22336,22337,22338,22339,22340,22341,22342,22343,22344,22345,22346,22347,22348,22349,22350,22351,22352,22353,22354,22355,22356,22357,22358,22359,22360,22361,22362,22363,22364,22365,22366,22367,22368,22369,22370,22371,22372,22373,22374,22375,22376,22377,22378,22379,22380,22381,22382,22383,22384,22385,22386,22387,22388,22389,22390,22391,22392,22393,22394,22395,22396,22397,22398,22399,22400,22401,22402,22403,22404,22405,22406,22407,22408,22409,22410,22411,22412,22413,22414,22415,22416,22417,22418,22419,22420,22421,22422,22423,22424,22425,22426,22427,22428,22429,22430,22431,22432,22433,22434,22435,22436,22437,22438,22439,22440,22441,22442,22443,22444,22445,22446,22447,22448,22449,22450,22451,22452,22453,22454,22455,22456,22457,22458,22459,22460,22461,22462,22463,22464,22465,22466,22467,22468,22469,22470,22471,22472,22473,22474,22475,22476,22477,22478,22479,22480,22481,22482,22483,22484,22485,22486,22487,22488,22489,22490,22491,22492,22493,22494,22495,22496,22497,22498,22499,22500,22501,22502,22503,22504,22505,22506,22507,22508,22509,22510,22511,22512,22513,22514,22515,22516,22517,22518,22519,22520,22521,22522,22523,22524,22525,22526,22527,22528,22529,22530,22531,22532,22533,22534,22535,22536,22537,22538,22539,22540,22541,22542,22543,22544,22545,22546,22547,22548,22549,22550,22551,22552,22553,22554,22555,22556,22557,22558,22559,22560,22561,22562,22563,22564,22565,22566,22567,22568,22569,22570,22571,22572,22573,22574,22575,22576,22577,22578,22579,22580,22581,22582,22583,22584,22585,22586,22587,22588,22589,22590,22591,22592,22593,22594,22595,22596,22597,22598,22599,22600,22601,22602,22603,22604,22605,22606,22607,22608,22609,22610,22611,22612,22613,22614,22615,22616,22617,22618,22619,22620,22621,22622,22623,22624,22625,22626,22627,22628,22629,22630,22631,22632,22633,22634,22635,22636,22637,22638,22639,22640,22641,22642,22643,22644,22645,22646,22647,22648,22649,22650,22651,22652,22653,22654,22655,22656,22657,22658,22659,22660,22661,22662,22663,22664,22665,22666,22667,22668,22669,22670,22671,22672,22673,22674,22675,22676,22677,22678,22679,22680,22681,22682,22683,22684,22685,22686,22687,22688,22689,22690,22691,22692,22693,22694,22695,22696,22697,22698,22699,22700,22701,22702,22703,22704,22705,22706,22707,22708,22709,22710,22711,22712,22713,22714,22715,22716,22717,22718,22719,22720,22721,22722,22723,22724,22725,22726,22727,22728,22729,22730,22731,22732,22733,22734,22735,22736,22737,22738,22739,22740,22741,22742,22743,22744,22745,22746,22747,22748,22749,22750,22751,22752,22753,22754,22755,22756,22757,22758,22759,22760,22761,22762,22763,22764,22765,22766,22767,22768,22769,22770,22771,22772,22773,22774,22775,22776,22777,22778,22779,22780,22781,22782,22783,22784,22785,22786,22787,22788,22789,22790,22791,22792,22793,22794,22795,22796,22797,22798,22799,22800,22801,22802,22803,22804,22805,22806,22807,22808,22809,22810,22811,22812,22813,22814,22815,22816,22817,22818,22819,22820,22821,22822,22823,22824,22825,22826,22827,22828,22829,22830,22831,22832,22833,22834,22835,22836,22837,22838,22839,22840,22841,22842,22843,22844,22845,22846,22847,22848,22849,22850,22851,22852,22853,22854,22855,22856,22857,22858,22859,22860,22861,22862,22863,22864,22865,22866,22867,22868,22869,22870,22871,22872,22873,22874,22875,22876,22877,22878,22879,22880,22881,22882,22883,22884,22885,22886,22887,22888,22889,22890,22891,22892,22893,22894,22895,22896,22897,22898,22899,22900,22901,22902,22903,22904,22905,22906,22907,22908,22909,22910,22911,22912,22913,22914,22915,22916,22917,22918,22919,22920,22921,22922,22923,22924,22925,22926,22927,22928,22929,22930,22931,22932,22933,22934,22935,22936,22937,22938,22939,22940,22941,22942,22943,22944,22945,22946,22947,22948,22949,22950,22951,22952,22953,22954,22955,22956,22957,22958,22959,22960,22961,22962,22963,22964,22965,22966,22967,22968,22969,22970,22971,22972,22973,22974,22975,22976,22977,22978,22979,22980,22981,22982,22983,22984,22985,22986,22987,22988,22989,22990,22991,22992,22993,22994,22995,22996,22997,22998,22999,23000,23001,23002,23003,23004,23005,23006,23007,23008,23009,23010,23011,23012,23013,23014,23015,23016,23017,23018,23019,23020,23021,23022,23023,23024,23025,23026,23027,23028,23029,23030,23031,23032,23033,23034,23035,23036,23037,23038,23039,23040,23041,23042,23043,23044,23045,23046,23047,23048,23049,23050,23051,23052,23053,23054,23055,23056,23057,23058,23059,23060,23061,23062,23063,23064,23065,23066,23067,23068,23069,23070,23071,23072,23073,23074,23075,23076,23077,23078,23079,23080,23081,23082,23083,23084,23085,23086,23087,23088,23089,23090,23091,23092,23093,23094,23095,23096,23097,23098,23099,23100,23101,23102,23103,23104,23105,23106,23107,23108,23109,23110,23111,23112,23113,23114,23115,23116,23117,23118,23119,23120,23121,23122,23123,23124,23125,23126,23127,23128,23129,23130,23131,23132,23133,23134,23135,23136,23137,23138,23139,23140,23141,23142,23143,23144,23145,23146,23147,23148,23149,23150,23151,23152,23153,23154,23155,23156,23157,23158,23159,23160,23161,23162,23163,23164,23165,23166,23167,23168,23169,23170,23171,23172,23173,23174,23175,23176,23177,23178,23179,23180,23181,23182,23183,23184,23185,23186,23187,23188,23189,23190,23191,23192,23193,23194,23195,23196,23197,23198,23199,23200,23201,23202,23203,23204,23205,23206,23207,23208,23209,23210,23211,23212,23213,23214,23215,23216,23217,23218,23219,23220,23221,23222,23223,23224,23225,23226,23227,23228,23229,23230,23231,23232,23233,23234,23235,23236,23237,23238,23239,23240,23241,23242,23243,23244,23245,23246,23247,23248,23249,23250,23251,23252,23253,23254,23255,23256,23257,23258,23259,23260,23261,23262,23263,23264,23265,23266,23267,23268,23269,23270,23271,23272,23273,23274,23275,23276,23277,23278,23279,23280,23281,23282,23283,23284,23285,23286,23287,23288,23289,23290,23291,23292,23293,23294,23295,23296,23297,23298,23299,23300,23301,23302,23303,23304,23305,23306,23307,23308,23309,23310,23311,23312,23313,23314,23315,23316,23317,23318,23319,23320,23321,23322,23323,23324,23325,23326,23327,23328,23329,23330,23331,23332,23333,23334,23335,23336,23337,23338,23339,23340,23341,23342,23343,23344,23345,23346,23347,23348,23349,23350,23351,23352,23353,23354,23355,23356,23357,23358,23359,23360,23361,23362,23363,23364,23365,23366,23367,23368,23369,23370,23371,23372,23373,23374,23375,23376,23377,23378,23379,23380,23381,23382,23383,23384,23385,23386,23387,23388,23389,23390,23391,23392,23393,23394,23395,23396,23397,23398,23399,23400,23401,23402,23403,23404,23405,23406,23407,23408,23409,23410,23411,23412,23413,23414,23415,23416,23417,23418,23419,23420,23421,23422,23423,23424,23425,23426,23427,23428,23429,23430,23431,23432,23433,23434,23435,23436,23437,23438,23439,23440,23441,23442,23443,23444,23445,23446,23447,23448,23449,23450,23451,23452,23453,23454,23455,23456,23457,23458,23459,23460,23461,23462,23463,23464,23465,23466,23467,23468,23469,23470,23471,23472,23473,23474,23475,23476,23477,23478,23479,23480,23481,23482,23483,23484,23485,23486,23487,23488,23489,23490,23491,23492,23493,23494,23495,23496,23497,23498,23499,23500,23501,23502,23503,23504,23505,23506,23507,23508,23509,23510,23511,23512,23513,23514,23515,23516,23517,23518,23519,23520,23521,23522,23523,23524,23525,23526,23527,23528,23529,23530,23531,23532,23533,23534,23535,23536,23537,23538,23539,23540,23541,23542,23543,23544,23545,23546,23547,23548,23549,23550,23551,23552,23553,23554,23555,23556,23557,23558,23559,23560,23561,23562,23563,23564,23565,23566,23567,23568,23569,23570,23571,23572,23573,23574,23575,23576,23577,23578,23579,23580,23581,23582,23583,23584,23585,23586,23587,23588,23589,23590,23591,23592,23593,23594,23595,23596,23597,23598,23599,23600,23601,23602,23603,23604,23605,23606,23607,23608,23609,23610,23611,23612,23613,23614,23615,23616,23617,23618,23619,23620,23621,23622,23623,23624,23625,23626,23627,23628,23629,23630,23631,23632,23633,23634,23635,23636,23637,23638,23639,23640,23641,23642,23643,23644,23645,23646,23647,23648,23649,23650,23651,23652,23653,23654,23655,23656,23657,23658,23659,23660,23661,23662,23663,23664,23665,23666,23667,23668,23669,23670,23671,23672,23673,23674,23675,23676,23677,23678,23679,23680,23681,23682,23683,23684,23685,23686,23687,23688,23689,23690,23691,23692,23693,23694,23695,23696,23697,23698,23699,23700,23701,23702,23703,23704,23705,23706,23707,23708,23709,23710,23711,23712,23713,23714,23715,23716,23717,23718,23719,23720,23721,23722,23723,23724,23725,23726,23727,23728,23729,23730,23731,23732,23733,23734,23735,23736,23737,23738,23739,23740,23741,23742,23743,23744,23745,23746,23747,23748,23749,23750,23751,23752,23753,23754,23755,23756,23757,23758,23759,23760,23761,23762,23763,23764,23765,23766,23767,23768,23769,23770,23771,23772,23773,23774,23775,23776,23777,23778,23779,23780,23781,23782,23783,23784,23785,23786,23787,23788,23789,23790,23791,23792,23793,23794,23795,23796,23797,23798,23799,23800,23801,23802,23803,23804,23805,23806,23807,23808,23809,23810,23811,23812,23813,23814,23815,23816,23817,23818,23819,23820,23821,23822,23823,23824,23825,23826,23827,23828,23829,23830,23831,23832,23833,23834,23835,23836,23837,23838,23839,23840,23841,23842,23843,23844,23845,23846,23847,23848,23849,23850,23851,23852,23853,23854,23855,23856,23857,23858,23859,23860,23861,23862,23863,23864,23865,23866,23867,23868,23869,23870,23871,23872,23873,23874,23875,23876,23877,23878,23879,23880,23881,23882,23883,23884,23885,23886,23887,23888,23889,23890,23891,23892,23893,23894,23895,23896,23897,23898,23899,23900,23901,23902,23903,23904,23905,23906,23907,23908,23909,23910,23911,23912,23913,23914,23915,23916,23917,23918,23919,23920,23921,23922,23923,23924,23925,23926,23927,23928,23929,23930,23931,23932,23933,23934,23935,23936,23937,23938,23939,23940,23941,23942,23943,23944,23945,23946,23947,23948,23949,23950,23951,23952,23953,23954,23955,23956,23957,23958,23959,23960,23961,23962,23963,23964,23965,23966,23967,23968,23969,23970,23971,23972,23973,23974,23975,23976,23977,23978,23979,23980,23981,23982,23983,23984,23985,23986,23987,23988,23989,23990,23991,23992,23993,23994,23995,23996,23997,23998,23999,24000,24001,24002,24003,24004,24005,24006,24007,24008,24009,24010,24011,24012,24013,24014,24015,24016,24017,24018,24019,24020,24021,24022,24023,24024,24025,24026,24027,24028,24029,24030,24031,24032,24033,24034,24035,24036,24037,24038,24039,24040,24041,24042,24043,24044,24045,24046,24047,24048,24049,24050,24051,24052,24053,24054,24055,24056,24057,24058,24059,24060,24061,24062,24063,24064,24065,24066,24067,24068,24069,24070,24071,24072,24073,24074,24075,24076,24077,24078,24079,24080,24081,24082,24083,24084,24085,24086,24087,24088,24089,24090,24091,24092,24093,24094,24095,24096,24097,24098,24099,24100,24101,24102,24103,24104,24105,24106,24107,24108,24109,24110,24111,24112,24113,24114,24115,24116,24117,24118,24119,24120,24121,24122,24123,24124,24125,24126,24127,24128,24129,24130,24131,24132,24133,24134,24135,24136,24137,24138,24139,24140,24141,24142,24143,24144,24145,24146,24147,24148,24149,24150,24151,24152,24153,24154,24155,24156,24157,24158,24159,24160,24161,24162,24163,24164,24165,24166,24167,24168,24169,24170,24171,24172,24173,24174,24175,24176,24177,24178,24179,24180,24181,24182,24183,24184,24185,24186,24187,24188,24189,24190,24191,24192,24193,24194,24195,24196,24197,24198,24199,24200,24201,24202,24203,24204,24205,24206,24207,24208,24209,24210,24211,24212,24213,24214,24215,24216,24217,24218,24219,24220,24221,24222,24223,24224,24225,24226,24227,24228,24229,24230,24231,24232,24233,24234,24235,24236,24237,24238,24239,24240,24241,24242,24243,24244,24245,24246,24247,24248,24249,24250,24251,24252,24253,24254,24255,24256,24257,24258,24259,24260,24261,24262,24263,24264,24265,24266,24267,24268,24269,24270,24271,24272,24273,24274,24275,24276,24277,24278,24279,24280,24281,24282,24283,24284,24285,24286,24287,24288,24289,24290,24291,24292,24293,24294,24295,24296,24297,24298,24299,24300,24301,24302,24303,24304,24305,24306,24307,24308,24309,24310,24311,24312,24313,24314,24315,24316,24317,24318,24319,24320,24321,24322,24323,24324,24325,24326,24327,24328,24329,24330,24331,24332,24333,24334,24335,24336,24337,24338,24339,24340,24341,24342,24343,24344,24345,24346,24347,24348,24349,24350,24351,24352,24353,24354,24355,24356,24357,24358,24359,24360,24361,24362,24363,24364,24365,24366,24367,24368,24369,24370,24371,24372,24373,24374,24375,24376,24377,24378,24379,24380,24381,24382,24383,24384,24385,24386,24387,24388,24389,24390,24391,24392,24393,24394,24395,24396,24397,24398,24399,24400,24401,24402,24403,24404,24405,24406,24407,24408,24409,24410,24411,24412,24413,24414,24415,24416,24417,24418,24419,24420,24421,24422,24423,24424,24425,24426,24427,24428,24429,24430,24431,24432,24433,24434,24435,24436,24437,24438,24439,24440,24441,24442,24443,24444,24445,24446,24447,24448,24449,24450,24451,24452,24453,24454,24455,24456,24457,24458,24459,24460,24461,24462,24463,24464,24465,24466,24467,24468,24469,24470,24471,24472,24473,24474,24475,24476,24477,24478,24479,24480,24481,24482,24483,24484,24485,24486,24487,24488,24489,24490,24491,24492,24493,24494,24495,24496,24497,24498,24499,24500,24501,24502,24503,24504,24505,24506,24507,24508,24509,24510,24511,24512,24513,24514,24515,24516,24517,24518,24519,24520,24521,24522,24523,24524,24525,24526,24527,24528,24529,24530,24531,24532,24533,24534,24535,24536,24537,24538,24539,24540,24541,24542,24543,24544,24545,24546,24547,24548,24549,24550,24551,24552,24553,24554,24555,24556,24557,24558,24559,24560,24561,24562,24563,24564,24565,24566,24567,24568,24569,24570,24571,24572,24573,24574,24575,24576,24577,24578,24579,24580,24581,24582,24583,24584,24585,24586,24587,24588,24589,24590,24591,24592,24593,24594,24595,24596,24597,24598,24599,24600,24601,24602,24603,24604,24605,24606,24607,24608,24609,24610,24611,24612,24613,24614,24615,24616,24617,24618,24619,24620,24621,24622,24623,24624,24625,24626,24627,24628,24629,24630,24631,24632,24633,24634,24635,24636,24637,24638,24639,24640,24641,24642,24643,24644,24645,24646,24647,24648,24649,24650,24651,24652,24653,24654,24655,24656,24657,24658,24659,24660,24661,24662,24663,24664,24665,24666,24667,24668,24669,24670,24671,24672,24673,24674,24675,24676,24677,24678,24679,24680,24681,24682,24683,24684,24685,24686,24687,24688,24689,24690,24691,24692,24693,24694,24695,24696,24697,24698,24699,24700,24701,24702,24703,24704,24705,24706,24707,24708,24709,24710,24711,24712,24713,24714,24715,24716,24717,24718,24719,24720,24721,24722,24723,24724,24725,24726,24727,24728,24729,24730,24731,24732,24733,24734,24735,24736,24737,24738,24739,24740,24741,24742,24743,24744,24745,24746,24747,24748,24749,24750,24751,24752,24753,24754,24755,24756,24757,24758,24759,24760,24761,24762,24763,24764,24765,24766,24767,24768,24769,24770,24771,24772,24773,24774,24775,24776,24777,24778,24779,24780,24781,24782,24783,24784,24785,24786,24787,24788,24789,24790,24791,24792,24793,24794,24795,24796,24797,24798,24799,24800,24801,24802,24803,24804,24805,24806,24807,24808,24809,24810,24811,24812,24813,24814,24815,24816,24817,24818,24819,24820,24821,24822,24823,24824,24825,24826,24827,24828,24829,24830,24831,24832,24833,24834,24835,24836,24837,24838,24839,24840,24841,24842,24843,24844,24845,24846,24847,24848,24849,24850,24851,24852,24853,24854,24855,24856,24857,24858,24859,24860,24861,24862,24863,24864,24865,24866,24867,24868,24869,24870,24871,24872,24873,24874,24875,24876,24877,24878,24879,24880,24881,24882,24883,24884,24885,24886,24887,24888,24889,24890,24891,24892,24893,24894,24895,24896,24897,24898,24899,24900,24901,24902,24903,24904,24905,24906,24907,24908,24909,24910,24911,24912,24913,24914,24915,24916,24917,24918,24919,24920,24921,24922,24923,24924,24925,24926,24927,24928,24929,24930,24931,24932,24933,24934,24935,24936,24937,24938,24939,24940,24941,24942,24943,24944,24945,24946,24947,24948,24949,24950,24951,24952,24953,24954,24955,24956,24957,24958,24959,24960,24961,24962,24963,24964,24965,24966,24967,24968,24969,24970,24971,24972,24973,24974,24975,24976,24977,24978,24979,24980,24981,24982,24983,24984,24985,24986,24987,24988,24989,24990,24991,24992,24993,24994,24995,24996,24997,24998,24999,25000,25001,25002,25003,25004,25005,25006,25007,25008,25009,25010,25011,25012,25013,25014,25015,25016,25017,25018,25019,25020,25021,25022,25023,25024,25025,25026,25027,25028,25029,25030,25031,25032,25033,25034,25035,25036,25037,25038,25039,25040,25041,25042,25043,25044,25045,25046,25047,25048,25049,25050,25051,25052,25053,25054,25055,25056,25057,25058,25059,25060,25061,25062,25063,25064,25065,25066,25067,25068,25069,25070,25071,25072,25073,25074,25075,25076,25077,25078,25079,25080,25081,25082,25083,25084,25085,25086,25087,25088,25089,25090,25091,25092,25093,25094,25095,25096,25097,25098,25099,25100,25101,25102,25103,25104,25105,25106,25107,25108,25109,25110,25111,25112,25113,25114,25115,25116,25117,25118,25119,25120,25121,25122,25123,25124,25125,25126,25127,25128,25129,25130,25131,25132,25133,25134,25135,25136,25137,25138,25139,25140,25141,25142,25143,25144,25145,25146,25147,25148,25149,25150,25151,25152,25153,25154,25155,25156,25157,25158,25159,25160,25161,25162,25163,25164,25165,25166,25167,25168,25169,25170,25171,25172,25173,25174,25175,25176,25177,25178,25179,25180,25181,25182,25183,25184,25185,25186,25187,25188,25189,25190,25191,25192,25193,25194,25195,25196,25197,25198,25199,25200,25201,25202,25203,25204,25205,25206,25207,25208,25209,25210,25211,25212,25213,25214,25215,25216,25217,25218,25219,25220,25221,25222,25223,25224,25225,25226,25227,25228,25229,25230,25231,25232,25233,25234,25235,25236,25237,25238,25239,25240,25241,25242,25243,25244,25245,25246,25247,25248,25249,25250,25251,25252,25253,25254,25255,25256,25257,25258,25259,25260,25261,25262,25263,25264,25265,25266,25267,25268,25269,25270,25271,25272,25273,25274,25275,25276,25277,25278,25279,25280,25281,25282,25283,25284,25285,25286,25287,25288,25289,25290,25291,25292,25293,25294,25295,25296,25297,25298,25299,25300,25301,25302,25303,25304,25305,25306,25307,25308,25309,25310,25311,25312,25313,25314,25315,25316,25317,25318,25319,25320,25321,25322,25323,25324,25325,25326,25327,25328,25329,25330,25331,25332,25333,25334,25335,25336,25337,25338,25339,25340,25341,25342,25343,25344,25345,25346,25347,25348,25349,25350,25351,25352,25353,25354,25355,25356,25357,25358,25359,25360,25361,25362,25363,25364,25365,25366,25367,25368,25369,25370,25371,25372,25373,25374,25375,25376,25377,25378,25379,25380,25381,25382,25383,25384,25385,25386,25387,25388,25389,25390,25391,25392,25393,25394,25395,25396,25397,25398,25399,25400,25401,25402,25403,25404,25405,25406,25407,25408,25409,25410,25411,25412,25413,25414,25415,25416,25417,25418,25419,25420,25421,25422,25423,25424,25425,25426,25427,25428,25429,25430,25431,25432,25433,25434,25435,25436,25437,25438,25439,25440,25441,25442,25443,25444,25445,25446,25447,25448,25449,25450,25451,25452,25453,25454,25455,25456,25457,25458,25459,25460,25461,25462,25463,25464,25465,25466,25467,25468,25469,25470,25471,25472,25473,25474,25475,25476,25477,25478,25479,25480,25481,25482,25483,25484,25485,25486,25487,25488,25489,25490,25491,25492,25493,25494,25495,25496,25497,25498,25499,25500,25501,25502,25503,25504,25505,25506,25507,25508,25509,25510,25511,25512,25513,25514,25515,25516,25517,25518,25519,25520,25521,25522,25523,25524,25525,25526,25527,25528,25529,25530,25531,25532,25533,25534,25535,25536,25537,25538,25539,25540,25541,25542,25543,25544,25545,25546,25547,25548,25549,25550,25551,25552,25553,25554,25555,25556,25557,25558,25559,25560,25561,25562,25563,25564,25565,25566,25567,25568,25569,25570,25571,25572,25573,25574,25575,25576,25577,25578,25579,25580,25581,25582,25583,25584,25585,25586,25587,25588,25589,25590,25591,25592,25593,25594,25595,25596,25597,25598,25599,25600,25601,25602,25603,25604,25605,25606,25607,25608,25609,25610,25611,25612,25613,25614,25615,25616,25617,25618,25619,25620,25621,25622,25623,25624,25625,25626,25627,25628,25629,25630,25631,25632,25633,25634,25635,25636,25637,25638,25639,25640,25641,25642,25643,25644,25645,25646,25647,25648,25649,25650,25651,25652,25653,25654,25655,25656,25657,25658,25659,25660,25661,25662,25663,25664,25665,25666,25667,25668,25669,25670,25671,25672,25673,25674,25675,25676,25677,25678,25679,25680,25681,25682,25683,25684,25685,25686,25687,25688,25689,25690,25691,25692,25693,25694,25695,25696,25697,25698,25699,25700,25701,25702,25703,25704,25705,25706,25707,25708,25709,25710,25711,25712,25713,25714,25715,25716,25717,25718,25719,25720,25721,25722,25723,25724,25725,25726,25727,25728,25729,25730,25731,25732,25733,25734,25735,25736,25737,25738,25739,25740,25741,25742,25743,25744,25745,25746,25747,25748,25749,25750,25751,25752,25753,25754,25755,25756,25757,25758,25759,25760,25761,25762,25763,25764,25765,25766,25767,25768,25769,25770,25771,25772,25773,25774,25775,25776,25777,25778,25779,25780,25781,25782,25783,25784,25785,25786,25787,25788,25789,25790,25791,25792,25793,25794,25795,25796,25797,25798,25799,25800,25801,25802,25803,25804,25805,25806,25807,25808,25809,25810,25811,25812,25813,25814,25815,25816,25817,25818,25819,25820,25821,25822,25823,25824,25825,25826,25827,25828,25829,25830,25831,25832,25833,25834,25835,25836,25837,25838,25839,25840,25841,25842,25843,25844,25845,25846,25847,25848,25849,25850,25851,25852,25853,25854,25855,25856,25857,25858,25859,25860,25861,25862,25863,25864,25865,25866,25867,25868,25869,25870,25871,25872,25873,25874,25875,25876,25877,25878,25879,25880,25881,25882,25883,25884,25885,25886,25887,25888,25889,25890,25891,25892,25893,25894,25895,25896,25897,25898,25899,25900,25901,25902,25903,25904,25905,25906,25907,25908,25909,25910,25911,25912,25913,25914,25915,25916,25917,25918,25919,25920,25921,25922,25923,25924,25925,25926,25927,25928,25929,25930,25931,25932,25933,25934,25935,25936,25937,25938,25939,25940,25941,25942,25943,25944,25945,25946,25947,25948,25949,25950,25951,25952,25953,25954,25955,25956,25957,25958,25959,25960,25961,25962,25963,25964,25965,25966,25967,25968,25969,25970,25971,25972,25973,25974,25975,25976,25977,25978,25979,25980,25981,25982,25983,25984,25985,25986,25987,25988,25989,25990,25991,25992,25993,25994,25995,25996,25997,25998,25999,26000,26001,26002,26003,26004,26005,26006,26007,26008,26009,26010,26011,26012,26013,26014,26015,26016,26017,26018,26019,26020,26021,26022,26023,26024,26025,26026,26027,26028,26029,26030,26031,26032,26033,26034,26035,26036,26037,26038,26039,26040,26041,26042,26043,26044,26045,26046,26047,26048,26049,26050,26051,26052,26053,26054,26055,26056,26057,26058,26059,26060,26061,26062,26063,26064,26065,26066,26067,26068,26069,26070,26071,26072,26073,26074,26075,26076,26077,26078,26079,26080,26081,26082,26083,26084,26085,26086,26087,26088,26089,26090,26091,26092,26093,26094,26095,26096,26097,26098,26099,26100,26101,26102,26103,26104,26105,26106,26107,26108,26109,26110,26111,26112,26113,26114,26115,26116,26117,26118,26119,26120,26121,26122,26123,26124,26125,26126,26127,26128,26129,26130,26131,26132,26133,26134,26135,26136,26137,26138,26139,26140,26141,26142,26143,26144,26145,26146,26147,26148,26149,26150,26151,26152,26153,26154,26155,26156,26157,26158,26159,26160,26161,26162,26163,26164,26165,26166,26167,26168,26169,26170,26171,26172,26173,26174,26175,26176,26177,26178,26179,26180,26181,26182,26183,26184,26185,26186,26187,26188,26189,26190,26191,26192,26193,26194,26195,26196,26197,26198,26199,26200,26201,26202,26203,26204,26205,26206,26207,26208,26209,26210,26211,26212,26213,26214,26215,26216,26217,26218,26219,26220,26221,26222,26223,26224,26225,26226,26227,26228,26229,26230,26231,26232,26233,26234,26235,26236,26237,26238,26239,26240,26241,26242,26243,26244,26245,26246,26247,26248,26249,26250,26251,26252,26253,26254,26255,26256,26257,26258,26259,26260,26261,26262,26263,26264,26265,26266,26267,26268,26269,26270,26271,26272,26273,26274,26275,26276,26277,26278,26279,26280,26281,26282,26283,26284,26285,26286,26287,26288,26289,26290,26291,26292,26293,26294,26295,26296,26297,26298,26299,26300,26301,26302,26303,26304,26305,26306,26307,26308,26309,26310,26311,26312,26313,26314,26315,26316,26317,26318,26319,26320,26321,26322,26323,26324,26325,26326,26327,26328,26329,26330,26331,26332,26333,26334,26335,26336,26337,26338,26339,26340,26341,26342,26343,26344,26345,26346,26347,26348,26349,26350,26351,26352,26353,26354,26355,26356,26357,26358,26359,26360,26361,26362,26363,26364,26365,26366,26367,26368,26369,26370,26371,26372,26373,26374,26375,26376,26377,26378,26379,26380,26381,26382,26383,26384,26385,26386,26387,26388,26389,26390,26391,26392,26393,26394,26395,26396,26397,26398,26399,26400,26401,26402,26403,26404,26405,26406,26407,26408,26409,26410,26411,26412,26413,26414,26415,26416,26417,26418,26419,26420,26421,26422,26423,26424,26425,26426,26427,26428,26429,26430,26431,26432,26433,26434,26435,26436,26437,26438,26439,26440,26441,26442,26443,26444,26445,26446,26447,26448,26449,26450,26451,26452,26453,26454,26455,26456,26457,26458,26459,26460,26461,26462,26463,26464,26465,26466,26467,26468,26469,26470,26471,26472,26473,26474,26475,26476,26477,26478,26479,26480,26481,26482,26483,26484,26485,26486,26487,26488,26489,26490,26491,26492,26493,26494,26495,26496,26497,26498,26499,26500,26501,26502,26503,26504,26505,26506,26507,26508,26509,26510,26511,26512,26513,26514,26515,26516,26517,26518,26519,26520,26521,26522,26523,26524,26525,26526,26527,26528,26529,26530,26531,26532,26533,26534,26535,26536,26537,26538,26539,26540,26541,26542,26543,26544,26545,26546,26547,26548,26549,26550,26551,26552,26553,26554,26555,26556,26557,26558,26559,26560,26561,26562,26563,26564,26565,26566,26567,26568,26569,26570,26571,26572,26573,26574,26575,26576,26577,26578,26579,26580,26581,26582,26583,26584,26585,26586,26587,26588,26589,26590,26591,26592,26593,26594,26595,26596,26597,26598,26599,26600,26601,26602,26603,26604,26605,26606,26607,26608,26609,26610,26611,26612,26613,26614,26615,26616,26617,26618,26619,26620,26621,26622,26623,26624,26625,26626,26627,26628,26629,26630,26631,26632,26633,26634,26635,26636,26637,26638,26639,26640,26641,26642,26643,26644,26645,26646,26647,26648,26649,26650,26651,26652,26653,26654,26655,26656,26657,26658,26659,26660,26661,26662,26663,26664,26665,26666,26667,26668,26669,26670,26671,26672,26673,26674,26675,26676,26677,26678,26679,26680,26681,26682,26683,26684,26685,26686,26687,26688,26689,26690,26691,26692,26693,26694,26695,26696,26697,26698,26699,26700,26701,26702,26703,26704,26705,26706,26707,26708,26709,26710,26711,26712,26713,26714,26715,26716,26717,26718,26719,26720,26721,26722,26723,26724,26725,26726,26727,26728,26729,26730,26731,26732,26733,26734,26735,26736,26737,26738,26739,26740,26741,26742,26743,26744,26745,26746,26747,26748,26749,26750,26751,26752,26753,26754,26755,26756,26757,26758,26759,26760,26761,26762,26763,26764,26765,26766,26767,26768,26769,26770,26771,26772,26773,26774,26775,26776,26777,26778,26779,26780,26781,26782,26783,26784,26785,26786,26787,26788,26789,26790,26791,26792,26793,26794,26795,26796,26797,26798,26799,26800,26801,26802,26803,26804,26805,26806,26807,26808,26809,26810,26811,26812,26813,26814,26815,26816,26817,26818,26819,26820,26821,26822,26823,26824,26825,26826,26827,26828,26829,26830,26831,26832,26833,26834,26835,26836,26837,26838,26839,26840,26841,26842,26843,26844,26845,26846,26847,26848,26849,26850,26851,26852,26853,26854,26855,26856,26857,26858,26859,26860,26861,26862,26863,26864,26865,26866,26867,26868,26869,26870,26871,26872,26873,26874,26875,26876,26877,26878,26879,26880,26881,26882,26883,26884,26885,26886,26887,26888,26889,26890,26891,26892,26893,26894,26895,26896,26897,26898,26899,26900,26901,26902,26903,26904,26905,26906,26907,26908,26909,26910,26911,26912,26913,26914,26915,26916,26917,26918,26919,26920,26921,26922,26923,26924,26925,26926,26927,26928,26929,26930,26931,26932,26933,26934,26935,26936,26937,26938,26939,26940,26941,26942,26943,26944,26945,26946,26947,26948,26949,26950,26951,26952,26953,26954,26955,26956,26957,26958,26959,26960,26961,26962,26963,26964,26965,26966,26967,26968,26969,26970,26971,26972,26973,26974,26975,26976,26977,26978,26979,26980,26981,26982,26983,26984,26985,26986,26987,26988,26989,26990,26991,26992,26993,26994,26995,26996,26997,26998,26999,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015,27016,27017,27018,27019,27020,27021,27022,27023,27024,27025,27026,27027,27028,27029,27030,27031,27032,27033,27034,27035,27036,27037,27038,27039,27040,27041,27042,27043,27044,27045,27046,27047,27048,27049,27050,27051,27052,27053,27054,27055,27056,27057,27058,27059,27060,27061,27062,27063,27064,27065,27066,27067,27068,27069,27070,27071,27072,27073,27074,27075,27076,27077,27078,27079,27080,27081,27082,27083,27084,27085,27086,27087,27088,27089,27090,27091,27092,27093,27094,27095,27096,27097,27098,27099,27100,27101,27102,27103,27104,27105,27106,27107,27108,27109,27110,27111,27112,27113,27114,27115,27116,27117,27118,27119,27120,27121,27122,27123,27124,27125,27126,27127,27128,27129,27130,27131,27132,27133,27134,27135,27136,27137,27138,27139,27140,27141,27142,27143,27144,27145,27146,27147,27148,27149,27150,27151,27152,27153,27154,27155,27156,27157,27158,27159,27160,27161,27162,27163,27164,27165,27166,27167,27168,27169,27170,27171,27172,27173,27174,27175,27176,27177,27178,27179,27180,27181,27182,27183,27184,27185,27186,27187,27188,27189,27190,27191,27192,27193,27194,27195,27196,27197,27198,27199,27200,27201,27202,27203,27204,27205,27206,27207,27208,27209,27210,27211,27212,27213,27214,27215,27216,27217,27218,27219,27220,27221,27222,27223,27224,27225,27226,27227,27228,27229,27230,27231,27232,27233,27234,27235,27236,27237,27238,27239,27240,27241,27242,27243,27244,27245,27246,27247,27248,27249,27250,27251,27252,27253,27254,27255,27256,27257,27258,27259,27260,27261,27262,27263,27264,27265,27266,27267,27268,27269,27270,27271,27272,27273,27274,27275,27276,27277,27278,27279,27280,27281,27282,27283,27284,27285,27286,27287,27288,27289,27290,27291,27292,27293,27294,27295,27296,27297,27298,27299,27300,27301,27302,27303,27304,27305,27306,27307,27308,27309,27310,27311,27312,27313,27314,27315,27316,27317,27318,27319,27320,27321,27322,27323,27324,27325,27326,27327,27328,27329,27330,27331,27332,27333,27334,27335,27336,27337,27338,27339,27340,27341,27342,27343,27344,27345,27346,27347,27348,27349,27350,27351,27352,27353,27354,27355,27356,27357,27358,27359,27360,27361,27362,27363,27364,27365,27366,27367,27368,27369,27370,27371,27372,27373,27374,27375,27376,27377,27378,27379,27380,27381,27382,27383,27384,27385,27386,27387,27388,27389,27390,27391,27392,27393,27394,27395,27396,27397,27398,27399,27400,27401,27402,27403,27404,27405,27406,27407,27408,27409,27410,27411,27412,27413,27414,27415,27416,27417,27418,27419,27420,27421,27422,27423,27424,27425,27426,27427,27428,27429,27430,27431,27432,27433,27434,27435,27436,27437,27438,27439,27440,27441,27442,27443,27444,27445,27446,27447,27448,27449,27450,27451,27452,27453,27454,27455,27456,27457,27458,27459,27460,27461,27462,27463,27464,27465,27466,27467,27468,27469,27470,27471,27472,27473,27474,27475,27476,27477,27478,27479,27480,27481,27482,27483,27484,27485,27486,27487,27488,27489,27490,27491,27492,27493,27494,27495,27496,27497,27498,27499,27500,27501,27502,27503,27504,27505,27506,27507,27508,27509,27510,27511,27512,27513,27514,27515,27516,27517,27518,27519,27520,27521,27522,27523,27524,27525,27526,27527,27528,27529,27530,27531,27532,27533,27534,27535,27536,27537,27538,27539,27540,27541,27542,27543,27544,27545,27546,27547,27548,27549,27550,27551,27552,27553,27554,27555,27556,27557,27558,27559,27560,27561,27562,27563,27564,27565,27566,27567,27568,27569,27570,27571,27572,27573,27574,27575,27576,27577,27578,27579,27580,27581,27582,27583,27584,27585,27586,27587,27588,27589,27590,27591,27592,27593,27594,27595,27596,27597,27598,27599,27600,27601,27602,27603,27604,27605,27606,27607,27608,27609,27610,27611,27612,27613,27614,27615,27616,27617,27618,27619,27620,27621,27622,27623,27624,27625,27626,27627,27628,27629,27630,27631,27632,27633,27634,27635,27636,27637,27638,27639,27640,27641,27642,27643,27644,27645,27646,27647,27648,27649,27650,27651,27652,27653,27654,27655,27656,27657,27658,27659,27660,27661,27662,27663,27664,27665,27666,27667,27668,27669,27670,27671,27672,27673,27674,27675,27676,27677,27678,27679,27680,27681,27682,27683,27684,27685,27686,27687,27688,27689,27690,27691,27692,27693,27694,27695,27696,27697,27698,27699,27700,27701,27702,27703,27704,27705,27706,27707,27708,27709,27710,27711,27712,27713,27714,27715,27716,27717,27718,27719,27720,27721,27722,27723,27724,27725,27726,27727,27728,27729,27730,27731,27732,27733,27734,27735,27736,27737,27738,27739,27740,27741,27742,27743,27744,27745,27746,27747,27748,27749,27750,27751,27752,27753,27754,27755,27756,27757,27758,27759,27760,27761,27762,27763,27764,27765,27766,27767,27768,27769,27770,27771,27772,27773,27774,27775,27776,27777,27778,27779,27780,27781,27782,27783,27784,27785,27786,27787,27788,27789,27790,27791,27792,27793,27794,27795,27796,27797,27798,27799,27800,27801,27802,27803,27804,27805,27806,27807,27808,27809,27810,27811,27812,27813,27814,27815,27816,27817,27818,27819,27820,27821,27822,27823,27824,27825,27826,27827,27828,27829,27830,27831,27832,27833,27834,27835,27836,27837,27838,27839,27840,27841,27842,27843,27844,27845,27846,27847,27848,27849,27850,27851,27852,27853,27854,27855,27856,27857,27858,27859,27860,27861,27862,27863,27864,27865,27866,27867,27868,27869,27870,27871,27872,27873,27874,27875,27876,27877,27878,27879,27880,27881,27882,27883,27884,27885,27886,27887,27888,27889,27890,27891,27892,27893,27894,27895,27896,27897,27898,27899,27900,27901,27902,27903,27904,27905,27906,27907,27908,27909,27910,27911,27912,27913,27914,27915,27916,27917,27918,27919,27920,27921,27922,27923,27924,27925,27926,27927,27928,27929,27930,27931,27932,27933,27934,27935,27936,27937,27938,27939,27940,27941,27942,27943,27944,27945,27946,27947,27948,27949,27950,27951,27952,27953,27954,27955,27956,27957,27958,27959,27960,27961,27962,27963,27964,27965,27966,27967,27968,27969,27970,27971,27972,27973,27974,27975,27976,27977,27978,27979,27980,27981,27982,27983,27984,27985,27986,27987,27988,27989,27990,27991,27992,27993,27994,27995,27996,27997,27998,27999,28000,28001,28002,28003,28004,28005,28006,28007,28008,28009,28010,28011,28012,28013,28014,28015,28016,28017,28018,28019,28020,28021,28022,28023,28024,28025,28026,28027,28028,28029,28030,28031,28032,28033,28034,28035,28036,28037,28038,28039,28040,28041,28042,28043,28044,28045,28046,28047,28048,28049,28050,28051,28052,28053,28054,28055,28056,28057,28058,28059,28060,28061,28062,28063,28064,28065,28066,28067,28068,28069,28070,28071,28072,28073,28074,28075,28076,28077,28078,28079,28080,28081,28082,28083,28084,28085,28086,28087,28088,28089,28090,28091,28092,28093,28094,28095,28096,28097,28098,28099,28100,28101,28102,28103,28104,28105,28106,28107,28108,28109,28110,28111,28112,28113,28114,28115,28116,28117,28118,28119,28120,28121,28122,28123,28124,28125,28126,28127,28128,28129,28130,28131,28132,28133,28134,28135,28136,28137,28138,28139,28140,28141,28142,28143,28144,28145,28146,28147,28148,28149,28150,28151,28152,28153,28154,28155,28156,28157,28158,28159,28160,28161,28162,28163,28164,28165,28166,28167,28168,28169,28170,28171,28172,28173,28174,28175,28176,28177,28178,28179,28180,28181,28182,28183,28184,28185,28186,28187,28188,28189,28190,28191,28192,28193,28194,28195,28196,28197,28198,28199,28200,28201,28202,28203,28204,28205,28206,28207,28208,28209,28210,28211,28212,28213,28214,28215,28216,28217,28218,28219,28220,28221,28222,28223,28224,28225,28226,28227,28228,28229,28230,28231,28232,28233,28234,28235,28236,28237,28238,28239,28240,28241,28242,28243,28244,28245,28246,28247,28248,28249,28250,28251,28252,28253,28254,28255,28256,28257,28258,28259,28260,28261,28262,28263,28264,28265,28266,28267,28268,28269,28270,28271,28272,28273,28274,28275,28276,28277,28278,28279,28280,28281,28282,28283,28284,28285,28286,28287,28288,28289,28290,28291,28292,28293,28294,28295,28296,28297,28298,28299,28300,28301,28302,28303,28304,28305,28306,28307,28308,28309,28310,28311,28312,28313,28314,28315,28316,28317,28318,28319,28320,28321,28322,28323,28324,28325,28326,28327,28328,28329,28330,28331,28332,28333,28334,28335,28336,28337,28338,28339,28340,28341,28342,28343,28344,28345,28346,28347,28348,28349,28350,28351,28352,28353,28354,28355,28356,28357,28358,28359,28360,28361,28362,28363,28364,28365,28366,28367,28368,28369,28370,28371,28372,28373,28374,28375,28376,28377,28378,28379,28380,28381,28382,28383,28384,28385,28386,28387,28388,28389,28390,28391,28392,28393,28394,28395,28396,28397,28398,28399,28400,28401,28402,28403,28404,28405,28406,28407,28408,28409,28410,28411,28412,28413,28414,28415,28416,28417,28418,28419,28420,28421,28422,28423,28424,28425,28426,28427,28428,28429,28430,28431,28432,28433,28434,28435,28436,28437,28438,28439,28440,28441,28442,28443,28444,28445,28446,28447,28448,28449,28450,28451,28452,28453,28454,28455,28456,28457,28458,28459,28460,28461,28462,28463,28464,28465,28466,28467,28468,28469,28470,28471,28472,28473,28474,28475,28476,28477,28478,28479,28480,28481,28482,28483,28484,28485,28486,28487,28488,28489,28490,28491,28492,28493,28494,28495,28496,28497,28498,28499,28500,28501,28502,28503,28504,28505,28506,28507,28508,28509,28510,28511,28512,28513,28514,28515,28516,28517,28518,28519,28520,28521,28522,28523,28524,28525,28526,28527,28528,28529,28530,28531,28532,28533,28534,28535,28536,28537,28538,28539,28540,28541,28542,28543,28544,28545,28546,28547,28548,28549,28550,28551,28552,28553,28554,28555,28556,28557,28558,28559,28560,28561,28562,28563,28564,28565,28566,28567,28568,28569,28570,28571,28572,28573,28574,28575,28576,28577,28578,28579,28580,28581,28582,28583,28584,28585,28586,28587,28588,28589,28590,28591,28592,28593,28594,28595,28596,28597,28598,28599,28600,28601,28602,28603,28604,28605,28606,28607,28608,28609,28610,28611,28612,28613,28614,28615,28616,28617,28618,28619,28620,28621,28622,28623,28624,28625,28626,28627,28628,28629,28630,28631,28632,28633,28634,28635,28636,28637,28638,28639,28640,28641,28642,28643,28644,28645,28646,28647,28648,28649,28650,28651,28652,28653,28654,28655,28656,28657,28658,28659,28660,28661,28662,28663,28664,28665,28666,28667,28668,28669,28670,28671,28672,28673,28674,28675,28676,28677,28678,28679,28680,28681,28682,28683,28684,28685,28686,28687,28688,28689,28690,28691,28692,28693,28694,28695,28696,28697,28698,28699,28700,28701,28702,28703,28704,28705,28706,28707,28708,28709,28710,28711,28712,28713,28714,28715,28716,28717,28718,28719,28720,28721,28722,28723,28724,28725,28726,28727,28728,28729,28730,28731,28732,28733,28734,28735,28736,28737,28738,28739,28740,28741,28742,28743,28744,28745,28746,28747,28748,28749,28750,28751,28752,28753,28754,28755,28756,28757,28758,28759,28760,28761,28762,28763,28764,28765,28766,28767,28768,28769,28770,28771,28772,28773,28774,28775,28776,28777,28778,28779,28780,28781,28782,28783,28784,28785,28786,28787,28788,28789,28790,28791,28792,28793,28794,28795,28796,28797,28798,28799,28800,28801,28802,28803,28804,28805,28806,28807,28808,28809,28810,28811,28812,28813,28814,28815,28816,28817,28818,28819,28820,28821,28822,28823,28824,28825,28826,28827,28828,28829,28830,28831,28832,28833,28834,28835,28836,28837,28838,28839,28840,28841,28842,28843,28844,28845,28846,28847,28848,28849,28850,28851,28852,28853,28854,28855,28856,28857,28858,28859,28860,28861,28862,28863,28864,28865,28866,28867,28868,28869,28870,28871,28872,28873,28874,28875,28876,28877,28878,28879,28880,28881,28882,28883,28884,28885,28886,28887,28888,28889,28890,28891,28892,28893,28894,28895,28896,28897,28898,28899,28900,28901,28902,28903,28904,28905,28906,28907,28908,28909,28910,28911,28912,28913,28914,28915,28916,28917,28918,28919,28920,28921,28922,28923,28924,28925,28926,28927,28928,28929,28930,28931,28932,28933,28934,28935,28936,28937,28938,28939,28940,28941,28942,28943,28944,28945,28946,28947,28948,28949,28950,28951,28952,28953,28954,28955,28956,28957,28958,28959,28960,28961,28962,28963,28964,28965,28966,28967,28968,28969,28970,28971,28972,28973,28974,28975,28976,28977,28978,28979,28980,28981,28982,28983,28984,28985,28986,28987,28988,28989,28990,28991,28992,28993,28994,28995,28996,28997,28998,28999,29000,29001,29002,29003,29004,29005,29006,29007,29008,29009,29010,29011,29012,29013,29014,29015,29016,29017,29018,29019,29020,29021,29022,29023,29024,29025,29026,29027,29028,29029,29030,29031,29032,29033,29034,29035,29036,29037,29038,29039,29040,29041,29042,29043,29044,29045,29046,29047,29048,29049,29050,29051,29052,29053,29054,29055,29056,29057,29058,29059,29060,29061,29062,29063,29064,29065,29066,29067,29068,29069,29070,29071,29072,29073,29074,29075,29076,29077,29078,29079,29080,29081,29082,29083,29084,29085,29086,29087,29088,29089,29090,29091,29092,29093,29094,29095,29096,29097,29098,29099,29100,29101,29102,29103,29104,29105,29106,29107,29108,29109,29110,29111,29112,29113,29114,29115,29116,29117,29118,29119,29120,29121,29122,29123,29124,29125,29126,29127,29128,29129,29130,29131,29132,29133,29134,29135,29136,29137,29138,29139,29140,29141,29142,29143,29144,29145,29146,29147,29148,29149,29150,29151,29152,29153,29154,29155,29156,29157,29158,29159,29160,29161,29162,29163,29164,29165,29166,29167,29168,29169,29170,29171,29172,29173,29174,29175,29176,29177,29178,29179,29180,29181,29182,29183,29184,29185,29186,29187,29188,29189,29190,29191,29192,29193,29194,29195,29196,29197,29198,29199,29200,29201,29202,29203,29204,29205,29206,29207,29208,29209,29210,29211,29212,29213,29214,29215,29216,29217,29218,29219,29220,29221,29222,29223,29224,29225,29226,29227,29228,29229,29230,29231,29232,29233,29234,29235,29236,29237,29238,29239,29240,29241,29242,29243,29244,29245,29246,29247,29248,29249,29250,29251,29252,29253,29254,29255,29256,29257,29258,29259,29260,29261,29262,29263,29264,29265,29266,29267,29268,29269,29270,29271,29272,29273,29274,29275,29276,29277,29278,29279,29280,29281,29282,29283,29284,29285,29286,29287,29288,29289,29290,29291,29292,29293,29294,29295,29296,29297,29298,29299,29300,29301,29302,29303,29304,29305,29306,29307,29308,29309,29310,29311,29312,29313,29314,29315,29316,29317,29318,29319,29320,29321,29322,29323,29324,29325,29326,29327,29328,29329,29330,29331,29332,29333,29334,29335,29336,29337,29338,29339,29340,29341,29342,29343,29344,29345,29346,29347,29348,29349,29350,29351,29352,29353,29354,29355,29356,29357,29358,29359,29360,29361,29362,29363,29364,29365,29366,29367,29368,29369,29370,29371,29372,29373,29374,29375,29376,29377,29378,29379,29380,29381,29382,29383,29384,29385,29386,29387,29388,29389,29390,29391,29392,29393,29394,29395,29396,29397,29398,29399,29400,29401,29402,29403,29404,29405,29406,29407,29408,29409,29410,29411,29412,29413,29414,29415,29416,29417,29418,29419,29420,29421,29422,29423,29424,29425,29426,29427,29428,29429,29430,29431,29432,29433,29434,29435,29436,29437,29438,29439,29440,29441,29442,29443,29444,29445,29446,29447,29448,29449,29450,29451,29452,29453,29454,29455,29456,29457,29458,29459,29460,29461,29462,29463,29464,29465,29466,29467,29468,29469,29470,29471,29472,29473,29474,29475,29476,29477,29478,29479,29480,29481,29482,29483,29484,29485,29486,29487,29488,29489,29490,29491,29492,29493,29494,29495,29496,29497,29498,29499,29500,29501,29502,29503,29504,29505,29506,29507,29508,29509,29510,29511,29512,29513,29514,29515,29516,29517,29518,29519,29520,29521,29522,29523,29524,29525,29526,29527,29528,29529,29530,29531,29532,29533,29534,29535,29536,29537,29538,29539,29540,29541,29542,29543,29544,29545,29546,29547,29548,29549,29550,29551,29552,29553,29554,29555,29556,29557,29558,29559,29560,29561,29562,29563,29564,29565,29566,29567,29568,29569,29570,29571,29572,29573,29574,29575,29576,29577,29578,29579,29580,29581,29582,29583,29584,29585,29586,29587,29588,29589,29590,29591,29592,29593,29594,29595,29596,29597,29598,29599,29600,29601,29602,29603,29604,29605,29606,29607,29608,29609,29610,29611,29612,29613,29614,29615,29616,29617,29618,29619,29620,29621,29622,29623,29624,29625,29626,29627,29628,29629,29630,29631,29632,29633,29634,29635,29636,29637,29638,29639,29640,29641,29642,29643,29644,29645,29646,29647,29648,29649,29650,29651,29652,29653,29654,29655,29656,29657,29658,29659,29660,29661,29662,29663,29664,29665,29666,29667,29668,29669,29670,29671,29672,29673,29674,29675,29676,29677,29678,29679,29680,29681,29682,29683,29684,29685,29686,29687,29688,29689,29690,29691,29692,29693,29694,29695,29696,29697,29698,29699,29700,29701,29702,29703,29704,29705,29706,29707,29708,29709,29710,29711,29712,29713,29714,29715,29716,29717,29718,29719,29720,29721,29722,29723,29724,29725,29726,29727,29728,29729,29730,29731,29732,29733,29734,29735,29736,29737,29738,29739,29740,29741,29742,29743,29744,29745,29746,29747,29748,29749,29750,29751,29752,29753,29754,29755,29756,29757,29758,29759,29760,29761,29762,29763,29764,29765,29766,29767,29768,29769,29770,29771,29772,29773,29774,29775,29776,29777,29778,29779,29780,29781,29782,29783,29784,29785,29786,29787,29788,29789,29790,29791,29792,29793,29794,29795,29796,29797,29798,29799,29800,29801,29802,29803,29804,29805,29806,29807,29808,29809,29810,29811,29812,29813,29814,29815,29816,29817,29818,29819,29820,29821,29822,29823,29824,29825,29826,29827,29828,29829,29830,29831,29832,29833,29834,29835,29836,29837,29838,29839,29840,29841,29842,29843,29844,29845,29846,29847,29848,29849,29850,29851,29852,29853,29854,29855,29856,29857,29858,29859,29860,29861,29862,29863,29864,29865,29866,29867,29868,29869,29870,29871,29872,29873,29874,29875,29876,29877,29878,29879,29880,29881,29882,29883,29884,29885,29886,29887,29888,29889,29890,29891,29892,29893,29894,29895,29896,29897,29898,29899,29900,29901,29902,29903,29904,29905,29906,29907,29908,29909,29910,29911,29912,29913,29914,29915,29916,29917,29918,29919,29920,29921,29922,29923,29924,29925,29926,29927,29928,29929,29930,29931,29932,29933,29934,29935,29936,29937,29938,29939,29940,29941,29942,29943,29944,29945,29946,29947,29948,29949,29950,29951,29952,29953,29954,29955,29956,29957,29958,29959,29960,29961,29962,29963,29964,29965,29966,29967,29968,29969,29970,29971,29972,29973,29974,29975,29976,29977,29978,29979,29980,29981,29982,29983,29984,29985,29986,29987,29988,29989,29990,29991,29992,29993,29994,29995,29996,29997,29998,29999,30000,30001,30002,30003,30004,30005,30006,30007,30008,30009,30010,30011,30012,30013,30014,30015,30016,30017,30018,30019,30020,30021,30022,30023,30024,30025,30026,30027,30028,30029,30030,30031,30032,30033,30034,30035,30036,30037,30038,30039,30040,30041,30042,30043,30044,30045,30046,30047,30048,30049,30050,30051,30052,30053,30054,30055,30056,30057,30058,30059,30060,30061,30062,30063,30064,30065,30066,30067,30068,30069,30070,30071,30072,30073,30074,30075,30076,30077,30078,30079,30080,30081,30082,30083,30084,30085,30086,30087,30088,30089,30090,30091,30092,30093,30094,30095,30096,30097,30098,30099,30100,30101,30102,30103,30104,30105,30106,30107,30108,30109,30110,30111,30112,30113,30114,30115,30116,30117,30118,30119,30120,30121,30122,30123,30124,30125,30126,30127,30128,30129,30130,30131,30132,30133,30134,30135,30136,30137,30138,30139,30140,30141,30142,30143,30144,30145,30146,30147,30148,30149,30150,30151,30152,30153,30154,30155,30156,30157,30158,30159,30160,30161,30162,30163,30164,30165,30166,30167,30168,30169,30170,30171,30172,30173,30174,30175,30176,30177,30178,30179,30180,30181,30182,30183,30184,30185,30186,30187,30188,30189,30190,30191,30192,30193,30194,30195,30196,30197,30198,30199,30200,30201,30202,30203,30204,30205,30206,30207,30208,30209,30210,30211,30212,30213,30214,30215,30216,30217,30218,30219,30220,30221,30222,30223,30224,30225,30226,30227,30228,30229,30230,30231,30232,30233,30234,30235,30236,30237,30238,30239,30240,30241,30242,30243,30244,30245,30246,30247,30248,30249,30250,30251,30252,30253,30254,30255,30256,30257,30258,30259,30260,30261,30262,30263,30264,30265,30266,30267,30268,30269,30270,30271,30272,30273,30274,30275,30276,30277,30278,30279,30280,30281,30282,30283,30284,30285,30286,30287,30288,30289,30290,30291,30292,30293,30294,30295,30296,30297,30298,30299,30300,30301,30302,30303,30304,30305,30306,30307,30308,30309,30310,30311,30312,30313,30314,30315,30316,30317,30318,30319,30320,30321,30322,30323,30324,30325,30326,30327,30328,30329,30330,30331,30332,30333,30334,30335,30336,30337,30338,30339,30340,30341,30342,30343,30344,30345,30346,30347,30348,30349,30350,30351,30352,30353,30354,30355,30356,30357,30358,30359,30360,30361,30362,30363,30364,30365,30366,30367,30368,30369,30370,30371,30372,30373,30374,30375,30376,30377,30378,30379,30380,30381,30382,30383,30384,30385,30386,30387,30388,30389,30390,30391,30392,30393,30394,30395,30396,30397,30398,30399,30400,30401,30402,30403,30404,30405,30406,30407,30408,30409,30410,30411,30412,30413,30414,30415,30416,30417,30418,30419,30420,30421,30422,30423,30424,30425,30426,30427,30428,30429,30430,30431,30432,30433,30434,30435,30436,30437,30438,30439,30440,30441,30442,30443,30444,30445,30446,30447,30448,30449,30450,30451,30452,30453,30454,30455,30456,30457,30458,30459,30460,30461,30462,30463,30464,30465,30466,30467,30468,30469,30470,30471,30472,30473,30474,30475,30476,30477,30478,30479,30480,30481,30482,30483,30484,30485,30486,30487,30488,30489,30490,30491,30492,30493,30494,30495,30496,30497,30498,30499,30500,30501,30502,30503,30504,30505,30506,30507,30508,30509,30510,30511,30512,30513,30514,30515,30516,30517,30518,30519,30520,30521,30522,30523,30524,30525,30526,30527,30528,30529,30530,30531,30532,30533,30534,30535,30536,30537,30538,30539,30540,30541,30542,30543,30544,30545,30546,30547,30548,30549,30550,30551,30552,30553,30554,30555,30556,30557,30558,30559,30560,30561,30562,30563,30564,30565,30566,30567,30568,30569,30570,30571,30572,30573,30574,30575,30576,30577,30578,30579,30580,30581,30582,30583,30584,30585,30586,30587,30588,30589,30590,30591,30592,30593,30594,30595,30596,30597,30598,30599,30600,30601,30602,30603,30604,30605,30606,30607,30608,30609,30610,30611,30612,30613,30614,30615,30616,30617,30618,30619,30620,30621,30622,30623,30624,30625,30626,30627,30628,30629,30630,30631,30632,30633,30634,30635,30636,30637,30638,30639,30640,30641,30642,30643,30644,30645,30646,30647,30648,30649,30650,30651,30652,30653,30654,30655,30656,30657,30658,30659,30660,30661,30662,30663,30664,30665,30666,30667,30668,30669,30670,30671,30672,30673,30674,30675,30676,30677,30678,30679,30680,30681,30682,30683,30684,30685,30686,30687,30688,30689,30690,30691,30692,30693,30694,30695,30696,30697,30698,30699,30700,30701,30702,30703,30704,30705,30706,30707,30708,30709,30710,30711,30712,30713,30714,30715,30716,30717,30718,30719,30720,30721,30722,30723,30724,30725,30726,30727,30728,30729,30730,30731,30732,30733,30734,30735,30736,30737,30738,30739,30740,30741,30742,30743,30744,30745,30746,30747,30748,30749,30750,30751,30752,30753,30754,30755,30756,30757,30758,30759,30760,30761,30762,30763,30764,30765,30766,30767,30768,30769,30770,30771,30772,30773,30774,30775,30776,30777,30778,30779,30780,30781,30782,30783,30784,30785,30786,30787,30788,30789,30790,30791,30792,30793,30794,30795,30796,30797,30798,30799,30800,30801,30802,30803,30804,30805,30806,30807,30808,30809,30810,30811,30812,30813,30814,30815,30816,30817,30818,30819,30820,30821,30822,30823,30824,30825,30826,30827,30828,30829,30830,30831,30832,30833,30834,30835,30836,30837,30838,30839,30840,30841,30842,30843,30844,30845,30846,30847,30848,30849,30850,30851,30852,30853,30854,30855,30856,30857,30858,30859,30860,30861,30862,30863,30864,30865,30866,30867,30868,30869,30870,30871,30872,30873,30874,30875,30876,30877,30878,30879,30880,30881,30882,30883,30884,30885,30886,30887,30888,30889,30890,30891,30892,30893,30894,30895,30896,30897,30898,30899,30900,30901,30902,30903,30904,30905,30906,30907,30908,30909,30910,30911,30912,30913,30914,30915,30916,30917,30918,30919,30920,30921,30922,30923,30924,30925,30926,30927,30928,30929,30930,30931,30932,30933,30934,30935,30936,30937,30938,30939,30940,30941,30942,30943,30944,30945,30946,30947,30948,30949,30950,30951,30952,30953,30954,30955,30956,30957,30958,30959,30960,30961,30962,30963,30964,30965,30966,30967,30968,30969,30970,30971,30972,30973,30974,30975,30976,30977,30978,30979,30980,30981,30982,30983,30984,30985,30986,30987,30988,30989,30990,30991,30992,30993,30994,30995,30996,30997,30998,30999,31000,31001,31002,31003,31004,31005,31006,31007,31008,31009,31010,31011,31012,31013,31014,31015,31016,31017,31018,31019,31020,31021,31022,31023,31024,31025,31026,31027,31028,31029,31030,31031,31032,31033,31034,31035,31036,31037,31038,31039,31040,31041,31042,31043,31044,31045,31046,31047,31048,31049,31050,31051,31052,31053,31054,31055,31056,31057,31058,31059,31060,31061,31062,31063,31064,31065,31066,31067,31068,31069,31070,31071,31072,31073,31074,31075,31076,31077,31078,31079,31080,31081,31082,31083,31084,31085,31086,31087,31088,31089,31090,31091,31092,31093,31094,31095,31096,31097,31098,31099,31100,31101,31102,31103,31104,31105,31106,31107,31108,31109,31110,31111,31112,31113,31114,31115,31116,31117,31118,31119,31120,31121,31122,31123,31124,31125,31126,31127,31128,31129,31130,31131,31132,31133,31134,31135,31136,31137,31138,31139,31140,31141,31142,31143,31144,31145,31146,31147,31148,31149,31150,31151,31152,31153,31154,31155,31156,31157,31158,31159,31160,31161,31162,31163,31164,31165,31166,31167,31168,31169,31170,31171,31172,31173,31174,31175,31176,31177,31178,31179,31180,31181,31182,31183,31184,31185,31186,31187,31188,31189,31190,31191,31192,31193,31194,31195,31196,31197,31198,31199,31200,31201,31202,31203,31204,31205,31206,31207,31208,31209,31210,31211,31212,31213,31214,31215,31216,31217,31218,31219,31220,31221,31222,31223,31224,31225,31226,31227,31228,31229,31230,31231,31232,31233,31234,31235,31236,31237,31238,31239,31240,31241,31242,31243,31244,31245,31246,31247,31248,31249,31250,31251,31252,31253,31254,31255,31256,31257,31258,31259,31260,31261,31262,31263,31264,31265,31266,31267,31268,31269,31270,31271,31272,31273,31274,31275,31276,31277,31278,31279,31280,31281,31282,31283,31284,31285,31286,31287,31288,31289,31290,31291,31292,31293,31294,31295,31296,31297,31298,31299,31300,31301,31302,31303,31304,31305,31306,31307,31308,31309,31310,31311,31312,31313,31314,31315,31316,31317,31318,31319,31320,31321,31322,31323,31324,31325,31326,31327,31328,31329,31330,31331,31332,31333,31334,31335,31336,31337,31338,31339,31340,31341,31342,31343,31344,31345,31346,31347,31348,31349,31350,31351,31352,31353,31354,31355,31356,31357,31358,31359,31360,31361,31362,31363,31364,31365,31366,31367,31368,31369,31370,31371,31372,31373,31374,31375,31376,31377,31378,31379,31380,31381,31382,31383,31384,31385,31386,31387,31388,31389,31390,31391,31392,31393,31394,31395,31396,31397,31398,31399,31400,31401,31402,31403,31404,31405,31406,31407,31408,31409,31410,31411,31412,31413,31414,31415,31416,31417,31418,31419,31420,31421,31422,31423,31424,31425,31426,31427,31428,31429,31430,31431,31432,31433,31434,31435,31436,31437,31438,31439,31440,31441,31442,31443,31444,31445,31446,31447,31448,31449,31450,31451,31452,31453,31454,31455,31456,31457,31458,31459,31460,31461,31462,31463,31464,31465,31466,31467,31468,31469,31470,31471,31472,31473,31474,31475,31476,31477,31478,31479,31480,31481,31482,31483,31484,31485,31486,31487,31488,31489,31490,31491,31492,31493,31494,31495,31496,31497,31498,31499,31500,31501,31502,31503,31504,31505,31506,31507,31508,31509,31510,31511,31512,31513,31514,31515,31516,31517,31518,31519,31520,31521,31522,31523,31524,31525,31526,31527,31528,31529,31530,31531,31532,31533,31534,31535,31536,31537,31538,31539,31540,31541,31542,31543,31544,31545,31546,31547,31548,31549,31550,31551,31552,31553,31554,31555,31556,31557,31558,31559,31560,31561,31562,31563,31564,31565,31566,31567,31568,31569,31570,31571,31572,31573,31574,31575,31576,31577,31578,31579,31580,31581,31582,31583,31584,31585,31586,31587,31588,31589,31590,31591,31592,31593,31594,31595,31596,31597,31598,31599,31600,31601,31602,31603,31604,31605,31606,31607,31608,31609,31610,31611,31612,31613,31614,31615,31616,31617,31618,31619,31620,31621,31622,31623,31624,31625,31626,31627,31628,31629,31630,31631,31632,31633,31634,31635,31636,31637,31638,31639,31640,31641,31642,31643,31644,31645,31646,31647,31648,31649,31650,31651,31652,31653,31654,31655,31656,31657,31658,31659,31660,31661,31662,31663,31664,31665,31666,31667,31668,31669,31670,31671,31672,31673,31674,31675,31676,31677,31678,31679,31680,31681,31682,31683,31684,31685,31686,31687,31688,31689,31690,31691,31692,31693,31694,31695,31696,31697,31698,31699,31700,31701,31702,31703,31704,31705,31706,31707,31708,31709,31710,31711,31712,31713,31714,31715,31716,31717,31718,31719,31720,31721,31722,31723,31724,31725,31726,31727,31728,31729,31730,31731,31732,31733,31734,31735,31736,31737,31738,31739,31740,31741,31742,31743,31744,31745,31746,31747,31748,31749,31750,31751,31752,31753,31754,31755,31756,31757,31758,31759,31760,31761,31762,31763,31764,31765,31766,31767,31768,31769,31770,31771,31772,31773,31774,31775,31776,31777,31778,31779,31780,31781,31782,31783,31784,31785,31786,31787,31788,31789,31790,31791,31792,31793,31794,31795,31796,31797,31798,31799,31800,31801,31802,31803,31804,31805,31806,31807,31808,31809,31810,31811,31812,31813,31814,31815,31816,31817,31818,31819,31820,31821,31822,31823,31824,31825,31826,31827,31828,31829,31830,31831,31832,31833,31834,31835,31836,31837,31838,31839,31840,31841,31842,31843,31844,31845,31846,31847,31848,31849,31850,31851,31852,31853,31854,31855,31856,31857,31858,31859,31860,31861,31862,31863,31864,31865,31866,31867,31868,31869,31870,31871,31872,31873,31874,31875,31876,31877,31878,31879,31880,31881,31882,31883,31884,31885,31886,31887,31888,31889,31890,31891,31892,31893,31894,31895,31896,31897,31898,31899,31900,31901,31902,31903,31904,31905,31906,31907,31908,31909,31910,31911,31912,31913,31914,31915,31916,31917,31918,31919,31920,31921,31922,31923,31924,31925,31926,31927,31928,31929,31930,31931,31932,31933,31934,31935,31936,31937,31938,31939,31940,31941,31942,31943,31944,31945,31946,31947,31948,31949,31950,31951,31952,31953,31954,31955,31956,31957,31958,31959,31960,31961,31962,31963,31964,31965,31966,31967,31968,31969,31970,31971,31972,31973,31974,31975,31976,31977,31978,31979,31980,31981,31982,31983,31984,31985,31986,31987,31988,31989,31990,31991,31992,31993,31994,31995,31996,31997,31998,31999,32000,32001,32002,32003,32004,32005,32006,32007,32008,32009,32010,32011,32012,32013,32014,32015,32016,32017,32018,32019,32020,32021,32022,32023,32024,32025,32026,32027,32028,32029,32030,32031,32032,32033,32034,32035,32036,32037,32038,32039,32040,32041,32042,32043,32044,32045,32046,32047,32048,32049,32050,32051,32052,32053,32054,32055,32056,32057,32058,32059,32060,32061,32062,32063,32064,32065,32066,32067,32068,32069,32070,32071,32072,32073,32074,32075,32076,32077,32078,32079,32080,32081,32082,32083,32084,32085,32086,32087,32088,32089,32090,32091,32092,32093,32094,32095,32096,32097,32098,32099,32100,32101,32102,32103,32104,32105,32106,32107,32108,32109,32110,32111,32112,32113,32114,32115,32116,32117,32118,32119,32120,32121,32122,32123,32124,32125,32126,32127,32128,32129,32130,32131,32132,32133,32134,32135,32136,32137,32138,32139,32140,32141,32142,32143,32144,32145,32146,32147,32148,32149,32150,32151,32152,32153,32154,32155,32156,32157,32158,32159,32160,32161,32162,32163,32164,32165,32166,32167,32168,32169,32170,32171,32172,32173,32174,32175,32176,32177,32178,32179,32180,32181,32182,32183,32184,32185,32186,32187,32188,32189,32190,32191,32192,32193,32194,32195,32196,32197,32198,32199,32200,32201,32202,32203,32204,32205,32206,32207,32208,32209,32210,32211,32212,32213,32214,32215,32216,32217,32218,32219,32220,32221,32222,32223,32224,32225,32226,32227,32228,32229,32230,32231,32232,32233,32234,32235,32236,32237,32238,32239,32240,32241,32242,32243,32244,32245,32246,32247,32248,32249,32250,32251,32252,32253,32254,32255,32256,32257,32258,32259,32260,32261,32262,32263,32264,32265,32266,32267,32268,32269,32270,32271,32272,32273,32274,32275,32276,32277,32278,32279,32280,32281,32282,32283,32284,32285,32286,32287,32288,32289,32290,32291,32292,32293,32294,32295,32296,32297,32298,32299,32300,32301,32302,32303,32304,32305,32306,32307,32308,32309,32310,32311,32312,32313,32314,32315,32316,32317,32318,32319,32320,32321,32322,32323,32324,32325,32326,32327,32328,32329,32330,32331,32332,32333,32334,32335,32336,32337,32338,32339,32340,32341,32342,32343,32344,32345,32346,32347,32348,32349,32350,32351,32352,32353,32354,32355,32356,32357,32358,32359,32360,32361,32362,32363,32364,32365,32366,32367,32368,32369,32370,32371,32372,32373,32374,32375,32376,32377,32378,32379,32380,32381,32382,32383,32384,32385,32386,32387,32388,32389,32390,32391,32392,32393,32394,32395,32396,32397,32398,32399,32400,32401,32402,32403,32404,32405,32406,32407,32408,32409,32410,32411,32412,32413,32414,32415,32416,32417,32418,32419,32420,32421,32422,32423,32424,32425,32426,32427,32428,32429,32430,32431,32432,32433,32434,32435,32436,32437,32438,32439,32440,32441,32442,32443,32444,32445,32446,32447,32448,32449,32450,32451,32452,32453,32454,32455,32456,32457,32458,32459,32460,32461,32462,32463,32464,32465,32466,32467,32468,32469,32470,32471,32472,32473,32474,32475,32476,32477,32478,32479,32480,32481,32482,32483,32484,32485,32486,32487,32488,32489,32490,32491,32492,32493,32494,32495,32496,32497,32498,32499,32500,32501,32502,32503,32504,32505,32506,32507,32508,32509,32510,32511,32512,32513,32514,32515,32516,32517,32518,32519,32520,32521,32522,32523,32524,32525,32526,32527,32528,32529,32530,32531,32532,32533,32534,32535,32536,32537,32538,32539,32540,32541,32542,32543,32544,32545,32546,32547,32548,32549,32550,32551,32552,32553,32554,32555,32556,32557,32558,32559,32560,32561,32562,32563,32564,32565,32566,32567,32568,32569,32570,32571,32572,32573,32574,32575,32576,32577,32578,32579,32580,32581,32582,32583,32584,32585,32586,32587,32588,32589,32590,32591,32592,32593,32594,32595,32596,32597,32598,32599,32600,32601,32602,32603,32604,32605,32606,32607,32608,32609,32610,32611,32612,32613,32614,32615,32616,32617,32618,32619,32620,32621,32622,32623,32624,32625,32626,32627,32628,32629,32630,32631,32632,32633,32634,32635,32636,32637,32638,32639,32640,32641,32642,32643,32644,32645,32646,32647,32648,32649,32650,32651,32652,32653,32654,32655,32656,32657,32658,32659,32660,32661,32662,32663,32664,32665,32666,32667,32668,32669,32670,32671,32672,32673,32674,32675,32676,32677,32678,32679,32680,32681,32682,32683,32684,32685,32686,32687,32688,32689,32690,32691,32692,32693,32694,32695,32696,32697,32698,32699,32700,32701,32702,32703,32704,32705,32706,32707,32708,32709,32710,32711,32712,32713,32714,32715,32716,32717,32718,32719,32720,32721,32722,32723,32724,32725,32726,32727,32728,32729,32730,32731,32732,32733,32734,32735,32736,32737,32738,32739,32740,32741,32742,32743,32744,32745,32746,32747,32748,32749,32750,32751,32752,32753,32754,32755,32756,32757,32758,32759,32760,32761,32762,32763,32764,32765,32766,32767,32768,32769,32770,32771,32772,32773,32774,32775,32776,32777,32778,32779,32780,32781,32782,32783,32784,32785,32786,32787,32788,32789,32790,32791,32792,32793,32794,32795,32796,32797,32798,32799,32800,32801,32802,32803,32804,32805,32806,32807,32808,32809,32810,32811,32812,32813,32814,32815,32816,32817,32818,32819,32820,32821,32822,32823,32824,32825,32826,32827,32828,32829,32830,32831,32832,32833,32834,32835,32836,32837,32838,32839,32840,32841,32842,32843,32844,32845,32846,32847,32848,32849,32850,32851,32852,32853,32854,32855,32856,32857,32858,32859,32860,32861,32862,32863,32864,32865,32866,32867,32868,32869,32870,32871,32872,32873,32874,32875,32876,32877,32878,32879,32880,32881,32882,32883,32884,32885,32886,32887,32888,32889,32890,32891,32892,32893,32894,32895,32896,32897,32898,32899,32900,32901,32902,32903,32904,32905,32906,32907,32908,32909,32910,32911,32912,32913,32914,32915,32916,32917,32918,32919,32920,32921,32922,32923,32924,32925,32926,32927,32928,32929,32930,32931,32932,32933,32934,32935,32936,32937,32938,32939,32940,32941,32942,32943,32944,32945,32946,32947,32948,32949,32950,32951,32952,32953,32954,32955,32956,32957,32958,32959,32960,32961,32962,32963,32964,32965,32966,32967,32968,32969,32970,32971,32972,32973,32974,32975,32976,32977,32978,32979,32980,32981,32982,32983,32984,32985,32986,32987,32988,32989,32990,32991,32992,32993,32994,32995,32996,32997,32998,32999,33000,33001,33002,33003,33004,33005,33006,33007,33008,33009,33010,33011,33012,33013,33014,33015,33016,33017,33018,33019,33020,33021,33022,33023,33024,33025,33026,33027,33028,33029,33030,33031,33032,33033,33034,33035,33036,33037,33038,33039,33040,33041,33042,33043,33044,33045,33046,33047,33048,33049,33050,33051,33052,33053,33054,33055,33056,33057,33058,33059,33060,33061,33062,33063,33064,33065,33066,33067,33068,33069,33070,33071,33072,33073,33074,33075,33076,33077,33078,33079,33080,33081,33082,33083,33084,33085,33086,33087,33088,33089,33090,33091,33092,33093,33094,33095,33096,33097,33098,33099,33100,33101,33102,33103,33104,33105,33106,33107,33108,33109,33110,33111,33112,33113,33114,33115,33116,33117,33118,33119,33120,33121,33122,33123,33124,33125,33126,33127,33128,33129,33130,33131,33132,33133,33134,33135,33136,33137,33138,33139,33140,33141,33142,33143,33144,33145,33146,33147,33148,33149,33150,33151,33152,33153,33154,33155,33156,33157,33158,33159,33160,33161,33162,33163,33164,33165,33166,33167,33168,33169,33170,33171,33172,33173,33174,33175,33176,33177,33178,33179,33180,33181,33182,33183,33184,33185,33186,33187,33188,33189,33190,33191,33192,33193,33194,33195,33196,33197,33198,33199,33200,33201,33202,33203,33204,33205,33206,33207,33208,33209,33210,33211,33212,33213,33214,33215,33216,33217,33218,33219,33220,33221,33222,33223,33224,33225,33226,33227,33228,33229,33230,33231,33232,33233,33234,33235,33236,33237,33238,33239,33240,33241,33242,33243,33244,33245,33246,33247,33248,33249,33250,33251,33252,33253,33254,33255,33256,33257,33258,33259,33260,33261,33262,33263,33264,33265,33266,33267,33268,33269,33270,33271,33272,33273,33274,33275,33276,33277,33278,33279,33280,33281,33282,33283,33284,33285,33286,33287,33288,33289,33290,33291,33292,33293,33294,33295,33296,33297,33298,33299,33300,33301,33302,33303,33304,33305,33306,33307,33308,33309,33310,33311,33312,33313,33314,33315,33316,33317,33318,33319,33320,33321,33322,33323,33324,33325,33326,33327,33328,33329,33330,33331,33332,33333,33334,33335,33336,33337,33338,33339,33340,33341,33342,33343,33344,33345,33346,33347,33348,33349,33350,33351,33352,33353,33354,33355,33356,33357,33358,33359,33360,33361,33362,33363,33364,33365,33366,33367,33368,33369,33370,33371,33372,33373,33374,33375,33376,33377,33378,33379,33380,33381,33382,33383,33384,33385,33386,33387,33388,33389,33390,33391,33392,33393,33394,33395,33396,33397,33398,33399,33400,33401,33402,33403,33404,33405,33406,33407,33408,33409,33410,33411,33412,33413,33414,33415,33416,33417,33418,33419,33420,33421,33422,33423,33424,33425,33426,33427,33428,33429,33430,33431,33432,33433,33434,33435,33436,33437,33438,33439,33440,33441,33442,33443,33444,33445,33446,33447,33448,33449,33450,33451,33452,33453,33454,33455,33456,33457,33458,33459,33460,33461,33462,33463,33464,33465,33466,33467,33468,33469,33470,33471,33472,33473,33474,33475,33476,33477,33478,33479,33480,33481,33482,33483,33484,33485,33486,33487,33488,33489,33490,33491,33492,33493,33494,33495,33496,33497,33498,33499,33500,33501,33502,33503,33504,33505,33506,33507,33508,33509,33510,33511,33512,33513,33514,33515,33516,33517,33518,33519,33520,33521,33522,33523,33524,33525,33526,33527,33528,33529,33530,33531,33532,33533,33534,33535,33536,33537,33538,33539,33540,33541,33542,33543,33544,33545,33546,33547,33548,33549,33550,33551,33552,33553,33554,33555,33556,33557,33558,33559,33560,33561,33562,33563,33564,33565,33566,33567,33568,33569,33570,33571,33572,33573,33574,33575,33576,33577,33578,33579,33580,33581,33582,33583,33584,33585,33586,33587,33588,33589,33590,33591,33592,33593,33594,33595,33596,33597,33598,33599,33600,33601,33602,33603,33604,33605,33606,33607,33608,33609,33610,33611,33612,33613,33614,33615,33616,33617,33618,33619,33620,33621,33622,33623,33624,33625,33626,33627,33628,33629,33630,33631,33632,33633,33634,33635,33636,33637,33638,33639,33640,33641,33642,33643,33644,33645,33646,33647,33648,33649,33650,33651,33652,33653,33654,33655,33656,33657,33658,33659,33660,33661,33662,33663,33664,33665,33666,33667,33668,33669,33670,33671,33672,33673,33674,33675,33676,33677,33678,33679,33680,33681,33682,33683,33684,33685,33686,33687,33688,33689,33690,33691,33692,33693,33694,33695,33696,33697,33698,33699,33700,33701,33702,33703,33704,33705,33706,33707,33708,33709,33710,33711,33712,33713,33714,33715,33716,33717,33718,33719,33720,33721,33722,33723,33724,33725,33726,33727,33728,33729,33730,33731,33732,33733,33734,33735,33736,33737,33738,33739,33740,33741,33742,33743,33744,33745,33746,33747,33748,33749,33750,33751,33752,33753,33754,33755,33756,33757,33758,33759,33760,33761,33762,33763,33764,33765,33766,33767,33768,33769,33770,33771,33772,33773,33774,33775,33776,33777,33778,33779,33780,33781,33782,33783,33784,33785,33786,33787,33788,33789,33790,33791,33792,33793,33794,33795,33796,33797,33798,33799,33800,33801,33802,33803,33804,33805,33806,33807,33808,33809,33810,33811,33812,33813,33814,33815,33816,33817,33818,33819,33820,33821,33822,33823,33824,33825,33826,33827,33828,33829,33830,33831,33832,33833,33834,33835,33836,33837,33838,33839,33840,33841,33842,33843,33844,33845,33846,33847,33848,33849,33850,33851,33852,33853,33854,33855,33856,33857,33858,33859,33860,33861,33862,33863,33864,33865,33866,33867,33868,33869,33870,33871,33872,33873,33874,33875,33876,33877,33878,33879,33880,33881,33882,33883,33884,33885,33886,33887,33888,33889,33890,33891,33892,33893,33894,33895,33896,33897,33898,33899,33900,33901,33902,33903,33904,33905,33906,33907,33908,33909,33910,33911,33912,33913,33914,33915,33916,33917,33918,33919,33920,33921,33922,33923,33924,33925,33926,33927,33928,33929,33930,33931,33932,33933,33934,33935,33936,33937,33938,33939,33940,33941,33942,33943,33944,33945,33946,33947,33948,33949,33950,33951,33952,33953,33954,33955,33956,33957,33958,33959,33960,33961,33962,33963,33964,33965,33966,33967,33968,33969,33970,33971,33972,33973,33974,33975,33976,33977,33978,33979,33980,33981,33982,33983,33984,33985,33986,33987,33988,33989,33990,33991,33992,33993,33994,33995,33996,33997,33998,33999,34000,34001,34002,34003,34004,34005,34006,34007,34008,34009,34010,34011,34012,34013,34014,34015,34016,34017,34018,34019,34020,34021,34022,34023,34024,34025,34026,34027,34028,34029,34030,34031,34032,34033,34034,34035,34036,34037,34038,34039,34040,34041,34042,34043,34044,34045,34046,34047,34048,34049,34050,34051,34052,34053,34054,34055,34056,34057,34058,34059,34060,34061,34062,34063,34064,34065,34066,34067,34068,34069,34070,34071,34072,34073,34074,34075,34076,34077,34078,34079,34080,34081,34082,34083,34084,34085,34086,34087,34088,34089,34090,34091,34092,34093,34094,34095,34096,34097,34098,34099,34100,34101,34102,34103,34104,34105,34106,34107,34108,34109,34110,34111,34112,34113,34114,34115,34116,34117,34118,34119,34120,34121,34122,34123,34124,34125,34126,34127,34128,34129,34130,34131,34132,34133,34134,34135,34136,34137,34138,34139,34140,34141,34142,34143,34144,34145,34146,34147,34148,34149,34150,34151,34152,34153,34154,34155,34156,34157,34158,34159,34160,34161,34162,34163,34164,34165,34166,34167,34168,34169,34170,34171,34172,34173,34174,34175,34176,34177,34178,34179,34180,34181,34182,34183,34184,34185,34186,34187,34188,34189,34190,34191,34192,34193,34194,34195,34196,34197,34198,34199,34200,34201,34202,34203,34204,34205,34206,34207,34208,34209,34210,34211,34212,34213,34214,34215,34216,34217,34218,34219,34220,34221,34222,34223,34224,34225,34226,34227,34228,34229,34230,34231,34232,34233,34234,34235,34236,34237,34238,34239,34240,34241,34242,34243,34244,34245,34246,34247,34248,34249,34250,34251,34252,34253,34254,34255,34256,34257,34258,34259,34260,34261,34262,34263,34264,34265,34266,34267,34268,34269,34270,34271,34272,34273,34274,34275,34276,34277,34278,34279,34280,34281,34282,34283,34284,34285,34286,34287,34288,34289,34290,34291,34292,34293,34294,34295,34296,34297,34298,34299,34300,34301,34302,34303,34304,34305,34306,34307,34308,34309,34310,34311,34312,34313,34314,34315,34316,34317,34318,34319,34320,34321,34322,34323,34324,34325,34326,34327,34328,34329,34330,34331,34332,34333,34334,34335,34336,34337,34338,34339,34340,34341,34342,34343,34344,34345,34346,34347,34348,34349,34350,34351,34352,34353,34354,34355,34356,34357,34358,34359,34360,34361,34362,34363,34364,34365,34366,34367,34368,34369,34370,34371,34372,34373,34374,34375,34376,34377,34378,34379,34380,34381,34382,34383,34384,34385,34386,34387,34388,34389,34390,34391,34392,34393,34394,34395,34396,34397,34398,34399,34400,34401,34402,34403,34404,34405,34406,34407,34408,34409,34410,34411,34412,34413,34414,34415,34416,34417,34418,34419,34420,34421,34422,34423,34424,34425,34426,34427,34428,34429,34430,34431,34432,34433,34434,34435,34436,34437,34438,34439,34440,34441,34442,34443,34444,34445,34446,34447,34448,34449,34450,34451,34452,34453,34454,34455,34456,34457,34458,34459,34460,34461,34462,34463,34464,34465,34466,34467,34468,34469,34470,34471,34472,34473,34474,34475,34476,34477,34478,34479,34480,34481,34482,34483,34484,34485,34486,34487,34488,34489,34490,34491,34492,34493,34494,34495,34496,34497,34498,34499,34500,34501,34502,34503,34504,34505,34506,34507,34508,34509,34510,34511,34512,34513,34514,34515,34516,34517,34518,34519,34520,34521,34522,34523,34524,34525,34526,34527,34528,34529,34530,34531,34532,34533,34534,34535,34536,34537,34538,34539,34540,34541,34542,34543,34544,34545,34546,34547,34548,34549,34550,34551,34552,34553,34554,34555,34556,34557,34558,34559,34560,34561,34562,34563,34564,34565,34566,34567,34568,34569,34570,34571,34572,34573,34574,34575,34576,34577,34578,34579,34580,34581,34582,34583,34584,34585,34586,34587,34588,34589,34590,34591,34592,34593,34594,34595,34596,34597,34598,34599,34600,34601,34602,34603,34604,34605,34606,34607,34608,34609,34610,34611,34612,34613,34614,34615,34616,34617,34618,34619,34620,34621,34622,34623,34624,34625,34626,34627,34628,34629,34630,34631,34632,34633,34634,34635,34636,34637,34638,34639,34640,34641,34642,34643,34644,34645,34646,34647,34648,34649,34650,34651,34652,34653,34654,34655,34656,34657,34658,34659,34660,34661,34662,34663,34664,34665,34666,34667,34668,34669,34670,34671,34672,34673,34674,34675,34676,34677,34678,34679,34680,34681,34682,34683,34684,34685,34686,34687,34688,34689,34690,34691,34692,34693,34694,34695,34696,34697,34698,34699,34700,34701,34702,34703,34704,34705,34706,34707,34708,34709,34710,34711,34712,34713,34714,34715,34716,34717,34718,34719,34720,34721,34722,34723,34724,34725,34726,34727,34728,34729,34730,34731,34732,34733,34734,34735,34736,34737,34738,34739,34740,34741,34742,34743,34744,34745,34746,34747,34748,34749,34750,34751,34752,34753,34754,34755,34756,34757,34758,34759,34760,34761,34762,34763,34764,34765,34766,34767,34768,34769,34770,34771,34772,34773,34774,34775,34776,34777,34778,34779,34780,34781,34782,34783,34784,34785,34786,34787,34788,34789,34790,34791,34792,34793,34794,34795,34796,34797,34798,34799,34800,34801,34802,34803,34804,34805,34806,34807,34808,34809,34810,34811,34812,34813,34814,34815,34816,34817,34818,34819,34820,34821,34822,34823,34824,34825,34826,34827,34828,34829,34830,34831,34832,34833,34834,34835,34836,34837,34838,34839,34840,34841,34842,34843,34844,34845,34846,34847,34848,34849,34850,34851,34852,34853,34854,34855,34856,34857,34858,34859,34860,34861,34862,34863,34864,34865,34866,34867,34868,34869,34870,34871,34872,34873,34874,34875,34876,34877,34878,34879,34880,34881,34882,34883,34884,34885,34886,34887,34888,34889,34890,34891,34892,34893,34894,34895,34896,34897,34898,34899,34900,34901,34902,34903,34904,34905,34906,34907,34908,34909,34910,34911,34912,34913,34914,34915,34916,34917,34918,34919,34920,34921,34922,34923,34924,34925,34926,34927,34928,34929,34930,34931,34932,34933,34934,34935,34936,34937,34938,34939,34940,34941,34942,34943,34944,34945,34946,34947,34948,34949,34950,34951,34952,34953,34954,34955,34956,34957,34958,34959,34960,34961,34962,34963,34964,34965,34966,34967,34968,34969,34970,34971,34972,34973,34974,34975,34976,34977,34978,34979,34980,34981,34982,34983,34984,34985,34986,34987,34988,34989,34990,34991,34992,34993,34994,34995,34996,34997,34998,34999,35000,35001,35002,35003,35004,35005,35006,35007,35008,35009,35010,35011,35012,35013,35014,35015,35016,35017,35018,35019,35020,35021,35022,35023,35024,35025,35026,35027,35028,35029,35030,35031,35032,35033,35034,35035,35036,35037,35038,35039,35040,35041,35042,35043,35044,35045,35046,35047,35048,35049,35050,35051,35052,35053,35054,35055,35056,35057,35058,35059,35060,35061,35062,35063,35064,35065,35066,35067,35068,35069,35070,35071,35072,35073,35074,35075,35076,35077,35078,35079,35080,35081,35082,35083,35084,35085,35086,35087,35088,35089,35090,35091,35092,35093,35094,35095,35096,35097,35098,35099,35100,35101,35102,35103,35104,35105,35106,35107,35108,35109,35110,35111,35112,35113,35114,35115,35116,35117,35118,35119,35120,35121,35122,35123,35124,35125,35126,35127,35128,35129,35130,35131,35132,35133,35134,35135,35136,35137,35138,35139,35140,35141,35142,35143,35144,35145,35146,35147,35148,35149,35150,35151,35152,35153,35154,35155,35156,35157,35158,35159,35160,35161,35162,35163,35164,35165,35166,35167,35168,35169,35170,35171,35172,35173,35174,35175,35176,35177,35178,35179,35180,35181,35182,35183,35184,35185,35186,35187,35188,35189,35190,35191,35192,35193,35194,35195,35196,35197,35198,35199,35200,35201,35202,35203,35204,35205,35206,35207,35208,35209,35210,35211,35212,35213,35214,35215,35216,35217,35218,35219,35220,35221,35222,35223,35224,35225,35226,35227,35228,35229,35230,35231,35232,35233,35234,35235,35236,35237,35238,35239,35240,35241,35242,35243,35244,35245,35246,35247,35248,35249,35250,35251,35252,35253,35254,35255,35256,35257,35258,35259,35260,35261,35262,35263,35264,35265,35266,35267,35268,35269,35270,35271,35272,35273,35274,35275,35276,35277,35278,35279,35280,35281,35282,35283,35284,35285,35286,35287,35288,35289,35290,35291,35292,35293,35294,35295,35296,35297,35298,35299,35300,35301,35302,35303,35304,35305,35306,35307,35308,35309,35310,35311,35312,35313,35314,35315,35316,35317,35318,35319,35320,35321,35322,35323,35324,35325,35326,35327,35328,35329,35330,35331,35332,35333,35334,35335,35336,35337,35338,35339,35340,35341,35342,35343,35344,35345,35346,35347,35348,35349,35350,35351,35352,35353,35354,35355,35356,35357,35358,35359,35360,35361,35362,35363,35364,35365,35366,35367,35368,35369,35370,35371,35372,35373,35374,35375,35376,35377,35378,35379,35380,35381,35382,35383,35384,35385,35386,35387,35388,35389,35390,35391,35392,35393,35394,35395,35396,35397,35398,35399,35400,35401,35402,35403,35404,35405,35406,35407,35408,35409,35410,35411,35412,35413,35414,35415,35416,35417,35418,35419,35420,35421,35422,35423,35424,35425,35426,35427,35428,35429,35430,35431,35432,35433,35434,35435,35436,35437,35438,35439,35440,35441,35442,35443,35444,35445,35446,35447,35448,35449,35450,35451,35452,35453,35454,35455,35456,35457,35458,35459,35460,35461,35462,35463,35464,35465,35466,35467,35468,35469,35470,35471,35472,35473,35474,35475,35476,35477,35478,35479,35480,35481,35482,35483,35484,35485,35486,35487,35488,35489,35490,35491,35492,35493,35494,35495,35496,35497,35498,35499,35500,35501,35502,35503,35504,35505,35506,35507,35508,35509,35510,35511,35512,35513,35514,35515,35516,35517,35518,35519,35520,35521,35522,35523,35524,35525,35526,35527,35528,35529,35530,35531,35532,35533,35534,35535,35536,35537,35538,35539,35540,35541,35542,35543,35544,35545,35546,35547,35548,35549,35550,35551,35552,35553,35554,35555,35556,35557,35558,35559,35560,35561,35562,35563,35564,35565,35566,35567,35568,35569,35570,35571,35572,35573,35574,35575,35576,35577,35578,35579,35580,35581,35582,35583,35584,35585,35586,35587,35588,35589,35590,35591,35592,35593,35594,35595,35596,35597,35598,35599,35600,35601,35602,35603,35604,35605,35606,35607,35608,35609,35610,35611,35612,35613,35614,35615,35616,35617,35618,35619,35620,35621,35622,35623,35624,35625,35626,35627,35628,35629,35630,35631,35632,35633,35634,35635,35636,35637,35638,35639,35640,35641,35642,35643,35644,35645,35646,35647,35648,35649,35650,35651,35652,35653,35654,35655,35656,35657,35658,35659,35660,35661,35662,35663,35664,35665,35666,35667,35668,35669,35670,35671,35672,35673,35674,35675,35676,35677,35678,35679,35680,35681,35682,35683,35684,35685,35686,35687,35688,35689,35690,35691,35692,35693,35694,35695,35696,35697,35698,35699,35700,35701,35702,35703,35704,35705,35706,35707,35708,35709,35710,35711,35712,35713,35714,35715,35716,35717,35718,35719,35720,35721,35722,35723,35724,35725,35726,35727,35728,35729,35730,35731,35732,35733,35734,35735,35736,35737,35738,35739,35740,35741,35742,35743,35744,35745,35746,35747,35748,35749,35750,35751,35752,35753,35754,35755,35756,35757,35758,35759,35760,35761,35762,35763,35764,35765,35766,35767,35768,35769,35770,35771,35772,35773,35774,35775,35776,35777,35778,35779,35780,35781,35782,35783,35784,35785,35786,35787,35788,35789,35790,35791,35792,35793,35794,35795,35796,35797,35798,35799,35800,35801,35802,35803,35804,35805,35806,35807,35808,35809,35810,35811,35812,35813,35814,35815,35816,35817,35818,35819,35820,35821,35822,35823,35824,35825,35826,35827,35828,35829,35830,35831,35832,35833,35834,35835,35836,35837,35838,35839,35840,35841,35842,35843,35844,35845,35846,35847,35848,35849,35850,35851,35852,35853,35854,35855,35856,35857,35858,35859,35860,35861,35862,35863,35864,35865,35866,35867,35868,35869,35870,35871,35872,35873,35874,35875,35876,35877,35878,35879,35880,35881,35882,35883,35884,35885,35886,35887,35888,35889,35890,35891,35892,35893,35894,35895,35896,35897,35898,35899,35900,35901,35902,35903,35904,35905,35906,35907,35908,35909,35910,35911,35912,35913,35914,35915,35916,35917,35918,35919,35920,35921,35922,35923,35924,35925,35926,35927,35928,35929,35930,35931,35932,35933,35934,35935,35936,35937,35938,35939,35940,35941,35942,35943,35944,35945,35946,35947,35948,35949,35950,35951,35952,35953,35954,35955,35956,35957,35958,35959,35960,35961,35962,35963,35964,35965,35966,35967,35968,35969,35970,35971,35972,35973,35974,35975,35976,35977,35978,35979,35980,35981,35982,35983,35984,35985,35986,35987,35988,35989,35990,35991,35992,35993,35994,35995,35996,35997,35998,35999,36000,36001,36002,36003,36004,36005,36006,36007,36008,36009,36010,36011,36012,36013,36014,36015,36016,36017,36018,36019,36020,36021,36022,36023,36024,36025,36026,36027,36028,36029,36030,36031,36032,36033,36034,36035,36036,36037,36038,36039,36040,36041,36042,36043,36044,36045,36046,36047,36048,36049,36050,36051,36052,36053,36054,36055,36056,36057,36058,36059,36060,36061,36062,36063,36064,36065,36066,36067,36068,36069,36070,36071,36072,36073,36074,36075,36076,36077,36078,36079,36080,36081,36082,36083,36084,36085,36086,36087,36088,36089,36090,36091,36092,36093,36094,36095,36096,36097,36098,36099,36100,36101,36102,36103,36104,36105,36106,36107,36108,36109,36110,36111,36112,36113,36114,36115,36116,36117,36118,36119,36120,36121,36122,36123,36124,36125,36126,36127,36128,36129,36130,36131,36132,36133,36134,36135,36136,36137,36138,36139,36140,36141,36142,36143,36144,36145,36146,36147,36148,36149,36150,36151,36152,36153,36154,36155,36156,36157,36158,36159,36160,36161,36162,36163,36164,36165,36166,36167,36168,36169,36170,36171,36172,36173,36174,36175,36176,36177,36178,36179,36180,36181,36182,36183,36184,36185,36186,36187,36188,36189,36190,36191,36192,36193,36194,36195,36196,36197,36198,36199,36200,36201,36202,36203,36204,36205,36206,36207,36208,36209,36210,36211,36212,36213,36214,36215,36216,36217,36218,36219,36220,36221,36222,36223,36224,36225,36226,36227,36228,36229,36230,36231,36232,36233,36234,36235,36236,36237,36238,36239,36240,36241,36242,36243,36244,36245,36246,36247,36248,36249,36250,36251,36252,36253,36254,36255,36256,36257,36258,36259,36260,36261,36262,36263,36264,36265,36266,36267,36268,36269,36270,36271,36272,36273,36274,36275,36276,36277,36278,36279,36280,36281,36282,36283,36284,36285,36286,36287,36288,36289,36290,36291,36292,36293,36294,36295,36296,36297,36298,36299,36300,36301,36302,36303,36304,36305,36306,36307,36308,36309,36310,36311,36312,36313,36314,36315,36316,36317,36318,36319,36320,36321,36322,36323,36324,36325,36326,36327,36328,36329,36330,36331,36332,36333,36334,36335,36336,36337,36338,36339,36340,36341,36342,36343,36344,36345,36346,36347,36348,36349,36350,36351,36352,36353,36354,36355,36356,36357,36358,36359,36360,36361,36362,36363,36364,36365,36366,36367,36368,36369,36370,36371,36372,36373,36374,36375,36376,36377,36378,36379,36380,36381,36382,36383,36384,36385,36386,36387,36388,36389,36390,36391,36392,36393,36394,36395,36396,36397,36398,36399,36400,36401,36402,36403,36404,36405,36406,36407,36408,36409,36410,36411,36412,36413,36414,36415,36416,36417,36418,36419,36420,36421,36422,36423,36424,36425,36426,36427,36428,36429,36430,36431,36432,36433,36434,36435,36436,36437,36438,36439,36440,36441,36442,36443,36444,36445,36446,36447,36448,36449,36450,36451,36452,36453,36454,36455,36456,36457,36458,36459,36460,36461,36462,36463,36464,36465,36466,36467,36468,36469,36470,36471,36472,36473,36474,36475,36476,36477,36478,36479,36480,36481,36482,36483,36484,36485,36486,36487,36488,36489,36490,36491,36492,36493,36494,36495,36496,36497,36498,36499,36500,36501,36502,36503,36504,36505,36506,36507,36508,36509,36510,36511,36512,36513,36514,36515,36516,36517,36518,36519,36520,36521,36522,36523,36524,36525,36526,36527,36528,36529,36530,36531,36532,36533,36534,36535,36536,36537,36538,36539,36540,36541,36542,36543,36544,36545,36546,36547,36548,36549,36550,36551,36552,36553,36554,36555,36556,36557,36558,36559,36560,36561,36562,36563,36564,36565,36566,36567,36568,36569,36570,36571,36572,36573,36574,36575,36576,36577,36578,36579,36580,36581,36582,36583,36584,36585,36586,36587,36588,36589,36590,36591,36592,36593,36594,36595,36596,36597,36598,36599,36600,36601,36602,36603,36604,36605,36606,36607,36608,36609,36610,36611,36612,36613,36614,36615,36616,36617,36618,36619,36620,36621,36622,36623,36624,36625,36626,36627,36628,36629,36630,36631,36632,36633,36634,36635,36636,36637,36638,36639,36640,36641,36642,36643,36644,36645,36646,36647,36648,36649,36650,36651,36652,36653,36654,36655,36656,36657,36658,36659,36660,36661,36662,36663,36664,36665,36666,36667,36668,36669,36670,36671,36672,36673,36674,36675,36676,36677,36678,36679,36680,36681,36682,36683,36684,36685,36686,36687,36688,36689,36690,36691,36692,36693,36694,36695,36696,36697,36698,36699,36700,36701,36702,36703,36704,36705,36706,36707,36708,36709,36710,36711,36712,36713,36714,36715,36716,36717,36718,36719,36720,36721,36722,36723,36724,36725,36726,36727,36728,36729,36730,36731,36732,36733,36734,36735,36736,36737,36738,36739,36740,36741,36742,36743,36744,36745,36746,36747,36748,36749,36750,36751,36752,36753,36754,36755,36756,36757,36758,36759,36760,36761,36762,36763,36764,36765,36766,36767,36768,36769,36770,36771,36772,36773,36774,36775,36776,36777,36778,36779,36780,36781,36782,36783,36784,36785,36786,36787,36788,36789,36790,36791,36792,36793,36794,36795,36796,36797,36798,36799,36800,36801,36802,36803,36804,36805,36806,36807,36808,36809,36810,36811,36812,36813,36814,36815,36816,36817,36818,36819,36820,36821,36822,36823,36824,36825,36826,36827,36828,36829,36830,36831,36832,36833,36834,36835,36836,36837,36838,36839,36840,36841,36842,36843,36844,36845,36846,36847,36848,36849,36850,36851,36852,36853,36854,36855,36856,36857,36858,36859,36860,36861,36862,36863,36864,36865,36866,36867,36868,36869,36870,36871,36872,36873,36874,36875,36876,36877,36878,36879,36880,36881,36882,36883,36884,36885,36886,36887,36888,36889,36890,36891,36892,36893,36894,36895,36896,36897,36898,36899,36900,36901,36902,36903,36904,36905,36906,36907,36908,36909,36910,36911,36912,36913,36914,36915,36916,36917,36918,36919,36920,36921,36922,36923,36924,36925,36926,36927,36928,36929,36930,36931,36932,36933,36934,36935,36936,36937,36938,36939,36940,36941,36942,36943,36944,36945,36946,36947,36948,36949,36950,36951,36952,36953,36954,36955,36956,36957,36958,36959,36960,36961,36962,36963,36964,36965,36966,36967,36968,36969,36970,36971,36972,36973,36974,36975,36976,36977,36978,36979,36980,36981,36982,36983,36984,36985,36986,36987,36988,36989,36990,36991,36992,36993,36994,36995,36996,36997,36998,36999,37000,37001,37002,37003,37004,37005,37006,37007,37008,37009,37010,37011,37012,37013,37014,37015,37016,37017,37018,37019,37020,37021,37022,37023,37024,37025,37026,37027,37028,37029,37030,37031,37032,37033,37034,37035,37036,37037,37038,37039,37040,37041,37042,37043,37044,37045,37046,37047,37048,37049,37050,37051,37052,37053,37054,37055,37056,37057,37058,37059,37060,37061,37062,37063,37064,37065,37066,37067,37068,37069,37070,37071,37072,37073,37074,37075,37076,37077,37078,37079,37080,37081,37082,37083,37084,37085,37086,37087,37088,37089,37090,37091,37092,37093,37094,37095,37096,37097,37098,37099,37100,37101,37102,37103,37104,37105,37106,37107,37108,37109,37110,37111,37112,37113,37114,37115,37116,37117,37118,37119,37120,37121,37122,37123,37124,37125,37126,37127,37128,37129,37130,37131,37132,37133,37134,37135,37136,37137,37138,37139,37140,37141,37142,37143,37144,37145,37146,37147,37148,37149,37150,37151,37152,37153,37154,37155,37156,37157,37158,37159,37160,37161,37162,37163,37164,37165,37166,37167,37168,37169,37170,37171,37172,37173,37174,37175,37176,37177,37178,37179,37180,37181,37182,37183,37184,37185,37186,37187,37188,37189,37190,37191,37192,37193,37194,37195,37196,37197,37198,37199,37200,37201,37202,37203,37204,37205,37206,37207,37208,37209,37210,37211,37212,37213,37214,37215,37216,37217,37218,37219,37220,37221,37222,37223,37224,37225,37226,37227,37228,37229,37230,37231,37232,37233,37234,37235,37236,37237,37238,37239,37240,37241,37242,37243,37244,37245,37246,37247,37248,37249,37250,37251,37252,37253,37254,37255,37256,37257,37258,37259,37260,37261,37262,37263,37264,37265,37266,37267,37268,37269,37270,37271,37272,37273,37274,37275,37276,37277,37278,37279,37280,37281,37282,37283,37284,37285,37286,37287,37288,37289,37290,37291,37292,37293,37294,37295,37296,37297,37298,37299,37300,37301,37302,37303,37304,37305,37306,37307,37308,37309,37310,37311,37312,37313,37314,37315,37316,37317,37318,37319,37320,37321,37322,37323,37324,37325,37326,37327,37328,37329,37330,37331,37332,37333,37334,37335,37336,37337,37338,37339,37340,37341,37342,37343,37344,37345,37346,37347,37348,37349,37350,37351,37352,37353,37354,37355,37356,37357,37358,37359,37360,37361,37362,37363,37364,37365,37366,37367,37368,37369,37370,37371,37372,37373,37374,37375,37376,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37387,37388,37389,37390,37391,37392,37393,37394,37395,37396,37397,37398,37399,37400,37401,37402,37403,37404,37405,37406,37407,37408,37409,37410,37411,37412,37413,37414,37415,37416,37417,37418,37419,37420,37421,37422,37423,37424,37425,37426,37427,37428,37429,37430,37431,37432,37433,37434,37435,37436,37437,37438,37439,37440,37441,37442,37443,37444,37445,37446,37447,37448,37449,37450,37451,37452,37453,37454,37455,37456,37457,37458,37459,37460,37461,37462,37463,37464,37465,37466,37467,37468,37469,37470,37471,37472,37473,37474,37475,37476,37477,37478,37479,37480,37481,37482,37483,37484,37485,37486,37487,37488,37489,37490,37491,37492,37493,37494,37495,37496,37497,37498,37499,37500,37501,37502,37503,37504,37505,37506,37507,37508,37509,37510,37511,37512,37513,37514,37515,37516,37517,37518,37519,37520,37521,37522,37523,37524,37525,37526,37527,37528,37529,37530,37531,37532,37533,37534,37535,37536,37537,37538,37539,37540,37541,37542,37543,37544,37545,37546,37547,37548,37549,37550,37551,37552,37553,37554,37555,37556,37557,37558,37559,37560,37561,37562,37563,37564,37565,37566,37567,37568,37569,37570,37571,37572,37573,37574,37575,37576,37577,37578,37579,37580,37581,37582,37583,37584,37585,37586,37587,37588,37589,37590,37591,37592,37593,37594,37595,37596,37597,37598,37599,37600,37601,37602,37603,37604,37605,37606,37607,37608,37609,37610,37611,37612,37613,37614,37615,37616,37617,37618,37619,37620,37621,37622,37623,37624,37625,37626,37627,37628,37629,37630,37631,37632,37633,37634,37635,37636,37637,37638,37639,37640,37641,37642,37643,37644,37645,37646,37647,37648,37649,37650,37651,37652,37653,37654,37655,37656,37657,37658,37659,37660,37661,37662,37663,37664,37665,37666,37667,37668,37669,37670,37671,37672,37673,37674,37675,37676,37677,37678,37679,37680,37681,37682,37683,37684,37685,37686,37687,37688,37689,37690,37691,37692,37693,37694,37695,37696,37697,37698,37699,37700,37701,37702,37703,37704,37705,37706,37707,37708,37709,37710,37711,37712,37713,37714,37715,37716,37717,37718,37719,37720,37721,37722,37723,37724,37725,37726,37727,37728,37729,37730,37731,37732,37733,37734,37735,37736,37737,37738,37739,37740,37741,37742,37743,37744,37745,37746,37747,37748,37749,37750,37751,37752,37753,37754,37755,37756,37757,37758,37759,37760,37761,37762,37763,37764,37765,37766,37767,37768,37769,37770,37771,37772,37773,37774,37775,37776,37777,37778,37779,37780,37781,37782,37783,37784,37785,37786,37787,37788,37789,37790,37791,37792,37793,37794,37795,37796,37797,37798,37799,37800,37801,37802,37803,37804,37805,37806,37807,37808,37809,37810,37811,37812,37813,37814,37815,37816,37817,37818,37819,37820,37821,37822,37823,37824,37825,37826,37827,37828,37829,37830,37831,37832,37833,37834,37835,37836,37837,37838,37839,37840,37841,37842,37843,37844,37845,37846,37847,37848,37849,37850,37851,37852,37853,37854,37855,37856,37857,37858,37859,37860,37861,37862,37863,37864,37865,37866,37867,37868,37869,37870,37871,37872,37873,37874,37875,37876,37877,37878,37879,37880,37881,37882,37883,37884,37885,37886,37887,37888,37889,37890,37891,37892,37893,37894,37895,37896,37897,37898,37899,37900,37901,37902,37903,37904,37905,37906,37907,37908,37909,37910,37911,37912,37913,37914,37915,37916,37917,37918,37919,37920,37921,37922,37923,37924,37925,37926,37927,37928,37929,37930,37931,37932,37933,37934,37935,37936,37937,37938,37939,37940,37941,37942,37943,37944,37945,37946,37947,37948,37949,37950,37951,37952,37953,37954,37955,37956,37957,37958,37959,37960,37961,37962,37963,37964,37965,37966,37967,37968,37969,37970,37971,37972,37973,37974,37975,37976,37977,37978,37979,37980,37981,37982,37983,37984,37985,37986,37987,37988,37989,37990,37991,37992,37993,37994,37995,37996,37997,37998,37999,38000,38001,38002,38003,38004,38005,38006,38007,38008,38009,38010,38011,38012,38013,38014,38015,38016,38017,38018,38019,38020,38021,38022,38023,38024,38025,38026,38027,38028,38029,38030,38031,38032,38033,38034,38035,38036,38037,38038,38039,38040,38041,38042,38043,38044,38045,38046,38047,38048,38049,38050,38051,38052,38053,38054,38055,38056,38057,38058,38059,38060,38061,38062,38063,38064,38065,38066,38067,38068,38069,38070,38071,38072,38073,38074,38075,38076,38077,38078,38079,38080,38081,38082,38083,38084,38085,38086,38087,38088,38089,38090,38091,38092,38093,38094,38095,38096,38097,38098,38099,38100,38101,38102,38103,38104,38105,38106,38107,38108,38109,38110,38111,38112,38113,38114,38115,38116,38117,38118,38119,38120,38121,38122,38123,38124,38125,38126,38127,38128,38129,38130,38131,38132,38133,38134,38135,38136,38137,38138,38139,38140,38141,38142,38143,38144,38145,38146,38147,38148,38149,38150,38151,38152,38153,38154,38155,38156,38157,38158,38159,38160,38161,38162,38163,38164,38165,38166,38167,38168,38169,38170,38171,38172,38173,38174,38175,38176,38177,38178,38179,38180,38181,38182,38183,38184,38185,38186,38187,38188,38189,38190,38191,38192,38193,38194,38195,38196,38197,38198,38199,38200,38201,38202,38203,38204,38205,38206,38207,38208,38209,38210,38211,38212,38213,38214,38215,38216,38217,38218,38219,38220,38221,38222,38223,38224,38225,38226,38227,38228,38229,38230,38231,38232,38233,38234,38235,38236,38237,38238,38239,38240,38241,38242,38243,38244,38245,38246,38247,38248,38249,38250,38251,38252,38253,38254,38255,38256,38257,38258,38259,38260,38261,38262,38263,38264,38265,38266,38267,38268,38269,38270,38271,38272,38273,38274,38275,38276,38277,38278,38279,38280,38281,38282,38283,38284,38285,38286,38287,38288,38289,38290,38291,38292,38293,38294,38295,38296,38297,38298,38299,38300,38301,38302,38303,38304,38305,38306,38307,38308,38309,38310,38311,38312,38313,38314,38315,38316,38317,38318,38319,38320,38321,38322,38323,38324,38325,38326,38327,38328,38329,38330,38331,38332,38333,38334,38335,38336,38337,38338,38339,38340,38341,38342,38343,38344,38345,38346,38347,38348,38349,38350,38351,38352,38353,38354,38355,38356,38357,38358,38359,38360,38361,38362,38363,38364,38365,38366,38367,38368,38369,38370,38371,38372,38373,38374,38375,38376,38377,38378,38379,38380,38381,38382,38383,38384,38385,38386,38387,38388,38389,38390,38391,38392,38393,38394,38395,38396,38397,38398,38399,38400,38401,38402,38403,38404,38405,38406,38407,38408,38409,38410,38411,38412,38413,38414,38415,38416,38417,38418,38419,38420,38421,38422,38423,38424,38425,38426,38427,38428,38429,38430,38431,38432,38433,38434,38435,38436,38437,38438,38439,38440,38441,38442,38443,38444,38445,38446,38447,38448,38449,38450,38451,38452,38453,38454,38455,38456,38457,38458,38459,38460,38461,38462,38463,38464,38465,38466,38467,38468,38469,38470,38471,38472,38473,38474,38475,38476,38477,38478,38479,38480,38481,38482,38483,38484,38485,38486,38487,38488,38489,38490,38491,38492,38493,38494,38495,38496,38497,38498,38499,38500,38501,38502,38503,38504,38505,38506,38507,38508,38509,38510,38511,38512,38513,38514,38515,38516,38517,38518,38519,38520,38521,38522,38523,38524,38525,38526,38527,38528,38529,38530,38531,38532,38533,38534,38535,38536,38537,38538,38539,38540,38541,38542,38543,38544,38545,38546,38547,38548,38549,38550,38551,38552,38553,38554,38555,38556,38557,38558,38559,38560,38561,38562,38563,38564,38565,38566,38567,38568,38569,38570,38571,38572,38573,38574,38575,38576,38577,38578,38579,38580,38581,38582,38583,38584,38585,38586,38587,38588,38589,38590,38591,38592,38593,38594,38595,38596,38597,38598,38599,38600,38601,38602,38603,38604,38605,38606,38607,38608,38609,38610,38611,38612,38613,38614,38615,38616,38617,38618,38619,38620,38621,38622,38623,38624,38625,38626,38627,38628,38629,38630,38631,38632,38633,38634,38635,38636,38637,38638,38639,38640,38641,38642,38643,38644,38645,38646,38647,38648,38649,38650,38651,38652,38653,38654,38655,38656,38657,38658,38659,38660,38661,38662,38663,38664,38665,38666,38667,38668,38669,38670,38671,38672,38673,38674,38675,38676,38677,38678,38679,38680,38681,38682,38683,38684,38685,38686,38687,38688,38689,38690,38691,38692,38693,38694,38695,38696,38697,38698,38699,38700,38701,38702,38703,38704,38705,38706,38707,38708,38709,38710,38711,38712,38713,38714,38715,38716,38717,38718,38719,38720,38721,38722,38723,38724,38725,38726,38727,38728,38729,38730,38731,38732,38733,38734,38735,38736,38737,38738,38739,38740,38741,38742,38743,38744,38745,38746,38747,38748,38749,38750,38751,38752,38753,38754,38755,38756,38757,38758,38759,38760,38761,38762,38763,38764,38765,38766,38767,38768,38769,38770,38771,38772,38773,38774,38775,38776,38777,38778,38779,38780,38781,38782,38783,38784,38785,38786,38787,38788,38789,38790,38791,38792,38793,38794,38795,38796,38797,38798,38799,38800,38801,38802,38803,38804,38805,38806,38807,38808,38809,38810,38811,38812,38813,38814,38815,38816,38817,38818,38819,38820,38821,38822,38823,38824,38825,38826,38827,38828,38829,38830,38831,38832,38833,38834,38835,38836,38837,38838,38839,38840,38841,38842,38843,38844,38845,38846,38847,38848,38849,38850,38851,38852,38853,38854,38855,38856,38857,38858,38859,38860,38861,38862,38863,38864,38865,38866,38867,38868,38869,38870,38871,38872,38873,38874,38875,38876,38877,38878,38879,38880,38881,38882,38883,38884,38885,38886,38887,38888,38889,38890,38891,38892,38893,38894,38895,38896,38897,38898,38899,38900,38901,38902,38903,38904,38905,38906,38907,38908,38909,38910,38911,38912,38913,38914,38915,38916,38917,38918,38919,38920,38921,38922,38923,38924,38925,38926,38927,38928,38929,38930,38931,38932,38933,38934,38935,38936,38937,38938,38939,38940,38941,38942,38943,38944,38945,38946,38947,38948,38949,38950,38951,38952,38953,38954,38955,38956,38957,38958,38959,38960,38961,38962,38963,38964,38965,38966,38967,38968,38969,38970,38971,38972,38973,38974,38975,38976,38977,38978,38979,38980,38981,38982,38983,38984,38985,38986,38987,38988,38989,38990,38991,38992,38993,38994,38995,38996,38997,38998,38999,39000,39001,39002,39003,39004,39005,39006,39007,39008,39009,39010,39011,39012,39013,39014,39015,39016,39017,39018,39019,39020,39021,39022,39023,39024,39025,39026,39027,39028,39029,39030,39031,39032,39033,39034,39035,39036,39037,39038,39039,39040,39041,39042,39043,39044,39045,39046,39047,39048,39049,39050,39051,39052,39053,39054,39055,39056,39057,39058,39059,39060,39061,39062,39063,39064,39065,39066,39067,39068,39069,39070,39071,39072,39073,39074,39075,39076,39077,39078,39079,39080,39081,39082,39083,39084,39085,39086,39087,39088,39089,39090,39091,39092,39093,39094,39095,39096,39097,39098,39099,39100,39101,39102,39103,39104,39105,39106,39107,39108,39109,39110,39111,39112,39113,39114,39115,39116,39117,39118,39119,39120,39121,39122,39123,39124,39125,39126,39127,39128,39129,39130,39131,39132,39133,39134,39135,39136,39137,39138,39139,39140,39141,39142,39143,39144,39145,39146,39147,39148,39149,39150,39151,39152,39153,39154,39155,39156,39157,39158,39159,39160,39161,39162,39163,39164,39165,39166,39167,39168,39169,39170,39171,39172,39173,39174,39175,39176,39177,39178,39179,39180,39181,39182,39183,39184,39185,39186,39187,39188,39189,39190,39191,39192,39193,39194,39195,39196,39197,39198,39199,39200,39201,39202,39203,39204,39205,39206,39207,39208,39209,39210,39211,39212,39213,39214,39215,39216,39217,39218,39219,39220,39221,39222,39223,39224,39225,39226,39227,39228,39229,39230,39231,39232,39233,39234,39235,39236,39237,39238,39239,39240,39241,39242,39243,39244,39245,39246,39247,39248,39249,39250,39251,39252,39253,39254,39255,39256,39257,39258,39259,39260,39261,39262,39263,39264,39265,39266,39267,39268,39269,39270,39271,39272,39273,39274,39275,39276,39277,39278,39279,39280,39281,39282,39283,39284,39285,39286,39287,39288,39289,39290,39291,39292,39293,39294,39295,39296,39297,39298,39299,39300,39301,39302,39303,39304,39305,39306,39307,39308,39309,39310,39311,39312,39313,39314,39315,39316,39317,39318,39319,39320,39321,39322,39323,39324,39325,39326,39327,39328,39329,39330,39331,39332,39333,39334,39335,39336,39337,39338,39339,39340,39341,39342,39343,39344,39345,39346,39347,39348,39349,39350,39351,39352,39353,39354,39355,39356,39357,39358,39359,39360,39361,39362,39363,39364,39365,39366,39367,39368,39369,39370,39371,39372,39373,39374,39375,39376,39377,39378,39379,39380,39381,39382,39383,39384,39385,39386,39387,39388,39389,39390,39391,39392,39393,39394,39395,39396,39397,39398,39399,39400,39401,39402,39403,39404,39405,39406,39407,39408,39409,39410,39411,39412,39413,39414,39415,39416,39417,39418,39419,39420,39421,39422,39423,39424,39425,39426,39427,39428,39429,39430,39431,39432,39433,39434,39435,39436,39437,39438,39439,39440,39441,39442,39443,39444,39445,39446,39447,39448,39449,39450,39451,39452,39453,39454,39455,39456,39457,39458,39459,39460,39461,39462,39463,39464,39465,39466,39467,39468,39469,39470,39471,39472,39473,39474,39475,39476,39477,39478,39479,39480,39481,39482,39483,39484,39485,39486,39487,39488,39489,39490,39491,39492,39493,39494,39495,39496,39497,39498,39499,39500,39501,39502,39503,39504,39505,39506,39507,39508,39509,39510,39511,39512,39513,39514,39515,39516,39517,39518,39519,39520,39521,39522,39523,39524,39525,39526,39527,39528,39529,39530,39531,39532,39533,39534,39535,39536,39537,39538,39539,39540,39541,39542,39543,39544,39545,39546,39547,39548,39549,39550,39551,39552,39553,39554,39555,39556,39557,39558,39559,39560,39561,39562,39563,39564,39565,39566,39567,39568,39569,39570,39571,39572,39573,39574,39575,39576,39577,39578,39579,39580,39581,39582,39583,39584,39585,39586,39587,39588,39589,39590,39591,39592,39593,39594,39595,39596,39597,39598,39599,39600,39601,39602,39603,39604,39605,39606,39607,39608,39609,39610,39611,39612,39613,39614,39615,39616,39617,39618,39619,39620,39621,39622,39623,39624,39625,39626,39627,39628,39629,39630,39631,39632,39633,39634,39635,39636,39637,39638,39639,39640,39641,39642,39643,39644,39645,39646,39647,39648,39649,39650,39651,39652,39653,39654,39655,39656,39657,39658,39659,39660,39661,39662,39663,39664,39665,39666,39667,39668,39669,39670,39671,39672,39673,39674,39675,39676,39677,39678,39679,39680,39681,39682,39683,39684,39685,39686,39687,39688,39689,39690,39691,39692,39693,39694,39695,39696,39697,39698,39699,39700,39701,39702,39703,39704,39705,39706,39707,39708,39709,39710,39711,39712,39713,39714,39715,39716,39717,39718,39719,39720,39721,39722,39723,39724,39725,39726,39727,39728,39729,39730,39731,39732,39733,39734,39735,39736,39737,39738,39739,39740,39741,39742,39743,39744,39745,39746,39747,39748,39749,39750,39751,39752,39753,39754,39755,39756,39757,39758,39759,39760,39761,39762,39763,39764,39765,39766,39767,39768,39769,39770,39771,39772,39773,39774,39775,39776,39777,39778,39779,39780,39781,39782,39783,39784,39785,39786,39787,39788,39789,39790,39791,39792,39793,39794,39795,39796,39797,39798,39799,39800,39801,39802,39803,39804,39805,39806,39807,39808,39809,39810,39811,39812,39813,39814,39815,39816,39817,39818,39819,39820,39821,39822,39823,39824,39825,39826,39827,39828,39829,39830,39831,39832,39833,39834,39835,39836,39837,39838,39839,39840,39841,39842,39843,39844,39845,39846,39847,39848,39849,39850,39851,39852,39853,39854,39855,39856,39857,39858,39859,39860,39861,39862,39863,39864,39865,39866,39867,39868,39869,39870,39871,39872,39873,39874,39875,39876,39877,39878,39879,39880,39881,39882,39883,39884,39885,39886,39887,39888,39889,39890,39891,39892,39893,39894,39895,39896,39897,39898,39899,39900,39901,39902,39903,39904,39905,39906,39907,39908,39909,39910,39911,39912,39913,39914,39915,39916,39917,39918,39919,39920,39921,39922,39923,39924,39925,39926,39927,39928,39929,39930,39931,39932,39933,39934,39935,39936,39937,39938,39939,39940,39941,39942,39943,39944,39945,39946,39947,39948,39949,39950,39951,39952,39953,39954,39955,39956,39957,39958,39959,39960,39961,39962,39963,39964,39965,39966,39967,39968,39969,39970,39971,39972,39973,39974,39975,39976,39977,39978,39979,39980,39981,39982,39983,39984,39985,39986,39987,39988,39989,39990,39991,39992,39993,39994,39995,39996,39997,39998,39999,40000,40001,40002,40003,40004,40005,40006,40007,40008,40009,40010,40011,40012,40013,40014,40015,40016,40017,40018,40019,40020,40021,40022,40023,40024,40025,40026,40027,40028,40029,40030,40031,40032,40033,40034,40035,40036,40037,40038,40039,40040,40041,40042,40043,40044,40045,40046,40047,40048,40049,40050,40051,40052,40053,40054,40055,40056,40057,40058,40059,40060,40061,40062,40063,40064,40065,40066,40067,40068,40069,40070,40071,40072,40073,40074,40075,40076,40077,40078,40079,40080,40081,40082,40083,40084,40085,40086,40087,40088,40089,40090,40091,40092,40093,40094,40095,40096,40097,40098,40099,40100,40101,40102,40103,40104,40105,40106,40107,40108,40109,40110,40111,40112,40113,40114,40115,40116,40117,40118,40119,40120,40121,40122,40123,40124,40125,40126,40127,40128,40129,40130,40131,40132,40133,40134,40135,40136,40137,40138,40139,40140,40141,40142,40143,40144,40145,40146,40147,40148,40149,40150,40151,40152,40153,40154,40155,40156,40157,40158,40159,40160,40161,40162,40163,40164,40165,40166,40167,40168,40169,40170,40171,40172,40173,40174,40175,40176,40177,40178,40179,40180,40181,40182,40183,40184,40185,40186,40187,40188,40189,40190,40191,40192,40193,40194,40195,40196,40197,40198,40199,40200,40201,40202,40203,40204,40205,40206,40207,40208,40209,40210,40211,40212,40213,40214,40215,40216,40217,40218,40219,40220,40221,40222,40223,40224,40225,40226,40227,40228,40229,40230,40231,40232,40233,40234,40235,40236,40237,40238,40239,40240,40241,40242,40243,40244,40245,40246,40247,40248,40249,40250,40251,40252,40253,40254,40255,40256,40257,40258,40259,40260,40261,40262,40263,40264,40265,40266,40267,40268,40269,40270,40271,40272,40273,40274,40275,40276,40277,40278,40279,40280,40281,40282,40283,40284,40285,40286,40287,40288,40289,40290,40291,40292,40293,40294,40295,40296,40297,40298,40299,40300,40301,40302,40303,40304,40305,40306,40307,40308,40309,40310,40311,40312,40313,40314,40315,40316,40317,40318,40319,40320,40321,40322,40323,40324,40325,40326,40327,40328,40329,40330,40331,40332,40333,40334,40335,40336,40337,40338,40339,40340,40341,40342,40343,40344,40345,40346,40347,40348,40349,40350,40351,40352,40353,40354,40355,40356,40357,40358,40359,40360,40361,40362,40363,40364,40365,40366,40367,40368,40369,40370,40371,40372,40373,40374,40375,40376,40377,40378,40379,40380,40381,40382,40383,40384,40385,40386,40387,40388,40389,40390,40391,40392,40393,40394,40395,40396,40397,40398,40399,40400,40401,40402,40403,40404,40405,40406,40407,40408,40409,40410,40411,40412,40413,40414,40415,40416,40417,40418,40419,40420,40421,40422,40423,40424,40425,40426,40427,40428,40429,40430,40431,40432,40433,40434,40435,40436,40437,40438,40439,40440,40441,40442,40443,40444,40445,40446,40447,40448,40449,40450,40451,40452,40453,40454,40455,40456,40457,40458,40459,40460,40461,40462,40463,40464,40465,40466,40467,40468,40469,40470,40471,40472,40473,40474,40475,40476,40477,40478,40479,40480,40481,40482,40483,40484,40485,40486,40487,40488,40489,40490,40491,40492,40493,40494,40495,40496,40497,40498,40499,40500,40501,40502,40503,40504,40505,40506,40507,40508,40509,40510,40511,40512,40513,40514,40515,40516,40517,40518,40519,40520,40521,40522,40523,40524,40525,40526,40527,40528,40529,40530,40531,40532,40533,40534,40535,40536,40537,40538,40539,40540,40541,40542,40543,40544,40545,40546,40547,40548,40549,40550,40551,40552,40553,40554,40555,40556,40557,40558,40559,40560,40561,40562,40563,40564,40565,40566,40567,40568,40569,40570,40571,40572,40573,40574,40575,40576,40577,40578,40579,40580,40581,40582,40583,40584,40585,40586,40587,40588,40589,40590,40591,40592,40593,40594,40595,40596,40597,40598,40599,40600,40601,40602,40603,40604,40605,40606,40607,40608,40609,40610,40611,40612,40613,40614,40615,40616,40617,40618,40619,40620,40621,40622,40623,40624,40625,40626,40627,40628,40629,40630,40631,40632,40633,40634,40635,40636,40637,40638,40639,40640,40641,40642,40643,40644,40645,40646,40647,40648,40649,40650,40651,40652,40653,40654,40655,40656,40657,40658,40659,40660,40661,40662,40663,40664,40665,40666,40667,40668,40669,40670,40671,40672,40673,40674,40675,40676,40677,40678,40679,40680,40681,40682,40683,40684,40685,40686,40687,40688,40689,40690,40691,40692,40693,40694,40695,40696,40697,40698,40699,40700,40701,40702,40703,40704,40705,40706,40707,40708,40709,40710,40711,40712,40713,40714,40715,40716,40717,40718,40719,40720,40721,40722,40723,40724,40725,40726,40727,40728,40729,40730,40731,40732,40733,40734,40735,40736,40737,40738,40739,40740,40741,40742,40743,40744,40745,40746,40747,40748,40749,40750,40751,40752,40753,40754,40755,40756,40757,40758,40759,40760,40761,40762,40763,40764,40765,40766,40767,40768,40769,40770,40771,40772,40773,40774,40775,40776,40777,40778,40779,40780,40781,40782,40783,40784,40785,40786,40787,40788,40789,40790,40791,40792,40793,40794,40795,40796,40797,40798,40799,40800,40801,40802,40803,40804,40805,40806,40807,40808,40809,40810,40811,40812,40813,40814,40815,40816,40817,40818,40819,40820,40821,40822,40823,40824,40825,40826,40827,40828,40829,40830,40831,40832,40833,40834,40835,40836,40837,40838,40839,40840,40841,40842,40843,40844,40845,40846,40847,40848,40849,40850,40851,40852,40853,40854,40855,40856,40857,40858,40859,40860,40861,40862,40863,40864,40865,40866,40867,40868,40869,40870,40871,40872,40873,40874,40875,40876,40877,40878,40879,40880,40881,40882,40883,40884,40885,40886,40887,40888,40889,40890,40891,40892,40893,40894,40895,40896,40897,40898,40899,40900,40901,40902,40903,40904,40905,40906,40907,40908,40909,40910,40911,40912,40913,40914,40915,40916,40917,40918,40919,40920,40921,40922,40923,40924,40925,40926,40927,40928,40929,40930,40931,40932,40933,40934,40935,40936,40937,40938,40939,40940,40941,40942,40943,40960,40961,40962,40963,40964,40965,40966,40967,40968,40969,40970,40971,40972,40973,40974,40975,40976,40977,40978,40979,40980,40981,40982,40983,40984,40985,40986,40987,40988,40989,40990,40991,40992,40993,40994,40995,40996,40997,40998,40999,41000,41001,41002,41003,41004,41005,41006,41007,41008,41009,41010,41011,41012,41013,41014,41015,41016,41017,41018,41019,41020,41021,41022,41023,41024,41025,41026,41027,41028,41029,41030,41031,41032,41033,41034,41035,41036,41037,41038,41039,41040,41041,41042,41043,41044,41045,41046,41047,41048,41049,41050,41051,41052,41053,41054,41055,41056,41057,41058,41059,41060,41061,41062,41063,41064,41065,41066,41067,41068,41069,41070,41071,41072,41073,41074,41075,41076,41077,41078,41079,41080,41081,41082,41083,41084,41085,41086,41087,41088,41089,41090,41091,41092,41093,41094,41095,41096,41097,41098,41099,41100,41101,41102,41103,41104,41105,41106,41107,41108,41109,41110,41111,41112,41113,41114,41115,41116,41117,41118,41119,41120,41121,41122,41123,41124,41125,41126,41127,41128,41129,41130,41131,41132,41133,41134,41135,41136,41137,41138,41139,41140,41141,41142,41143,41144,41145,41146,41147,41148,41149,41150,41151,41152,41153,41154,41155,41156,41157,41158,41159,41160,41161,41162,41163,41164,41165,41166,41167,41168,41169,41170,41171,41172,41173,41174,41175,41176,41177,41178,41179,41180,41181,41182,41183,41184,41185,41186,41187,41188,41189,41190,41191,41192,41193,41194,41195,41196,41197,41198,41199,41200,41201,41202,41203,41204,41205,41206,41207,41208,41209,41210,41211,41212,41213,41214,41215,41216,41217,41218,41219,41220,41221,41222,41223,41224,41225,41226,41227,41228,41229,41230,41231,41232,41233,41234,41235,41236,41237,41238,41239,41240,41241,41242,41243,41244,41245,41246,41247,41248,41249,41250,41251,41252,41253,41254,41255,41256,41257,41258,41259,41260,41261,41262,41263,41264,41265,41266,41267,41268,41269,41270,41271,41272,41273,41274,41275,41276,41277,41278,41279,41280,41281,41282,41283,41284,41285,41286,41287,41288,41289,41290,41291,41292,41293,41294,41295,41296,41297,41298,41299,41300,41301,41302,41303,41304,41305,41306,41307,41308,41309,41310,41311,41312,41313,41314,41315,41316,41317,41318,41319,41320,41321,41322,41323,41324,41325,41326,41327,41328,41329,41330,41331,41332,41333,41334,41335,41336,41337,41338,41339,41340,41341,41342,41343,41344,41345,41346,41347,41348,41349,41350,41351,41352,41353,41354,41355,41356,41357,41358,41359,41360,41361,41362,41363,41364,41365,41366,41367,41368,41369,41370,41371,41372,41373,41374,41375,41376,41377,41378,41379,41380,41381,41382,41383,41384,41385,41386,41387,41388,41389,41390,41391,41392,41393,41394,41395,41396,41397,41398,41399,41400,41401,41402,41403,41404,41405,41406,41407,41408,41409,41410,41411,41412,41413,41414,41415,41416,41417,41418,41419,41420,41421,41422,41423,41424,41425,41426,41427,41428,41429,41430,41431,41432,41433,41434,41435,41436,41437,41438,41439,41440,41441,41442,41443,41444,41445,41446,41447,41448,41449,41450,41451,41452,41453,41454,41455,41456,41457,41458,41459,41460,41461,41462,41463,41464,41465,41466,41467,41468,41469,41470,41471,41472,41473,41474,41475,41476,41477,41478,41479,41480,41481,41482,41483,41484,41485,41486,41487,41488,41489,41490,41491,41492,41493,41494,41495,41496,41497,41498,41499,41500,41501,41502,41503,41504,41505,41506,41507,41508,41509,41510,41511,41512,41513,41514,41515,41516,41517,41518,41519,41520,41521,41522,41523,41524,41525,41526,41527,41528,41529,41530,41531,41532,41533,41534,41535,41536,41537,41538,41539,41540,41541,41542,41543,41544,41545,41546,41547,41548,41549,41550,41551,41552,41553,41554,41555,41556,41557,41558,41559,41560,41561,41562,41563,41564,41565,41566,41567,41568,41569,41570,41571,41572,41573,41574,41575,41576,41577,41578,41579,41580,41581,41582,41583,41584,41585,41586,41587,41588,41589,41590,41591,41592,41593,41594,41595,41596,41597,41598,41599,41600,41601,41602,41603,41604,41605,41606,41607,41608,41609,41610,41611,41612,41613,41614,41615,41616,41617,41618,41619,41620,41621,41622,41623,41624,41625,41626,41627,41628,41629,41630,41631,41632,41633,41634,41635,41636,41637,41638,41639,41640,41641,41642,41643,41644,41645,41646,41647,41648,41649,41650,41651,41652,41653,41654,41655,41656,41657,41658,41659,41660,41661,41662,41663,41664,41665,41666,41667,41668,41669,41670,41671,41672,41673,41674,41675,41676,41677,41678,41679,41680,41681,41682,41683,41684,41685,41686,41687,41688,41689,41690,41691,41692,41693,41694,41695,41696,41697,41698,41699,41700,41701,41702,41703,41704,41705,41706,41707,41708,41709,41710,41711,41712,41713,41714,41715,41716,41717,41718,41719,41720,41721,41722,41723,41724,41725,41726,41727,41728,41729,41730,41731,41732,41733,41734,41735,41736,41737,41738,41739,41740,41741,41742,41743,41744,41745,41746,41747,41748,41749,41750,41751,41752,41753,41754,41755,41756,41757,41758,41759,41760,41761,41762,41763,41764,41765,41766,41767,41768,41769,41770,41771,41772,41773,41774,41775,41776,41777,41778,41779,41780,41781,41782,41783,41784,41785,41786,41787,41788,41789,41790,41791,41792,41793,41794,41795,41796,41797,41798,41799,41800,41801,41802,41803,41804,41805,41806,41807,41808,41809,41810,41811,41812,41813,41814,41815,41816,41817,41818,41819,41820,41821,41822,41823,41824,41825,41826,41827,41828,41829,41830,41831,41832,41833,41834,41835,41836,41837,41838,41839,41840,41841,41842,41843,41844,41845,41846,41847,41848,41849,41850,41851,41852,41853,41854,41855,41856,41857,41858,41859,41860,41861,41862,41863,41864,41865,41866,41867,41868,41869,41870,41871,41872,41873,41874,41875,41876,41877,41878,41879,41880,41881,41882,41883,41884,41885,41886,41887,41888,41889,41890,41891,41892,41893,41894,41895,41896,41897,41898,41899,41900,41901,41902,41903,41904,41905,41906,41907,41908,41909,41910,41911,41912,41913,41914,41915,41916,41917,41918,41919,41920,41921,41922,41923,41924,41925,41926,41927,41928,41929,41930,41931,41932,41933,41934,41935,41936,41937,41938,41939,41940,41941,41942,41943,41944,41945,41946,41947,41948,41949,41950,41951,41952,41953,41954,41955,41956,41957,41958,41959,41960,41961,41962,41963,41964,41965,41966,41967,41968,41969,41970,41971,41972,41973,41974,41975,41976,41977,41978,41979,41980,41981,41982,41983,41984,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,41997,41998,41999,42000,42001,42002,42003,42004,42005,42006,42007,42008,42009,42010,42011,42012,42013,42014,42015,42016,42017,42018,42019,42020,42021,42022,42023,42024,42025,42026,42027,42028,42029,42030,42031,42032,42033,42034,42035,42036,42037,42038,42039,42040,42041,42042,42043,42044,42045,42046,42047,42048,42049,42050,42051,42052,42053,42054,42055,42056,42057,42058,42059,42060,42061,42062,42063,42064,42065,42066,42067,42068,42069,42070,42071,42072,42073,42074,42075,42076,42077,42078,42079,42080,42081,42082,42083,42084,42085,42086,42087,42088,42089,42090,42091,42092,42093,42094,42095,42096,42097,42098,42099,42100,42101,42102,42103,42104,42105,42106,42107,42108,42109,42110,42111,42112,42113,42114,42115,42116,42117,42118,42119,42120,42121,42122,42123,42124,42192,42193,42194,42195,42196,42197,42198,42199,42200,42201,42202,42203,42204,42205,42206,42207,42208,42209,42210,42211,42212,42213,42214,42215,42216,42217,42218,42219,42220,42221,42222,42223,42224,42225,42226,42227,42228,42229,42230,42231,42232,42233,42234,42235,42236,42237,42240,42241,42242,42243,42244,42245,42246,42247,42248,42249,42250,42251,42252,42253,42254,42255,42256,42257,42258,42259,42260,42261,42262,42263,42264,42265,42266,42267,42268,42269,42270,42271,42272,42273,42274,42275,42276,42277,42278,42279,42280,42281,42282,42283,42284,42285,42286,42287,42288,42289,42290,42291,42292,42293,42294,42295,42296,42297,42298,42299,42300,42301,42302,42303,42304,42305,42306,42307,42308,42309,42310,42311,42312,42313,42314,42315,42316,42317,42318,42319,42320,42321,42322,42323,42324,42325,42326,42327,42328,42329,42330,42331,42332,42333,42334,42335,42336,42337,42338,42339,42340,42341,42342,42343,42344,42345,42346,42347,42348,42349,42350,42351,42352,42353,42354,42355,42356,42357,42358,42359,42360,42361,42362,42363,42364,42365,42366,42367,42368,42369,42370,42371,42372,42373,42374,42375,42376,42377,42378,42379,42380,42381,42382,42383,42384,42385,42386,42387,42388,42389,42390,42391,42392,42393,42394,42395,42396,42397,42398,42399,42400,42401,42402,42403,42404,42405,42406,42407,42408,42409,42410,42411,42412,42413,42414,42415,42416,42417,42418,42419,42420,42421,42422,42423,42424,42425,42426,42427,42428,42429,42430,42431,42432,42433,42434,42435,42436,42437,42438,42439,42440,42441,42442,42443,42444,42445,42446,42447,42448,42449,42450,42451,42452,42453,42454,42455,42456,42457,42458,42459,42460,42461,42462,42463,42464,42465,42466,42467,42468,42469,42470,42471,42472,42473,42474,42475,42476,42477,42478,42479,42480,42481,42482,42483,42484,42485,42486,42487,42488,42489,42490,42491,42492,42493,42494,42495,42496,42497,42498,42499,42500,42501,42502,42503,42504,42505,42506,42507,42508,42512,42513,42514,42515,42516,42517,42518,42519,42520,42521,42522,42523,42524,42525,42526,42527,42538,42539,42560,42561,42562,42563,42564,42565,42566,42567,42568,42569,42570,42571,42572,42573,42574,42575,42576,42577,42578,42579,42580,42581,42582,42583,42584,42585,42586,42587,42588,42589,42590,42591,42592,42593,42594,42595,42596,42597,42598,42599,42600,42601,42602,42603,42604,42605,42606,42623,42624,42625,42626,42627,42628,42629,42630,42631,42632,42633,42634,42635,42636,42637,42638,42639,42640,42641,42642,42643,42644,42645,42646,42647,42648,42649,42650,42651,42652,42653,42656,42657,42658,42659,42660,42661,42662,42663,42664,42665,42666,42667,42668,42669,42670,42671,42672,42673,42674,42675,42676,42677,42678,42679,42680,42681,42682,42683,42684,42685,42686,42687,42688,42689,42690,42691,42692,42693,42694,42695,42696,42697,42698,42699,42700,42701,42702,42703,42704,42705,42706,42707,42708,42709,42710,42711,42712,42713,42714,42715,42716,42717,42718,42719,42720,42721,42722,42723,42724,42725,42726,42727,42728,42729,42730,42731,42732,42733,42734,42735,42775,42776,42777,42778,42779,42780,42781,42782,42783,42786,42787,42788,42789,42790,42791,42792,42793,42794,42795,42796,42797,42798,42799,42800,42801,42802,42803,42804,42805,42806,42807,42808,42809,42810,42811,42812,42813,42814,42815,42816,42817,42818,42819,42820,42821,42822,42823,42824,42825,42826,42827,42828,42829,42830,42831,42832,42833,42834,42835,42836,42837,42838,42839,42840,42841,42842,42843,42844,42845,42846,42847,42848,42849,42850,42851,42852,42853,42854,42855,42856,42857,42858,42859,42860,42861,42862,42863,42864,42865,42866,42867,42868,42869,42870,42871,42872,42873,42874,42875,42876,42877,42878,42879,42880,42881,42882,42883,42884,42885,42886,42887,42888,42891,42892,42893,42894,42895,42896,42897,42898,42899,42900,42901,42902,42903,42904,42905,42906,42907,42908,42909,42910,42911,42912,42913,42914,42915,42916,42917,42918,42919,42920,42921,42922,42923,42924,42925,42926,42927,42928,42929,42930,42931,42932,42933,42934,42935,42936,42937,42999,43000,43001,43002,43003,43004,43005,43006,43007,43008,43009,43011,43012,43013,43015,43016,43017,43018,43020,43021,43022,43023,43024,43025,43026,43027,43028,43029,43030,43031,43032,43033,43034,43035,43036,43037,43038,43039,43040,43041,43042,43072,43073,43074,43075,43076,43077,43078,43079,43080,43081,43082,43083,43084,43085,43086,43087,43088,43089,43090,43091,43092,43093,43094,43095,43096,43097,43098,43099,43100,43101,43102,43103,43104,43105,43106,43107,43108,43109,43110,43111,43112,43113,43114,43115,43116,43117,43118,43119,43120,43121,43122,43123,43138,43139,43140,43141,43142,43143,43144,43145,43146,43147,43148,43149,43150,43151,43152,43153,43154,43155,43156,43157,43158,43159,43160,43161,43162,43163,43164,43165,43166,43167,43168,43169,43170,43171,43172,43173,43174,43175,43176,43177,43178,43179,43180,43181,43182,43183,43184,43185,43186,43187,43250,43251,43252,43253,43254,43255,43259,43261,43262,43274,43275,43276,43277,43278,43279,43280,43281,43282,43283,43284,43285,43286,43287,43288,43289,43290,43291,43292,43293,43294,43295,43296,43297,43298,43299,43300,43301,43312,43313,43314,43315,43316,43317,43318,43319,43320,43321,43322,43323,43324,43325,43326,43327,43328,43329,43330,43331,43332,43333,43334,43360,43361,43362,43363,43364,43365,43366,43367,43368,43369,43370,43371,43372,43373,43374,43375,43376,43377,43378,43379,43380,43381,43382,43383,43384,43385,43386,43387,43388,43396,43397,43398,43399,43400,43401,43402,43403,43404,43405,43406,43407,43408,43409,43410,43411,43412,43413,43414,43415,43416,43417,43418,43419,43420,43421,43422,43423,43424,43425,43426,43427,43428,43429,43430,43431,43432,43433,43434,43435,43436,43437,43438,43439,43440,43441,43442,43471,43488,43489,43490,43491,43492,43494,43495,43496,43497,43498,43499,43500,43501,43502,43503,43514,43515,43516,43517,43518,43520,43521,43522,43523,43524,43525,43526,43527,43528,43529,43530,43531,43532,43533,43534,43535,43536,43537,43538,43539,43540,43541,43542,43543,43544,43545,43546,43547,43548,43549,43550,43551,43552,43553,43554,43555,43556,43557,43558,43559,43560,43584,43585,43586,43588,43589,43590,43591,43592,43593,43594,43595,43616,43617,43618,43619,43620,43621,43622,43623,43624,43625,43626,43627,43628,43629,43630,43631,43632,43633,43634,43635,43636,43637,43638,43642,43646,43647,43648,43649,43650,43651,43652,43653,43654,43655,43656,43657,43658,43659,43660,43661,43662,43663,43664,43665,43666,43667,43668,43669,43670,43671,43672,43673,43674,43675,43676,43677,43678,43679,43680,43681,43682,43683,43684,43685,43686,43687,43688,43689,43690,43691,43692,43693,43694,43695,43697,43701,43702,43705,43706,43707,43708,43709,43712,43714,43739,43740,43741,43744,43745,43746,43747,43748,43749,43750,43751,43752,43753,43754,43762,43763,43764,43777,43778,43779,43780,43781,43782,43785,43786,43787,43788,43789,43790,43793,43794,43795,43796,43797,43798,43808,43809,43810,43811,43812,43813,43814,43816,43817,43818,43819,43820,43821,43822,43824,43825,43826,43827,43828,43829,43830,43831,43832,43833,43834,43835,43836,43837,43838,43839,43840,43841,43842,43843,43844,43845,43846,43847,43848,43849,43850,43851,43852,43853,43854,43855,43856,43857,43858,43859,43860,43861,43862,43863,43864,43865,43866,43868,43869,43870,43871,43872,43873,43874,43875,43876,43877,43888,43889,43890,43891,43892,43893,43894,43895,43896,43897,43898,43899,43900,43901,43902,43903,43904,43905,43906,43907,43908,43909,43910,43911,43912,43913,43914,43915,43916,43917,43918,43919,43920,43921,43922,43923,43924,43925,43926,43927,43928,43929,43930,43931,43932,43933,43934,43935,43936,43937,43938,43939,43940,43941,43942,43943,43944,43945,43946,43947,43948,43949,43950,43951,43952,43953,43954,43955,43956,43957,43958,43959,43960,43961,43962,43963,43964,43965,43966,43967,43968,43969,43970,43971,43972,43973,43974,43975,43976,43977,43978,43979,43980,43981,43982,43983,43984,43985,43986,43987,43988,43989,43990,43991,43992,43993,43994,43995,43996,43997,43998,43999,44000,44001,44002,44032,44033,44034,44035,44036,44037,44038,44039,44040,44041,44042,44043,44044,44045,44046,44047,44048,44049,44050,44051,44052,44053,44054,44055,44056,44057,44058,44059,44060,44061,44062,44063,44064,44065,44066,44067,44068,44069,44070,44071,44072,44073,44074,44075,44076,44077,44078,44079,44080,44081,44082,44083,44084,44085,44086,44087,44088,44089,44090,44091,44092,44093,44094,44095,44096,44097,44098,44099,44100,44101,44102,44103,44104,44105,44106,44107,44108,44109,44110,44111,44112,44113,44114,44115,44116,44117,44118,44119,44120,44121,44122,44123,44124,44125,44126,44127,44128,44129,44130,44131,44132,44133,44134,44135,44136,44137,44138,44139,44140,44141,44142,44143,44144,44145,44146,44147,44148,44149,44150,44151,44152,44153,44154,44155,44156,44157,44158,44159,44160,44161,44162,44163,44164,44165,44166,44167,44168,44169,44170,44171,44172,44173,44174,44175,44176,44177,44178,44179,44180,44181,44182,44183,44184,44185,44186,44187,44188,44189,44190,44191,44192,44193,44194,44195,44196,44197,44198,44199,44200,44201,44202,44203,44204,44205,44206,44207,44208,44209,44210,44211,44212,44213,44214,44215,44216,44217,44218,44219,44220,44221,44222,44223,44224,44225,44226,44227,44228,44229,44230,44231,44232,44233,44234,44235,44236,44237,44238,44239,44240,44241,44242,44243,44244,44245,44246,44247,44248,44249,44250,44251,44252,44253,44254,44255,44256,44257,44258,44259,44260,44261,44262,44263,44264,44265,44266,44267,44268,44269,44270,44271,44272,44273,44274,44275,44276,44277,44278,44279,44280,44281,44282,44283,44284,44285,44286,44287,44288,44289,44290,44291,44292,44293,44294,44295,44296,44297,44298,44299,44300,44301,44302,44303,44304,44305,44306,44307,44308,44309,44310,44311,44312,44313,44314,44315,44316,44317,44318,44319,44320,44321,44322,44323,44324,44325,44326,44327,44328,44329,44330,44331,44332,44333,44334,44335,44336,44337,44338,44339,44340,44341,44342,44343,44344,44345,44346,44347,44348,44349,44350,44351,44352,44353,44354,44355,44356,44357,44358,44359,44360,44361,44362,44363,44364,44365,44366,44367,44368,44369,44370,44371,44372,44373,44374,44375,44376,44377,44378,44379,44380,44381,44382,44383,44384,44385,44386,44387,44388,44389,44390,44391,44392,44393,44394,44395,44396,44397,44398,44399,44400,44401,44402,44403,44404,44405,44406,44407,44408,44409,44410,44411,44412,44413,44414,44415,44416,44417,44418,44419,44420,44421,44422,44423,44424,44425,44426,44427,44428,44429,44430,44431,44432,44433,44434,44435,44436,44437,44438,44439,44440,44441,44442,44443,44444,44445,44446,44447,44448,44449,44450,44451,44452,44453,44454,44455,44456,44457,44458,44459,44460,44461,44462,44463,44464,44465,44466,44467,44468,44469,44470,44471,44472,44473,44474,44475,44476,44477,44478,44479,44480,44481,44482,44483,44484,44485,44486,44487,44488,44489,44490,44491,44492,44493,44494,44495,44496,44497,44498,44499,44500,44501,44502,44503,44504,44505,44506,44507,44508,44509,44510,44511,44512,44513,44514,44515,44516,44517,44518,44519,44520,44521,44522,44523,44524,44525,44526,44527,44528,44529,44530,44531,44532,44533,44534,44535,44536,44537,44538,44539,44540,44541,44542,44543,44544,44545,44546,44547,44548,44549,44550,44551,44552,44553,44554,44555,44556,44557,44558,44559,44560,44561,44562,44563,44564,44565,44566,44567,44568,44569,44570,44571,44572,44573,44574,44575,44576,44577,44578,44579,44580,44581,44582,44583,44584,44585,44586,44587,44588,44589,44590,44591,44592,44593,44594,44595,44596,44597,44598,44599,44600,44601,44602,44603,44604,44605,44606,44607,44608,44609,44610,44611,44612,44613,44614,44615,44616,44617,44618,44619,44620,44621,44622,44623,44624,44625,44626,44627,44628,44629,44630,44631,44632,44633,44634,44635,44636,44637,44638,44639,44640,44641,44642,44643,44644,44645,44646,44647,44648,44649,44650,44651,44652,44653,44654,44655,44656,44657,44658,44659,44660,44661,44662,44663,44664,44665,44666,44667,44668,44669,44670,44671,44672,44673,44674,44675,44676,44677,44678,44679,44680,44681,44682,44683,44684,44685,44686,44687,44688,44689,44690,44691,44692,44693,44694,44695,44696,44697,44698,44699,44700,44701,44702,44703,44704,44705,44706,44707,44708,44709,44710,44711,44712,44713,44714,44715,44716,44717,44718,44719,44720,44721,44722,44723,44724,44725,44726,44727,44728,44729,44730,44731,44732,44733,44734,44735,44736,44737,44738,44739,44740,44741,44742,44743,44744,44745,44746,44747,44748,44749,44750,44751,44752,44753,44754,44755,44756,44757,44758,44759,44760,44761,44762,44763,44764,44765,44766,44767,44768,44769,44770,44771,44772,44773,44774,44775,44776,44777,44778,44779,44780,44781,44782,44783,44784,44785,44786,44787,44788,44789,44790,44791,44792,44793,44794,44795,44796,44797,44798,44799,44800,44801,44802,44803,44804,44805,44806,44807,44808,44809,44810,44811,44812,44813,44814,44815,44816,44817,44818,44819,44820,44821,44822,44823,44824,44825,44826,44827,44828,44829,44830,44831,44832,44833,44834,44835,44836,44837,44838,44839,44840,44841,44842,44843,44844,44845,44846,44847,44848,44849,44850,44851,44852,44853,44854,44855,44856,44857,44858,44859,44860,44861,44862,44863,44864,44865,44866,44867,44868,44869,44870,44871,44872,44873,44874,44875,44876,44877,44878,44879,44880,44881,44882,44883,44884,44885,44886,44887,44888,44889,44890,44891,44892,44893,44894,44895,44896,44897,44898,44899,44900,44901,44902,44903,44904,44905,44906,44907,44908,44909,44910,44911,44912,44913,44914,44915,44916,44917,44918,44919,44920,44921,44922,44923,44924,44925,44926,44927,44928,44929,44930,44931,44932,44933,44934,44935,44936,44937,44938,44939,44940,44941,44942,44943,44944,44945,44946,44947,44948,44949,44950,44951,44952,44953,44954,44955,44956,44957,44958,44959,44960,44961,44962,44963,44964,44965,44966,44967,44968,44969,44970,44971,44972,44973,44974,44975,44976,44977,44978,44979,44980,44981,44982,44983,44984,44985,44986,44987,44988,44989,44990,44991,44992,44993,44994,44995,44996,44997,44998,44999,45000,45001,45002,45003,45004,45005,45006,45007,45008,45009,45010,45011,45012,45013,45014,45015,45016,45017,45018,45019,45020,45021,45022,45023,45024,45025,45026,45027,45028,45029,45030,45031,45032,45033,45034,45035,45036,45037,45038,45039,45040,45041,45042,45043,45044,45045,45046,45047,45048,45049,45050,45051,45052,45053,45054,45055,45056,45057,45058,45059,45060,45061,45062,45063,45064,45065,45066,45067,45068,45069,45070,45071,45072,45073,45074,45075,45076,45077,45078,45079,45080,45081,45082,45083,45084,45085,45086,45087,45088,45089,45090,45091,45092,45093,45094,45095,45096,45097,45098,45099,45100,45101,45102,45103,45104,45105,45106,45107,45108,45109,45110,45111,45112,45113,45114,45115,45116,45117,45118,45119,45120,45121,45122,45123,45124,45125,45126,45127,45128,45129,45130,45131,45132,45133,45134,45135,45136,45137,45138,45139,45140,45141,45142,45143,45144,45145,45146,45147,45148,45149,45150,45151,45152,45153,45154,45155,45156,45157,45158,45159,45160,45161,45162,45163,45164,45165,45166,45167,45168,45169,45170,45171,45172,45173,45174,45175,45176,45177,45178,45179,45180,45181,45182,45183,45184,45185,45186,45187,45188,45189,45190,45191,45192,45193,45194,45195,45196,45197,45198,45199,45200,45201,45202,45203,45204,45205,45206,45207,45208,45209,45210,45211,45212,45213,45214,45215,45216,45217,45218,45219,45220,45221,45222,45223,45224,45225,45226,45227,45228,45229,45230,45231,45232,45233,45234,45235,45236,45237,45238,45239,45240,45241,45242,45243,45244,45245,45246,45247,45248,45249,45250,45251,45252,45253,45254,45255,45256,45257,45258,45259,45260,45261,45262,45263,45264,45265,45266,45267,45268,45269,45270,45271,45272,45273,45274,45275,45276,45277,45278,45279,45280,45281,45282,45283,45284,45285,45286,45287,45288,45289,45290,45291,45292,45293,45294,45295,45296,45297,45298,45299,45300,45301,45302,45303,45304,45305,45306,45307,45308,45309,45310,45311,45312,45313,45314,45315,45316,45317,45318,45319,45320,45321,45322,45323,45324,45325,45326,45327,45328,45329,45330,45331,45332,45333,45334,45335,45336,45337,45338,45339,45340,45341,45342,45343,45344,45345,45346,45347,45348,45349,45350,45351,45352,45353,45354,45355,45356,45357,45358,45359,45360,45361,45362,45363,45364,45365,45366,45367,45368,45369,45370,45371,45372,45373,45374,45375,45376,45377,45378,45379,45380,45381,45382,45383,45384,45385,45386,45387,45388,45389,45390,45391,45392,45393,45394,45395,45396,45397,45398,45399,45400,45401,45402,45403,45404,45405,45406,45407,45408,45409,45410,45411,45412,45413,45414,45415,45416,45417,45418,45419,45420,45421,45422,45423,45424,45425,45426,45427,45428,45429,45430,45431,45432,45433,45434,45435,45436,45437,45438,45439,45440,45441,45442,45443,45444,45445,45446,45447,45448,45449,45450,45451,45452,45453,45454,45455,45456,45457,45458,45459,45460,45461,45462,45463,45464,45465,45466,45467,45468,45469,45470,45471,45472,45473,45474,45475,45476,45477,45478,45479,45480,45481,45482,45483,45484,45485,45486,45487,45488,45489,45490,45491,45492,45493,45494,45495,45496,45497,45498,45499,45500,45501,45502,45503,45504,45505,45506,45507,45508,45509,45510,45511,45512,45513,45514,45515,45516,45517,45518,45519,45520,45521,45522,45523,45524,45525,45526,45527,45528,45529,45530,45531,45532,45533,45534,45535,45536,45537,45538,45539,45540,45541,45542,45543,45544,45545,45546,45547,45548,45549,45550,45551,45552,45553,45554,45555,45556,45557,45558,45559,45560,45561,45562,45563,45564,45565,45566,45567,45568,45569,45570,45571,45572,45573,45574,45575,45576,45577,45578,45579,45580,45581,45582,45583,45584,45585,45586,45587,45588,45589,45590,45591,45592,45593,45594,45595,45596,45597,45598,45599,45600,45601,45602,45603,45604,45605,45606,45607,45608,45609,45610,45611,45612,45613,45614,45615,45616,45617,45618,45619,45620,45621,45622,45623,45624,45625,45626,45627,45628,45629,45630,45631,45632,45633,45634,45635,45636,45637,45638,45639,45640,45641,45642,45643,45644,45645,45646,45647,45648,45649,45650,45651,45652,45653,45654,45655,45656,45657,45658,45659,45660,45661,45662,45663,45664,45665,45666,45667,45668,45669,45670,45671,45672,45673,45674,45675,45676,45677,45678,45679,45680,45681,45682,45683,45684,45685,45686,45687,45688,45689,45690,45691,45692,45693,45694,45695,45696,45697,45698,45699,45700,45701,45702,45703,45704,45705,45706,45707,45708,45709,45710,45711,45712,45713,45714,45715,45716,45717,45718,45719,45720,45721,45722,45723,45724,45725,45726,45727,45728,45729,45730,45731,45732,45733,45734,45735,45736,45737,45738,45739,45740,45741,45742,45743,45744,45745,45746,45747,45748,45749,45750,45751,45752,45753,45754,45755,45756,45757,45758,45759,45760,45761,45762,45763,45764,45765,45766,45767,45768,45769,45770,45771,45772,45773,45774,45775,45776,45777,45778,45779,45780,45781,45782,45783,45784,45785,45786,45787,45788,45789,45790,45791,45792,45793,45794,45795,45796,45797,45798,45799,45800,45801,45802,45803,45804,45805,45806,45807,45808,45809,45810,45811,45812,45813,45814,45815,45816,45817,45818,45819,45820,45821,45822,45823,45824,45825,45826,45827,45828,45829,45830,45831,45832,45833,45834,45835,45836,45837,45838,45839,45840,45841,45842,45843,45844,45845,45846,45847,45848,45849,45850,45851,45852,45853,45854,45855,45856,45857,45858,45859,45860,45861,45862,45863,45864,45865,45866,45867,45868,45869,45870,45871,45872,45873,45874,45875,45876,45877,45878,45879,45880,45881,45882,45883,45884,45885,45886,45887,45888,45889,45890,45891,45892,45893,45894,45895,45896,45897,45898,45899,45900,45901,45902,45903,45904,45905,45906,45907,45908,45909,45910,45911,45912,45913,45914,45915,45916,45917,45918,45919,45920,45921,45922,45923,45924,45925,45926,45927,45928,45929,45930,45931,45932,45933,45934,45935,45936,45937,45938,45939,45940,45941,45942,45943,45944,45945,45946,45947,45948,45949,45950,45951,45952,45953,45954,45955,45956,45957,45958,45959,45960,45961,45962,45963,45964,45965,45966,45967,45968,45969,45970,45971,45972,45973,45974,45975,45976,45977,45978,45979,45980,45981,45982,45983,45984,45985,45986,45987,45988,45989,45990,45991,45992,45993,45994,45995,45996,45997,45998,45999,46000,46001,46002,46003,46004,46005,46006,46007,46008,46009,46010,46011,46012,46013,46014,46015,46016,46017,46018,46019,46020,46021,46022,46023,46024,46025,46026,46027,46028,46029,46030,46031,46032,46033,46034,46035,46036,46037,46038,46039,46040,46041,46042,46043,46044,46045,46046,46047,46048,46049,46050,46051,46052,46053,46054,46055,46056,46057,46058,46059,46060,46061,46062,46063,46064,46065,46066,46067,46068,46069,46070,46071,46072,46073,46074,46075,46076,46077,46078,46079,46080,46081,46082,46083,46084,46085,46086,46087,46088,46089,46090,46091,46092,46093,46094,46095,46096,46097,46098,46099,46100,46101,46102,46103,46104,46105,46106,46107,46108,46109,46110,46111,46112,46113,46114,46115,46116,46117,46118,46119,46120,46121,46122,46123,46124,46125,46126,46127,46128,46129,46130,46131,46132,46133,46134,46135,46136,46137,46138,46139,46140,46141,46142,46143,46144,46145,46146,46147,46148,46149,46150,46151,46152,46153,46154,46155,46156,46157,46158,46159,46160,46161,46162,46163,46164,46165,46166,46167,46168,46169,46170,46171,46172,46173,46174,46175,46176,46177,46178,46179,46180,46181,46182,46183,46184,46185,46186,46187,46188,46189,46190,46191,46192,46193,46194,46195,46196,46197,46198,46199,46200,46201,46202,46203,46204,46205,46206,46207,46208,46209,46210,46211,46212,46213,46214,46215,46216,46217,46218,46219,46220,46221,46222,46223,46224,46225,46226,46227,46228,46229,46230,46231,46232,46233,46234,46235,46236,46237,46238,46239,46240,46241,46242,46243,46244,46245,46246,46247,46248,46249,46250,46251,46252,46253,46254,46255,46256,46257,46258,46259,46260,46261,46262,46263,46264,46265,46266,46267,46268,46269,46270,46271,46272,46273,46274,46275,46276,46277,46278,46279,46280,46281,46282,46283,46284,46285,46286,46287,46288,46289,46290,46291,46292,46293,46294,46295,46296,46297,46298,46299,46300,46301,46302,46303,46304,46305,46306,46307,46308,46309,46310,46311,46312,46313,46314,46315,46316,46317,46318,46319,46320,46321,46322,46323,46324,46325,46326,46327,46328,46329,46330,46331,46332,46333,46334,46335,46336,46337,46338,46339,46340,46341,46342,46343,46344,46345,46346,46347,46348,46349,46350,46351,46352,46353,46354,46355,46356,46357,46358,46359,46360,46361,46362,46363,46364,46365,46366,46367,46368,46369,46370,46371,46372,46373,46374,46375,46376,46377,46378,46379,46380,46381,46382,46383,46384,46385,46386,46387,46388,46389,46390,46391,46392,46393,46394,46395,46396,46397,46398,46399,46400,46401,46402,46403,46404,46405,46406,46407,46408,46409,46410,46411,46412,46413,46414,46415,46416,46417,46418,46419,46420,46421,46422,46423,46424,46425,46426,46427,46428,46429,46430,46431,46432,46433,46434,46435,46436,46437,46438,46439,46440,46441,46442,46443,46444,46445,46446,46447,46448,46449,46450,46451,46452,46453,46454,46455,46456,46457,46458,46459,46460,46461,46462,46463,46464,46465,46466,46467,46468,46469,46470,46471,46472,46473,46474,46475,46476,46477,46478,46479,46480,46481,46482,46483,46484,46485,46486,46487,46488,46489,46490,46491,46492,46493,46494,46495,46496,46497,46498,46499,46500,46501,46502,46503,46504,46505,46506,46507,46508,46509,46510,46511,46512,46513,46514,46515,46516,46517,46518,46519,46520,46521,46522,46523,46524,46525,46526,46527,46528,46529,46530,46531,46532,46533,46534,46535,46536,46537,46538,46539,46540,46541,46542,46543,46544,46545,46546,46547,46548,46549,46550,46551,46552,46553,46554,46555,46556,46557,46558,46559,46560,46561,46562,46563,46564,46565,46566,46567,46568,46569,46570,46571,46572,46573,46574,46575,46576,46577,46578,46579,46580,46581,46582,46583,46584,46585,46586,46587,46588,46589,46590,46591,46592,46593,46594,46595,46596,46597,46598,46599,46600,46601,46602,46603,46604,46605,46606,46607,46608,46609,46610,46611,46612,46613,46614,46615,46616,46617,46618,46619,46620,46621,46622,46623,46624,46625,46626,46627,46628,46629,46630,46631,46632,46633,46634,46635,46636,46637,46638,46639,46640,46641,46642,46643,46644,46645,46646,46647,46648,46649,46650,46651,46652,46653,46654,46655,46656,46657,46658,46659,46660,46661,46662,46663,46664,46665,46666,46667,46668,46669,46670,46671,46672,46673,46674,46675,46676,46677,46678,46679,46680,46681,46682,46683,46684,46685,46686,46687,46688,46689,46690,46691,46692,46693,46694,46695,46696,46697,46698,46699,46700,46701,46702,46703,46704,46705,46706,46707,46708,46709,46710,46711,46712,46713,46714,46715,46716,46717,46718,46719,46720,46721,46722,46723,46724,46725,46726,46727,46728,46729,46730,46731,46732,46733,46734,46735,46736,46737,46738,46739,46740,46741,46742,46743,46744,46745,46746,46747,46748,46749,46750,46751,46752,46753,46754,46755,46756,46757,46758,46759,46760,46761,46762,46763,46764,46765,46766,46767,46768,46769,46770,46771,46772,46773,46774,46775,46776,46777,46778,46779,46780,46781,46782,46783,46784,46785,46786,46787,46788,46789,46790,46791,46792,46793,46794,46795,46796,46797,46798,46799,46800,46801,46802,46803,46804,46805,46806,46807,46808,46809,46810,46811,46812,46813,46814,46815,46816,46817,46818,46819,46820,46821,46822,46823,46824,46825,46826,46827,46828,46829,46830,46831,46832,46833,46834,46835,46836,46837,46838,46839,46840,46841,46842,46843,46844,46845,46846,46847,46848,46849,46850,46851,46852,46853,46854,46855,46856,46857,46858,46859,46860,46861,46862,46863,46864,46865,46866,46867,46868,46869,46870,46871,46872,46873,46874,46875,46876,46877,46878,46879,46880,46881,46882,46883,46884,46885,46886,46887,46888,46889,46890,46891,46892,46893,46894,46895,46896,46897,46898,46899,46900,46901,46902,46903,46904,46905,46906,46907,46908,46909,46910,46911,46912,46913,46914,46915,46916,46917,46918,46919,46920,46921,46922,46923,46924,46925,46926,46927,46928,46929,46930,46931,46932,46933,46934,46935,46936,46937,46938,46939,46940,46941,46942,46943,46944,46945,46946,46947,46948,46949,46950,46951,46952,46953,46954,46955,46956,46957,46958,46959,46960,46961,46962,46963,46964,46965,46966,46967,46968,46969,46970,46971,46972,46973,46974,46975,46976,46977,46978,46979,46980,46981,46982,46983,46984,46985,46986,46987,46988,46989,46990,46991,46992,46993,46994,46995,46996,46997,46998,46999,47000,47001,47002,47003,47004,47005,47006,47007,47008,47009,47010,47011,47012,47013,47014,47015,47016,47017,47018,47019,47020,47021,47022,47023,47024,47025,47026,47027,47028,47029,47030,47031,47032,47033,47034,47035,47036,47037,47038,47039,47040,47041,47042,47043,47044,47045,47046,47047,47048,47049,47050,47051,47052,47053,47054,47055,47056,47057,47058,47059,47060,47061,47062,47063,47064,47065,47066,47067,47068,47069,47070,47071,47072,47073,47074,47075,47076,47077,47078,47079,47080,47081,47082,47083,47084,47085,47086,47087,47088,47089,47090,47091,47092,47093,47094,47095,47096,47097,47098,47099,47100,47101,47102,47103,47104,47105,47106,47107,47108,47109,47110,47111,47112,47113,47114,47115,47116,47117,47118,47119,47120,47121,47122,47123,47124,47125,47126,47127,47128,47129,47130,47131,47132,47133,47134,47135,47136,47137,47138,47139,47140,47141,47142,47143,47144,47145,47146,47147,47148,47149,47150,47151,47152,47153,47154,47155,47156,47157,47158,47159,47160,47161,47162,47163,47164,47165,47166,47167,47168,47169,47170,47171,47172,47173,47174,47175,47176,47177,47178,47179,47180,47181,47182,47183,47184,47185,47186,47187,47188,47189,47190,47191,47192,47193,47194,47195,47196,47197,47198,47199,47200,47201,47202,47203,47204,47205,47206,47207,47208,47209,47210,47211,47212,47213,47214,47215,47216,47217,47218,47219,47220,47221,47222,47223,47224,47225,47226,47227,47228,47229,47230,47231,47232,47233,47234,47235,47236,47237,47238,47239,47240,47241,47242,47243,47244,47245,47246,47247,47248,47249,47250,47251,47252,47253,47254,47255,47256,47257,47258,47259,47260,47261,47262,47263,47264,47265,47266,47267,47268,47269,47270,47271,47272,47273,47274,47275,47276,47277,47278,47279,47280,47281,47282,47283,47284,47285,47286,47287,47288,47289,47290,47291,47292,47293,47294,47295,47296,47297,47298,47299,47300,47301,47302,47303,47304,47305,47306,47307,47308,47309,47310,47311,47312,47313,47314,47315,47316,47317,47318,47319,47320,47321,47322,47323,47324,47325,47326,47327,47328,47329,47330,47331,47332,47333,47334,47335,47336,47337,47338,47339,47340,47341,47342,47343,47344,47345,47346,47347,47348,47349,47350,47351,47352,47353,47354,47355,47356,47357,47358,47359,47360,47361,47362,47363,47364,47365,47366,47367,47368,47369,47370,47371,47372,47373,47374,47375,47376,47377,47378,47379,47380,47381,47382,47383,47384,47385,47386,47387,47388,47389,47390,47391,47392,47393,47394,47395,47396,47397,47398,47399,47400,47401,47402,47403,47404,47405,47406,47407,47408,47409,47410,47411,47412,47413,47414,47415,47416,47417,47418,47419,47420,47421,47422,47423,47424,47425,47426,47427,47428,47429,47430,47431,47432,47433,47434,47435,47436,47437,47438,47439,47440,47441,47442,47443,47444,47445,47446,47447,47448,47449,47450,47451,47452,47453,47454,47455,47456,47457,47458,47459,47460,47461,47462,47463,47464,47465,47466,47467,47468,47469,47470,47471,47472,47473,47474,47475,47476,47477,47478,47479,47480,47481,47482,47483,47484,47485,47486,47487,47488,47489,47490,47491,47492,47493,47494,47495,47496,47497,47498,47499,47500,47501,47502,47503,47504,47505,47506,47507,47508,47509,47510,47511,47512,47513,47514,47515,47516,47517,47518,47519,47520,47521,47522,47523,47524,47525,47526,47527,47528,47529,47530,47531,47532,47533,47534,47535,47536,47537,47538,47539,47540,47541,47542,47543,47544,47545,47546,47547,47548,47549,47550,47551,47552,47553,47554,47555,47556,47557,47558,47559,47560,47561,47562,47563,47564,47565,47566,47567,47568,47569,47570,47571,47572,47573,47574,47575,47576,47577,47578,47579,47580,47581,47582,47583,47584,47585,47586,47587,47588,47589,47590,47591,47592,47593,47594,47595,47596,47597,47598,47599,47600,47601,47602,47603,47604,47605,47606,47607,47608,47609,47610,47611,47612,47613,47614,47615,47616,47617,47618,47619,47620,47621,47622,47623,47624,47625,47626,47627,47628,47629,47630,47631,47632,47633,47634,47635,47636,47637,47638,47639,47640,47641,47642,47643,47644,47645,47646,47647,47648,47649,47650,47651,47652,47653,47654,47655,47656,47657,47658,47659,47660,47661,47662,47663,47664,47665,47666,47667,47668,47669,47670,47671,47672,47673,47674,47675,47676,47677,47678,47679,47680,47681,47682,47683,47684,47685,47686,47687,47688,47689,47690,47691,47692,47693,47694,47695,47696,47697,47698,47699,47700,47701,47702,47703,47704,47705,47706,47707,47708,47709,47710,47711,47712,47713,47714,47715,47716,47717,47718,47719,47720,47721,47722,47723,47724,47725,47726,47727,47728,47729,47730,47731,47732,47733,47734,47735,47736,47737,47738,47739,47740,47741,47742,47743,47744,47745,47746,47747,47748,47749,47750,47751,47752,47753,47754,47755,47756,47757,47758,47759,47760,47761,47762,47763,47764,47765,47766,47767,47768,47769,47770,47771,47772,47773,47774,47775,47776,47777,47778,47779,47780,47781,47782,47783,47784,47785,47786,47787,47788,47789,47790,47791,47792,47793,47794,47795,47796,47797,47798,47799,47800,47801,47802,47803,47804,47805,47806,47807,47808,47809,47810,47811,47812,47813,47814,47815,47816,47817,47818,47819,47820,47821,47822,47823,47824,47825,47826,47827,47828,47829,47830,47831,47832,47833,47834,47835,47836,47837,47838,47839,47840,47841,47842,47843,47844,47845,47846,47847,47848,47849,47850,47851,47852,47853,47854,47855,47856,47857,47858,47859,47860,47861,47862,47863,47864,47865,47866,47867,47868,47869,47870,47871,47872,47873,47874,47875,47876,47877,47878,47879,47880,47881,47882,47883,47884,47885,47886,47887,47888,47889,47890,47891,47892,47893,47894,47895,47896,47897,47898,47899,47900,47901,47902,47903,47904,47905,47906,47907,47908,47909,47910,47911,47912,47913,47914,47915,47916,47917,47918,47919,47920,47921,47922,47923,47924,47925,47926,47927,47928,47929,47930,47931,47932,47933,47934,47935,47936,47937,47938,47939,47940,47941,47942,47943,47944,47945,47946,47947,47948,47949,47950,47951,47952,47953,47954,47955,47956,47957,47958,47959,47960,47961,47962,47963,47964,47965,47966,47967,47968,47969,47970,47971,47972,47973,47974,47975,47976,47977,47978,47979,47980,47981,47982,47983,47984,47985,47986,47987,47988,47989,47990,47991,47992,47993,47994,47995,47996,47997,47998,47999,48000,48001,48002,48003,48004,48005,48006,48007,48008,48009,48010,48011,48012,48013,48014,48015,48016,48017,48018,48019,48020,48021,48022,48023,48024,48025,48026,48027,48028,48029,48030,48031,48032,48033,48034,48035,48036,48037,48038,48039,48040,48041,48042,48043,48044,48045,48046,48047,48048,48049,48050,48051,48052,48053,48054,48055,48056,48057,48058,48059,48060,48061,48062,48063,48064,48065,48066,48067,48068,48069,48070,48071,48072,48073,48074,48075,48076,48077,48078,48079,48080,48081,48082,48083,48084,48085,48086,48087,48088,48089,48090,48091,48092,48093,48094,48095,48096,48097,48098,48099,48100,48101,48102,48103,48104,48105,48106,48107,48108,48109,48110,48111,48112,48113,48114,48115,48116,48117,48118,48119,48120,48121,48122,48123,48124,48125,48126,48127,48128,48129,48130,48131,48132,48133,48134,48135,48136,48137,48138,48139,48140,48141,48142,48143,48144,48145,48146,48147,48148,48149,48150,48151,48152,48153,48154,48155,48156,48157,48158,48159,48160,48161,48162,48163,48164,48165,48166,48167,48168,48169,48170,48171,48172,48173,48174,48175,48176,48177,48178,48179,48180,48181,48182,48183,48184,48185,48186,48187,48188,48189,48190,48191,48192,48193,48194,48195,48196,48197,48198,48199,48200,48201,48202,48203,48204,48205,48206,48207,48208,48209,48210,48211,48212,48213,48214,48215,48216,48217,48218,48219,48220,48221,48222,48223,48224,48225,48226,48227,48228,48229,48230,48231,48232,48233,48234,48235,48236,48237,48238,48239,48240,48241,48242,48243,48244,48245,48246,48247,48248,48249,48250,48251,48252,48253,48254,48255,48256,48257,48258,48259,48260,48261,48262,48263,48264,48265,48266,48267,48268,48269,48270,48271,48272,48273,48274,48275,48276,48277,48278,48279,48280,48281,48282,48283,48284,48285,48286,48287,48288,48289,48290,48291,48292,48293,48294,48295,48296,48297,48298,48299,48300,48301,48302,48303,48304,48305,48306,48307,48308,48309,48310,48311,48312,48313,48314,48315,48316,48317,48318,48319,48320,48321,48322,48323,48324,48325,48326,48327,48328,48329,48330,48331,48332,48333,48334,48335,48336,48337,48338,48339,48340,48341,48342,48343,48344,48345,48346,48347,48348,48349,48350,48351,48352,48353,48354,48355,48356,48357,48358,48359,48360,48361,48362,48363,48364,48365,48366,48367,48368,48369,48370,48371,48372,48373,48374,48375,48376,48377,48378,48379,48380,48381,48382,48383,48384,48385,48386,48387,48388,48389,48390,48391,48392,48393,48394,48395,48396,48397,48398,48399,48400,48401,48402,48403,48404,48405,48406,48407,48408,48409,48410,48411,48412,48413,48414,48415,48416,48417,48418,48419,48420,48421,48422,48423,48424,48425,48426,48427,48428,48429,48430,48431,48432,48433,48434,48435,48436,48437,48438,48439,48440,48441,48442,48443,48444,48445,48446,48447,48448,48449,48450,48451,48452,48453,48454,48455,48456,48457,48458,48459,48460,48461,48462,48463,48464,48465,48466,48467,48468,48469,48470,48471,48472,48473,48474,48475,48476,48477,48478,48479,48480,48481,48482,48483,48484,48485,48486,48487,48488,48489,48490,48491,48492,48493,48494,48495,48496,48497,48498,48499,48500,48501,48502,48503,48504,48505,48506,48507,48508,48509,48510,48511,48512,48513,48514,48515,48516,48517,48518,48519,48520,48521,48522,48523,48524,48525,48526,48527,48528,48529,48530,48531,48532,48533,48534,48535,48536,48537,48538,48539,48540,48541,48542,48543,48544,48545,48546,48547,48548,48549,48550,48551,48552,48553,48554,48555,48556,48557,48558,48559,48560,48561,48562,48563,48564,48565,48566,48567,48568,48569,48570,48571,48572,48573,48574,48575,48576,48577,48578,48579,48580,48581,48582,48583,48584,48585,48586,48587,48588,48589,48590,48591,48592,48593,48594,48595,48596,48597,48598,48599,48600,48601,48602,48603,48604,48605,48606,48607,48608,48609,48610,48611,48612,48613,48614,48615,48616,48617,48618,48619,48620,48621,48622,48623,48624,48625,48626,48627,48628,48629,48630,48631,48632,48633,48634,48635,48636,48637,48638,48639,48640,48641,48642,48643,48644,48645,48646,48647,48648,48649,48650,48651,48652,48653,48654,48655,48656,48657,48658,48659,48660,48661,48662,48663,48664,48665,48666,48667,48668,48669,48670,48671,48672,48673,48674,48675,48676,48677,48678,48679,48680,48681,48682,48683,48684,48685,48686,48687,48688,48689,48690,48691,48692,48693,48694,48695,48696,48697,48698,48699,48700,48701,48702,48703,48704,48705,48706,48707,48708,48709,48710,48711,48712,48713,48714,48715,48716,48717,48718,48719,48720,48721,48722,48723,48724,48725,48726,48727,48728,48729,48730,48731,48732,48733,48734,48735,48736,48737,48738,48739,48740,48741,48742,48743,48744,48745,48746,48747,48748,48749,48750,48751,48752,48753,48754,48755,48756,48757,48758,48759,48760,48761,48762,48763,48764,48765,48766,48767,48768,48769,48770,48771,48772,48773,48774,48775,48776,48777,48778,48779,48780,48781,48782,48783,48784,48785,48786,48787,48788,48789,48790,48791,48792,48793,48794,48795,48796,48797,48798,48799,48800,48801,48802,48803,48804,48805,48806,48807,48808,48809,48810,48811,48812,48813,48814,48815,48816,48817,48818,48819,48820,48821,48822,48823,48824,48825,48826,48827,48828,48829,48830,48831,48832,48833,48834,48835,48836,48837,48838,48839,48840,48841,48842,48843,48844,48845,48846,48847,48848,48849,48850,48851,48852,48853,48854,48855,48856,48857,48858,48859,48860,48861,48862,48863,48864,48865,48866,48867,48868,48869,48870,48871,48872,48873,48874,48875,48876,48877,48878,48879,48880,48881,48882,48883,48884,48885,48886,48887,48888,48889,48890,48891,48892,48893,48894,48895,48896,48897,48898,48899,48900,48901,48902,48903,48904,48905,48906,48907,48908,48909,48910,48911,48912,48913,48914,48915,48916,48917,48918,48919,48920,48921,48922,48923,48924,48925,48926,48927,48928,48929,48930,48931,48932,48933,48934,48935,48936,48937,48938,48939,48940,48941,48942,48943,48944,48945,48946,48947,48948,48949,48950,48951,48952,48953,48954,48955,48956,48957,48958,48959,48960,48961,48962,48963,48964,48965,48966,48967,48968,48969,48970,48971,48972,48973,48974,48975,48976,48977,48978,48979,48980,48981,48982,48983,48984,48985,48986,48987,48988,48989,48990,48991,48992,48993,48994,48995,48996,48997,48998,48999,49000,49001,49002,49003,49004,49005,49006,49007,49008,49009,49010,49011,49012,49013,49014,49015,49016,49017,49018,49019,49020,49021,49022,49023,49024,49025,49026,49027,49028,49029,49030,49031,49032,49033,49034,49035,49036,49037,49038,49039,49040,49041,49042,49043,49044,49045,49046,49047,49048,49049,49050,49051,49052,49053,49054,49055,49056,49057,49058,49059,49060,49061,49062,49063,49064,49065,49066,49067,49068,49069,49070,49071,49072,49073,49074,49075,49076,49077,49078,49079,49080,49081,49082,49083,49084,49085,49086,49087,49088,49089,49090,49091,49092,49093,49094,49095,49096,49097,49098,49099,49100,49101,49102,49103,49104,49105,49106,49107,49108,49109,49110,49111,49112,49113,49114,49115,49116,49117,49118,49119,49120,49121,49122,49123,49124,49125,49126,49127,49128,49129,49130,49131,49132,49133,49134,49135,49136,49137,49138,49139,49140,49141,49142,49143,49144,49145,49146,49147,49148,49149,49150,49151,49152,49153,49154,49155,49156,49157,49158,49159,49160,49161,49162,49163,49164,49165,49166,49167,49168,49169,49170,49171,49172,49173,49174,49175,49176,49177,49178,49179,49180,49181,49182,49183,49184,49185,49186,49187,49188,49189,49190,49191,49192,49193,49194,49195,49196,49197,49198,49199,49200,49201,49202,49203,49204,49205,49206,49207,49208,49209,49210,49211,49212,49213,49214,49215,49216,49217,49218,49219,49220,49221,49222,49223,49224,49225,49226,49227,49228,49229,49230,49231,49232,49233,49234,49235,49236,49237,49238,49239,49240,49241,49242,49243,49244,49245,49246,49247,49248,49249,49250,49251,49252,49253,49254,49255,49256,49257,49258,49259,49260,49261,49262,49263,49264,49265,49266,49267,49268,49269,49270,49271,49272,49273,49274,49275,49276,49277,49278,49279,49280,49281,49282,49283,49284,49285,49286,49287,49288,49289,49290,49291,49292,49293,49294,49295,49296,49297,49298,49299,49300,49301,49302,49303,49304,49305,49306,49307,49308,49309,49310,49311,49312,49313,49314,49315,49316,49317,49318,49319,49320,49321,49322,49323,49324,49325,49326,49327,49328,49329,49330,49331,49332,49333,49334,49335,49336,49337,49338,49339,49340,49341,49342,49343,49344,49345,49346,49347,49348,49349,49350,49351,49352,49353,49354,49355,49356,49357,49358,49359,49360,49361,49362,49363,49364,49365,49366,49367,49368,49369,49370,49371,49372,49373,49374,49375,49376,49377,49378,49379,49380,49381,49382,49383,49384,49385,49386,49387,49388,49389,49390,49391,49392,49393,49394,49395,49396,49397,49398,49399,49400,49401,49402,49403,49404,49405,49406,49407,49408,49409,49410,49411,49412,49413,49414,49415,49416,49417,49418,49419,49420,49421,49422,49423,49424,49425,49426,49427,49428,49429,49430,49431,49432,49433,49434,49435,49436,49437,49438,49439,49440,49441,49442,49443,49444,49445,49446,49447,49448,49449,49450,49451,49452,49453,49454,49455,49456,49457,49458,49459,49460,49461,49462,49463,49464,49465,49466,49467,49468,49469,49470,49471,49472,49473,49474,49475,49476,49477,49478,49479,49480,49481,49482,49483,49484,49485,49486,49487,49488,49489,49490,49491,49492,49493,49494,49495,49496,49497,49498,49499,49500,49501,49502,49503,49504,49505,49506,49507,49508,49509,49510,49511,49512,49513,49514,49515,49516,49517,49518,49519,49520,49521,49522,49523,49524,49525,49526,49527,49528,49529,49530,49531,49532,49533,49534,49535,49536,49537,49538,49539,49540,49541,49542,49543,49544,49545,49546,49547,49548,49549,49550,49551,49552,49553,49554,49555,49556,49557,49558,49559,49560,49561,49562,49563,49564,49565,49566,49567,49568,49569,49570,49571,49572,49573,49574,49575,49576,49577,49578,49579,49580,49581,49582,49583,49584,49585,49586,49587,49588,49589,49590,49591,49592,49593,49594,49595,49596,49597,49598,49599,49600,49601,49602,49603,49604,49605,49606,49607,49608,49609,49610,49611,49612,49613,49614,49615,49616,49617,49618,49619,49620,49621,49622,49623,49624,49625,49626,49627,49628,49629,49630,49631,49632,49633,49634,49635,49636,49637,49638,49639,49640,49641,49642,49643,49644,49645,49646,49647,49648,49649,49650,49651,49652,49653,49654,49655,49656,49657,49658,49659,49660,49661,49662,49663,49664,49665,49666,49667,49668,49669,49670,49671,49672,49673,49674,49675,49676,49677,49678,49679,49680,49681,49682,49683,49684,49685,49686,49687,49688,49689,49690,49691,49692,49693,49694,49695,49696,49697,49698,49699,49700,49701,49702,49703,49704,49705,49706,49707,49708,49709,49710,49711,49712,49713,49714,49715,49716,49717,49718,49719,49720,49721,49722,49723,49724,49725,49726,49727,49728,49729,49730,49731,49732,49733,49734,49735,49736,49737,49738,49739,49740,49741,49742,49743,49744,49745,49746,49747,49748,49749,49750,49751,49752,49753,49754,49755,49756,49757,49758,49759,49760,49761,49762,49763,49764,49765,49766,49767,49768,49769,49770,49771,49772,49773,49774,49775,49776,49777,49778,49779,49780,49781,49782,49783,49784,49785,49786,49787,49788,49789,49790,49791,49792,49793,49794,49795,49796,49797,49798,49799,49800,49801,49802,49803,49804,49805,49806,49807,49808,49809,49810,49811,49812,49813,49814,49815,49816,49817,49818,49819,49820,49821,49822,49823,49824,49825,49826,49827,49828,49829,49830,49831,49832,49833,49834,49835,49836,49837,49838,49839,49840,49841,49842,49843,49844,49845,49846,49847,49848,49849,49850,49851,49852,49853,49854,49855,49856,49857,49858,49859,49860,49861,49862,49863,49864,49865,49866,49867,49868,49869,49870,49871,49872,49873,49874,49875,49876,49877,49878,49879,49880,49881,49882,49883,49884,49885,49886,49887,49888,49889,49890,49891,49892,49893,49894,49895,49896,49897,49898,49899,49900,49901,49902,49903,49904,49905,49906,49907,49908,49909,49910,49911,49912,49913,49914,49915,49916,49917,49918,49919,49920,49921,49922,49923,49924,49925,49926,49927,49928,49929,49930,49931,49932,49933,49934,49935,49936,49937,49938,49939,49940,49941,49942,49943,49944,49945,49946,49947,49948,49949,49950,49951,49952,49953,49954,49955,49956,49957,49958,49959,49960,49961,49962,49963,49964,49965,49966,49967,49968,49969,49970,49971,49972,49973,49974,49975,49976,49977,49978,49979,49980,49981,49982,49983,49984,49985,49986,49987,49988,49989,49990,49991,49992,49993,49994,49995,49996,49997,49998,49999,50000,50001,50002,50003,50004,50005,50006,50007,50008,50009,50010,50011,50012,50013,50014,50015,50016,50017,50018,50019,50020,50021,50022,50023,50024,50025,50026,50027,50028,50029,50030,50031,50032,50033,50034,50035,50036,50037,50038,50039,50040,50041,50042,50043,50044,50045,50046,50047,50048,50049,50050,50051,50052,50053,50054,50055,50056,50057,50058,50059,50060,50061,50062,50063,50064,50065,50066,50067,50068,50069,50070,50071,50072,50073,50074,50075,50076,50077,50078,50079,50080,50081,50082,50083,50084,50085,50086,50087,50088,50089,50090,50091,50092,50093,50094,50095,50096,50097,50098,50099,50100,50101,50102,50103,50104,50105,50106,50107,50108,50109,50110,50111,50112,50113,50114,50115,50116,50117,50118,50119,50120,50121,50122,50123,50124,50125,50126,50127,50128,50129,50130,50131,50132,50133,50134,50135,50136,50137,50138,50139,50140,50141,50142,50143,50144,50145,50146,50147,50148,50149,50150,50151,50152,50153,50154,50155,50156,50157,50158,50159,50160,50161,50162,50163,50164,50165,50166,50167,50168,50169,50170,50171,50172,50173,50174,50175,50176,50177,50178,50179,50180,50181,50182,50183,50184,50185,50186,50187,50188,50189,50190,50191,50192,50193,50194,50195,50196,50197,50198,50199,50200,50201,50202,50203,50204,50205,50206,50207,50208,50209,50210,50211,50212,50213,50214,50215,50216,50217,50218,50219,50220,50221,50222,50223,50224,50225,50226,50227,50228,50229,50230,50231,50232,50233,50234,50235,50236,50237,50238,50239,50240,50241,50242,50243,50244,50245,50246,50247,50248,50249,50250,50251,50252,50253,50254,50255,50256,50257,50258,50259,50260,50261,50262,50263,50264,50265,50266,50267,50268,50269,50270,50271,50272,50273,50274,50275,50276,50277,50278,50279,50280,50281,50282,50283,50284,50285,50286,50287,50288,50289,50290,50291,50292,50293,50294,50295,50296,50297,50298,50299,50300,50301,50302,50303,50304,50305,50306,50307,50308,50309,50310,50311,50312,50313,50314,50315,50316,50317,50318,50319,50320,50321,50322,50323,50324,50325,50326,50327,50328,50329,50330,50331,50332,50333,50334,50335,50336,50337,50338,50339,50340,50341,50342,50343,50344,50345,50346,50347,50348,50349,50350,50351,50352,50353,50354,50355,50356,50357,50358,50359,50360,50361,50362,50363,50364,50365,50366,50367,50368,50369,50370,50371,50372,50373,50374,50375,50376,50377,50378,50379,50380,50381,50382,50383,50384,50385,50386,50387,50388,50389,50390,50391,50392,50393,50394,50395,50396,50397,50398,50399,50400,50401,50402,50403,50404,50405,50406,50407,50408,50409,50410,50411,50412,50413,50414,50415,50416,50417,50418,50419,50420,50421,50422,50423,50424,50425,50426,50427,50428,50429,50430,50431,50432,50433,50434,50435,50436,50437,50438,50439,50440,50441,50442,50443,50444,50445,50446,50447,50448,50449,50450,50451,50452,50453,50454,50455,50456,50457,50458,50459,50460,50461,50462,50463,50464,50465,50466,50467,50468,50469,50470,50471,50472,50473,50474,50475,50476,50477,50478,50479,50480,50481,50482,50483,50484,50485,50486,50487,50488,50489,50490,50491,50492,50493,50494,50495,50496,50497,50498,50499,50500,50501,50502,50503,50504,50505,50506,50507,50508,50509,50510,50511,50512,50513,50514,50515,50516,50517,50518,50519,50520,50521,50522,50523,50524,50525,50526,50527,50528,50529,50530,50531,50532,50533,50534,50535,50536,50537,50538,50539,50540,50541,50542,50543,50544,50545,50546,50547,50548,50549,50550,50551,50552,50553,50554,50555,50556,50557,50558,50559,50560,50561,50562,50563,50564,50565,50566,50567,50568,50569,50570,50571,50572,50573,50574,50575,50576,50577,50578,50579,50580,50581,50582,50583,50584,50585,50586,50587,50588,50589,50590,50591,50592,50593,50594,50595,50596,50597,50598,50599,50600,50601,50602,50603,50604,50605,50606,50607,50608,50609,50610,50611,50612,50613,50614,50615,50616,50617,50618,50619,50620,50621,50622,50623,50624,50625,50626,50627,50628,50629,50630,50631,50632,50633,50634,50635,50636,50637,50638,50639,50640,50641,50642,50643,50644,50645,50646,50647,50648,50649,50650,50651,50652,50653,50654,50655,50656,50657,50658,50659,50660,50661,50662,50663,50664,50665,50666,50667,50668,50669,50670,50671,50672,50673,50674,50675,50676,50677,50678,50679,50680,50681,50682,50683,50684,50685,50686,50687,50688,50689,50690,50691,50692,50693,50694,50695,50696,50697,50698,50699,50700,50701,50702,50703,50704,50705,50706,50707,50708,50709,50710,50711,50712,50713,50714,50715,50716,50717,50718,50719,50720,50721,50722,50723,50724,50725,50726,50727,50728,50729,50730,50731,50732,50733,50734,50735,50736,50737,50738,50739,50740,50741,50742,50743,50744,50745,50746,50747,50748,50749,50750,50751,50752,50753,50754,50755,50756,50757,50758,50759,50760,50761,50762,50763,50764,50765,50766,50767,50768,50769,50770,50771,50772,50773,50774,50775,50776,50777,50778,50779,50780,50781,50782,50783,50784,50785,50786,50787,50788,50789,50790,50791,50792,50793,50794,50795,50796,50797,50798,50799,50800,50801,50802,50803,50804,50805,50806,50807,50808,50809,50810,50811,50812,50813,50814,50815,50816,50817,50818,50819,50820,50821,50822,50823,50824,50825,50826,50827,50828,50829,50830,50831,50832,50833,50834,50835,50836,50837,50838,50839,50840,50841,50842,50843,50844,50845,50846,50847,50848,50849,50850,50851,50852,50853,50854,50855,50856,50857,50858,50859,50860,50861,50862,50863,50864,50865,50866,50867,50868,50869,50870,50871,50872,50873,50874,50875,50876,50877,50878,50879,50880,50881,50882,50883,50884,50885,50886,50887,50888,50889,50890,50891,50892,50893,50894,50895,50896,50897,50898,50899,50900,50901,50902,50903,50904,50905,50906,50907,50908,50909,50910,50911,50912,50913,50914,50915,50916,50917,50918,50919,50920,50921,50922,50923,50924,50925,50926,50927,50928,50929,50930,50931,50932,50933,50934,50935,50936,50937,50938,50939,50940,50941,50942,50943,50944,50945,50946,50947,50948,50949,50950,50951,50952,50953,50954,50955,50956,50957,50958,50959,50960,50961,50962,50963,50964,50965,50966,50967,50968,50969,50970,50971,50972,50973,50974,50975,50976,50977,50978,50979,50980,50981,50982,50983,50984,50985,50986,50987,50988,50989,50990,50991,50992,50993,50994,50995,50996,50997,50998,50999,51000,51001,51002,51003,51004,51005,51006,51007,51008,51009,51010,51011,51012,51013,51014,51015,51016,51017,51018,51019,51020,51021,51022,51023,51024,51025,51026,51027,51028,51029,51030,51031,51032,51033,51034,51035,51036,51037,51038,51039,51040,51041,51042,51043,51044,51045,51046,51047,51048,51049,51050,51051,51052,51053,51054,51055,51056,51057,51058,51059,51060,51061,51062,51063,51064,51065,51066,51067,51068,51069,51070,51071,51072,51073,51074,51075,51076,51077,51078,51079,51080,51081,51082,51083,51084,51085,51086,51087,51088,51089,51090,51091,51092,51093,51094,51095,51096,51097,51098,51099,51100,51101,51102,51103,51104,51105,51106,51107,51108,51109,51110,51111,51112,51113,51114,51115,51116,51117,51118,51119,51120,51121,51122,51123,51124,51125,51126,51127,51128,51129,51130,51131,51132,51133,51134,51135,51136,51137,51138,51139,51140,51141,51142,51143,51144,51145,51146,51147,51148,51149,51150,51151,51152,51153,51154,51155,51156,51157,51158,51159,51160,51161,51162,51163,51164,51165,51166,51167,51168,51169,51170,51171,51172,51173,51174,51175,51176,51177,51178,51179,51180,51181,51182,51183,51184,51185,51186,51187,51188,51189,51190,51191,51192,51193,51194,51195,51196,51197,51198,51199,51200,51201,51202,51203,51204,51205,51206,51207,51208,51209,51210,51211,51212,51213,51214,51215,51216,51217,51218,51219,51220,51221,51222,51223,51224,51225,51226,51227,51228,51229,51230,51231,51232,51233,51234,51235,51236,51237,51238,51239,51240,51241,51242,51243,51244,51245,51246,51247,51248,51249,51250,51251,51252,51253,51254,51255,51256,51257,51258,51259,51260,51261,51262,51263,51264,51265,51266,51267,51268,51269,51270,51271,51272,51273,51274,51275,51276,51277,51278,51279,51280,51281,51282,51283,51284,51285,51286,51287,51288,51289,51290,51291,51292,51293,51294,51295,51296,51297,51298,51299,51300,51301,51302,51303,51304,51305,51306,51307,51308,51309,51310,51311,51312,51313,51314,51315,51316,51317,51318,51319,51320,51321,51322,51323,51324,51325,51326,51327,51328,51329,51330,51331,51332,51333,51334,51335,51336,51337,51338,51339,51340,51341,51342,51343,51344,51345,51346,51347,51348,51349,51350,51351,51352,51353,51354,51355,51356,51357,51358,51359,51360,51361,51362,51363,51364,51365,51366,51367,51368,51369,51370,51371,51372,51373,51374,51375,51376,51377,51378,51379,51380,51381,51382,51383,51384,51385,51386,51387,51388,51389,51390,51391,51392,51393,51394,51395,51396,51397,51398,51399,51400,51401,51402,51403,51404,51405,51406,51407,51408,51409,51410,51411,51412,51413,51414,51415,51416,51417,51418,51419,51420,51421,51422,51423,51424,51425,51426,51427,51428,51429,51430,51431,51432,51433,51434,51435,51436,51437,51438,51439,51440,51441,51442,51443,51444,51445,51446,51447,51448,51449,51450,51451,51452,51453,51454,51455,51456,51457,51458,51459,51460,51461,51462,51463,51464,51465,51466,51467,51468,51469,51470,51471,51472,51473,51474,51475,51476,51477,51478,51479,51480,51481,51482,51483,51484,51485,51486,51487,51488,51489,51490,51491,51492,51493,51494,51495,51496,51497,51498,51499,51500,51501,51502,51503,51504,51505,51506,51507,51508,51509,51510,51511,51512,51513,51514,51515,51516,51517,51518,51519,51520,51521,51522,51523,51524,51525,51526,51527,51528,51529,51530,51531,51532,51533,51534,51535,51536,51537,51538,51539,51540,51541,51542,51543,51544,51545,51546,51547,51548,51549,51550,51551,51552,51553,51554,51555,51556,51557,51558,51559,51560,51561,51562,51563,51564,51565,51566,51567,51568,51569,51570,51571,51572,51573,51574,51575,51576,51577,51578,51579,51580,51581,51582,51583,51584,51585,51586,51587,51588,51589,51590,51591,51592,51593,51594,51595,51596,51597,51598,51599,51600,51601,51602,51603,51604,51605,51606,51607,51608,51609,51610,51611,51612,51613,51614,51615,51616,51617,51618,51619,51620,51621,51622,51623,51624,51625,51626,51627,51628,51629,51630,51631,51632,51633,51634,51635,51636,51637,51638,51639,51640,51641,51642,51643,51644,51645,51646,51647,51648,51649,51650,51651,51652,51653,51654,51655,51656,51657,51658,51659,51660,51661,51662,51663,51664,51665,51666,51667,51668,51669,51670,51671,51672,51673,51674,51675,51676,51677,51678,51679,51680,51681,51682,51683,51684,51685,51686,51687,51688,51689,51690,51691,51692,51693,51694,51695,51696,51697,51698,51699,51700,51701,51702,51703,51704,51705,51706,51707,51708,51709,51710,51711,51712,51713,51714,51715,51716,51717,51718,51719,51720,51721,51722,51723,51724,51725,51726,51727,51728,51729,51730,51731,51732,51733,51734,51735,51736,51737,51738,51739,51740,51741,51742,51743,51744,51745,51746,51747,51748,51749,51750,51751,51752,51753,51754,51755,51756,51757,51758,51759,51760,51761,51762,51763,51764,51765,51766,51767,51768,51769,51770,51771,51772,51773,51774,51775,51776,51777,51778,51779,51780,51781,51782,51783,51784,51785,51786,51787,51788,51789,51790,51791,51792,51793,51794,51795,51796,51797,51798,51799,51800,51801,51802,51803,51804,51805,51806,51807,51808,51809,51810,51811,51812,51813,51814,51815,51816,51817,51818,51819,51820,51821,51822,51823,51824,51825,51826,51827,51828,51829,51830,51831,51832,51833,51834,51835,51836,51837,51838,51839,51840,51841,51842,51843,51844,51845,51846,51847,51848,51849,51850,51851,51852,51853,51854,51855,51856,51857,51858,51859,51860,51861,51862,51863,51864,51865,51866,51867,51868,51869,51870,51871,51872,51873,51874,51875,51876,51877,51878,51879,51880,51881,51882,51883,51884,51885,51886,51887,51888,51889,51890,51891,51892,51893,51894,51895,51896,51897,51898,51899,51900,51901,51902,51903,51904,51905,51906,51907,51908,51909,51910,51911,51912,51913,51914,51915,51916,51917,51918,51919,51920,51921,51922,51923,51924,51925,51926,51927,51928,51929,51930,51931,51932,51933,51934,51935,51936,51937,51938,51939,51940,51941,51942,51943,51944,51945,51946,51947,51948,51949,51950,51951,51952,51953,51954,51955,51956,51957,51958,51959,51960,51961,51962,51963,51964,51965,51966,51967,51968,51969,51970,51971,51972,51973,51974,51975,51976,51977,51978,51979,51980,51981,51982,51983,51984,51985,51986,51987,51988,51989,51990,51991,51992,51993,51994,51995,51996,51997,51998,51999,52000,52001,52002,52003,52004,52005,52006,52007,52008,52009,52010,52011,52012,52013,52014,52015,52016,52017,52018,52019,52020,52021,52022,52023,52024,52025,52026,52027,52028,52029,52030,52031,52032,52033,52034,52035,52036,52037,52038,52039,52040,52041,52042,52043,52044,52045,52046,52047,52048,52049,52050,52051,52052,52053,52054,52055,52056,52057,52058,52059,52060,52061,52062,52063,52064,52065,52066,52067,52068,52069,52070,52071,52072,52073,52074,52075,52076,52077,52078,52079,52080,52081,52082,52083,52084,52085,52086,52087,52088,52089,52090,52091,52092,52093,52094,52095,52096,52097,52098,52099,52100,52101,52102,52103,52104,52105,52106,52107,52108,52109,52110,52111,52112,52113,52114,52115,52116,52117,52118,52119,52120,52121,52122,52123,52124,52125,52126,52127,52128,52129,52130,52131,52132,52133,52134,52135,52136,52137,52138,52139,52140,52141,52142,52143,52144,52145,52146,52147,52148,52149,52150,52151,52152,52153,52154,52155,52156,52157,52158,52159,52160,52161,52162,52163,52164,52165,52166,52167,52168,52169,52170,52171,52172,52173,52174,52175,52176,52177,52178,52179,52180,52181,52182,52183,52184,52185,52186,52187,52188,52189,52190,52191,52192,52193,52194,52195,52196,52197,52198,52199,52200,52201,52202,52203,52204,52205,52206,52207,52208,52209,52210,52211,52212,52213,52214,52215,52216,52217,52218,52219,52220,52221,52222,52223,52224,52225,52226,52227,52228,52229,52230,52231,52232,52233,52234,52235,52236,52237,52238,52239,52240,52241,52242,52243,52244,52245,52246,52247,52248,52249,52250,52251,52252,52253,52254,52255,52256,52257,52258,52259,52260,52261,52262,52263,52264,52265,52266,52267,52268,52269,52270,52271,52272,52273,52274,52275,52276,52277,52278,52279,52280,52281,52282,52283,52284,52285,52286,52287,52288,52289,52290,52291,52292,52293,52294,52295,52296,52297,52298,52299,52300,52301,52302,52303,52304,52305,52306,52307,52308,52309,52310,52311,52312,52313,52314,52315,52316,52317,52318,52319,52320,52321,52322,52323,52324,52325,52326,52327,52328,52329,52330,52331,52332,52333,52334,52335,52336,52337,52338,52339,52340,52341,52342,52343,52344,52345,52346,52347,52348,52349,52350,52351,52352,52353,52354,52355,52356,52357,52358,52359,52360,52361,52362,52363,52364,52365,52366,52367,52368,52369,52370,52371,52372,52373,52374,52375,52376,52377,52378,52379,52380,52381,52382,52383,52384,52385,52386,52387,52388,52389,52390,52391,52392,52393,52394,52395,52396,52397,52398,52399,52400,52401,52402,52403,52404,52405,52406,52407,52408,52409,52410,52411,52412,52413,52414,52415,52416,52417,52418,52419,52420,52421,52422,52423,52424,52425,52426,52427,52428,52429,52430,52431,52432,52433,52434,52435,52436,52437,52438,52439,52440,52441,52442,52443,52444,52445,52446,52447,52448,52449,52450,52451,52452,52453,52454,52455,52456,52457,52458,52459,52460,52461,52462,52463,52464,52465,52466,52467,52468,52469,52470,52471,52472,52473,52474,52475,52476,52477,52478,52479,52480,52481,52482,52483,52484,52485,52486,52487,52488,52489,52490,52491,52492,52493,52494,52495,52496,52497,52498,52499,52500,52501,52502,52503,52504,52505,52506,52507,52508,52509,52510,52511,52512,52513,52514,52515,52516,52517,52518,52519,52520,52521,52522,52523,52524,52525,52526,52527,52528,52529,52530,52531,52532,52533,52534,52535,52536,52537,52538,52539,52540,52541,52542,52543,52544,52545,52546,52547,52548,52549,52550,52551,52552,52553,52554,52555,52556,52557,52558,52559,52560,52561,52562,52563,52564,52565,52566,52567,52568,52569,52570,52571,52572,52573,52574,52575,52576,52577,52578,52579,52580,52581,52582,52583,52584,52585,52586,52587,52588,52589,52590,52591,52592,52593,52594,52595,52596,52597,52598,52599,52600,52601,52602,52603,52604,52605,52606,52607,52608,52609,52610,52611,52612,52613,52614,52615,52616,52617,52618,52619,52620,52621,52622,52623,52624,52625,52626,52627,52628,52629,52630,52631,52632,52633,52634,52635,52636,52637,52638,52639,52640,52641,52642,52643,52644,52645,52646,52647,52648,52649,52650,52651,52652,52653,52654,52655,52656,52657,52658,52659,52660,52661,52662,52663,52664,52665,52666,52667,52668,52669,52670,52671,52672,52673,52674,52675,52676,52677,52678,52679,52680,52681,52682,52683,52684,52685,52686,52687,52688,52689,52690,52691,52692,52693,52694,52695,52696,52697,52698,52699,52700,52701,52702,52703,52704,52705,52706,52707,52708,52709,52710,52711,52712,52713,52714,52715,52716,52717,52718,52719,52720,52721,52722,52723,52724,52725,52726,52727,52728,52729,52730,52731,52732,52733,52734,52735,52736,52737,52738,52739,52740,52741,52742,52743,52744,52745,52746,52747,52748,52749,52750,52751,52752,52753,52754,52755,52756,52757,52758,52759,52760,52761,52762,52763,52764,52765,52766,52767,52768,52769,52770,52771,52772,52773,52774,52775,52776,52777,52778,52779,52780,52781,52782,52783,52784,52785,52786,52787,52788,52789,52790,52791,52792,52793,52794,52795,52796,52797,52798,52799,52800,52801,52802,52803,52804,52805,52806,52807,52808,52809,52810,52811,52812,52813,52814,52815,52816,52817,52818,52819,52820,52821,52822,52823,52824,52825,52826,52827,52828,52829,52830,52831,52832,52833,52834,52835,52836,52837,52838,52839,52840,52841,52842,52843,52844,52845,52846,52847,52848,52849,52850,52851,52852,52853,52854,52855,52856,52857,52858,52859,52860,52861,52862,52863,52864,52865,52866,52867,52868,52869,52870,52871,52872,52873,52874,52875,52876,52877,52878,52879,52880,52881,52882,52883,52884,52885,52886,52887,52888,52889,52890,52891,52892,52893,52894,52895,52896,52897,52898,52899,52900,52901,52902,52903,52904,52905,52906,52907,52908,52909,52910,52911,52912,52913,52914,52915,52916,52917,52918,52919,52920,52921,52922,52923,52924,52925,52926,52927,52928,52929,52930,52931,52932,52933,52934,52935,52936,52937,52938,52939,52940,52941,52942,52943,52944,52945,52946,52947,52948,52949,52950,52951,52952,52953,52954,52955,52956,52957,52958,52959,52960,52961,52962,52963,52964,52965,52966,52967,52968,52969,52970,52971,52972,52973,52974,52975,52976,52977,52978,52979,52980,52981,52982,52983,52984,52985,52986,52987,52988,52989,52990,52991,52992,52993,52994,52995,52996,52997,52998,52999,53000,53001,53002,53003,53004,53005,53006,53007,53008,53009,53010,53011,53012,53013,53014,53015,53016,53017,53018,53019,53020,53021,53022,53023,53024,53025,53026,53027,53028,53029,53030,53031,53032,53033,53034,53035,53036,53037,53038,53039,53040,53041,53042,53043,53044,53045,53046,53047,53048,53049,53050,53051,53052,53053,53054,53055,53056,53057,53058,53059,53060,53061,53062,53063,53064,53065,53066,53067,53068,53069,53070,53071,53072,53073,53074,53075,53076,53077,53078,53079,53080,53081,53082,53083,53084,53085,53086,53087,53088,53089,53090,53091,53092,53093,53094,53095,53096,53097,53098,53099,53100,53101,53102,53103,53104,53105,53106,53107,53108,53109,53110,53111,53112,53113,53114,53115,53116,53117,53118,53119,53120,53121,53122,53123,53124,53125,53126,53127,53128,53129,53130,53131,53132,53133,53134,53135,53136,53137,53138,53139,53140,53141,53142,53143,53144,53145,53146,53147,53148,53149,53150,53151,53152,53153,53154,53155,53156,53157,53158,53159,53160,53161,53162,53163,53164,53165,53166,53167,53168,53169,53170,53171,53172,53173,53174,53175,53176,53177,53178,53179,53180,53181,53182,53183,53184,53185,53186,53187,53188,53189,53190,53191,53192,53193,53194,53195,53196,53197,53198,53199,53200,53201,53202,53203,53204,53205,53206,53207,53208,53209,53210,53211,53212,53213,53214,53215,53216,53217,53218,53219,53220,53221,53222,53223,53224,53225,53226,53227,53228,53229,53230,53231,53232,53233,53234,53235,53236,53237,53238,53239,53240,53241,53242,53243,53244,53245,53246,53247,53248,53249,53250,53251,53252,53253,53254,53255,53256,53257,53258,53259,53260,53261,53262,53263,53264,53265,53266,53267,53268,53269,53270,53271,53272,53273,53274,53275,53276,53277,53278,53279,53280,53281,53282,53283,53284,53285,53286,53287,53288,53289,53290,53291,53292,53293,53294,53295,53296,53297,53298,53299,53300,53301,53302,53303,53304,53305,53306,53307,53308,53309,53310,53311,53312,53313,53314,53315,53316,53317,53318,53319,53320,53321,53322,53323,53324,53325,53326,53327,53328,53329,53330,53331,53332,53333,53334,53335,53336,53337,53338,53339,53340,53341,53342,53343,53344,53345,53346,53347,53348,53349,53350,53351,53352,53353,53354,53355,53356,53357,53358,53359,53360,53361,53362,53363,53364,53365,53366,53367,53368,53369,53370,53371,53372,53373,53374,53375,53376,53377,53378,53379,53380,53381,53382,53383,53384,53385,53386,53387,53388,53389,53390,53391,53392,53393,53394,53395,53396,53397,53398,53399,53400,53401,53402,53403,53404,53405,53406,53407,53408,53409,53410,53411,53412,53413,53414,53415,53416,53417,53418,53419,53420,53421,53422,53423,53424,53425,53426,53427,53428,53429,53430,53431,53432,53433,53434,53435,53436,53437,53438,53439,53440,53441,53442,53443,53444,53445,53446,53447,53448,53449,53450,53451,53452,53453,53454,53455,53456,53457,53458,53459,53460,53461,53462,53463,53464,53465,53466,53467,53468,53469,53470,53471,53472,53473,53474,53475,53476,53477,53478,53479,53480,53481,53482,53483,53484,53485,53486,53487,53488,53489,53490,53491,53492,53493,53494,53495,53496,53497,53498,53499,53500,53501,53502,53503,53504,53505,53506,53507,53508,53509,53510,53511,53512,53513,53514,53515,53516,53517,53518,53519,53520,53521,53522,53523,53524,53525,53526,53527,53528,53529,53530,53531,53532,53533,53534,53535,53536,53537,53538,53539,53540,53541,53542,53543,53544,53545,53546,53547,53548,53549,53550,53551,53552,53553,53554,53555,53556,53557,53558,53559,53560,53561,53562,53563,53564,53565,53566,53567,53568,53569,53570,53571,53572,53573,53574,53575,53576,53577,53578,53579,53580,53581,53582,53583,53584,53585,53586,53587,53588,53589,53590,53591,53592,53593,53594,53595,53596,53597,53598,53599,53600,53601,53602,53603,53604,53605,53606,53607,53608,53609,53610,53611,53612,53613,53614,53615,53616,53617,53618,53619,53620,53621,53622,53623,53624,53625,53626,53627,53628,53629,53630,53631,53632,53633,53634,53635,53636,53637,53638,53639,53640,53641,53642,53643,53644,53645,53646,53647,53648,53649,53650,53651,53652,53653,53654,53655,53656,53657,53658,53659,53660,53661,53662,53663,53664,53665,53666,53667,53668,53669,53670,53671,53672,53673,53674,53675,53676,53677,53678,53679,53680,53681,53682,53683,53684,53685,53686,53687,53688,53689,53690,53691,53692,53693,53694,53695,53696,53697,53698,53699,53700,53701,53702,53703,53704,53705,53706,53707,53708,53709,53710,53711,53712,53713,53714,53715,53716,53717,53718,53719,53720,53721,53722,53723,53724,53725,53726,53727,53728,53729,53730,53731,53732,53733,53734,53735,53736,53737,53738,53739,53740,53741,53742,53743,53744,53745,53746,53747,53748,53749,53750,53751,53752,53753,53754,53755,53756,53757,53758,53759,53760,53761,53762,53763,53764,53765,53766,53767,53768,53769,53770,53771,53772,53773,53774,53775,53776,53777,53778,53779,53780,53781,53782,53783,53784,53785,53786,53787,53788,53789,53790,53791,53792,53793,53794,53795,53796,53797,53798,53799,53800,53801,53802,53803,53804,53805,53806,53807,53808,53809,53810,53811,53812,53813,53814,53815,53816,53817,53818,53819,53820,53821,53822,53823,53824,53825,53826,53827,53828,53829,53830,53831,53832,53833,53834,53835,53836,53837,53838,53839,53840,53841,53842,53843,53844,53845,53846,53847,53848,53849,53850,53851,53852,53853,53854,53855,53856,53857,53858,53859,53860,53861,53862,53863,53864,53865,53866,53867,53868,53869,53870,53871,53872,53873,53874,53875,53876,53877,53878,53879,53880,53881,53882,53883,53884,53885,53886,53887,53888,53889,53890,53891,53892,53893,53894,53895,53896,53897,53898,53899,53900,53901,53902,53903,53904,53905,53906,53907,53908,53909,53910,53911,53912,53913,53914,53915,53916,53917,53918,53919,53920,53921,53922,53923,53924,53925,53926,53927,53928,53929,53930,53931,53932,53933,53934,53935,53936,53937,53938,53939,53940,53941,53942,53943,53944,53945,53946,53947,53948,53949,53950,53951,53952,53953,53954,53955,53956,53957,53958,53959,53960,53961,53962,53963,53964,53965,53966,53967,53968,53969,53970,53971,53972,53973,53974,53975,53976,53977,53978,53979,53980,53981,53982,53983,53984,53985,53986,53987,53988,53989,53990,53991,53992,53993,53994,53995,53996,53997,53998,53999,54000,54001,54002,54003,54004,54005,54006,54007,54008,54009,54010,54011,54012,54013,54014,54015,54016,54017,54018,54019,54020,54021,54022,54023,54024,54025,54026,54027,54028,54029,54030,54031,54032,54033,54034,54035,54036,54037,54038,54039,54040,54041,54042,54043,54044,54045,54046,54047,54048,54049,54050,54051,54052,54053,54054,54055,54056,54057,54058,54059,54060,54061,54062,54063,54064,54065,54066,54067,54068,54069,54070,54071,54072,54073,54074,54075,54076,54077,54078,54079,54080,54081,54082,54083,54084,54085,54086,54087,54088,54089,54090,54091,54092,54093,54094,54095,54096,54097,54098,54099,54100,54101,54102,54103,54104,54105,54106,54107,54108,54109,54110,54111,54112,54113,54114,54115,54116,54117,54118,54119,54120,54121,54122,54123,54124,54125,54126,54127,54128,54129,54130,54131,54132,54133,54134,54135,54136,54137,54138,54139,54140,54141,54142,54143,54144,54145,54146,54147,54148,54149,54150,54151,54152,54153,54154,54155,54156,54157,54158,54159,54160,54161,54162,54163,54164,54165,54166,54167,54168,54169,54170,54171,54172,54173,54174,54175,54176,54177,54178,54179,54180,54181,54182,54183,54184,54185,54186,54187,54188,54189,54190,54191,54192,54193,54194,54195,54196,54197,54198,54199,54200,54201,54202,54203,54204,54205,54206,54207,54208,54209,54210,54211,54212,54213,54214,54215,54216,54217,54218,54219,54220,54221,54222,54223,54224,54225,54226,54227,54228,54229,54230,54231,54232,54233,54234,54235,54236,54237,54238,54239,54240,54241,54242,54243,54244,54245,54246,54247,54248,54249,54250,54251,54252,54253,54254,54255,54256,54257,54258,54259,54260,54261,54262,54263,54264,54265,54266,54267,54268,54269,54270,54271,54272,54273,54274,54275,54276,54277,54278,54279,54280,54281,54282,54283,54284,54285,54286,54287,54288,54289,54290,54291,54292,54293,54294,54295,54296,54297,54298,54299,54300,54301,54302,54303,54304,54305,54306,54307,54308,54309,54310,54311,54312,54313,54314,54315,54316,54317,54318,54319,54320,54321,54322,54323,54324,54325,54326,54327,54328,54329,54330,54331,54332,54333,54334,54335,54336,54337,54338,54339,54340,54341,54342,54343,54344,54345,54346,54347,54348,54349,54350,54351,54352,54353,54354,54355,54356,54357,54358,54359,54360,54361,54362,54363,54364,54365,54366,54367,54368,54369,54370,54371,54372,54373,54374,54375,54376,54377,54378,54379,54380,54381,54382,54383,54384,54385,54386,54387,54388,54389,54390,54391,54392,54393,54394,54395,54396,54397,54398,54399,54400,54401,54402,54403,54404,54405,54406,54407,54408,54409,54410,54411,54412,54413,54414,54415,54416,54417,54418,54419,54420,54421,54422,54423,54424,54425,54426,54427,54428,54429,54430,54431,54432,54433,54434,54435,54436,54437,54438,54439,54440,54441,54442,54443,54444,54445,54446,54447,54448,54449,54450,54451,54452,54453,54454,54455,54456,54457,54458,54459,54460,54461,54462,54463,54464,54465,54466,54467,54468,54469,54470,54471,54472,54473,54474,54475,54476,54477,54478,54479,54480,54481,54482,54483,54484,54485,54486,54487,54488,54489,54490,54491,54492,54493,54494,54495,54496,54497,54498,54499,54500,54501,54502,54503,54504,54505,54506,54507,54508,54509,54510,54511,54512,54513,54514,54515,54516,54517,54518,54519,54520,54521,54522,54523,54524,54525,54526,54527,54528,54529,54530,54531,54532,54533,54534,54535,54536,54537,54538,54539,54540,54541,54542,54543,54544,54545,54546,54547,54548,54549,54550,54551,54552,54553,54554,54555,54556,54557,54558,54559,54560,54561,54562,54563,54564,54565,54566,54567,54568,54569,54570,54571,54572,54573,54574,54575,54576,54577,54578,54579,54580,54581,54582,54583,54584,54585,54586,54587,54588,54589,54590,54591,54592,54593,54594,54595,54596,54597,54598,54599,54600,54601,54602,54603,54604,54605,54606,54607,54608,54609,54610,54611,54612,54613,54614,54615,54616,54617,54618,54619,54620,54621,54622,54623,54624,54625,54626,54627,54628,54629,54630,54631,54632,54633,54634,54635,54636,54637,54638,54639,54640,54641,54642,54643,54644,54645,54646,54647,54648,54649,54650,54651,54652,54653,54654,54655,54656,54657,54658,54659,54660,54661,54662,54663,54664,54665,54666,54667,54668,54669,54670,54671,54672,54673,54674,54675,54676,54677,54678,54679,54680,54681,54682,54683,54684,54685,54686,54687,54688,54689,54690,54691,54692,54693,54694,54695,54696,54697,54698,54699,54700,54701,54702,54703,54704,54705,54706,54707,54708,54709,54710,54711,54712,54713,54714,54715,54716,54717,54718,54719,54720,54721,54722,54723,54724,54725,54726,54727,54728,54729,54730,54731,54732,54733,54734,54735,54736,54737,54738,54739,54740,54741,54742,54743,54744,54745,54746,54747,54748,54749,54750,54751,54752,54753,54754,54755,54756,54757,54758,54759,54760,54761,54762,54763,54764,54765,54766,54767,54768,54769,54770,54771,54772,54773,54774,54775,54776,54777,54778,54779,54780,54781,54782,54783,54784,54785,54786,54787,54788,54789,54790,54791,54792,54793,54794,54795,54796,54797,54798,54799,54800,54801,54802,54803,54804,54805,54806,54807,54808,54809,54810,54811,54812,54813,54814,54815,54816,54817,54818,54819,54820,54821,54822,54823,54824,54825,54826,54827,54828,54829,54830,54831,54832,54833,54834,54835,54836,54837,54838,54839,54840,54841,54842,54843,54844,54845,54846,54847,54848,54849,54850,54851,54852,54853,54854,54855,54856,54857,54858,54859,54860,54861,54862,54863,54864,54865,54866,54867,54868,54869,54870,54871,54872,54873,54874,54875,54876,54877,54878,54879,54880,54881,54882,54883,54884,54885,54886,54887,54888,54889,54890,54891,54892,54893,54894,54895,54896,54897,54898,54899,54900,54901,54902,54903,54904,54905,54906,54907,54908,54909,54910,54911,54912,54913,54914,54915,54916,54917,54918,54919,54920,54921,54922,54923,54924,54925,54926,54927,54928,54929,54930,54931,54932,54933,54934,54935,54936,54937,54938,54939,54940,54941,54942,54943,54944,54945,54946,54947,54948,54949,54950,54951,54952,54953,54954,54955,54956,54957,54958,54959,54960,54961,54962,54963,54964,54965,54966,54967,54968,54969,54970,54971,54972,54973,54974,54975,54976,54977,54978,54979,54980,54981,54982,54983,54984,54985,54986,54987,54988,54989,54990,54991,54992,54993,54994,54995,54996,54997,54998,54999,55000,55001,55002,55003,55004,55005,55006,55007,55008,55009,55010,55011,55012,55013,55014,55015,55016,55017,55018,55019,55020,55021,55022,55023,55024,55025,55026,55027,55028,55029,55030,55031,55032,55033,55034,55035,55036,55037,55038,55039,55040,55041,55042,55043,55044,55045,55046,55047,55048,55049,55050,55051,55052,55053,55054,55055,55056,55057,55058,55059,55060,55061,55062,55063,55064,55065,55066,55067,55068,55069,55070,55071,55072,55073,55074,55075,55076,55077,55078,55079,55080,55081,55082,55083,55084,55085,55086,55087,55088,55089,55090,55091,55092,55093,55094,55095,55096,55097,55098,55099,55100,55101,55102,55103,55104,55105,55106,55107,55108,55109,55110,55111,55112,55113,55114,55115,55116,55117,55118,55119,55120,55121,55122,55123,55124,55125,55126,55127,55128,55129,55130,55131,55132,55133,55134,55135,55136,55137,55138,55139,55140,55141,55142,55143,55144,55145,55146,55147,55148,55149,55150,55151,55152,55153,55154,55155,55156,55157,55158,55159,55160,55161,55162,55163,55164,55165,55166,55167,55168,55169,55170,55171,55172,55173,55174,55175,55176,55177,55178,55179,55180,55181,55182,55183,55184,55185,55186,55187,55188,55189,55190,55191,55192,55193,55194,55195,55196,55197,55198,55199,55200,55201,55202,55203,55216,55217,55218,55219,55220,55221,55222,55223,55224,55225,55226,55227,55228,55229,55230,55231,55232,55233,55234,55235,55236,55237,55238,55243,55244,55245,55246,55247,55248,55249,55250,55251,55252,55253,55254,55255,55256,55257,55258,55259,55260,55261,55262,55263,55264,55265,55266,55267,55268,55269,55270,55271,55272,55273,55274,55275,55276,55277,55278,55279,55280,55281,55282,55283,55284,55285,55286,55287,55288,55289,55290,55291,63744,63745,63746,63747,63748,63749,63750,63751,63752,63753,63754,63755,63756,63757,63758,63759,63760,63761,63762,63763,63764,63765,63766,63767,63768,63769,63770,63771,63772,63773,63774,63775,63776,63777,63778,63779,63780,63781,63782,63783,63784,63785,63786,63787,63788,63789,63790,63791,63792,63793,63794,63795,63796,63797,63798,63799,63800,63801,63802,63803,63804,63805,63806,63807,63808,63809,63810,63811,63812,63813,63814,63815,63816,63817,63818,63819,63820,63821,63822,63823,63824,63825,63826,63827,63828,63829,63830,63831,63832,63833,63834,63835,63836,63837,63838,63839,63840,63841,63842,63843,63844,63845,63846,63847,63848,63849,63850,63851,63852,63853,63854,63855,63856,63857,63858,63859,63860,63861,63862,63863,63864,63865,63866,63867,63868,63869,63870,63871,63872,63873,63874,63875,63876,63877,63878,63879,63880,63881,63882,63883,63884,63885,63886,63887,63888,63889,63890,63891,63892,63893,63894,63895,63896,63897,63898,63899,63900,63901,63902,63903,63904,63905,63906,63907,63908,63909,63910,63911,63912,63913,63914,63915,63916,63917,63918,63919,63920,63921,63922,63923,63924,63925,63926,63927,63928,63929,63930,63931,63932,63933,63934,63935,63936,63937,63938,63939,63940,63941,63942,63943,63944,63945,63946,63947,63948,63949,63950,63951,63952,63953,63954,63955,63956,63957,63958,63959,63960,63961,63962,63963,63964,63965,63966,63967,63968,63969,63970,63971,63972,63973,63974,63975,63976,63977,63978,63979,63980,63981,63982,63983,63984,63985,63986,63987,63988,63989,63990,63991,63992,63993,63994,63995,63996,63997,63998,63999,64000,64001,64002,64003,64004,64005,64006,64007,64008,64009,64010,64011,64012,64013,64014,64015,64016,64017,64018,64019,64020,64021,64022,64023,64024,64025,64026,64027,64028,64029,64030,64031,64032,64033,64034,64035,64036,64037,64038,64039,64040,64041,64042,64043,64044,64045,64046,64047,64048,64049,64050,64051,64052,64053,64054,64055,64056,64057,64058,64059,64060,64061,64062,64063,64064,64065,64066,64067,64068,64069,64070,64071,64072,64073,64074,64075,64076,64077,64078,64079,64080,64081,64082,64083,64084,64085,64086,64087,64088,64089,64090,64091,64092,64093,64094,64095,64096,64097,64098,64099,64100,64101,64102,64103,64104,64105,64106,64107,64108,64109,64112,64113,64114,64115,64116,64117,64118,64119,64120,64121,64122,64123,64124,64125,64126,64127,64128,64129,64130,64131,64132,64133,64134,64135,64136,64137,64138,64139,64140,64141,64142,64143,64144,64145,64146,64147,64148,64149,64150,64151,64152,64153,64154,64155,64156,64157,64158,64159,64160,64161,64162,64163,64164,64165,64166,64167,64168,64169,64170,64171,64172,64173,64174,64175,64176,64177,64178,64179,64180,64181,64182,64183,64184,64185,64186,64187,64188,64189,64190,64191,64192,64193,64194,64195,64196,64197,64198,64199,64200,64201,64202,64203,64204,64205,64206,64207,64208,64209,64210,64211,64212,64213,64214,64215,64216,64217,64256,64257,64258,64259,64260,64261,64262,64275,64276,64277,64278,64279,64285,64287,64288,64289,64290,64291,64292,64293,64294,64295,64296,64298,64299,64300,64301,64302,64303,64304,64305,64306,64307,64308,64309,64310,64312,64313,64314,64315,64316,64318,64320,64321,64323,64324,64326,64327,64328,64329,64330,64331,64332,64333,64334,64335,64336,64337,64338,64339,64340,64341,64342,64343,64344,64345,64346,64347,64348,64349,64350,64351,64352,64353,64354,64355,64356,64357,64358,64359,64360,64361,64362,64363,64364,64365,64366,64367,64368,64369,64370,64371,64372,64373,64374,64375,64376,64377,64378,64379,64380,64381,64382,64383,64384,64385,64386,64387,64388,64389,64390,64391,64392,64393,64394,64395,64396,64397,64398,64399,64400,64401,64402,64403,64404,64405,64406,64407,64408,64409,64410,64411,64412,64413,64414,64415,64416,64417,64418,64419,64420,64421,64422,64423,64424,64425,64426,64427,64428,64429,64430,64431,64432,64433,64467,64468,64469,64470,64471,64472,64473,64474,64475,64476,64477,64478,64479,64480,64481,64482,64483,64484,64485,64486,64487,64488,64489,64490,64491,64492,64493,64494,64495,64496,64497,64498,64499,64500,64501,64502,64503,64504,64505,64506,64507,64508,64509,64510,64511,64512,64513,64514,64515,64516,64517,64518,64519,64520,64521,64522,64523,64524,64525,64526,64527,64528,64529,64530,64531,64532,64533,64534,64535,64536,64537,64538,64539,64540,64541,64542,64543,64544,64545,64546,64547,64548,64549,64550,64551,64552,64553,64554,64555,64556,64557,64558,64559,64560,64561,64562,64563,64564,64565,64566,64567,64568,64569,64570,64571,64572,64573,64574,64575,64576,64577,64578,64579,64580,64581,64582,64583,64584,64585,64586,64587,64588,64589,64590,64591,64592,64593,64594,64595,64596,64597,64598,64599,64600,64601,64602,64603,64604,64605,64606,64607,64608,64609,64610,64611,64612,64613,64614,64615,64616,64617,64618,64619,64620,64621,64622,64623,64624,64625,64626,64627,64628,64629,64630,64631,64632,64633,64634,64635,64636,64637,64638,64639,64640,64641,64642,64643,64644,64645,64646,64647,64648,64649,64650,64651,64652,64653,64654,64655,64656,64657,64658,64659,64660,64661,64662,64663,64664,64665,64666,64667,64668,64669,64670,64671,64672,64673,64674,64675,64676,64677,64678,64679,64680,64681,64682,64683,64684,64685,64686,64687,64688,64689,64690,64691,64692,64693,64694,64695,64696,64697,64698,64699,64700,64701,64702,64703,64704,64705,64706,64707,64708,64709,64710,64711,64712,64713,64714,64715,64716,64717,64718,64719,64720,64721,64722,64723,64724,64725,64726,64727,64728,64729,64730,64731,64732,64733,64734,64735,64736,64737,64738,64739,64740,64741,64742,64743,64744,64745,64746,64747,64748,64749,64750,64751,64752,64753,64754,64755,64756,64757,64758,64759,64760,64761,64762,64763,64764,64765,64766,64767,64768,64769,64770,64771,64772,64773,64774,64775,64776,64777,64778,64779,64780,64781,64782,64783,64784,64785,64786,64787,64788,64789,64790,64791,64792,64793,64794,64795,64796,64797,64798,64799,64800,64801,64802,64803,64804,64805,64806,64807,64808,64809,64810,64811,64812,64813,64814,64815,64816,64817,64818,64819,64820,64821,64822,64823,64824,64825,64826,64827,64828,64829,64848,64849,64850,64851,64852,64853,64854,64855,64856,64857,64858,64859,64860,64861,64862,64863,64864,64865,64866,64867,64868,64869,64870,64871,64872,64873,64874,64875,64876,64877,64878,64879,64880,64881,64882,64883,64884,64885,64886,64887,64888,64889,64890,64891,64892,64893,64894,64895,64896,64897,64898,64899,64900,64901,64902,64903,64904,64905,64906,64907,64908,64909,64910,64911,64914,64915,64916,64917,64918,64919,64920,64921,64922,64923,64924,64925,64926,64927,64928,64929,64930,64931,64932,64933,64934,64935,64936,64937,64938,64939,64940,64941,64942,64943,64944,64945,64946,64947,64948,64949,64950,64951,64952,64953,64954,64955,64956,64957,64958,64959,64960,64961,64962,64963,64964,64965,64966,64967,65008,65009,65010,65011,65012,65013,65014,65015,65016,65017,65018,65019,65136,65137,65138,65139,65140,65142,65143,65144,65145,65146,65147,65148,65149,65150,65151,65152,65153,65154,65155,65156,65157,65158,65159,65160,65161,65162,65163,65164,65165,65166,65167,65168,65169,65170,65171,65172,65173,65174,65175,65176,65177,65178,65179,65180,65181,65182,65183,65184,65185,65186,65187,65188,65189,65190,65191,65192,65193,65194,65195,65196,65197,65198,65199,65200,65201,65202,65203,65204,65205,65206,65207,65208,65209,65210,65211,65212,65213,65214,65215,65216,65217,65218,65219,65220,65221,65222,65223,65224,65225,65226,65227,65228,65229,65230,65231,65232,65233,65234,65235,65236,65237,65238,65239,65240,65241,65242,65243,65244,65245,65246,65247,65248,65249,65250,65251,65252,65253,65254,65255,65256,65257,65258,65259,65260,65261,65262,65263,65264,65265,65266,65267,65268,65269,65270,65271,65272,65273,65274,65275,65276,65313,65314,65315,65316,65317,65318,65319,65320,65321,65322,65323,65324,65325,65326,65327,65328,65329,65330,65331,65332,65333,65334,65335,65336,65337,65338,65345,65346,65347,65348,65349,65350,65351,65352,65353,65354,65355,65356,65357,65358,65359,65360,65361,65362,65363,65364,65365,65366,65367,65368,65369,65370,65382,65383,65384,65385,65386,65387,65388,65389,65390,65391,65392,65393,65394,65395,65396,65397,65398,65399,65400,65401,65402,65403,65404,65405,65406,65407,65408,65409,65410,65411,65412,65413,65414,65415,65416,65417,65418,65419,65420,65421,65422,65423,65424,65425,65426,65427,65428,65429,65430,65431,65432,65433,65434,65435,65436,65437,65438,65439,65440,65441,65442,65443,65444,65445,65446,65447,65448,65449,65450,65451,65452,65453,65454,65455,65456,65457,65458,65459,65460,65461,65462,65463,65464,65465,65466,65467,65468,65469,65470,65474,65475,65476,65477,65478,65479,65482,65483,65484,65485,65486,65487,65490,65491,65492,65493,65494,65495,65498,65499,65500".split(",").map(function(_){return parseInt(_,10)});$.exports=t},{}],5:[function(_,$,e){var t=_("util/"),n=Array.prototype.slice,r=Object.prototype.hasOwnProperty,i=$.exports=s;function u(_,$){return t.isUndefined($)?""+$:t.isNumber($)&&!isFinite($)||t.isFunction($)||t.isRegExp($)?$.toString():$}function o(_,$){return t.isString(_)?_.length<$?_:_.slice(0,$):_}function a(_,$,e,t,n){throw new i.AssertionError({message:e,actual:_,expected:$,operator:t,stackStartFunction:n})}function s(_,$){_||a(_,!0,$,"==",i.ok)}function c(_,$){if(_===$)return!0;if(t.isBuffer(_)&&t.isBuffer($)){if(_.length!=$.length)return!1;for(var e=0;e<_.length;e++)if(_[e]!==$[e])return!1;return!0}if(t.isDate(_)&&t.isDate($))return _.getTime()===$.getTime();if(t.isRegExp(_)&&t.isRegExp($))return _.source===$.source&&_.global===$.global&&_.multiline===$.multiline&&_.lastIndex===$.lastIndex&&_.ignoreCase===$.ignoreCase;if(!t.isObject(_)&&!t.isObject($))return _==$;else return function _($,e){if(t.isNullOrUndefined($)||t.isNullOrUndefined(e)||$.prototype!==e.prototype)return!1;if(t.isPrimitive($)||t.isPrimitive(e))return $===e;var r=l($),i=l(e);if(r&&!i||!r&&i)return!1;if(r)return $=n.call($),e=n.call(e),c($,e);var u,o,a=p($),s=p(e);if(a.length!=s.length)return!1;for(a.sort(),s.sort(),o=a.length-1;o>=0;o--)if(a[o]!=s[o])return!1;for(o=a.length-1;o>=0;o--)if(!c($[u=a[o]],e[u]))return!1;return!0}(_,$)}function l(_){return"[object Arguments]"==Object.prototype.toString.call(_)}function f(_,$){return!!_&&!!$&&("[object RegExp]"==Object.prototype.toString.call($)?$.test(_):_ instanceof $||!0===$.call({},_))}function d(_,$,e,n){var r;t.isString(e)&&(n=e,e=null);try{$()}catch(i){r=i}if(n=(e&&e.name?" ("+e.name+").":".")+(n?" "+n:"."),_&&!r&&a(r,e,"Missing expected exception"+n),!_&&f(r,e)&&a(r,e,"Got unwanted exception"+n),_&&r&&e&&!f(r,e)||!_&&r)throw r}i.AssertionError=function _($){this.name="AssertionError",this.actual=$.actual,this.expected=$.expected,this.operator=$.operator,$.message?(this.message=$.message,this.generatedMessage=!1):(this.message=(e=this,o(JSON.stringify(e.actual,u),128)+" "+e.operator+" "+o(JSON.stringify(e.expected,u),128)),this.generatedMessage=!0);var e,t=$.stackStartFunction||a;if(Error.captureStackTrace)Error.captureStackTrace(this,t);else{var n=Error();if(n.stack){var r=n.stack,i=t.name,s=r.indexOf("\n"+i);if(s>=0){var c=r.indexOf("\n",s+1);r=r.substring(c+1)}this.stack=r}}},t.inherits(i.AssertionError,Error),i.fail=a,i.ok=s,i.equal=function _($,e,t){$!=e&&a($,e,t,"==",i.equal)},i.notEqual=function _($,e,t){$==e&&a($,e,t,"!=",i.notEqual)},i.deepEqual=function _($,e,t){c($,e)||a($,e,t,"deepEqual",i.deepEqual)},i.notDeepEqual=function _($,e,t){c($,e)&&a($,e,t,"notDeepEqual",i.notDeepEqual)},i.strictEqual=function _($,e,t){$!==e&&a($,e,t,"===",i.strictEqual)},i.notStrictEqual=function _($,e,t){$===e&&a($,e,t,"!==",i.notStrictEqual)},i.throws=function(_,$,e){d.apply(this,[!0].concat(n.call(arguments)))},i.doesNotThrow=function(_,$){d.apply(this,[!1].concat(n.call(arguments)))},i.ifError=function(_){if(_)throw _};var p=Object.keys||function(_){var $=[];for(var e in _)r.call(_,e)&&$.push(e);return $}},{"util/":8}],6:[function(_,$,e){"function"==typeof Object.create?$.exports=function _($,e){$.super_=e,$.prototype=Object.create(e.prototype,{constructor:{value:$,enumerable:!1,writable:!0,configurable:!0}})}:$.exports=function _($,e){$.super_=e;var t=function(){};t.prototype=e.prototype,$.prototype=new t,$.prototype.constructor=$}},{}],7:[function(_,$,e){$.exports=function _($){return $&&"object"==typeof $&&"function"==typeof $.copy&&"function"==typeof $.fill&&"function"==typeof $.readUInt8}},{}],8:[function(_,$,e){(function($,t){var n,r=/%[sdj%]/g;e.format=function(_){if(!v(_)){for(var $=[],e=0;e=n)return _;switch(_){case"%s":return String(t[e++]);case"%d":return Number(t[e++]);case"%j":try{return JSON.stringify(t[e++])}catch($){return"[Circular]"}default:return _}}),o=t[e];e=3&&(t.depth=arguments[2]),arguments.length>=4&&(t.colors=arguments[3]),d($)?t.showHidden=$:$&&e._extend(t,$),g(t.showHidden)&&(t.showHidden=!1),g(t.depth)&&(t.depth=2),g(t.colors)&&(t.colors=!1),g(t.customInspect)&&(t.customInspect=!0),t.colors&&(t.stylize=o),s(t,_,t.depth)}function o(_,$){var e=u.styles[$];return e?"\x1b["+u.colors[e][0]+"m"+_+"\x1b["+u.colors[e][1]+"m":_}function a(_,$){return _}function s(_,$,t){if(_.customInspect&&$&&b($.inspect)&&$.inspect!==e.inspect&&!($.constructor&&$.constructor.prototype===$)){var n,r,i,u,o,a=$.inspect(t,_);return v(a)||(a=s(_,a,t)),a}var y=function _($,e){if(g(e))return $.stylize("undefined","undefined");if(v(e)){var t="'"+JSON.stringify(e).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return $.stylize(t,"string")}return h(e)?$.stylize(""+e,"number"):d(e)?$.stylize(""+e,"boolean"):p(e)?$.stylize("null","null"):void 0}(_,$);if(y)return y;var E,A,S=Object.keys($),F=(E=S,A={},E.forEach(function(_,$){A[_]=!0}),A);if(_.showHidden&&(S=Object.getOwnPropertyNames($)),x($)&&(S.indexOf("message")>=0||S.indexOf("description")>=0))return c($);if(0===S.length){if(b($)){var w=$.name?": "+$.name:"";return _.stylize("[Function"+w+"]","special")}if(m($))return _.stylize(RegExp.prototype.toString.call($),"regexp");if(k($))return _.stylize(Date.prototype.toString.call($),"date");if(x($))return c($)}var W="",D=!1,B=["{","}"];if(f($)&&(D=!0,B=["[","]"]),b($)&&(W=" [Function"+($.name?": "+$.name:"")+"]"),m($)&&(W=" "+RegExp.prototype.toString.call($)),k($)&&(W=" "+Date.prototype.toUTCString.call($)),x($)&&(W=" "+c($)),0===S.length&&(!D||0==$.length))return B[0]+W+B[1];if(t<0)return m($)?_.stylize(RegExp.prototype.toString.call($),"regexp"):_.stylize("[Object]","special");return _.seen.push($),o=D?function _($,e,t,n,r){for(var i=[],u=0,o=e.length;u=0&&u++,_+$.replace(/\u001b\[\d\d?m/g,"").length+1},0)>60?i[0]+(""===r?"":r+"\n ")+" "+n.join(",\n ")+" "+i[1]:i[0]+r+" "+n.join(", ")+" "+i[1]}function c(_){return"["+Error.prototype.toString.call(_)+"]"}function l(_,$,e,t,n,r){var i,u,o;if((o=Object.getOwnPropertyDescriptor($,n)||{value:$[n]}).get?u=o.set?_.stylize("[Getter/Setter]","special"):_.stylize("[Getter]","special"):o.set&&(u=_.stylize("[Setter]","special")),C(t,n)||(i="["+n+"]"),!u&&(0>_.seen.indexOf(o.value)?(u=p(e)?s(_,o.value,null):s(_,o.value,e-1)).indexOf("\n")>-1&&(u=r?u.split("\n").map(function(_){return" "+_}).join("\n").substr(2):"\n"+u.split("\n").map(function(_){return" "+_}).join("\n")):u=_.stylize("[Circular]","special")),g(i)){if(r&&n.match(/^\d+$/))return u;(i=JSON.stringify(""+n)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(i=i.substr(1,i.length-2),i=_.stylize(i,"name")):(i=i.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),i=_.stylize(i,"string"))}return i+": "+u}function f(_){return Array.isArray(_)}function d(_){return"boolean"==typeof _}function p(_){return null===_}function h(_){return"number"==typeof _}function v(_){return"string"==typeof _}function g(_){return void 0===_}function m(_){return y(_)&&"[object RegExp]"===E(_)}function y(_){return"object"==typeof _&&null!==_}function k(_){return y(_)&&"[object Date]"===E(_)}function x(_){return y(_)&&("[object Error]"===E(_)||_ instanceof Error)}function b(_){return"function"==typeof _}function E(_){return Object.prototype.toString.call(_)}function A(_){return _<10?"0"+_.toString(10):_.toString(10)}e.debuglog=function(_){if(g(n)&&(n=$.env.NODE_DEBUG||""),!i[_=_.toUpperCase()]){if(RegExp("\\b"+_+"\\b","i").test(n)){var t=$.pid;i[_]=function(){var $=e.format.apply(e,arguments);console.error("%s %d: %s",_,t,$)}}else i[_]=function(){}}return i[_]},e.inspect=u,u.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},u.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},e.isArray=f,e.isBoolean=d,e.isNull=p,e.isNullOrUndefined=function _($){return null==$},e.isNumber=h,e.isString=v,e.isSymbol=function _($){return"symbol"==typeof $},e.isUndefined=g,e.isRegExp=m,e.isObject=y,e.isDate=k,e.isError=x,e.isFunction=b,e.isPrimitive=function _($){return null===$||"boolean"==typeof $||"number"==typeof $||"string"==typeof $||"symbol"==typeof $||void 0===$},e.isBuffer=_("./support/isBuffer");var S=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function C(_,$){return Object.prototype.hasOwnProperty.call(_,$)}e.log=function(){var _,$;console.log("%s - %s",($=[A((_=new Date).getHours()),A(_.getMinutes()),A(_.getSeconds())].join(":"),[_.getDate(),S[_.getMonth()],$].join(" ")),e.format.apply(e,arguments))},e.inherits=_("inherits"),e._extend=function(_,$){if(!$||!y($))return _;for(var e=Object.keys($),t=e.length;t--;)_[e[t]]=$[e[t]];return _}}).call(this,_("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./support/isBuffer":7,_process:13,inherits:6}],9:[function(_,$,e){(function(e){var t,n=_("util"),r=_("assert"),i=_("date-now"),u=Array.prototype.slice,o={};t=void 0!==e&&e.console?e.console:"undefined"!=typeof window&&window.console?window.console:{};for(var a=[[function _(){},"log"],[function _(){t.log.apply(t,arguments)},"info"],[function _(){t.log.apply(t,arguments)},"warn"],[function _(){t.warn.apply(t,arguments)},"error"],[function _($){o[$]=i()},"time"],[function _($){var e=o[$];if(!e)throw Error("No such label: "+$);var n=i()-e;t.log($+": "+n+"ms")},"timeEnd"],[function _(){var $=Error();$.name="Trace",$.message=n.format.apply(null,arguments),t.error($.stack)},"trace"],[function _($){t.log(n.inspect($)+"\n")},"dir"],[function _($){if(!$){var e=u.call(arguments,1);r.ok(!1,n.format.apply(null,e))}},"assert"]],s=0;s0&&this._events[_].length>e&&(this._events[_].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[_].length),"function"==typeof console.trace&&console.trace()),this},t.prototype.on=t.prototype.addListener,t.prototype.once=function(_,$){if(!n($))throw TypeError("listener must be a function");var e=!1;function t(){this.removeListener(_,t),e||(e=!0,$.apply(this,arguments))}return t.listener=$,this.on(_,t),this},t.prototype.removeListener=function(_,$){var e,t,i,u;if(!n($))throw TypeError("listener must be a function");if(!this._events||!this._events[_])return this;if(i=(e=this._events[_]).length,t=-1,e===$||n(e.listener)&&e.listener===$)delete this._events[_],this._events.removeListener&&this.emit("removeListener",_,$);else if(r(e)){for(u=i;u-- >0;)if(e[u]===$||e[u].listener&&e[u].listener===$){t=u;break}if(t<0)return this;1===e.length?(e.length=0,delete this._events[_]):e.splice(t,1),this._events.removeListener&&this.emit("removeListener",_,$)}return this},t.prototype.removeAllListeners=function(_){var $,e;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[_]&&delete this._events[_],this;if(0===arguments.length){for($ in this._events)"removeListener"!==$&&this.removeAllListeners($);return this.removeAllListeners("removeListener"),this._events={},this}if(n(e=this._events[_]))this.removeListener(_,e);else for(;e.length;)this.removeListener(_,e[e.length-1]);return delete this._events[_],this},t.prototype.listeners=function(_){var $;return this._events&&this._events[_]?n(this._events[_])?[this._events[_]]:this._events[_].slice():[]},t.listenerCount=function(_,$){var e;return _._events&&_._events[$]?n(_._events[$])?1:_._events[$].length:0}},{}],12:[function(_,$,e){(function(_){(function(){var t,n="Expected a function",r="__lodash_hash_undefined__",i="__lodash_placeholder__",u=1/0,o=0/0,a=4294967295,s=a-1,c=a>>>1,l=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],f="[object Arguments]",d="[object Array]",p="[object Boolean]",h="[object Date]",v="[object Error]",g="[object Function]",m="[object GeneratorFunction]",y="[object Map]",k="[object Number]",x="[object Object]",b="[object Promise]",E="[object RegExp]",A="[object Set]",S="[object String]",C="[object Symbol]",F="[object WeakMap]",w="[object ArrayBuffer]",W="[object DataView]",D="[object Float32Array]",B="[object Float64Array]",L="[object Int8Array]",M="[object Int16Array]",T="[object Int32Array]",j="[object Uint8Array]",G="[object Uint8ClampedArray]",I="[object Uint16Array]",P="[object Uint32Array]",O=/\b__p \+= '';/g,V=/\b(__p \+=) '' \+/g,R=/(__e\(.*?\)|\b__t\)) \+\n'';/g,U=/&(?:amp|lt|gt|quot|#39);/g,z=/[&<>"']/g,H=RegExp(U.source),N=RegExp(z.source),q=/<%-([\s\S]+?)%>/g,K=/<%([\s\S]+?)%>/g,Z=/<%=([\s\S]+?)%>/g,J=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,X=/^\w*$/,Q=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Y=/[\\^$.*+?()[\]{}|]/g,_2=RegExp(Y.source),_1=/^\s+/,_0=/\s/,__=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,_3=/\{\n\/\* \[wrapped with (.+)\] \*/,_$=/,? & /,_4=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,_5=/[()=,{}\[\]\/\s]/,_6=/\\(\\)?/g,_7=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,_e=/\w*$/,_t=/^[-+]0x[0-9a-f]+$/i,_n=/^0b[01]+$/i,_r=/^\[object .+?Constructor\]$/,_i=/^0o[0-7]+$/i,_u=/^(?:0|[1-9]\d*)$/,_o=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,_a=/($^)/,_s=/['\n\r\u2028\u2029\\]/g,_c="\ud800-\udfff",_l="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",_f="\\u2700-\\u27bf",_d="a-z\\xdf-\\xf6\\xf8-\\xff",_p="A-Z\\xc0-\\xd6\\xd8-\\xde",_h="\\ufe0e\\ufe0f",_v="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",_g="['’]",_m="["+_v+"]",_y="["+_l+"]",_k="["+_d+"]",_x="[^"+_c+_v+"\\d+"+_f+_d+_p+"]",_b="\ud83c[\udffb-\udfff]",_E="[^"+_c+"]",_8="(?:\ud83c[\udde6-\uddff]){2}",_A="[\ud800-\udbff][\udc00-\udfff]",_S="["+_p+"]",_C="\\u200d",_F="(?:"+_k+"|"+_x+")",_w="(?:"+_g+"(?:d|ll|m|re|s|t|ve))?",_W="(?:"+_g+"(?:D|LL|M|RE|S|T|VE))?",_D="(?:"+_y+"|"+_b+")?",_B="["+_h+"]?",_L="(?:"+_C+"(?:"+[_E,_8,_A].join("|")+")"+_B+_D+")*",_M=_B+_D+_L,_T="(?:"+["["+_f+"]",_8,_A].join("|")+")"+_M,_j="(?:"+[_E+_y+"?",_y,_8,_A,"["+_c+"]"].join("|")+")",_G=RegExp(_g,"g"),_I=RegExp(_y,"g"),_P=RegExp(_b+"(?="+_b+")|"+_j+_M,"g"),_O=RegExp([_S+"?"+_k+"+"+_w+"(?="+[_m,_S,"$"].join("|")+")","(?:"+_S+"|"+_x+")+"+_W+"(?="+[_m,_S+_F,"$"].join("|")+")",_S+"?"+_F+"+"+_w,_S+"+"+_W,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",_T].join("|"),"g"),_V=RegExp("["+_C+_c+_l+_h+"]"),_R=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,_U=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],_z=-1,_H={};_H[D]=_H[B]=_H[L]=_H[M]=_H[T]=_H[j]=_H[G]=_H[I]=_H[P]=!0,_H[f]=_H[d]=_H[w]=_H[p]=_H[W]=_H[h]=_H[v]=_H[g]=_H[y]=_H[k]=_H[x]=_H[E]=_H[A]=_H[S]=_H[F]=!1;var _N={};_N[f]=_N[d]=_N[w]=_N[W]=_N[p]=_N[h]=_N[D]=_N[B]=_N[L]=_N[M]=_N[T]=_N[y]=_N[k]=_N[x]=_N[E]=_N[A]=_N[S]=_N[C]=_N[j]=_N[G]=_N[I]=_N[P]=!0,_N[v]=_N[g]=_N[F]=!1;var _q={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},_9=parseFloat,_K=parseInt,_Z="object"==typeof _&&_&&_.Object===Object&&_,_J="object"==typeof self&&self&&self.Object===Object&&self,_X=_Z||_J||Function("return this")(),_Q="object"==typeof e&&e&&!e.nodeType&&e,_Y=_Q&&"object"==typeof $&&$&&!$.nodeType&&$,$2=_Y&&_Y.exports===_Q,$1=$2&&_Z.process,$0=function(){try{var _=_Y&&_Y.require&&_Y.require("util").types;if(_)return _;return $1&&$1.binding&&$1.binding("util")}catch($){}}(),$_=$0&&$0.isArrayBuffer,$3=$0&&$0.isDate,$$=$0&&$0.isMap,$4=$0&&$0.isRegExp,$5=$0&&$0.isSet,$6=$0&&$0.isTypedArray;function $7(_,$,e){switch(e.length){case 0:return _.call($);case 1:return _.call($,e[0]);case 2:return _.call($,e[0],e[1]);case 3:return _.call($,e[0],e[1],e[2])}return _.apply($,e)}function $e(_,$,e,t){for(var n=-1,r=null==_?0:_.length;++n-1}function $o(_,$,e){for(var t=-1,n=null==_?0:_.length;++t-1;);return e}function $W(_,$){for(var e=_.length;e--&&$v($,_[e],0)>-1;);return e}var $D=$x({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),$B=$x({"&":"&","<":"<",">":">",'"':""","'":"'"});function $L(_){return"\\"+_q[_]}function $M(_){return _V.test(_)}function $T(_){var $=-1,e=Array(_.size);return _.forEach(function(_,t){e[++$]=[t,_]}),e}function $j(_,$){return function(e){return _($(e))}}function $G(_,$){for(var e=-1,t=_.length,n=0,r=[];++e",""":'"',"'":"'"}),$U=function _($){var e,_0=($=null==$?_X:$U.defaults(_X.Object(),$,$U.pick(_X,_U))).Array,_c=$.Date,_l=$.Error,_f=$.Function,_d=$.Math,_p=$.Object,_h=$.RegExp,_v=$.String,_g=$.TypeError,_m=_0.prototype,_y=_f.prototype,_k=_p.prototype,_x=$["__core-js_shared__"],_b=_y.toString,_E=_k.hasOwnProperty,_8=0,_A=(e=/[^.]+$/.exec(_x&&_x.keys&&_x.keys.IE_PROTO||""))?"Symbol(src)_1."+e:"",_S=_k.toString,_C=_b.call(_p),_F=_X._,_w=_h("^"+_b.call(_E).replace(Y,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),_W=$2?$.Buffer:t,_D=$.Symbol,_B=$.Uint8Array,_L=_W?_W.allocUnsafe:t,_M=$j(_p.getPrototypeOf,_p),_T=_p.create,_j=_k.propertyIsEnumerable,_P=_m.splice,_V=_D?_D.isConcatSpreadable:t,_q=_D?_D.iterator:t,_Z=_D?_D.toStringTag:t,_J=function(){try{var _=na(_p,"defineProperty");return _({},"",{}),_}catch($){}}(),_Q=$.clearTimeout!==_X.clearTimeout&&$.clearTimeout,_Y=_c&&_c.now!==_X.Date.now&&_c.now,$1=$.setTimeout!==_X.setTimeout&&$.setTimeout,$0=_d.ceil,$d=_d.floor,$x=_p.getOwnPropertySymbols,$z=_W?_W.isBuffer:t,$H=$.isFinite,$N=_m.join,$q=$j(_p.keys,_p),$9=_d.max,$K=_d.min,$Z=_c.now,$J=$.parseInt,$X=_d.random,$Q=_m.reverse,$Y=na($,"DataView"),e2=na($,"Map"),e1=na($,"Promise"),e0=na($,"Set"),e_=na($,"WeakMap"),e3=na(_p,"create"),e$=e_&&new e_,e4={},e5=nG($Y),e6=nG(e2),e7=nG(e1),ee=nG(e0),et=nG(e_),en=_D?_D.prototype:t,er=en?en.valueOf:t,ei=en?en.toString:t;function eu(_){if(rK(_)&&!rG(_)&&!(_ instanceof ec)){if(_ instanceof es)return _;if(_E.call(_,"__wrapped__"))return nI(_)}return new es(_)}var eo=function(){function _(){}return function($){if(!r9($))return{};if(_T)return _T($);_.prototype=$;var e=new _;return _.prototype=t,e}}();function ea(){}function es(_,$){this.__wrapped__=_,this.__actions__=[],this.__chain__=!!$,this.__index__=0,this.__values__=t}function ec(_){this.__wrapped__=_,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=a,this.__views__=[]}function el(_){var $=-1,e=null==_?0:_.length;for(this.clear();++$=$?_:$)),_}function eF(_,$,e,n,r,i){var u,o=1&$,a=2&$;if(e&&(u=r?e(_,n,r,i):e(_)),u!==t)return u;if(!r9(_))return _;var s=rG(_);if(s){if(u=(c=_,l=c.length,d=new c.constructor(l),l&&"string"==typeof c[0]&&_E.call(c,"index")&&(d.index=c.index,d.input=c.input),d),!o)return tj(_,u)}else{var c,l,d,v,b,F,O,V,R,U=nl(_),z=U==g||U==m;if(rV(_))return tW(_,o);if(U==x||U==f||z&&!r){if(u=a||z?{}:nd(_),!o){return a?(v=_,b=(V=u,R=_,V&&tG(R,im(R),V)),tG(v,nc(v),b)):(F=_,O=e8(u,_),tG(F,ns(F),O))}}else{if(!_N[U])return r?_:{};u=function _($,e,t){var n,r,i,u,o,a,s=$.constructor;switch(e){case w:return tD($);case p:case h:return new s(+$);case W:return n=$,i=(r=t)?tD(n.buffer):n.buffer,new n.constructor(i,n.byteOffset,n.byteLength);case D:case B:case L:case M:case T:case j:case G:case I:case P:return tB($,t);case y:return new s;case k:case S:return new s($);case E:return(o=new(u=$).constructor(u.source,_e.exec(u))).lastIndex=u.lastIndex,o;case A:return new s;case C:return a=$,er?_p(er.call(a)):{}}}(_,U,o)}}i||(i=new eh);var H=i.get(_);if(H)return H;i.set(_,u),rY(_)?_.forEach(function(t){u.add(eF(t,$,e,t,_,i))}):rZ(_)&&_.forEach(function(t,n){u.set(n,eF(t,$,e,n,_,i))});var N=s?t:(4&$?a?ne:n7:a?im:ig)(_);return $t(N||_,function(t,n){N&&(t=_[n=t]),ex(u,n,eF(t,$,e,n,_,i))}),u}function ew(_,$,e){var n=e.length;if(null==_)return!n;for(_=_p(_);n--;){var r=e[n],i=$[r],u=_[r];if(u===t&&!(r in _)||!i(u))return!1}return!0}function eW(_,$,e){if("function"!=typeof _)throw new _g(n);return nC(function(){_.apply(t,e)},$)}function eD(_,$,e,t){var n=-1,r=$u,i=!0,u=_.length,o=[],a=$.length;if(!u)return o;e&&($=$a($,$S(e))),t?(r=$o,i=!1):$.length>=200&&(r=$F,i=!1,$=new ep($));outer:for(;++n-1},ef.prototype.set=function _($,e){var t=this.__data__,n=eb(t,$);return n<0?(++this.size,t.push([$,e])):t[n][1]=e,this},ed.prototype.clear=function _(){this.size=0,this.__data__={hash:new el,map:new(e2||ef),string:new el}},ed.prototype.delete=function _($){var e=nu(this,$).delete($);return this.size-=e?1:0,e},ed.prototype.get=function _($){return nu(this,$).get($)},ed.prototype.has=function _($){return nu(this,$).has($)},ed.prototype.set=function _($,e){var t=nu(this,$),n=t.size;return t.set($,e),this.size+=t.size==n?0:1,this},ep.prototype.add=ep.prototype.push=function _($){return this.__data__.set($,r),this},ep.prototype.has=function _($){return this.__data__.has($)},eh.prototype.clear=function _(){this.__data__=new ef,this.size=0},eh.prototype.delete=function _($){var e=this.__data__,t=e.delete($);return this.size=e.size,t},eh.prototype.get=function _($){return this.__data__.get($)},eh.prototype.has=function _($){return this.__data__.has($)},eh.prototype.set=function _($,e){var t=this.__data__;if(t instanceof ef){var n=t.__data__;if(!e2||n.length<199)return n.push([$,e]),this.size=++t.size,this;t=this.__data__=new ed(n)}return t.set($,e),this.size=t.size,this};var eB=tO(eO),eL=tO(eV,!0);function eM(_,$){var e=!0;return eB(_,function(_,t,n){return e=!!$(_,t,n)}),e}function eT(_,$,e){for(var n=-1,r=_.length;++n0&&e(u)?$>1?eG(u,$-1,e,t,n):$s(n,u):t||(n[n.length]=u)}return n}var eI=tV(),eP=tV(!0);function eO(_,$){return _&&eI(_,$,ig)}function eV(_,$){return _&&eP(_,$,ig)}function eR(_,$){return $i($,function($){return rH(_[$])})}function eU(_,$){$=tC($,_);for(var e=0,n=$.length;null!=_&&e$}function eq(_,$){return null!=_&&_E.call(_,$)}function e9(_,$){return null!=_&&$ in _p(_)}function eK(_,$,e){for(var n=e?$o:$u,r=_[0].length,i=_.length,u=i,o=_0(i),a=1/0,s=[];u--;){var c=_[u];u&&$&&(c=$a(c,$S($))),a=$K(c.length,a),o[u]=!e&&($||r>=120&&c.length>=120)?new ep(u&&c):t}c=_[0];var l=-1,f=o[0];outer:for(;++l=o)return a;return a*("desc"==t[n]?-1:1)}}return $.index-e.index}(_,$,e)})}function t7(_,$,e){for(var t=-1,n=$.length,r={};++t-1;)u!==_&&_P.call(u,o,1),_P.call(_,o,1);return _}function tt(_,$){for(var e=_?$.length:0,t=e-1;e--;){var n=$[e];if(e==t||n!==r){var r=n;nh(n)?_P.call(_,n,1):ty(_,n)}}return _}function tn(_,$){return _+$d($X()*($-_+1))}function tr(_,$){var e="";if(!_||$<1||$>9007199254740991)return e;do $%2&&(e+=_),($=$d($/2))&&(_+=_);while($);return e}function ti(_,$){return nF(nE(_,$,iU),_+"")}function tu(_){return eg(iS(_))}function to(_,$){var e=iS(_);return nD(e,eC($,0,e.length))}function ta(_,$,e,n){if(!r9(_))return _;$=tC($,_);for(var r=-1,i=$.length,u=i-1,o=_;null!=o&&++rn?0:n+$),(e=e>n?n:e)<0&&(e+=n),n=$>e?0:e-$>>>0,$>>>=0;for(var r=_0(n);++t>>1,i=_[r];null!==i&&!i1(i)&&(e?i<=$:i<$)?t=r+1:n=r}return n}return tp(_,$,iU,e)}function tp(_,$,e,n){var r=0,i=null==_?0:_.length;if(0===i)return 0;for(var u=($=e($))!=$,o=null===$,a=i1($),c=$===t;r=200){var a=$?null:n1(_);if(a)return $I(a);i=!1,n=$F,o=new ep}else o=$?[]:u;outer:for(;++t=n?_:tl(_,$,e)}var tw=_Q||function(_){return _X.clearTimeout(_)};function tW(_,$){if($)return _.slice();var e=_.length,t=_L?_L(e):new _.constructor(e);return _.copy(t),t}function tD(_){var $=new _.constructor(_.byteLength);return new _B($).set(new _B(_)),$}function tB(_,$){var e=$?tD(_.buffer):_.buffer;return new _.constructor(e,_.byteOffset,_.length)}function tL(_,$){if(_!==$){var e=_!==t,n=null===_,r=_==_,i=i1(_),u=$!==t,o=null===$,a=$==$,s=i1($);if(!o&&!s&&!i&&_>$||i&&u&&a&&!o&&!s||n&&u&&a||!e&&a||!r)return 1;if(!n&&!i&&!s&&_<$||s&&e&&r&&!n&&!i||o&&e&&r||!u&&r||!a)return -1}return 0}function tM(_,$,e,t){for(var n=-1,r=_.length,i=e.length,u=-1,o=$.length,a=$9(r-i,0),s=_0(o+a),c=!t;++u1?e[r-1]:t,u=r>2?e[2]:t;for(i=_.length>3&&"function"==typeof i?(r--,i):t,u&&nv(e[0],e[1],u)&&(i=r<3?t:i,r=1),$=_p($);++n-1?r[i?$[u]:u]:t}}function tN(_){return n6(function($){var e=$.length,r=e,i=es.prototype.thru;for(_&&$.reverse();r--;){var u=$[r];if("function"!=typeof u)throw new _g(n);if(i&&!o&&"wrapper"==nn(u))var o=new es([],!0)}for(r=o?r:e;++r1&&m.reverse(),c&&ao))return!1;var s=i.get(_),c=i.get($);if(s&&c)return s==$&&c==_;var l=-1,f=!0,d=2&e?new ep:t;for(i.set(_,$),i.set($,_);++l-1&&_%1==0&&_<$}function nv(_,$,e){if(!r9(e))return!1;var t=typeof $;return("number"==t?!!(rP(e)&&nh($,e.length)):"string"==t&&$ in e)&&rL(e[$],_)}function ng(_,$){if(rG(_))return!1;var e=typeof _;return!!("number"==e||"symbol"==e||"boolean"==e||null==_||i1(_))||X.test(_)||!J.test(_)||null!=$&&_ in _p($)}function nm(_){var $=nn(_),e=eu[$];if("function"!=typeof e||!($ in ec.prototype))return!1;if(_===e)return!0;var t=nt(e);return!!t&&_===t[0]}($Y&&nl(new $Y(new ArrayBuffer(1)))!=W||e2&&nl(new e2)!=y||e1&&nl(e1.resolve())!=b||e0&&nl(new e0)!=A||e_&&nl(new e_)!=F)&&(nl=function(_){var $=eH(_),e=$==x?_.constructor:t,n=e?nG(e):"";if(n)switch(n){case e5:return W;case e6:return y;case e7:return b;case ee:return A;case et:return F}return $});var ny=_x?rH:u1;function nk(_){var $=_&&_.constructor;return _===("function"==typeof $&&$.prototype||_k)}function nx(_){return _==_&&!r9(_)}function nb(_,$){return function(e){return null!=e&&e[_]===$&&($!==t||_ in _p(e))}}function nE(_,$,e){return $=$9($===t?_.length-1:$,0),function(){for(var t=arguments,n=-1,r=$9(t.length-$,0),i=_0(r);++n1?"& ":"")+e[n],e=e.join(t>2?", ":" "),$.replace(__,"{\n/* [wrapped with "+e+"] */\n")}(u,(r=(n=(t=u).match(_3))?n[1].split(_$):[],i=e,$t(l,function(_){var $="_."+_[0];i&_[1]&&!$u(r,$)&&r.push($)}),r.sort())))}function nW(_){var $=0,e=0;return function(){var n=$Z(),r=16-(n-e);if(e=n,r>0){if(++$>=800)return arguments[0]}else $=0;return _.apply(t,arguments)}}function nD(_,$){var e=-1,n=_.length,r=n-1;for($=$===t?n:$;++e<$;){var i=tn(e,r),u=_[i];_[i]=_[e],_[e]=u}return _.length=$,_}var nB,nL,nM,nT=(nM=(nL=rC(nB=function(_){var $=[];return 46===_.charCodeAt(0)&&$.push(""),_.replace(Q,function(_,e,t,n){$.push(t?n.replace(_6,"$1"):e||_)}),$},function(_){return 500===nM.size&&nM.clear(),_})).cache,nL);function nj(_){if("string"==typeof _||i1(_))return _;var $=_+"";return"0"==$&&1/_==-u?"-0":$}function nG(_){if(null!=_){try{return _b.call(_)}catch($){}try{return _+""}catch(e){}}return""}function nI(_){if(_ instanceof ec)return _.clone();var $=new es(_.__wrapped__,_.__chain__);return $.__actions__=tj(_.__actions__),$.__index__=_.__index__,$.__values__=_.__values__,$}var nP=ti(function(_,$){return rO(_)?eD(_,eG($,1,rO,!0)):[]}),nO=ti(function(_,$){var e=nK($);return rO(e)&&(e=t),rO(_)?eD(_,eG($,1,rO,!0),ni(e,2)):[]}),nV=ti(function(_,$){var e=nK($);return rO(e)&&(e=t),rO(_)?eD(_,eG($,1,rO,!0),t,e):[]});function nR(_,$,e){var t=null==_?0:_.length;if(!t)return -1;var n=null==e?0:i5(e);return n<0&&(n=$9(t+n,0)),$h(_,ni($,3),n)}function nU(_,$,e){var n=null==_?0:_.length;if(!n)return -1;var r=n-1;return e!==t&&(r=i5(e),r=e<0?$9(n+r,0):$K(r,n-1)),$h(_,ni($,3),r,!0)}function nz(_){return(null==_?0:_.length)?eG(_,1):[]}function nH(_){return _&&_.length?_[0]:t}var nN=ti(function(_){var $=$a(_,tA);return $.length&&$[0]===_[0]?eK($):[]}),nq=ti(function(_){var $=nK(_),e=$a(_,tA);return $===nK(e)?$=t:e.pop(),e.length&&e[0]===_[0]?eK(e,ni($,2)):[]}),n9=ti(function(_){var $=nK(_),e=$a(_,tA);return($="function"==typeof $?$:t)&&e.pop(),e.length&&e[0]===_[0]?eK(e,t,$):[]});function nK(_){var $=null==_?0:_.length;return $?_[$-1]:t}var nZ=ti(nJ);function nJ(_,$){return _&&_.length&&$&&$.length?te(_,$):_}var nX=n6(function(_,$){var e=null==_?0:_.length,t=eS(_,$);return tt(_,$a($,function(_){return nh(_,e)?+_:_}).sort(tL)),t});function nQ(_){return null==_?_:$Q.call(_)}function nY(_,$){return td(_,$)}var r2=ti(function(_){return tm(eG(_,1,rO,!0))}),r1=ti(function(_){var $=nK(_);return rO($)&&($=t),tm(eG(_,1,rO,!0),ni($,2))}),r0=ti(function(_){var $=nK(_);return $="function"==typeof $?$:t,tm(eG(_,1,rO,!0),t,$)});function r_(_){if(!(_&&_.length))return[];var $=0;return _=$i(_,function(_){if(rO(_))return $=$9(_.length,$),!0}),$8($,function($){return $a(_,$k($))})}function r3(_,$){if(!(_&&_.length))return[];var e=r_(_);return null==$?e:$a(e,function(_){return $7($,t,_)})}var r$=ti(function(_,$){return rO(_)?eD(_,$):[]}),r4=ti(function(_){return tE($i(_,rO))}),r5=ti(function(_){var $=nK(_);return rO($)&&($=t),tE($i(_,rO),ni($,2))}),r6=ti(function(_){var $=nK(_);return $="function"==typeof $?$:t,tE($i(_,rO),t,$)}),r7=ti(r_),re=ti(function(_){var $=_.length,e=$>1?_[$-1]:t;return e="function"==typeof e?(_.pop(),e):t,r3(_,e)});function rt(_){var $=eu(_);return $.__chain__=!0,$}function rn(_,$){return $(_),_}function rr(_,$){return $(_)}var ri=n6(function(_){var $=_.length,e=$?_[0]:0,n=this.__wrapped__,r=function($){return eS($,_)};return!($>1)&&!this.__actions__.length&&n instanceof ec&&nh(e)?((n=n.slice(e,+e+($?1:0))).__actions__.push({func:rr,args:[r],thisArg:t}),new es(n,this.__chain__).thru(function(_){return $&&!_.length&&_.push(t),_})):this.thru(r)}),ru=tI(function(_,$,e){_E.call(_,e)?++_[e]:eA(_,e,1)}),ro=tH(nR),ra=tH(nU);function rs(_,$){return(rG(_)?$t:eB)(_,ni($,3))}function rc(_,$){return(rG(_)?$n:eL)(_,ni($,3))}var rl=tI(function(_,$,e){_E.call(_,e)?_[e].push($):eA(_,e,[$])}),rf=ti(function(_,$,e){var t=-1,n="function"==typeof $,r=rP(_)?_0(_.length):[];return eB(_,function(_){r[++t]=n?$7($,_,e):eZ(_,$,e)}),r}),rd=tI(function(_,$,e){eA(_,e,$)});function rp(_,$){return(rG(_)?$a:t_)(_,ni($,3))}var rh=tI(function(_,$,e){_[e?0:1].push($)},function(){return[[],[]]}),rv=ti(function(_,$){if(null==_)return[];var e=$.length;return e>1&&nv(_,$[0],$[1])?$=[]:e>2&&nv($[0],$[1],$[2])&&($=[$[0]]),t6(_,eG($,1),[])}),rg=_Y||function(){return _X.Date.now()};function rm(_,$,e){return $=e?t:$,$=_&&null==$?_.length:$,n_(_,128,t,t,t,t,$)}function ry(_,$){var e;if("function"!=typeof $)throw new _g(n);return _=i5(_),function(){return--_>0&&(e=$.apply(this,arguments)),_<=1&&($=t),e}}var rk=ti(function(_,$,e){var t=1;if(e.length){var n=$G(e,nr(rk));t|=32}return n_(_,t,$,e,n)}),rx=ti(function(_,$,e){var t=3;if(e.length){var n=$G(e,nr(rx));t|=32}return n_($,t,_,e,n)});function rb(_,$,e){$=e?t:$;var n=n_(_,8,t,t,t,t,t,$);return n.placeholder=rb.placeholder,n}function rE(_,$,e){$=e?t:$;var n=n_(_,16,t,t,t,t,t,$);return n.placeholder=rE.placeholder,n}function r8(_,$,e){var r,i,u,o,a,s,c=0,l=!1,f=!1,d=!0;if("function"!=typeof _)throw new _g(n);function p($){var e=r,n=i;return r=i=t,c=$,o=_.apply(n,e)}function h(_){var e=_-s,n=_-c;return s===t||e>=$||e<0||f&&n>=u}function v(){var _,e,t,n,r=rg();if(h(r))return g(r);a=nC(v,(e=(_=r)-s,t=_-c,n=$-e,f?$K(n,u-t):n))}function g(_){return(a=t,d&&r)?p(_):(r=i=t,o)}function m(){var _,e=rg(),n=h(e);if(r=arguments,i=this,s=e,n){if(a===t)return c=_=s,a=nC(v,$),l?p(_):o;if(f)return tw(a),a=nC(v,$),p(s)}return a===t&&(a=nC(v,$)),o}return $=i7($)||0,r9(e)&&(l=!!e.leading,u=(f="maxWait"in e)?$9(i7(e.maxWait)||0,$):u,d="trailing"in e?!!e.trailing:d),m.cancel=function _(){a!==t&&tw(a),c=0,r=s=i=a=t},m.flush=function _(){return a===t?o:g(rg())},m}var rA=ti(function(_,$){return eW(_,1,$)}),rS=ti(function(_,$,e){return eW(_,i7($)||0,e)});function rC(_,$){if("function"!=typeof _||null!=$&&"function"!=typeof $)throw new _g(n);var e=function(){var t=arguments,n=$?$.apply(this,t):t[0],r=e.cache;if(r.has(n))return r.get(n);var i=_.apply(this,t);return e.cache=r.set(n,i)||r,i};return e.cache=new(rC.Cache||ed),e}function rF(_){if("function"!=typeof _)throw new _g(n);return function(){var $=arguments;switch($.length){case 0:return!_.call(this);case 1:return!_.call(this,$[0]);case 2:return!_.call(this,$[0],$[1]);case 3:return!_.call(this,$[0],$[1],$[2])}return!_.apply(this,$)}}rC.Cache=ed;var rw=ti(function(_,$){var e=($=1==$.length&&rG($[0])?$a($[0],$S(ni())):$a(eG($,1),$S(ni()))).length;return ti(function(t){for(var n=-1,r=$K(t.length,e);++n=$}),rj=eJ(function(){return arguments}())?eJ:function(_){return rK(_)&&_E.call(_,"callee")&&!_j.call(_,"callee")},rG=_0.isArray,rI=$_?$S($_):function _($){return rK($)&&eH($)==w};function rP(_){return null!=_&&rq(_.length)&&!rH(_)}function rO(_){return rK(_)&&rP(_)}var rV=$z||u1,rR=$3?$S($3):function _($){return rK($)&&eH($)==h};function rU(_,$){return eX(_,$)}function rz(_){if(!rK(_))return!1;var $=eH(_);return $==v||"[object DOMException]"==$||"string"==typeof _.message&&"string"==typeof _.name&&!rX(_)}function rH(_){if(!r9(_))return!1;var $=eH(_);return $==g||$==m||"[object AsyncFunction]"==$||"[object Proxy]"==$}function rN(_){return"number"==typeof _&&_==i5(_)}function rq(_){return"number"==typeof _&&_>-1&&_%1==0&&_<=9007199254740991}function r9(_){var $=typeof _;return null!=_&&("object"==$||"function"==$)}function rK(_){return null!=_&&"object"==typeof _}var rZ=$$?$S($$):function _($){return rK($)&&nl($)==y};function rJ(_){return"number"==typeof _||rK(_)&&eH(_)==k}function rX(_){if(!rK(_)||eH(_)!=x)return!1;var $=_M(_);if(null===$)return!0;var e=_E.call($,"constructor")&&$.constructor;return"function"==typeof e&&e instanceof e&&_b.call(e)==_C}var rQ=$4?$S($4):function _($){return rK($)&&eH($)==E},rY=$5?$S($5):function _($){return rK($)&&nl($)==A};function i2(_){return"string"==typeof _||!rG(_)&&rK(_)&&eH(_)==S}function i1(_){return"symbol"==typeof _||rK(_)&&eH(_)==C}var i0=$6?$S($6):function _($){return rK($)&&rq($.length)&&!!_H[eH($)]},i_=tQ(t0),i3=tQ(function(_,$){return _<=$});function i$(_){if(!_)return[];if(rP(_))return i2(_)?$O(_):tj(_);if(_q&&_[_q])return function _($){for(var e,t=[];!(e=$.next()).done;)t.push(e.value);return t}(_[_q]());var $=nl(_);return($==y?$T:$==A?$I:iS)(_)}function i4(_){return _?(_=i7(_))===u||_===-u?(_<0?-1:1)*17976931348623157e292:_==_?_:0:0===_?_:0}function i5(_){var $=i4(_),e=$%1;return $==$?e?$-e:$:0}function i6(_){return _?eC(i5(_),0,a):0}function i7(_){if("number"==typeof _)return _;if(i1(_))return o;if(r9(_)){var $="function"==typeof _.valueOf?_.valueOf():_;_=r9($)?$+"":$}if("string"!=typeof _)return 0===_?_:+_;_=$A(_);var e=_n.test(_);return e||_i.test(_)?_K(_.slice(2),e?2:8):_t.test(_)?o:+_}function ie(_){return tG(_,im(_))}function it(_){return null==_?"":tg(_)}var ir=tP(function(_,$){if(nk($)||rP($)){tG($,ig($),_);return}for(var e in $)_E.call($,e)&&ex(_,e,$[e])}),ii=tP(function(_,$){tG($,im($),_)}),iu=tP(function(_,$,e,t){tG($,im($),_,t)}),io=tP(function(_,$,e,t){tG($,ig($),_,t)}),ia=n6(eS),is=ti(function(_,$){_=_p(_);var e=-1,n=$.length,r=n>2?$[2]:t;for(r&&nv($[0],$[1],r)&&(n=1);++e1),$}),tG(_,ne(_),e),t&&(e=eF(e,7,n4));for(var n=$.length;n--;)ty(e,$[n]);return e}),ib=n6(function(_,$){var e,t;return null==_?{}:(e=_,t7(e,t=$,function(_,$){return id(e,$)}))});function iE(_,$){if(null==_)return{};var e=$a(ne(_),function(_){return[_]});return $=ni($),t7(_,e,function(_,e){return $(_,e[0])})}var i8=n0(ig),iA=n0(im);function iS(_){return null==_?[]:$C(_,ig(_))}var iC=tU(function(_,$,e){return $=$.toLowerCase(),_+(e?iF($):$)});function iF(_){return ij(it(_).toLowerCase())}function iw(_){return(_=it(_))&&_.replace(_o,$D).replace(_I,"")}var iW=tU(function(_,$,e){return _+(e?"-":"")+$.toLowerCase()}),iD=tU(function(_,$,e){return _+(e?" ":"")+$.toLowerCase()}),iB=tR("toLowerCase"),iL=tU(function(_,$,e){return _+(e?"_":"")+$.toLowerCase()}),iM=tU(function(_,$,e){return _+(e?" ":"")+ij($)}),iT=tU(function(_,$,e){return _+(e?" ":"")+$.toUpperCase()}),ij=tR("toUpperCase");function iG(_,$,e){if(_=it(_),t===($=e?t:$)){var n,r,i;return(n=_,_R.test(n))?(r=_).match(_O)||[]:(i=_).match(_4)||[]}return _.match($)||[]}var iI=ti(function(_,$){try{return $7(_,t,$)}catch(e){return rz(e)?e:new _l(e)}}),iP=n6(function(_,$){return $t($,function($){$=nj($),eA(_,$,rk(_[$],_))}),_});function iO(_){return function(){return _}}var iV=tN(),iR=tN(!0);function iU(_){return _}function iz(_){return t2("function"==typeof _?_:eF(_,1))}var iH=ti(function(_,$){return function(e){return eZ(e,_,$)}}),iN=ti(function(_,$){return function(e){return eZ(_,e,$)}});function iq(_,$,e){var t=ig($),n=eR($,t);null!=e||r9($)&&(n.length||!t.length)||(e=$,$=_,_=this,n=eR($,ig($)));var r=!(r9(e)&&"chain"in e)||!!e.chain,i=rH(_);return $t(n,function(e){var t=$[e];_[e]=t,i&&(_.prototype[e]=function(){var $=this.__chain__;if(r||$){var e=_(this.__wrapped__);return(e.__actions__=tj(this.__actions__)).push({func:t,args:arguments,thisArg:_}),e.__chain__=$,e}return t.apply(_,$s([this.value()],arguments))})}),_}function i9(){}var iK=tZ($a),iZ=tZ($r),iJ=tZ($f);function iX(_){var $;return ng(_)?$k(nj(_)):($=_,function(_){return eU(_,$)})}var iQ=tX(),iY=tX(!0);function u2(){return[]}function u1(){return!1}function u0(){return{}}function u_(){return""}function u3(){return!0}var u$,u4=tK(function(_,$){return _+$},0),u5=n2("ceil"),u6=tK(function(_,$){return _/$},1),u7=n2("floor"),ue=tK(function(_,$){return _*$},1),ut=n2("round"),un=tK(function(_,$){return _-$},0);return eu.after=function _($,e){if("function"!=typeof e)throw new _g(n);return $=i5($),function(){if(--$<1)return e.apply(this,arguments)}},eu.ary=rm,eu.assign=ir,eu.assignIn=ii,eu.assignInWith=iu,eu.assignWith=io,eu.at=ia,eu.before=ry,eu.bind=rk,eu.bindAll=iP,eu.bindKey=rx,eu.castArray=function _(){if(!arguments.length)return[];var $=arguments[0];return rG($)?$:[$]},eu.chain=rt,eu.chunk=function _($,e,n){e=(n?nv($,e,n):e===t)?1:$9(i5(e),0);var r=null==$?0:$.length;if(!r||e<1)return[];for(var i=0,u=0,o=_0($0(r/e));ii?0:i+n),(r=r===t||r>i?i:i5(r))<0&&(r+=i),r=n>r?0:i6(r);n>>0)?($=it($))&&("string"==typeof e||null!=e&&!rQ(e))&&!(e=tg(e))&&$M($)?tF($O($),0,n):$.split(e,n):[]},eu.spread=function _($,e){if("function"!=typeof $)throw new _g(n);return e=null==e?0:$9(i5(e),0),ti(function(_){var t=_[e],n=tF(_,0,e);return t&&$s(n,t),$7($,this,n)})},eu.tail=function _($){var e=null==$?0:$.length;return e?tl($,1,e):[]},eu.take=function _($,e,n){return $&&$.length?(e=n||e===t?1:i5(e),tl($,0,e<0?0:e)):[]},eu.takeRight=function _($,e,n){var r=null==$?0:$.length;return r?(e=n||e===t?1:i5(e),tl($,(e=r-e)<0?0:e,r)):[]},eu.takeRightWhile=function _($,e){return $&&$.length?tx($,ni(e,3),!1,!0):[]},eu.takeWhile=function _($,e){return $&&$.length?tx($,ni(e,3)):[]},eu.tap=rn,eu.throttle=function _($,e,t){var r=!0,i=!0;if("function"!=typeof $)throw new _g(n);return r9(t)&&(r="leading"in t?!!t.leading:r,i="trailing"in t?!!t.trailing:i),r8($,e,{leading:r,maxWait:e,trailing:i})},eu.thru=rr,eu.toArray=i$,eu.toPairs=i8,eu.toPairsIn=iA,eu.toPath=function _($){return rG($)?$a($,nj):i1($)?[$]:tj(nT(it($)))},eu.toPlainObject=ie,eu.transform=function _($,e,t){var n=rG($),r=n||rV($)||i0($);if(e=ni(e,4),null==t){var i=$&&$.constructor;t=r?n?new i:[]:r9($)&&rH(i)?eo(_M($)):{}}return(r?$t:eO)($,function(_,$,n){return e(t,_,$,n)}),t},eu.unary=function _($){return rm($,1)},eu.union=r2,eu.unionBy=r1,eu.unionWith=r0,eu.uniq=function _($){return $&&$.length?tm($):[]},eu.uniqBy=function _($,e){return $&&$.length?tm($,ni(e,2)):[]},eu.uniqWith=function _($,e){return e="function"==typeof e?e:t,$&&$.length?tm($,t,e):[]},eu.unset=function _($,e){return null==$||ty($,e)},eu.unzip=r_,eu.unzipWith=r3,eu.update=function _($,e,t){return null==$?$:tk($,e,tS(t))},eu.updateWith=function _($,e,n,r){return r="function"==typeof r?r:t,null==$?$:tk($,e,tS(n),r)},eu.values=iS,eu.valuesIn=function _($){return null==$?[]:$C($,im($))},eu.without=r$,eu.words=iG,eu.wrap=function _($,e){return rW(tS(e),$)},eu.xor=r4,eu.xorBy=r5,eu.xorWith=r6,eu.zip=r7,eu.zipObject=function _($,e){return t8($||[],e||[],ex)},eu.zipObjectDeep=function _($,e){return t8($||[],e||[],ta)},eu.zipWith=re,eu.entries=i8,eu.entriesIn=iA,eu.extend=ii,eu.extendWith=iu,iq(eu,eu),eu.add=u4,eu.attempt=iI,eu.camelCase=iC,eu.capitalize=iF,eu.ceil=u5,eu.clamp=function _($,e,n){return n===t&&(n=e,e=t),n!==t&&(n=(n=i7(n))==n?n:0),e!==t&&(e=(e=i7(e))==e?e:0),eC(i7($),e,n)},eu.clone=function _($){return eF($,4)},eu.cloneDeep=function _($){return eF($,5)},eu.cloneDeepWith=function _($,e){return e="function"==typeof e?e:t,eF($,5,e)},eu.cloneWith=function _($,e){return e="function"==typeof e?e:t,eF($,4,e)},eu.conformsTo=function _($,e){return null==e||ew($,e,ig(e))},eu.deburr=iw,eu.defaultTo=function _($,e){return null==$||$!=$?e:$},eu.divide=u6,eu.endsWith=function _($,e,n){$=it($),e=tg(e);var r=$.length,i=n=n===t?r:eC(i5(n),0,r);return(n-=e.length)>=0&&$.slice(n,i)==e},eu.eq=rL,eu.escape=function _($){return($=it($))&&N.test($)?$.replace(z,$B):$},eu.escapeRegExp=function _($){return($=it($))&&_2.test($)?$.replace(Y,"\\$&"):$},eu.every=function _($,e,n){var r=rG($)?$r:eM;return n&&nv($,e,n)&&(e=t),r($,ni(e,3))},eu.find=ro,eu.findIndex=nR,eu.findKey=function _($,e){return $p($,ni(e,3),eO)},eu.findLast=ra,eu.findLastIndex=nU,eu.findLastKey=function _($,e){return $p($,ni(e,3),eV)},eu.floor=u7,eu.forEach=rs,eu.forEachRight=rc,eu.forIn=function _($,e){return null==$?$:eI($,ni(e,3),im)},eu.forInRight=function _($,e){return null==$?$:eP($,ni(e,3),im)},eu.forOwn=function _($,e){return $&&eO($,ni(e,3))},eu.forOwnRight=function _($,e){return $&&eV($,ni(e,3))},eu.get=il,eu.gt=rM,eu.gte=rT,eu.has=function _($,e){return null!=$&&nf($,e,eq)},eu.hasIn=id,eu.head=nH,eu.identity=iU,eu.includes=function _($,e,t,n){$=rP($)?$:iS($),t=t&&!n?i5(t):0;var r=$.length;return t<0&&(t=$9(r+t,0)),i2($)?t<=r&&$.indexOf(e,t)>-1:!!r&&$v($,e,t)>-1},eu.indexOf=function _($,e,t){var n=null==$?0:$.length;if(!n)return -1;var r=null==t?0:i5(t);return r<0&&(r=$9(n+r,0)),$v($,e,r)},eu.inRange=function _($,e,n){var r,i,u;return e=i4(e),n===t?(n=e,e=0):n=i4(n),r=$=i7($),i=e,r>=$K(i,u=n)&&r<$9(i,u)},eu.invoke=iv,eu.isArguments=rj,eu.isArray=rG,eu.isArrayBuffer=rI,eu.isArrayLike=rP,eu.isArrayLikeObject=rO,eu.isBoolean=function _($){return!0===$||!1===$||rK($)&&eH($)==p},eu.isBuffer=rV,eu.isDate=rR,eu.isElement=function _($){return rK($)&&1===$.nodeType&&!rX($)},eu.isEmpty=function _($){if(null==$)return!0;if(rP($)&&(rG($)||"string"==typeof $||"function"==typeof $.splice||rV($)||i0($)||rj($)))return!$.length;var e=nl($);if(e==y||e==A)return!$.size;if(nk($))return!t1($).length;for(var t in $)if(_E.call($,t))return!1;return!0},eu.isEqual=rU,eu.isEqualWith=function _($,e,n){var r=(n="function"==typeof n?n:t)?n($,e):t;return r===t?eX($,e,t,n):!!r},eu.isError=rz,eu.isFinite=function _($){return"number"==typeof $&&$H($)},eu.isFunction=rH,eu.isInteger=rN,eu.isLength=rq,eu.isMap=rZ,eu.isMatch=function _($,e){return $===e||eQ($,e,no(e))},eu.isMatchWith=function _($,e,n){return n="function"==typeof n?n:t,eQ($,e,no(e),n)},eu.isNaN=function _($){return rJ($)&&$!=+$},eu.isNative=function _($){if(ny($))throw new _l("Unsupported core-js use. Try https://npms.io/search?q=ponyfill.");return eY($)},eu.isNil=function _($){return null==$},eu.isNull=function _($){return null===$},eu.isNumber=rJ,eu.isObject=r9,eu.isObjectLike=rK,eu.isPlainObject=rX,eu.isRegExp=rQ,eu.isSafeInteger=function _($){return rN($)&&$>=-9007199254740991&&$<=9007199254740991},eu.isSet=rY,eu.isString=i2,eu.isSymbol=i1,eu.isTypedArray=i0,eu.isUndefined=function _($){return $===t},eu.isWeakMap=function _($){return rK($)&&nl($)==F},eu.isWeakSet=function _($){return rK($)&&"[object WeakSet]"==eH($)},eu.join=function _($,e){return null==$?"":$N.call($,e)},eu.kebabCase=iW,eu.last=nK,eu.lastIndexOf=function _($,e,n){var r=null==$?0:$.length;if(!r)return -1;var i=r;return n!==t&&(i=(i=i5(n))<0?$9(r+i,0):$K(i,r-1)),e==e?function _($,e,t){for(var n=t+1;n--&&$[n]!==e;);return n}($,e,i):$h($,$m,i,!0)},eu.lowerCase=iD,eu.lowerFirst=iB,eu.lt=i_,eu.lte=i3,eu.max=function _($){return $&&$.length?eT($,iU,eN):t},eu.maxBy=function _($,e){return $&&$.length?eT($,ni(e,2),eN):t},eu.mean=function _($){return $y($,iU)},eu.meanBy=function _($,e){return $y($,ni(e,2))},eu.min=function _($){return $&&$.length?eT($,iU,t0):t},eu.minBy=function _($,e){return $&&$.length?eT($,ni(e,2),t0):t},eu.stubArray=u2,eu.stubFalse=u1,eu.stubObject=u0,eu.stubString=u_,eu.stubTrue=u3,eu.multiply=ue,eu.nth=function _($,e){return $&&$.length?t5($,i5(e)):t},eu.noConflict=function _(){return _X._===this&&(_X._=_F),this},eu.noop=i9,eu.now=rg,eu.pad=function _($,e,t){$=it($);var n=(e=i5(e))?$P($):0;if(!e||n>=e)return $;var r=(e-n)/2;return tJ($d(r),t)+$+tJ($0(r),t)},eu.padEnd=function _($,e,t){$=it($);var n=(e=i5(e))?$P($):0;return e&&ne){var r=$;$=e,e=r}if(n||$%1||e%1){var i=$X();return $K($+i*(e-$+_9("1e-"+((i+"").length-1))),e)}return tn($,e)},eu.reduce=function _($,e,t){var n=rG($)?$c:$b,r=arguments.length<3;return n($,ni(e,4),t,r,eB)},eu.reduceRight=function _($,e,t){var n=rG($)?$l:$b,r=arguments.length<3;return n($,ni(e,4),t,r,eL)},eu.repeat=function _($,e,n){return e=(n?nv($,e,n):e===t)?1:i5(e),tr(it($),e)},eu.replace=function _(){var $=arguments,e=it($[0]);return $.length<3?e:e.replace($[1],$[2])},eu.result=function _($,e,n){e=tC(e,$);var r=-1,i=e.length;for(i||(i=1,$=t);++r9007199254740991)return[];var t=a,n=$K($,a);e=ni(e),$-=a;for(var r=$8(n,e);++t<$;)e(t);return r},eu.toFinite=i4,eu.toInteger=i5,eu.toLength=i6,eu.toLower=function _($){return it($).toLowerCase()},eu.toNumber=i7,eu.toSafeInteger=function _($){return $?eC(i5($),-9007199254740991,9007199254740991):0===$?$:0},eu.toString=it,eu.toUpper=function _($){return it($).toUpperCase()},eu.trim=function _($,e,n){if(($=it($))&&(n||e===t))return $A($);if(!$||!(e=tg(e)))return $;var r=$O($),i=$O(e),u=$w(r,i),o=$W(r,i)+1;return tF(r,u,o).join("")},eu.trimEnd=function _($,e,n){if(($=it($))&&(n||e===t))return $.slice(0,$V($)+1);if(!$||!(e=tg(e)))return $;var r=$O($),i=$W(r,$O(e))+1;return tF(r,0,i).join("")},eu.trimStart=function _($,e,n){if(($=it($))&&(n||e===t))return $.replace(_1,"");if(!$||!(e=tg(e)))return $;var r=$O($),i=$w(r,$O(e));return tF(r,i).join("")},eu.truncate=function _($,e){var n=30,r="...";if(r9(e)){var i="separator"in e?e.separator:i;n="length"in e?i5(e.length):n,r="omission"in e?tg(e.omission):r}var u=($=it($)).length;if($M($)){var o=$O($);u=o.length}if(n>=u)return $;var a=n-$P(r);if(a<1)return r;var s=o?tF(o,0,a).join(""):$.slice(0,a);if(i===t)return s+r;if(o&&(a+=s.length-a),rQ(i)){if($.slice(a).search(i)){var c,l=s;for(i.global||(i=_h(i.source,it(_e.exec(i))+"g")),i.lastIndex=0;c=i.exec(l);)var f=c.index;s=s.slice(0,f===t?a:f)}}else if($.indexOf(tg(i),a)!=a){var d=s.lastIndexOf(i);d>-1&&(s=s.slice(0,d))}return s+r},eu.unescape=function _($){return($=it($))&&H.test($)?$.replace(U,$R):$},eu.uniqueId=function _($){var e=++_8;return it($)+e},eu.upperCase=iT,eu.upperFirst=ij,eu.each=rs,eu.eachRight=rc,eu.first=nH,iq(eu,(u$={},eO(eu,function(_,$){_E.call(eu.prototype,$)||(u$[$]=_)}),u$),{chain:!1}),eu.VERSION="4.17.21",$t(["bind","bindKey","curry","curryRight","partial","partialRight"],function(_){eu[_].placeholder=eu}),$t(["drop","take"],function(_,$){ec.prototype[_]=function(e){e=e===t?1:$9(i5(e),0);var n=this.__filtered__&&!$?new ec(this):this.clone();return n.__filtered__?n.__takeCount__=$K(e,n.__takeCount__):n.__views__.push({size:$K(e,a),type:_+(n.__dir__<0?"Right":"")}),n},ec.prototype[_+"Right"]=function($){return this.reverse()[_]($).reverse()}}),$t(["filter","map","takeWhile"],function(_,$){var e=$+1,t=1==e||3==e;ec.prototype[_]=function(_){var $=this.clone();return $.__iteratees__.push({iteratee:ni(_,3),type:e}),$.__filtered__=$.__filtered__||t,$}}),$t(["head","last"],function(_,$){var e="take"+($?"Right":"");ec.prototype[_]=function(){return this[e](1).value()[0]}}),$t(["initial","tail"],function(_,$){var e="drop"+($?"":"Right");ec.prototype[_]=function(){return this.__filtered__?new ec(this):this[e](1)}}),ec.prototype.compact=function(){return this.filter(iU)},ec.prototype.find=function(_){return this.filter(_).head()},ec.prototype.findLast=function(_){return this.reverse().find(_)},ec.prototype.invokeMap=ti(function(_,$){return"function"==typeof _?new ec(this):this.map(function(e){return eZ(e,_,$)})}),ec.prototype.reject=function(_){return this.filter(rF(ni(_)))},ec.prototype.slice=function(_,$){_=i5(_);var e=this;return e.__filtered__&&(_>0||$<0)?new ec(e):(_<0?e=e.takeRight(-_):_&&(e=e.drop(_)),$!==t&&(e=($=i5($))<0?e.dropRight(-$):e.take($-_)),e)},ec.prototype.takeRightWhile=function(_){return this.reverse().takeWhile(_).reverse()},ec.prototype.toArray=function(){return this.take(a)},eO(ec.prototype,function(_,$){var e=/^(?:filter|find|map|reject)|While$/.test($),n=/^(?:head|last)$/.test($),r=eu[n?"take"+("last"==$?"Right":""):$],i=n||/^find/.test($);r&&(eu.prototype[$]=function(){var $=this.__wrapped__,u=n?[1]:arguments,o=$ instanceof ec,a=u[0],s=o||rG($),c=function(_){var $=r.apply(eu,$s([_],u));return n&&l?$[0]:$};s&&e&&"function"==typeof a&&1!=a.length&&(o=s=!1);var l=this.__chain__,f=!!this.__actions__.length,d=i&&!l,p=o&&!f;if(!i&&s){$=p?$:new ec(this);var h=_.apply($,u);return h.__actions__.push({func:rr,args:[c],thisArg:t}),new es(h,l)}return d&&p?_.apply(this,u):(h=this.thru(c),d?n?h.value()[0]:h.value():h)})}),$t(["pop","push","shift","sort","splice","unshift"],function(_){var $=_m[_],e=/^(?:push|sort|unshift)$/.test(_)?"tap":"thru",t=/^(?:pop|shift)$/.test(_);eu.prototype[_]=function(){var _=arguments;if(t&&!this.__chain__){var n=this.value();return $.apply(rG(n)?n:[],_)}return this[e](function(e){return $.apply(rG(e)?e:[],_)})}}),eO(ec.prototype,function(_,$){var e=eu[$];if(e){var t=e.name+"";_E.call(e4,t)||(e4[t]=[]),e4[t].push({name:$,func:e})}}),e4[tq(t,2).name]=[{name:"wrapper",func:t}],ec.prototype.clone=function _(){var $=new ec(this.__wrapped__);return $.__actions__=tj(this.__actions__),$.__dir__=this.__dir__,$.__filtered__=this.__filtered__,$.__iteratees__=tj(this.__iteratees__),$.__takeCount__=this.__takeCount__,$.__views__=tj(this.__views__),$},ec.prototype.reverse=function _(){if(this.__filtered__){var $=new ec(this);$.__dir__=-1,$.__filtered__=!0}else $=this.clone(),$.__dir__*=-1;return $},ec.prototype.value=function _(){var $=this.__wrapped__.value(),e=this.__dir__,t=rG($),n=e<0,r=t?$.length:0,i=function _($,e,t){for(var n=-1,r=t.length;++n=this.__values__.length,e=$?t:this.__values__[this.__index__++];return{done:$,value:e}},eu.prototype.plant=function _($){for(var e,n=this;n instanceof ea;){var r=nI(n);r.__index__=0,r.__values__=t,e?i.__wrapped__=r:e=r;var i=r;n=n.__wrapped__}return i.__wrapped__=$,e},eu.prototype.reverse=function _(){var $=this.__wrapped__;if($ instanceof ec){var e=$;return this.__actions__.length&&(e=new ec(this)),(e=e.reverse()).__actions__.push({func:rr,args:[nQ],thisArg:t}),new es(e,this.__chain__)}return this.thru(nQ)},eu.prototype.toJSON=eu.prototype.valueOf=eu.prototype.value=function _(){return tb(this.__wrapped__,this.__actions__)},eu.prototype.first=eu.prototype.head,_q&&(eu.prototype[_q]=function _(){return this}),eu}();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(_X._=$U,define(function(){return $U})):_Y?((_Y.exports=$U)._=$U,_Q._=$U):_X._=$U}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],13:[function(_,$,e){var t=$.exports={},n=[],r=!1;function i(){if(!r){r=!0;for(var _,$=n.length;$;){_=n,n=[];for(var e=-1;++e<$;)_[e]();$=n.length}r=!1}}function u(){}t.nextTick=function(_){n.push(_),r||setTimeout(i,0)},t.title="browser",t.browser=!0,t.env={},t.argv=[],t.version="",t.versions={},t.on=u,t.addListener=u,t.once=u,t.off=u,t.removeListener=u,t.removeAllListeners=u,t.emit=u,t.binding=function(_){throw Error("process.binding is not supported")},t.cwd=function(){return"/"},t.chdir=function(_){throw Error("process.chdir is not supported")},t.umask=function(){return 0}},{}],14:[function(_,$,e){arguments[4][6][0].apply(e,arguments)},{dup:6}],15:[function(_,$,e){arguments[4][7][0].apply(e,arguments)},{dup:7}],16:[function(_,$,e){arguments[4][8][0].apply(e,arguments)},{"./support/isBuffer":15,_process:13,dup:8,inherits:14}],17:[function(_,$,e){"use strict";var t,n=_("lodash"),r=_("events"),i=_("./reg.js"),u=_("./state.js").state,o=_("../data/ascii-identifier-data.js"),a=o.asciiIdentifierStartTable,s=o.asciiIdentifierPartTable,c=_("../data/non-ascii-identifier-start.js"),l=_("../data/non-ascii-identifier-part-only.js"),f={Identifier:1,Punctuator:2,NumericLiteral:3,StringLiteral:4,Comment:5,Keyword:6,RegExp:9,TemplateHead:10,TemplateMiddle:11,TemplateTail:12,NoSubstTemplate:13},d={Block:1,Template:2};function p(_){return/^[0-9a-fA-F]+$/.test(_)}function h(_){return 1===_.length&&p(_)}function v(_){var $=_;"string"==typeof $&&($=$.replace(/\r\n/g,"\n").replace(/\r/g,"\n").split("\n")),$[0]&&"#!"===$[0].substr(0,2)&&(-1!==$[0].indexOf("node")&&(u.option.node=!0),$[0]=""),this.emitter=new r.EventEmitter,this.source=_,this.setLines($),this.prereg=!0,this.line=0,this.char=1,this.from=1,this.input="",this.inComment=!1,this.context=[],this.templateStarts=[];for(var e=0;e0&&this.context[this.context.length-1].type===_},pushContext:function(_){this.context.push({type:_})},popContext:function(){return this.context.pop()},currentContext:function(){return this.context.length>0&&this.context[this.context.length-1]},getLines:function(){return this._lines=u.lines,this._lines},setLines:function(_){this._lines=_,u.lines=this._lines},peek:function(_){return this.input.charAt(_||0)},skip:function(_){_=_||1,this.char+=_,this.input=this.input.slice(_)},on:function(_,$){_.split(" ").forEach((function(_){this.emitter.on(_,$)}).bind(this))},trigger:function(){this.emitter.emit.apply(this.emitter,Array.prototype.slice.call(arguments))},triggerAsync:function(_,$,e,t){e.push((function(){t()&&this.trigger(_,$)}).bind(this))},scanPunctuator:function(){var _,$,e,t=this.peek();switch(t){case".":if(/^[0-9]$/.test(this.peek(1)))return null;if("."===this.peek(1)&&"."===this.peek(2))return{type:f.Punctuator,value:"..."};case"(":case")":case";":case",":case"[":case"]":case":":case"~":case"#":return{type:f.Punctuator,value:t};case"{":return this.pushContext(d.Block),{type:f.Punctuator,value:t};case"}":return this.inContext(d.Block)&&this.popContext(),{type:f.Punctuator,value:t};case"":return null}return(_=this.peek(1),$=this.peek(2),"?"===t)?"."!==_||i.decimalDigit.test($)?{type:f.Punctuator,value:"?"===_?"??":"?"}:{type:f.Punctuator,value:"?."}:(e=this.peek(3),">"===t&&">"===_&&">"===$&&"="===e)?{type:f.Punctuator,value:">>>="}:"="===t&&"="===_&&"="===$?{type:f.Punctuator,value:"==="}:"!"===t&&"="===_&&"="===$?{type:f.Punctuator,value:"!=="}:">"===t&&">"===_&&">"===$?{type:f.Punctuator,value:">>>"}:"<"===t&&"<"===_&&"="===$?{type:f.Punctuator,value:"<<="}:">"===t&&">"===_&&"="===$?{type:f.Punctuator,value:">>="}:"="===t&&">"===_?{type:f.Punctuator,value:t+_}:t===_&&"+-<>&|*".indexOf(t)>=0?"*"===t&&"="===$?{type:f.Punctuator,value:t+_+$}:{type:f.Punctuator,value:t+_}:"<>=!+-*%&|^/".indexOf(t)>=0?"="===_?{type:f.Punctuator,value:t+_}:{type:f.Punctuator,value:t}:null},scanComments:function(_){var $=this.peek(),e=this.peek(1),t=this.input.substr(2),n=this.line,r=this.char,u=this;function o(_,$,e){var t=!1,n=_+$,r="plain";return(e=e||{}).isMultiline&&(n+="*/"),$=$.replace(/\n/g," "),"/*"===_&&i.fallsThrough.test($)&&(t=!0,r="falls through"),["jshint","jshint.unstable","jslint","members","member","globals","global","exported"].forEach(function(e){if(!t&&("//"!==_||"jshint"===e||"jshint.unstable"===e)){" "===$.charAt(e.length)&&$.substr(0,e.length)===e&&(t=!0,_+=e,$=$.substr(e.length)),t||" "!==$.charAt(0)||" "!==$.charAt(e.length+1)||$.substr(1,e.length)!==e||(t=!0,_=_+" "+e,$=$.substr(e.length+1));var n=$.indexOf(e);if(!t&&n>=0&&" "===$.charAt(n+e.length)&&0===$.substr(0,n).trim().length&&(t=!0,$=$.substr(e.length+n)),t)switch(e){case"member":r="members";break;case"global":r="globals";break;default:var i=$.split(":").map(function(_){return _.replace(/^\s+/,"").replace(/\s+$/,"")});if(2===i.length&&"ignore"===i[0])switch(i[1]){case"start":u.ignoringLinterErrors=!0,t=!1;break;case"end":u.ignoringLinterErrors=!1,t=!1}r=e}}}),{type:f.Comment,commentType:r,value:n,body:$,isSpecial:t,isMalformed:e.isMalformed||!1}}if("*"===$&&"/"===e)return this.trigger("error",{code:"E018",line:n,character:r}),this.skip(2),null;if("/"!==$||"*"!==e&&"/"!==e)return null;if("/"===e)return this.skip(this.input.length),o("//",t);var a="";if("*"===e){for(this.inComment=!0,this.skip(2);"*"!==this.peek()||"/"!==this.peek(1);)if(""===this.peek()){if(a+="\n",!this.nextLine(_))return this.trigger("error",{code:"E017",line:n,character:r}),this.inComment=!1,o("/*",a,{isMultiline:!0,isMalformed:!0})}else a+=this.peek(),this.skip();return this.skip(2),this.inComment=!1,o("/*",a,{isMultiline:!0})}},scanKeyword:function(){var _=/^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input);return _&&["if","in","do","var","for","new","try","let","this","else","case","void","with","enum","while","break","catch","throw","const","yield","class","super","return","typeof","delete","switch","export","import","default","finally","extends","function","continue","debugger","instanceof","true","false","null","async","await"].indexOf(_[0])>=0?{type:f.Keyword,value:_[0]}:null},scanIdentifier:function($){var e,n,r,i="",o=0;function d(_){return c.indexOf(_)>-1}function h(_){return d(_)||l.indexOf(_)>-1}var v=(function(){if(o+=1,"u"!==this.peek(o))return null;var _,$=this.peek(o+1)+this.peek(o+2)+this.peek(o+3)+this.peek(o+4);return p($)&&(s[_=parseInt($,16)]||h(_))?(o+=5,"\\u"+$):null}).bind(this),g=(function(){var _=this.peek(o),$=_.charCodeAt(0);return 92===$?v():$<128?a[$]?(o+=1,_):null:d($)?(o+=1,_):null}).bind(this),m=(function(){var _=this.peek(o),$=_.charCodeAt(0);return 92===$?v():$<128?s[$]?(o+=1,_):null:h($)?(o+=1,_):null}).bind(this);if(null===(n=g()))return null;for(i=n;null!==(n=m());)i+=n;return r=(e=i).replace(/\\u([0-9a-fA-F]{4})/g,function(_,$){return String.fromCharCode(parseInt($,16))}),u.inES6(!0)||(t=_("../data/es5-identifier-names.js")).test(r)||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["unicode 8","6"]},$,function(){return!0}),{type:f.Identifier,value:r,text:i,tokenLength:i.length}},scanNumericLiteral:function(_){var $=0,e="",t=this.input.length,n=this.peek($),r=s,i=10,o=!1,a=!1;function s(_){return/^[0-9]$/.test(_)}function c(_){return/^[0-7]$/.test(_)}function l(_){return"8"===_||"9"===_}function d(_){return"$"===_||"_"===_||"\\"===_||_>="a"&&_<="z"||_>="A"&&_<="Z"}if("."!==n&&!s(n))return null;if("."!==n){for(e=this.peek($),$+=1,n=this.peek($),"0"===e&&(("x"===n||"X"===n)&&(r=h,i=16,$+=1,e+=n),("o"===n||"O"===n)&&(r=c,i=8,u.inES6(!0)||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["Octal integer literal","6"]},_,function(){return!0}),$+=1,e+=n),("b"===n||"B"===n)&&(r=function _($){return/^[01]$/.test($)},i=2,u.inES6(!0)||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["Binary integer literal","6"]},_,function(){return!0}),$+=1,e+=n),c(n)?(r=c,i=8,o=!0):s(n)&&(a=!0));$=0&&n<=7&&u.isStrict()});break;case"1":case"2":case"3":case"4":case"5":case"6":case"7":t="\\"+t,this.triggerAsync("warning",{code:"W115",line:this.line,character:this.char},_,function(){return u.isStrict()});break;case"u":var r=this.input.substr(1,4),i=parseInt(r,16);p(r)||this.trigger("warning",{code:"W052",line:this.line,character:this.char,data:["u"+r]}),t=String.fromCharCode(i),e=5;break;case"v":this.triggerAsync("warning",{code:"W114",line:this.line,character:this.char,data:["\\v"]},_,function(){return u.jsonMode}),t="\v";break;case"x":var o=parseInt(this.input.substr(1,2),16);this.triggerAsync("warning",{code:"W114",line:this.line,character:this.char,data:["\\x-"]},_,function(){return u.jsonMode}),t=String.fromCharCode(o),e=3;break;case"\\":t="\\\\";break;case"/":break;case"":$=!0,t=""}return{char:t,jump:e,allowNewLine:$}},scanTemplateLiteral:function(_){var $,e,t="",n=this.line,r=this.char,i=this.templateStarts.length;if("`"===this.peek())u.inES6(!0)||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["template literal syntax","6"]},_,function(){return!0}),$=f.TemplateHead,this.templateStarts.push({line:this.line,char:this.char}),i=this.templateStarts.length,this.skip(1),this.pushContext(d.Template);else{if(!this.inContext(d.Template)||"}"!==this.peek())return null;$=f.TemplateMiddle}for(;"`"!==this.peek();){for(;""===(e=this.peek());)if(t+="\n",!this.nextLine(_)){var o=this.templateStarts.pop();return this.trigger("error",{code:"E052",line:o.line,character:o.char}),{type:$,value:t,startLine:n,startChar:r,isUnclosed:!0,depth:i,context:this.popContext()}}if("$"===e&&"{"===this.peek(1))return t+="${",this.skip(2),{type:$,value:t,startLine:n,startChar:r,isUnclosed:!1,depth:i,context:this.currentContext()};if("\\"===e){var a=this.scanEscapeSequence(_);t+=a.char,this.skip(a.jump)}else"`"!==e&&(t+=e,this.skip(1))}return $=$===f.TemplateHead?f.NoSubstTemplate:f.TemplateTail,this.skip(1),this.templateStarts.pop(),{type:$,value:t,startLine:n,startChar:r,isUnclosed:!1,depth:i,context:this.popContext()}},scanStringLiteral:function(_){var $=this.peek();if('"'!==$&&"'"!==$)return null;this.triggerAsync("warning",{code:"W108",line:this.line,character:this.char},_,function(){return u.jsonMode&&'"'!==$});var e="",t=this.line,n=this.char,r=!1;for(this.skip();this.peek()!==$;)if(""===this.peek()){if(r?(r=!1,this.triggerAsync("warning",{code:"W043",line:this.line,character:this.char},_,function(){return!u.option.multistr}),this.triggerAsync("warning",{code:"W042",line:this.line,character:this.char},_,function(){return u.jsonMode&&u.option.multistr})):this.trigger("warning",{code:"W112",line:this.line,character:this.char}),!this.nextLine(_))return{type:f.StringLiteral,value:e,startLine:t,startChar:n,isUnclosed:!0,quote:$}}else{r=!1;var i=this.peek(),o=1;if(i<" "&&this.triggerAsync("warning",{code:"W113",line:this.line,character:this.char,data:[""]},_,function(){return!0}),"\\"===i){var a=this.scanEscapeSequence(_);i=a.char,o=a.jump,r=a.allowNewLine}""!==i&&(e+=i,this.skip(o))}return this.skip(),{type:f.StringLiteral,value:e,startLine:t,startChar:n,isUnclosed:!1,quote:$}},scanRegExp:function($){var e,t,n,r=0,o=this.input.length,a=this.peek(),s=a,c="",l=[],d="",h="",v=!1,g=!1,m=!1,y=!1,k=!1,x=!1,b="",E=function(){return d.indexOf("u")>-1},A=0,S=(function(){var e,t;if(r+=1,a=this.peek(r),i.nonzeroDigit.test(a)){for(t=a,e=this.peek(r+1);i.nonzeroDigit.test(e)||"0"===e;)r+=1,t+=a=e,c+=a,s+=a,e=this.peek(r+1);return l.push(Number(t)),t}if(b+=a,"u"===a&&"{"===this.peek(r+1)){var n=r+2;for(t="u{",e=this.peek(n);p(e);)t+=e,n+=1,e=this.peek(n);if("}"!==e)this.triggerAsync("error",{code:"E016",line:this.line,character:this.char,data:["Invalid Unicode escape sequence"]},$,E);else if(t.length>2)return t+="}",c+=t,s+=t,r=n+1,t}if("p"===a||"P"===a){var o=r+2;if(t="",e="","{"===this.peek(r+1))for(e=this.peek(o);e&&"}"!==e;)t+=e,o+=1,e=this.peek(o);if(t&&_("./validate-unicode-escape-sequence")(t)||this.triggerAsync("error",{code:"E016",line:this.line,character:this.char,data:["Invalid Unicode property escape sequence"]},$,E),t)return c+=t=a+"{"+t+"}",s+=t,r=o+1,u.inES9()||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["Unicode property escape","9"]},$,E),t}return a<" "&&(v=!0,this.triggerAsync("warning",{code:"W048",line:this.line,character:this.char},$,function(){return!0})),"<"===a?(v=!0,this.triggerAsync("warning",{code:"W049",line:this.line,character:this.char,data:[a]},$,function(){return!0})):"0"===a&&i.decimalDigit.test(this.peek(r+1))&&this.triggerAsync("error",{code:"E016",line:this.line,character:this.char,data:["Invalid decimal escape sequence"]},$,E),r+=1,c+=a,s+=a,a}).bind(this),C=(function(){var _,$=r,e="",t="";for(_=this.peek($+1);i.decimalDigit.test(_);)$+=1,e+=_,_=this.peek($+1);if(!e)return!1;if("}"===_)return!0;if(","!==_)return!1;for($+=1,_=this.peek($+1);i.decimalDigit.test(_);)$+=1,t+=_,_=this.peek($+1);return"}"===_&&(!t||Number(e)<=Number(t))}).bind(this),F=(function(_){return _.replace(/\\u\{([0-9a-fA-F]+)\}|\\u([a-fA-F0-9]{4})/g,(function(_,$,e){var t,n=parseInt($||e,16);if(n>1114111){v=!0,this.trigger("error",{code:"E016",line:this.line,character:this.char,data:[a]});return}return(t=String.fromCharCode(n),i.regexpSyntaxChars.test(t))?_:n<=65535?String.fromCharCode(n):"￿"}).bind(this)).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"￿")}).bind(this);if(!this.prereg||"/"!==a)return null;for(r+=1,t=!1;r$)return!0})||!e.split("").every(function(_){return"u"===_||"/"===_||"0"===_||t.regexpControlEscapes.test(_)||t.regexpCharClasses.test(_)||t.regexpSyntaxChars.test(_)})}(l,A,b,i)?n="Invalid escape":x&&(n="Invalid quantifier"),c=F(c)):"s"===a?(u.inES9()||this.triggerAsync("warning",{code:"W119",line:this.line,character:this.char,data:["DotAll RegExp flag","9"]},$,function(){return!0}),s.indexOf("s")>-1&&(n="Duplicate RegExp flag")):h+=a,d.indexOf(a)>-1&&(n="Duplicate RegExp flag"),d+=a,s+=a,d+=a,r+=1;-1===d.indexOf("u")&&this.triggerAsync("warning",{code:"W147",line:this.line,character:this.char},$,function(){return u.option.regexpu});try{RegExp(c,h)}catch(w){n=w.message}return n?(v=!0,this.trigger("error",{code:"E016",line:this.line,character:this.char,data:[n]})):d.indexOf("s")>-1&&!i.regexpDot.test(c)&&this.trigger("warning",{code:"W148",line:this.line,character:this.char}),{type:f.RegExp,value:s,isMalformed:v}},scanNonBreakingSpaces:function(){return u.option.nonbsp?this.input.search(/(\u00A0)/):-1},next:function(_){for(this.from=this.char;i.whitespace.test(this.peek());)this.from+=1,this.skip();var $=this.scanComments(_)||this.scanStringLiteral(_)||this.scanTemplateLiteral(_);return $||(($=this.scanRegExp(_)||this.scanPunctuator()||this.scanKeyword()||this.scanIdentifier(_)||this.scanNumericLiteral(_))?(this.skip($.tokenLength||$.value.length),$):null)},nextLine:function(_){if(this.line>=this.getLines().length)return!1;this.input=this.getLines()[this.line],this.line+=1,this.char=1,this.from=1;var $,e=this.input.trim(),t=function(){return n.some(arguments,function(_){return 0===e.indexOf(_)})};return!0!==this.ignoringLinterErrors||t("/*","//")||this.inComment&&function(){return n.some(arguments,function(_){return -1!==e.indexOf(_,e.length-_.length)})}("*/")||(this.input=""),($=this.scanNonBreakingSpaces())>=0&&this.triggerAsync("warning",{code:"W125",line:this.line,character:$+1},_,function(){return!0}),this.input=this.input.replace(/\t/g,u.tab),!this.ignoringLinterErrors&&u.option.maxlen&&u.option.maxlen=0;--$){var e=o[$]["(bindings)"];if(e[_])return e}}function x($,e){if("outer"===_.option.shadow)for(var t="global"===s["(type)"],n="functionparams"===u["(type)"],r=!t,i=0;i1?o[o.length-2]:null,d=u===s,p="functionparams"===u["(type)"],g="functionouter"===u["(type)"],k=u["(usages)"],x=u["(bindings)"],b=Object.keys(k);for(k.__proto__&&-1===b.indexOf("__proto__")&&b.push("__proto__"),$=0;$-1&&v("E069",$,$.value),d.push($.value)),_&&this.block.use(_.value,_)},initialize:function(_){u["(bindings)"][_]&&(u["(bindings)"][_]["(initialized)"]=!0)},addbinding:function($,n){var i=n.type,a=n.token,l="let"===i||"const"===i||"class"===i||"import"===i||"generator function"===i||"async function"===i||"async generator function"===i,f="function"===i||"generator function"===i||"async function"===i||"import"===i,d="global"===(l?u:s)["(type)"]&&t.has(e,$);if(x($,a),_.isStrict()&&("arguments"===$||"eval"===$)&&h("E008",a),l){var p=u["(bindings)"][$];p||u!==s||"global"===u["(type)"]||(p=!!s["(parent)"]["(bindings)"][$]),!p&&u["(usages)"][$]&&(u["(usages)"][$]["(onlyUsedSubFunction)"]||f?b(i,$,a):f||h("E056",a,$,i)),p&&(!f||"global"!==u["(type)"]||"import"===i)?h("E011",a,$):"outer"===_.option.shadow&&E.funct.has($)&&h("W004",a,$),E.block.add($,i,a,!d,n.initialized)}else{var v=E.funct.has($);!v&&function _($){for(var e=o.length-1;e>=0;e--){var t=o[e];if(t["(usages)"][$])return t["(usages)"][$];if(t===s)break}return!1}($)&&b(i,$,a),E.funct.has($,{onlyBlockscoped:!0})?h("E011",a,$):!0!==_.option.shadow&&v&&"__proto__"!==$&&"global"!==s["(type)"]&&h("W004",a,$),E.funct.add($,i,a,!d),"global"!==s["(type)"]||_.impliedClosure()||(c[$]=r)}},funct:{bindingtype:function(_,$){for(var e=$&&$.onlyBlockscoped,t=$&&$.excludeParams,n=o.length-($&&$.excludeCurrent?2:1),r=n;r>=0;r--){var i=o[r];if(i["(bindings)"][_]&&(!e||i["(bindings)"][_]["(blockscoped)"]))return i["(bindings)"][_]["(type)"];var u=t?o[r-1]:i;if(u&&"functionparams"===u["(type)"])break}return null},hasLabel:function(_){for(var $=o.length-1;$>=0;$--){var e=o[$];if(e["(labels)"][_])return!0;if("functionparams"===e["(type)"])break}return!1},has:function(_,$){return Boolean(this.bindingtype(_,$))},add:function(_,$,e,t){u["(bindings)"][_]={"(type)":$,"(token)":e,"(blockscoped)":!1,"(function)":s,"(unused)":t}}},block:{isGlobal:function(){return"global"===u["(type)"]},use:function($,e){var t=s["(parent)"];t&&t["(bindings)"][$]&&"param"===t["(bindings)"][$]["(type)"]&&!E.funct.has($,{excludeParams:!0,onlyBlockscoped:!0})&&(t["(bindings)"][$]["(unused)"]=!1),e&&(_.ignored.W117||!1===_.option.undef)&&(e.ignoreUndef=!0),g($),u["(usages)"][$]["(onlyUsedSubFunction)"]=!1,e&&(e["(function)"]=s,u["(usages)"][$]["(tokens)"].push(e));var n=u["(bindings)"][$];n&&n["(blockscoped)"]&&!n["(initialized)"]&&v("E056",e,$,n["(type)"])},reassign:function($,e){e.ignoreW020=_.ignored.W020,e.ignoreW021=_.ignored.W021,this.modify($,e),u["(usages)"][$]["(reassigned)"].push(e)},modify:function(_,$){g(_),u["(usages)"][_]["(onlyUsedSubFunction)"]=!1,u["(usages)"][_]["(modified)"].push($)},add:function(_,$,e,t,n){u["(bindings)"][_]={"(type)":$,"(token)":e,"(initialized)":!!n,"(blockscoped)":!0,"(unused)":t}},addLabel:function($,e){var t=e.token;E.funct.hasLabel($)?h("E011",t,$):"outer"===_.option.shadow&&(E.funct.has($)?h("W004",t,$):x($,t)),u["(labels)"][$]=t}}};return E};$.exports=i},{events:11,lodash:12}],24:[function(_,$,e){"use strict";var t=_("./name-stack.js");e.state={syntax:{},isStrict:function(){return!!this.directive["use strict"]||this.inClassBody||this.option.module||"implied"===this.option.strict},stmtMissingStrict:function(){return"global"===this.option.strict||!1!==this.option.strict&&!!this.option.globalstrict},allowsGlobalUsd:function(){return"global"===this.option.strict||this.option.globalstrict||this.option.module||this.impliedClosure()},impliedClosure:function(){return this.option.node||this.option.phantom||this.option.browserify},inMoz:function(){return this.option.moz},inES11:function(){return this.esVersion>=11},inES10:function(){return this.esVersion>=10},inES9:function(){return this.esVersion>=9},inES8:function(){return this.esVersion>=8},inES7:function(){return this.esVersion>=7},inES6:function(_){return!_&&!!this.option.moz||this.esVersion>=6},inES5:function(){return!this.esVersion||this.esVersion>=5||this.option.moz},inferEsVersion:function(){var _=null;if(this.option.esversion){if(this.option.es3?_="es3":this.option.es5?_="es5":this.option.esnext&&(_="esnext"),_)return _;2015===this.option.esversion?this.esVersion=6:this.esVersion=this.option.esversion}else this.option.es3?this.esVersion=3:this.option.esnext&&(this.esVersion=6);return null},reset:function(){this.tokens={prev:null,next:null,curr:null},this.option={unstable:{}},this.esVersion=5,this.funct=null,this.ignored={},this.directive=Object.create(null),this.jsonMode=!1,this.lines=[],this.tab="",this.cache={},this.ignoredLines={},this.forinifcheckneeded=!1,this.nameStack=new t,this.inClassBody=!1}}},{"./name-stack.js":19}],25:[function(_,$,e){"use strict";e.register=function(_){_.on("Identifier",function $(e){!_.getOption("proto")&&"__proto__"===e.name&&_.warn("W103",{line:e.line,char:e.char,data:[e.name,"6"]})}),_.on("Identifier",function $(e){!_.getOption("iterator")&&"__iterator__"===e.name&&_.warn("W103",{line:e.line,char:e.char,data:[e.name]})}),_.on("Identifier",function $(e){_.getOption("camelcase")&&e.name.replace(/^_+|_+$/g,"").indexOf("_")>-1&&!e.name.match(/^[A-Z0-9_]*$/)&&_.warn("W106",{line:e.line,char:e.char,data:[e.name]})}),_.on("String",function $(e){var t,n=_.getOption("quotmark");n&&("single"===n&&"'"!==e.quote&&(t="W109"),"double"===n&&'"'!==e.quote&&(t="W108"),!0===n&&(_.getCache("quotmark")||_.setCache("quotmark",e.quote),_.getCache("quotmark")!==e.quote&&(t="W110")),t&&_.warn(t,{line:e.line,char:e.char}))}),_.on("Number",function $(e){"."===e.value.charAt(0)&&_.warn("W008",{line:e.line,char:e.char,data:[e.value]}),"."===e.value.substr(e.value.length-1)&&_.warn("W047",{line:e.line,char:e.char,data:[e.value]}),/^00+/.test(e.value)&&_.warn("W046",{line:e.line,char:e.char,data:[e.value]})}),_.on("String",function $(e){!_.getOption("scripturl")&&/^(?:javascript|jscript|ecmascript|vbscript|livescript)\s*:/i.test(e.value)&&_.warn("W107",{line:e.line,char:e.char})})}},{}],26:[function(_,$,e){"use strict";$.exports=function _($){var e=$.indexOf("=");if(-1===e)return $ in t.binary||$ in n.general;var r=$.substr(0,e),i=$.substr(e+1);return"General_Category"===r||"gc"===r?i in n.general:("Script"===r||"sc"===r||"Script_Extensions"===r||"scx"===r)&&i in n.script};var t={nonBinary:Object.create(null),binary:Object.create(null)},n={general:Object.create(null),script:Object.create(null)},r=t.nonBinary;r.General_Category=!0,r.gc=!0,r.Script=!0,r.sc=!0,r.Script_Extensions=!0,r.scx=!0;var i=t.binary;i.ASCII=!0,i.ASCII_Hex_Digit=!0,i.AHex=!0,i.Alphabetic=!0,i.Alpha=!0,i.Any=!0,i.Assigned=!0,i.Bidi_Control=!0,i.Bidi_C=!0,i.Bidi_Mirrored=!0,i.Bidi_M=!0,i.Case_Ignorable=!0,i.CI=!0,i.Cased=!0,i.Changes_When_Casefolded=!0,i.CWCF=!0,i.Changes_When_Casemapped=!0,i.CWCM=!0,i.Changes_When_Lowercased=!0,i.CWL=!0,i.Changes_When_NFKC_Casefolded=!0,i.CWKCF=!0,i.Changes_When_Titlecased=!0,i.CWT=!0,i.Changes_When_Uppercased=!0,i.CWU=!0,i.Dash=!0,i.Default_Ignorable_Code_Point=!0,i.DI=!0,i.Deprecated=!0,i.Dep=!0,i.Diacritic=!0,i.Dia=!0,i.Emoji=!0,i.Emoji_Component=!0,i.EComp=!0,i.Emoji_Modifier=!0,i.EMod=!0,i.Emoji_Modifier_Base=!0,i.EBase=!0,i.Emoji_Presentation=!0,i.EPres=!0,i.Extended_Pictographic=!0,i.ExtPict=!0,i.Extender=!0,i.Ext=!0,i.Grapheme_Base=!0,i.Gr_Base=!0,i.Grapheme_Extend=!0,i.Gr_Ext=!0,i.Hex_Digit=!0,i.Hex=!0,i.IDS_Binary_Operator=!0,i.IDSB=!0,i.IDS_Trinary_Operator=!0,i.IDST=!0,i.ID_Continue=!0,i.IDC=!0,i.ID_Start=!0,i.IDS=!0,i.Ideographic=!0,i.Ideo=!0,i.Join_Control=!0,i.Join_C=!0,i.Logical_Order_Exception=!0,i.LOE=!0,i.Lowercase=!0,i.Lower=!0,i.Math=!0,i.Noncharacter_Code_Point=!0,i.NChar=!0,i.Pattern_Syntax=!0,i.Pat_Syn=!0,i.Pattern_White_Space=!0,i.Pat_WS=!0,i.Quotation_Mark=!0,i.QMark=!0,i.Radical=!0,i.Regional_Indicator=!0,i.RI=!0,i.Sentence_Terminal=!0,i.STerm=!0,i.Soft_Dotted=!0,i.SD=!0,i.Terminal_Punctuation=!0,i.Term=!0,i.Unified_Ideograph=!0,i.UIdeo=!0,i.Uppercase=!0,i.Upper=!0,i.Variation_Selector=!0,i.VS=!0,i.White_Space=!0,i.space=!0,i.XID_Continue=!0,i.XIDC=!0,i.XID_Start=!0,i.XIDS=!0;var u=n.general;u.Cased_Letter=!0,u.LC=!0,u.Close_Punctuation=!0,u.Pe=!0,u.Connector_Punctuation=!0,u.Pc=!0,u.Control=!0,u.Cc=!0,u.cntrl=!0,u.Currency_Symbol=!0,u.Sc=!0,u.Dash_Punctuation=!0,u.Pd=!0,u.Decimal_Number=!0,u.Nd=!0,u.digit=!0,u.Enclosing_Mark=!0,u.Me=!0,u.Final_Punctuation=!0,u.Pf=!0,u.Format=!0,u.Cf=!0,u.Initial_Punctuation=!0,u.Pi=!0,u.Letter=!0,u.L=!0,u.Letter_Number=!0,u.Nl=!0,u.Line_Separator=!0,u.Zl=!0,u.Lowercase_Letter=!0,u.Ll=!0,u.Mark=!0,u.M=!0,u.Combining_Mark=!0,u.Math_Symbol=!0,u.Sm=!0,u.Modifier_Letter=!0,u.Lm=!0,u.Modifier_Symbol=!0,u.Sk=!0,u.Nonspacing_Mark=!0,u.Mn=!0,u.Number=!0,u.N=!0,u.Open_Punctuation=!0,u.Ps=!0,u.Other=!0,u.C=!0,u.Other_Letter=!0,u.Lo=!0,u.Other_Number=!0,u.No=!0,u.Other_Punctuation=!0,u.Po=!0,u.Other_Symbol=!0,u.So=!0,u.Paragraph_Separator=!0,u.Zp=!0,u.Private_Use=!0,u.Co=!0,u.Punctuation=!0,u.P=!0,u.punct=!0,u.Separator=!0,u.Z=!0,u.Space_Separator=!0,u.Zs=!0,u.Spacing_Mark=!0,u.Mc=!0,u.Surrogate=!0,u.Cs=!0,u.Symbol=!0,u.S=!0,u.Titlecase_Letter=!0,u.Lt=!0,u.Unassigned=!0,u.Cn=!0,u.Uppercase_Letter=!0,u.Lu=!0;var o=n.script;o.Adlam=!0,o.Adlm=!0,o.Ahom=!0,o.Anatolian_Hieroglyphs=!0,o.Hluw=!0,o.Arabic=!0,o.Arab=!0,o.Armenian=!0,o.Armn=!0,o.Avestan=!0,o.Avst=!0,o.Balinese=!0,o.Bali=!0,o.Bamum=!0,o.Bamu=!0,o.Bassa_Vah=!0,o.Bass=!0,o.Batak=!0,o.Batk=!0,o.Bengali=!0,o.Beng=!0,o.Bhaiksuki=!0,o.Bhks=!0,o.Bopomofo=!0,o.Bopo=!0,o.Brahmi=!0,o.Brah=!0,o.Braille=!0,o.Brai=!0,o.Buginese=!0,o.Bugi=!0,o.Buhid=!0,o.Buhd=!0,o.Canadian_Aboriginal=!0,o.Cans=!0,o.Carian=!0,o.Cari=!0,o.Caucasian_Albanian=!0,o.Aghb=!0,o.Chakma=!0,o.Cakm=!0,o.Cham=!0,o.Chorasmian=!0,o.Chrs=!0,o.Cherokee=!0,o.Cher=!0,o.Common=!0,o.Zyyy=!0,o.Coptic=!0,o.Copt=!0,o.Qaac=!0,o.Cuneiform=!0,o.Xsux=!0,o.Cypriot=!0,o.Cprt=!0,o.Cyrillic=!0,o.Cyrl=!0,o.Deseret=!0,o.Dsrt=!0,o.Devanagari=!0,o.Deva=!0,o.Dives_Akuru=!0,o.Diak=!0,o.Dogra=!0,o.Dogr=!0,o.Duployan=!0,o.Dupl=!0,o.Egyptian_Hieroglyphs=!0,o.Egyp=!0,o.Elbasan=!0,o.Elba=!0,o.Elymaic=!0,o.Elym=!0,o.Ethiopic=!0,o.Ethi=!0,o.Georgian=!0,o.Geor=!0,o.Glagolitic=!0,o.Glag=!0,o.Gothic=!0,o.Goth=!0,o.Grantha=!0,o.Gran=!0,o.Greek=!0,o.Grek=!0,o.Gujarati=!0,o.Gujr=!0,o.Gunjala_Gondi=!0,o.Gong=!0,o.Gurmukhi=!0,o.Guru=!0,o.Han=!0,o.Hani=!0,o.Hangul=!0,o.Hang=!0,o.Hanifi_Rohingya=!0,o.Rohg=!0,o.Hanunoo=!0,o.Hano=!0,o.Hatran=!0,o.Hatr=!0,o.Hebrew=!0,o.Hebr=!0,o.Hiragana=!0,o.Hira=!0,o.Imperial_Aramaic=!0,o.Armi=!0,o.Inherited=!0,o.Zinh=!0,o.Qaai=!0,o.Inscriptional_Pahlavi=!0,o.Phli=!0,o.Inscriptional_Parthian=!0,o.Prti=!0,o.Javanese=!0,o.Java=!0,o.Kaithi=!0,o.Kthi=!0,o.Kannada=!0,o.Knda=!0,o.Katakana=!0,o.Kana=!0,o.Kayah_Li=!0,o.Kali=!0,o.Kharoshthi=!0,o.Khar=!0,o.Khitan_Small_Script=!0,o.Kits=!0,o.Khmer=!0,o.Khmr=!0,o.Khojki=!0,o.Khoj=!0,o.Khudawadi=!0,o.Sind=!0,o.Lao=!0,o.Laoo=!0,o.Latin=!0,o.Latn=!0,o.Lepcha=!0,o.Lepc=!0,o.Limbu=!0,o.Limb=!0,o.Linear_A=!0,o.Lina=!0,o.Linear_B=!0,o.Linb=!0,o.Lisu=!0,o.Lycian=!0,o.Lyci=!0,o.Lydian=!0,o.Lydi=!0,o.Mahajani=!0,o.Mahj=!0,o.Makasar=!0,o.Maka=!0,o.Malayalam=!0,o.Mlym=!0,o.Mandaic=!0,o.Mand=!0,o.Manichaean=!0,o.Mani=!0,o.Marchen=!0,o.Marc=!0,o.Medefaidrin=!0,o.Medf=!0,o.Masaram_Gondi=!0,o.Gonm=!0,o.Meetei_Mayek=!0,o.Mtei=!0,o.Mende_Kikakui=!0,o.Mend=!0,o.Meroitic_Cursive=!0,o.Merc=!0,o.Meroitic_Hieroglyphs=!0,o.Mero=!0,o.Miao=!0,o.Plrd=!0,o.Modi=!0,o.Mongolian=!0,o.Mong=!0,o.Mro=!0,o.Mroo=!0,o.Multani=!0,o.Mult=!0,o.Myanmar=!0,o.Mymr=!0,o.Nabataean=!0,o.Nbat=!0,o.Nandinagari=!0,o.Nand=!0,o.New_Tai_Lue=!0,o.Talu=!0,o.Newa=!0,o.Nko=!0,o.Nkoo=!0,o.Nushu=!0,o.Nshu=!0,o.Nyiakeng_Puachue_Hmong=!0,o.Hmnp=!0,o.Ogham=!0,o.Ogam=!0,o.Ol_Chiki=!0,o.Olck=!0,o.Old_Hungarian=!0,o.Hung=!0,o.Old_Italic=!0,o.Ital=!0,o.Old_North_Arabian=!0,o.Narb=!0,o.Old_Permic=!0,o.Perm=!0,o.Old_Persian=!0,o.Xpeo=!0,o.Old_Sogdian=!0,o.Sogo=!0,o.Old_South_Arabian=!0,o.Sarb=!0,o.Old_Turkic=!0,o.Orkh=!0,o.Oriya=!0,o.Orya=!0,o.Osage=!0,o.Osge=!0,o.Osmanya=!0,o.Osma=!0,o.Pahawh_Hmong=!0,o.Hmng=!0,o.Palmyrene=!0,o.Palm=!0,o.Pau_Cin_Hau=!0,o.Pauc=!0,o.Phags_Pa=!0,o.Phag=!0,o.Phoenician=!0,o.Phnx=!0,o.Psalter_Pahlavi=!0,o.Phlp=!0,o.Rejang=!0,o.Rjng=!0,o.Runic=!0,o.Runr=!0,o.Samaritan=!0,o.Samr=!0,o.Saurashtra=!0,o.Saur=!0,o.Sharada=!0,o.Shrd=!0,o.Shavian=!0,o.Shaw=!0,o.Siddham=!0,o.Sidd=!0,o.SignWriting=!0,o.Sgnw=!0,o.Sinhala=!0,o.Sinh=!0,o.Sogdian=!0,o.Sogd=!0,o.Sora_Sompeng=!0,o.Sora=!0,o.Soyombo=!0,o.Soyo=!0,o.Sundanese=!0,o.Sund=!0,o.Syloti_Nagri=!0,o.Sylo=!0,o.Syriac=!0,o.Syrc=!0,o.Tagalog=!0,o.Tglg=!0,o.Tagbanwa=!0,o.Tagb=!0,o.Tai_Le=!0,o.Tale=!0,o.Tai_Tham=!0,o.Lana=!0,o.Tai_Viet=!0,o.Tavt=!0,o.Takri=!0,o.Takr=!0,o.Tamil=!0,o.Taml=!0,o.Tangut=!0,o.Tang=!0,o.Telugu=!0,o.Telu=!0,o.Thaana=!0,o.Thaa=!0,o.Thai=!0,o.Tibetan=!0,o.Tibt=!0,o.Tifinagh=!0,o.Tfng=!0,o.Tirhuta=!0,o.Tirh=!0,o.Ugaritic=!0,o.Ugar=!0,o.Vai=!0,o.Vaii=!0,o.Wancho=!0,o.Wcho=!0,o.Warang_Citi=!0,o.Wara=!0,o.Yezidi=!0,o.Yezi=!0,o.Yi=!0,o.Yiii=!0,o.Zanabazar_Square=!0,o.Zanb=!0},{}],27:[function(_,$,e){"use strict";e.reservedVars={NaN:!1,undefined:!1},e.ecmaIdentifiers={3:{Array:!1,Boolean:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,eval:!1,EvalError:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,isFinite:!1,isNaN:!1,Math:!1,Number:!1,Object:!1,parseInt:!1,parseFloat:!1,RangeError:!1,ReferenceError:!1,RegExp:!1,String:!1,SyntaxError:!1,TypeError:!1,URIError:!1},5:{JSON:!1},6:{ArrayBuffer:!1,DataView:!1,Float32Array:!1,Float64Array:!1,Int8Array:!1,Int16Array:!1,Int32Array:!1,Map:!1,Promise:!1,Proxy:!1,Reflect:!1,Set:!1,Symbol:!1,Uint8Array:!1,Uint16Array:!1,Uint32Array:!1,Uint8ClampedArray:!1,WeakMap:!1,WeakSet:!1},8:{Atomics:!1,SharedArrayBuffer:!1},11:{BigInt:!1,globalThis:!1}},e.browser={Audio:!1,Blob:!1,addEventListener:!1,applicationCache:!1,atob:!1,blur:!1,btoa:!1,cancelAnimationFrame:!1,CanvasGradient:!1,CanvasPattern:!1,CanvasRenderingContext2D:!1,CSS:!1,CSSImportRule:!1,CSSGroupingRule:!1,CSSMarginRule:!1,CSSMediaRule:!1,CSSNamespaceRule:!1,CSSPageRule:!1,CSSRule:!1,CSSRuleList:!1,CSSStyleDeclaration:!1,CSSStyleRule:!1,CSSStyleSheet:!1,clearInterval:!1,clearTimeout:!1,close:!1,closed:!1,Comment:!1,CompositionEvent:!1,createImageBitmap:!1,CustomEvent:!1,DOMParser:!1,defaultStatus:!1,dispatchEvent:!1,Document:!1,document:!1,DocumentFragment:!1,Element:!1,ElementTimeControl:!1,Event:!1,event:!1,fetch:!1,File:!1,FileList:!1,FileReader:!1,FormData:!1,focus:!1,frames:!1,getComputedStyle:!1,Headers:!1,HTMLAnchorElement:!1,HTMLAreaElement:!1,HTMLAudioElement:!1,HTMLBaseElement:!1,HTMLBlockquoteElement:!1,HTMLBodyElement:!1,HTMLBRElement:!1,HTMLButtonElement:!1,HTMLCanvasElement:!1,HTMLCollection:!1,HTMLDataElement:!1,HTMLDataListElement:!1,HTMLDetailsElement:!1,HTMLDialogElement:!1,HTMLDirectoryElement:!1,HTMLDivElement:!1,HTMLDListElement:!1,HTMLElement:!1,HTMLEmbedElement:!1,HTMLFieldSetElement:!1,HTMLFontElement:!1,HTMLFormElement:!1,HTMLFrameElement:!1,HTMLFrameSetElement:!1,HTMLHeadElement:!1,HTMLHeadingElement:!1,HTMLHRElement:!1,HTMLHtmlElement:!1,HTMLIFrameElement:!1,HTMLImageElement:!1,HTMLInputElement:!1,HTMLIsIndexElement:!1,HTMLLabelElement:!1,HTMLLayerElement:!1,HTMLLegendElement:!1,HTMLLIElement:!1,HTMLLinkElement:!1,HTMLMapElement:!1,HTMLMarqueeElement:!1,HTMLMediaElement:!1,HTMLMenuElement:!1,HTMLMetaElement:!1,HTMLMeterElement:!1,HTMLModElement:!1,HTMLObjectElement:!1,HTMLOListElement:!1,HTMLOptGroupElement:!1,HTMLOptionElement:!1,HTMLParagraphElement:!1,HTMLParamElement:!1,HTMLPictureElement:!1,HTMLPreElement:!1,HTMLProgressElement:!1,HTMLQuoteElement:!1,HTMLScriptElement:!1,HTMLSelectElement:!1,HTMLSlotElement:!1,HTMLSourceElement:!1,HTMLStyleElement:!1,HTMLTableCaptionElement:!1,HTMLTableCellElement:!1,HTMLTableColElement:!1,HTMLTableElement:!1,HTMLTableRowElement:!1,HTMLTableSectionElement:!1,HTMLTemplateElement:!1,HTMLTextAreaElement:!1,HTMLTimeElement:!1,HTMLTitleElement:!1,HTMLTrackElement:!1,HTMLUListElement:!1,HTMLVideoElement:!1,history:!1,Image:!1,IntersectionObserver:!1,Intl:!1,length:!1,localStorage:!1,location:!1,matchMedia:!1,MediaList:!1,MediaRecorder:!1,MessageChannel:!1,MessageEvent:!1,MessagePort:!1,MouseEvent:!1,moveBy:!1,moveTo:!1,MutationObserver:!1,name:!1,Node:!1,NodeFilter:!1,NodeList:!1,Notification:!1,navigator:!1,onbeforeunload:!0,onblur:!0,onerror:!0,onfocus:!0,onload:!0,onresize:!0,onunload:!0,open:!1,openDatabase:!1,opener:!1,Option:!1,origin:!1,parent:!1,performance:!1,print:!1,queueMicrotask:!1,Range:!1,requestAnimationFrame:!1,removeEventListener:!1,Request:!1,resizeBy:!1,resizeTo:!1,Response:!1,screen:!1,scroll:!1,scrollBy:!1,scrollTo:!1,sessionStorage:!1,setInterval:!1,setTimeout:!1,SharedWorker:!1,status:!1,Storage:!1,StyleSheet:!1,SVGAElement:!1,SVGAltGlyphDefElement:!1,SVGAltGlyphElement:!1,SVGAltGlyphItemElement:!1,SVGAngle:!1,SVGAnimateColorElement:!1,SVGAnimateElement:!1,SVGAnimateMotionElement:!1,SVGAnimateTransformElement:!1,SVGAnimatedAngle:!1,SVGAnimatedBoolean:!1,SVGAnimatedEnumeration:!1,SVGAnimatedInteger:!1,SVGAnimatedLength:!1,SVGAnimatedLengthList:!1,SVGAnimatedNumber:!1,SVGAnimatedNumberList:!1,SVGAnimatedPathData:!1,SVGAnimatedPoints:!1,SVGAnimatedPreserveAspectRatio:!1,SVGAnimatedRect:!1,SVGAnimatedString:!1,SVGAnimatedTransformList:!1,SVGAnimationElement:!1,SVGCSSRule:!1,SVGCircleElement:!1,SVGClipPathElement:!1,SVGColor:!1,SVGColorProfileElement:!1,SVGColorProfileRule:!1,SVGComponentTransferFunctionElement:!1,SVGCursorElement:!1,SVGDefsElement:!1,SVGDescElement:!1,SVGDocument:!1,SVGElement:!1,SVGElementInstance:!1,SVGElementInstanceList:!1,SVGEllipseElement:!1,SVGExternalResourcesRequired:!1,SVGFEBlendElement:!1,SVGFEColorMatrixElement:!1,SVGFEComponentTransferElement:!1,SVGFECompositeElement:!1,SVGFEConvolveMatrixElement:!1,SVGFEDiffuseLightingElement:!1,SVGFEDisplacementMapElement:!1,SVGFEDistantLightElement:!1,SVGFEFloodElement:!1,SVGFEFuncAElement:!1,SVGFEFuncBElement:!1,SVGFEFuncGElement:!1,SVGFEFuncRElement:!1,SVGFEGaussianBlurElement:!1,SVGFEImageElement:!1,SVGFEMergeElement:!1,SVGFEMergeNodeElement:!1,SVGFEMorphologyElement:!1,SVGFEOffsetElement:!1,SVGFEPointLightElement:!1,SVGFESpecularLightingElement:!1,SVGFESpotLightElement:!1,SVGFETileElement:!1,SVGFETurbulenceElement:!1,SVGFilterElement:!1,SVGFilterPrimitiveStandardAttributes:!1,SVGFitToViewBox:!1,SVGFontElement:!1,SVGFontFaceElement:!1,SVGFontFaceFormatElement:!1,SVGFontFaceNameElement:!1,SVGFontFaceSrcElement:!1,SVGFontFaceUriElement:!1,SVGForeignObjectElement:!1,SVGGElement:!1,SVGGlyphElement:!1,SVGGlyphRefElement:!1,SVGGradientElement:!1,SVGHKernElement:!1,SVGICCColor:!1,SVGImageElement:!1,SVGLangSpace:!1,SVGLength:!1,SVGLengthList:!1,SVGLineElement:!1,SVGLinearGradientElement:!1,SVGLocatable:!1,SVGMPathElement:!1,SVGMarkerElement:!1,SVGMaskElement:!1,SVGMatrix:!1,SVGMetadataElement:!1,SVGMissingGlyphElement:!1,SVGNumber:!1,SVGNumberList:!1,SVGPaint:!1,SVGPathElement:!1,SVGPathSeg:!1,SVGPathSegArcAbs:!1,SVGPathSegArcRel:!1,SVGPathSegClosePath:!1,SVGPathSegCurvetoCubicAbs:!1,SVGPathSegCurvetoCubicRel:!1,SVGPathSegCurvetoCubicSmoothAbs:!1,SVGPathSegCurvetoCubicSmoothRel:!1,SVGPathSegCurvetoQuadraticAbs:!1,SVGPathSegCurvetoQuadraticRel:!1,SVGPathSegCurvetoQuadraticSmoothAbs:!1,SVGPathSegCurvetoQuadraticSmoothRel:!1,SVGPathSegLinetoAbs:!1,SVGPathSegLinetoHorizontalAbs:!1,SVGPathSegLinetoHorizontalRel:!1,SVGPathSegLinetoRel:!1,SVGPathSegLinetoVerticalAbs:!1,SVGPathSegLinetoVerticalRel:!1,SVGPathSegList:!1,SVGPathSegMovetoAbs:!1,SVGPathSegMovetoRel:!1,SVGPatternElement:!1,SVGPoint:!1,SVGPointList:!1,SVGPolygonElement:!1,SVGPolylineElement:!1,SVGPreserveAspectRatio:!1,SVGRadialGradientElement:!1,SVGRect:!1,SVGRectElement:!1,SVGRenderingIntent:!1,SVGSVGElement:!1,SVGScriptElement:!1,SVGSetElement:!1,SVGStopElement:!1,SVGStringList:!1,SVGStylable:!1,SVGStyleElement:!1,SVGSwitchElement:!1,SVGSymbolElement:!1,SVGTRefElement:!1,SVGTSpanElement:!1,SVGTests:!1,SVGTextContentElement:!1,SVGTextElement:!1,SVGTextPathElement:!1,SVGTextPositioningElement:!1,SVGTitleElement:!1,SVGTransform:!1,SVGTransformList:!1,SVGTransformable:!1,SVGURIReference:!1,SVGUnitTypes:!1,SVGUseElement:!1,SVGVKernElement:!1,SVGViewElement:!1,SVGViewSpec:!1,SVGZoomAndPan:!1,Text:!1,TextDecoder:!1,TextEncoder:!1,TimeEvent:!1,top:!1,URL:!1,URLSearchParams:!1,WebGLActiveInfo:!1,WebGLBuffer:!1,WebGLContextEvent:!1,WebGLFramebuffer:!1,WebGLProgram:!1,WebGLRenderbuffer:!1,WebGLRenderingContext:!1,WebGLShader:!1,WebGLShaderPrecisionFormat:!1,WebGLTexture:!1,WebGLUniformLocation:!1,WebSocket:!1,window:!1,Window:!1,Worker:!1,XDomainRequest:!1,XMLDocument:!1,XMLHttpRequest:!1,XMLSerializer:!1,XPathEvaluator:!1,XPathException:!1,XPathExpression:!1,XPathNamespace:!1,XPathNSResolver:!1,XPathResult:!1},e.devel={alert:!1,confirm:!1,console:!1,Debug:!1,opera:!1,prompt:!1},e.worker={addEventListener:!0,atob:!0,btoa:!0,clearInterval:!0,clearTimeout:!0,createImageBitmap:!0,dispatchEvent:!0,importScripts:!0,onmessage:!0,origin:!0,postMessage:!0,queueMicrotask:!0,removeEventListener:!0,self:!0,setInterval:!0,setTimeout:!0,FileReaderSync:!0},e.nonstandard={escape:!1,unescape:!1},e.couch={require:!1,respond:!1,getRow:!1,emit:!1,send:!1,start:!1,sum:!1,log:!1,exports:!1,module:!1,provides:!1},e.node={__filename:!1,__dirname:!1,arguments:!1,GLOBAL:!1,global:!1,module:!1,require:!1,Intl:!1,Buffer:!0,console:!0,exports:!0,process:!0,setTimeout:!0,clearTimeout:!0,setInterval:!0,clearInterval:!0,setImmediate:!0,clearImmediate:!0,URL:!0,URLSearchParams:!0},e.browserify={__filename:!1,__dirname:!1,global:!1,module:!1,require:!1,Buffer:!0,exports:!0,process:!0},e.phantom={phantom:!0,require:!0,WebPage:!0,console:!0,exports:!0},e.qunit={asyncTest:!1,deepEqual:!1,equal:!1,expect:!1,module:!1,notDeepEqual:!1,notEqual:!1,notOk:!1,notPropEqual:!1,notStrictEqual:!1,ok:!1,propEqual:!1,QUnit:!1,raises:!1,start:!1,stop:!1,strictEqual:!1,test:!1,throws:!1},e.rhino={arguments:!1,defineClass:!1,deserialize:!1,gc:!1,help:!1,importClass:!1,importPackage:!1,java:!1,load:!1,loadClass:!1,Packages:!1,print:!1,quit:!1,readFile:!1,readUrl:!1,runCommand:!1,seal:!1,serialize:!1,spawn:!1,sync:!1,toint32:!1,version:!1},e.shelljs={target:!1,echo:!1,exit:!1,cd:!1,pwd:!1,ls:!1,find:!1,cp:!1,rm:!1,mv:!1,mkdir:!1,test:!1,cat:!1,sed:!1,grep:!1,which:!1,dirs:!1,pushd:!1,popd:!1,env:!1,exec:!1,chmod:!1,config:!1,error:!1,tempdir:!1},e.typed={ArrayBuffer:!1,ArrayBufferView:!1,DataView:!1,Float32Array:!1,Float64Array:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1},e.wsh={ActiveXObject:!0,Enumerator:!0,GetObject:!0,ScriptEngine:!0,ScriptEngineBuildVersion:!0,ScriptEngineMajorVersion:!0,ScriptEngineMinorVersion:!0,VBArray:!0,WSH:!0,WScript:!0,XDomainRequest:!0},e.dojo={dojo:!1,dijit:!1,dojox:!1,define:!1,require:!1},e.jquery={$:!1,jQuery:!1},e.mootools={$:!1,$$:!1,Asset:!1,Browser:!1,Chain:!1,Class:!1,Color:!1,Cookie:!1,Core:!1,Document:!1,DomReady:!1,DOMEvent:!1,DOMReady:!1,Drag:!1,Element:!1,Elements:!1,Event:!1,Events:!1,Fx:!1,Group:!1,Hash:!1,HtmlTable:!1,IFrame:!1,IframeShim:!1,InputValidator:!1,instanceOf:!1,Keyboard:!1,Locale:!1,Mask:!1,MooTools:!1,Native:!1,Options:!1,OverText:!1,Request:!1,Scroller:!1,Slick:!1,Slider:!1,Sortables:!1,Spinner:!1,Swiff:!1,Tips:!1,Type:!1,typeOf:!1,URI:!1,Window:!1},e.prototypejs={$:!1,$$:!1,$A:!1,$F:!1,$H:!1,$R:!1,$break:!1,$continue:!1,$w:!1,Abstract:!1,Ajax:!1,Class:!1,Enumerable:!1,Element:!1,Event:!1,Field:!1,Form:!1,Hash:!1,Insertion:!1,ObjectRange:!1,PeriodicalExecuter:!1,Position:!1,Prototype:!1,Selector:!1,Template:!1,Toggle:!1,Try:!1,Autocompleter:!1,Builder:!1,Control:!1,Draggable:!1,Draggables:!1,Droppables:!1,Effect:!1,Sortable:!1,SortableObserver:!1,Sound:!1,Scriptaculous:!1},e.yui={YUI:!1,Y:!1,YUI_config:!1},e.mocha={mocha:!1,describe:!1,xdescribe:!1,context:!1,xcontext:!1,it:!1,xit:!1,specify:!1,xspecify:!1,before:!1,after:!1,beforeEach:!1,afterEach:!1,suite:!1,test:!1,setup:!1,teardown:!1,suiteSetup:!1,suiteTeardown:!1},e.jasmine={jasmine:!1,describe:!1,xdescribe:!1,it:!1,xit:!1,beforeEach:!1,afterEach:!1,setFixtures:!1,loadFixtures:!1,spyOn:!1,expect:!1,runs:!1,waitsFor:!1,waits:!1,beforeAll:!1,afterAll:!1,fail:!1,fdescribe:!1,fit:!1,pending:!1,spyOnProperty:!1}},{}],jshint:[function(_,$,e){/*! + * JSHint, by JSHint Community. + * + * Licensed under the MIT license. + * + * JSHint is a derivative work of JSLint: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * 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. + * + */ var t=_("lodash"),n=_("events"),r=_("./vars.js"),i=_("./messages.js"),u=_("./lex.js").Lexer,o=_("./reg.js"),a=_("./state.js").state,s=_("./style.js"),c=_("./options.js"),l=_("./scope-manager.js"),f=_("./prod-params.js");_("console-browserify");var d=function(){"use strict";var _,$,e,p,h,v,g,m,y,k,x={"<":!0,"<=":!0,"==":!0,"===":!0,"!==":!0,"!=":!0,">":!0,">=":!0,"+":!0,"-":!0,"*":!0,"/":!0,"%":!0},b=[],E=new n.EventEmitter;function A(_,$,e){var n,r,i,u,o,a,s,l="E001";return $?(n="",r=c.validNames):(n="unstable ",r=c.unstableNames),_=_.trim(),!!/^[+-]W\d{3}$/g.test(_)||!!(-1!==r.indexOf(_)||"jslint"===e.type||t.has(c.removed,_))||(i=e,u=n,L(l,i,u,o=_,a,s),!1)}function S(_,$){return!!_&&!!_.identifier&&_.value===$}function C(_,$){if(!$.reserved)return!1;var e=$.meta;if(e&&e.isFutureReservedWord){if(a.inES5()&&(!e.es5||$.isProperty))return!1}else if(e&&e.es5&&!a.inES5())return!1;return(!(e&&e.strictOnly&&a.inES5())||!!a.option.strict||!!a.isStrict())&&("await"!==$.id||!!(_&f.async)||!!a.option.module)&&("yield"!==$.id||!!(_&f.yield)||a.isStrict())}function F(_,$){return _.replace(/\{([^{}]*)\}/g,function(_,e){var t=$[e];return"string"==typeof t||"number"==typeof t?t:_})}function w(_,$){Object.keys($).forEach(function(e){t.has(d.blacklist,e)||(_[e]=$[e])})}function W(){var _=null;(function _(){if(a.option.enforceall){for(var $ in c.bool.enforcing)void 0!==a.option[$]||c.noenforceall[$]||(a.option[$]=!0);for(var e in c.bool.relaxing)void 0===a.option[e]&&(a.option[e]=!1)}})(),(_=a.inferEsVersion())&&D("E059",a.tokens.next,"esversion",_),a.inES5()&&w(k,r.ecmaIdentifiers[5]),a.inES6()&&w(k,r.ecmaIdentifiers[6]),a.inES8()&&w(k,r.ecmaIdentifiers[8]),a.inES11()&&w(k,r.ecmaIdentifiers[11]),"global"===a.option.strict&&"globalstrict"in a.option&&D("E059",a.tokens.next,"strict","globalstrict"),a.option.module&&!a.inES6()&&L("W134",a.tokens.next,"module",6),a.option.regexpu&&!a.inES6()&&L("W134",a.tokens.next,"regexpu",6),a.option.couch&&w(k,r.couch),a.option.qunit&&w(k,r.qunit),a.option.rhino&&w(k,r.rhino),a.option.shelljs&&(w(k,r.shelljs),w(k,r.node)),a.option.typed&&w(k,r.typed),a.option.phantom&&w(k,r.phantom),a.option.prototypejs&&w(k,r.prototypejs),a.option.node&&(w(k,r.node),w(k,r.typed)),a.option.devel&&w(k,r.devel),a.option.dojo&&w(k,r.dojo),a.option.browser&&(w(k,r.browser),w(k,r.typed)),a.option.browserify&&(w(k,r.browser),w(k,r.typed),w(k,r.browserify)),a.option.nonstandard&&w(k,r.nonstandard),a.option.jasmine&&w(k,r.jasmine),a.option.jquery&&w(k,r.jquery),a.option.mootools&&w(k,r.mootools),a.option.worker&&w(k,r.worker),a.option.wsh&&w(k,r.wsh),a.option.yui&&w(k,r.yui),a.option.mocha&&w(k,r.mocha)}function D(_,$,e,t){var n=Math.floor($.line/a.lines.length*100),r=i.errors[_].desc,u={name:"JSHintError",line:$.line,character:$.from,message:r+" ("+n+"% scanned).",raw:r,code:_,a:e,b:t};throw u.reason=F(r,u)+" ("+n+"% scanned).",u}function B(){var _=a.ignoredLines;t.isEmpty(_)||(d.errors=t.reject(d.errors,function($){return _[$.line]}))}function L(_,$,e,t,n,r){var u,o,s,c;if(/^W\d{3}$/.test(_)){if(a.ignored[_])return;c=i.warnings[_]}else/E\d{3}/.test(_)?c=i.errors[_]:/I\d{3}/.test(_)&&(c=i.info[_]);return"(end)"===($=$||a.tokens.next||{}).id&&($=a.tokens.curr),o=$.line,u=$.from,(s={id:"(error)",raw:c.desc,code:c.code,evidence:a.lines[o-1]||"",line:o,character:u,scope:d.scope,a:e,b:t,c:n,d:r}).reason=F(c.desc,s),d.errors.push(s),B(),d.errors.filter(function(_){return/E\d{3}/.test(_.code)}).length>=a.option.maxerr&&D("E043",$),s}function M(_,$,e,t,n,r,i){return L(_,{line:$,from:e},t,n,r,i)}function T(_,$,e,t,n,r){L(_,$,e,t,n,r)}function j(_,$,e,t,n,r,i){var u,o,a,s,c,l;return u=_,o={line:$,from:e},a=t,s=n,c=r,void L(u,o,a,s,c,l=i)}function G(_,$){d.internals.push({id:"(internal)",elem:_,token:$,code:$.value.replace(/([^\\])(\\*)\2\\n/g,"$1\n")})}function I(_,e){var n=_.body.split(",").map(function(_){return _.trim()}),r={};if("falls through"===_.type){e.caseFallsThrough=!0;return}if("globals"===_.type)for(var i in n.forEach(function($,e){var t=$.split(":"),i=t[0].trim();if("-"===i||!i.length){if(e>0&&e===n.length-1)return;var u,o="E002";L(o,u=_,void 0,void 0,void 0,void 0);return}"-"===i.charAt(0)?(i=i.slice(1),d.blacklist[i]=i,delete k[i]):r[i]=t.length>1&&"true"===t[1].trim()}),w(k,r),r)t.has(r,i)&&($[i]=_);"exported"===_.type&&n.forEach(function($,e){if(!$.length){var t;if(e>0&&e===n.length-1)return;L("E002",t=_,void 0,void 0,void 0,void 0);return}a.funct["(scope)"].addExported($)}),"members"===_.type&&(y=y||{},n.forEach(function(_){var $=_.charAt(0);$===_.charAt(_.length-1)&&('"'===$||"'"===$)&&(_=_.substr(1,_.length-2).replace('\\"','"')),y[_]=!1}));var u=["maxstatements","maxparams","maxdepth","maxcomplexity","maxerr","maxlen","indent"];("jshint"===_.type||"jslint"===_.type||"jshint.unstable"===_.type)&&(n.forEach(function($){var e,t,n=$.split(":"),r=n[0].trim(),i=n.length>1?n[1].trim():"";if(A(r,"jshint.unstable"!==_.type,_)){if(u.indexOf(r)>=0){if("false"!==i){if("number"!=typeof(e=+i)||!isFinite(e)||e<=0||Math.floor(e)!==e){o=_,L("E032",o,s=i,void 0,void 0,void 0);return}a.option[r]=e}else a.option[r]="indent"===r&&4;return}if("validthis"===r){if(a.funct["(global)"])return void void L("E009",void 0,void 0,void 0,void 0,void 0);if("true"!==i&&"false"!==i){return void void L("E002",l=_,void 0,void 0,void 0,void 0)}return void(a.option.validthis="true"===i)}if("quotmark"===r){switch(i){case"true":case"false":a.option.quotmark="true"===i;break;case"double":case"single":a.option.quotmark=i;break;default:L("E002",f=_,void 0,void 0,void 0,void 0)}return}if("shadow"===r){switch(i){case"true":a.option.shadow=!0;break;case"outer":a.option.shadow="outer";break;case"false":case"inner":a.option.shadow="inner";break;default:L("E002",d=_,void 0,void 0,void 0,void 0)}return}if("unused"===r){switch(i){case"true":a.option.unused=!0;break;case"false":a.option.unused=!1;break;case"vars":case"strict":a.option.unused=i;break;default:L("E002",p=_,void 0,void 0,void 0,void 0)}return}if("latedef"===r){switch(i){case"true":a.option.latedef=!0;break;case"false":a.option.latedef=!1;break;case"nofunc":a.option.latedef="nofunc";break;default:L("E002",h=_,void 0,void 0,void 0,void 0)}return}if("ignore"===r){if("line"===i)a.ignoredLines[_.line]=!0,B();else{L("E002",v=_,void 0,void 0,void 0,void 0)}return}if("strict"===r){switch(i){case"true":a.option.strict=!0;break;case"false":a.option.strict=!1;break;case"global":case"implied":a.option.strict=i;break;default:L("E002",g=_,void 0,void 0,void 0,void 0)}return}if("module"===r&&!_8(a.funct)){L("E055",m=_,"module",void 0,void 0,void 0)}if("esversion"===r){switch(i){case"3":case"5":case"6":case"7":case"8":case"9":case"10":case"11":a.option.moz=!1,a.option.esversion=+i;break;case"2015":case"2016":case"2017":case"2018":case"2019":case"2020":a.option.moz=!1,a.option.esversion=+i-2009;break;default:L("E002",y=_,void 0,void 0,void 0,void 0)}if(!_8(a.funct)){L("E055",k=_,"esversion",void 0,void 0,void 0)}return}var o,s,l,f,d,p,h,v,g,m,y,k,x,b=/^([+-])(W\d{3})$/g.exec(r);if(b){a.ignored[b[2]]="-"===b[1];return}if("true"===i||"false"===i){"jslint"===_.type?(t=c.renamed[r]||r,a.option[t]="true"===i,void 0!==c.inverted[t]&&(a.option[t]=!a.option[t])):"jshint.unstable"===_.type?a.option.unstable[r]="true"===i:a.option[r]="true"===i;return}L("E002",x=_,void 0,void 0,void 0,void 0)}}),W())}function P(_){var $,e=_||0,t=v.length;if(e="a"&&$<="z"||$>="A"&&$<="Z")&&(_.identifier=_.reserved=!0),_}function Y(_,$){var e=K(_,150);return Q(e),e.nud="function"==typeof $?$:function(_){return this.arity="unary",this.right=U(_,150),("++"===this.id||"--"===this.id)&&(a.option.plusplus&&L("W016",this,this.id),this.right&&_7(_,this.right,this)),this},e}function _2(_,$){var e=K(_,0);return e.type=_,e.nud=$,e}function _1(_,$){var e=_2(_,$);return e.identifier=!0,e.reserved=!0,e}function _0(_,$){var e=_2(_,a.syntax["(identifier)"].nud);return($=$||{}).isFutureReservedWord=!0,e.value=_,e.identifier=!0,e.reserved=!0,e.meta=$,e}function __(_,$,e,t){var n=K(_,e);return Q(n),n.infix=!0,n.led=function(n,r){return(t||H(a.tokens.prev,a.tokens.curr),("in"===_||"instanceof"===_)&&"!"===r.id&&L("W018",r,"!"),"function"==typeof $)?$(n,r,this):(this.left=r,this.right=U(n,e),this)},n}function _3(_,$){var e=K(_,100);return e.infix=!0,e.led=function(_,e){H(a.tokens.prev,a.tokens.curr),this.left=e;var t=this.right=U(_,100);return S(e,"NaN")||S(t,"NaN")?L("W019",this):$&&$.apply(this,[_,e,t]),e&&t||D("E041",a.tokens.curr),"!"===e.id&&L("W018",e,"!"),"!"===t.id&&L("W018",t,"!"),this},e}function _$(_){return"unary"===_.arity&&"++"!==_.id&&"--"!==_.id}var _4={};function _5(_,$,e){var n;return!e.option.notypeof&&!!_&&!!$&&(n=e.inES6()?_4.es6:_4.es3,"(identifier)"===$.type&&"typeof"===$.value&&"(string)"===_.type&&("bigint"===_.value?(e.inES11()||L("W119",_,"BigInt","11"),!1):!t.includes(n,_.value)))}function _6(_,$){var e=!1;return"this"===_.type&&null===$.funct["(context)"]?e=!0:"(identifier)"===_.type&&($.option.node&&"global"===_.value?e=!0:$.option.browser&&("window"===_.value||"document"===_.value)&&(e=!0)),e}function _7(_,$,e,t){var n,r,i,u=t&&t.allowDestructuring;if(e=e||$,a.option.freeze){var o=function _($){var e=["Array","ArrayBuffer","Boolean","Collator","DataView","Date","DateTimeFormat","Error","EvalError","Float32Array","Float64Array","Function","Infinity","Intl","Int16Array","Int32Array","Int8Array","Iterator","Number","NumberFormat","Object","RangeError","ReferenceError","RegExp","StopIteration","String","SyntaxError","TypeError","Uint16Array","Uint32Array","Uint8Array","Uint8ClampedArray","URIError"],t=function _($){if("object"==typeof $)return"prototype"===$.right?$:_($.left)}($);if(t)return function _($){for(;!$.identifier&&"object"==typeof $.left;)$=$.left;if($.identifier&&e.indexOf($.value)>=0&&a.funct["(scope)"].isPredefined($.value))return $.value}(t)}($);o&&L("W121",$,o)}if($.identifier&&!$.isMetaProperty&&a.funct["(scope)"].block.reassign($.value,$),"."===$.id)return $.left&&("arguments"!==$.left.value||a.isStrict())||L("W143",e),a.nameStack.set(a.tokens.prev),!0;if("{"===$.id||"["===$.id)return u&&$.destructAssign||("{"!==$.id&&$.left?"arguments"!==$.left.value||a.isStrict()||L("W143",e):L("E031",e)),"["===$.id&&a.nameStack.set($.right),!0;if($.identifier&&!C(_,$)&&!$.isMetaProperty){if("exception"===a.funct["(scope)"].bindingtype($.value)&&L("W022",$),"eval"===$.value&&a.isStrict()){return L("E031",n=e,void 0,void 0,void 0,void 0),!1}if("arguments"===$.value){if(a.isStrict()){return L("E031",r=e,void 0,void 0,void 0,void 0),!1}L("W143",e)}return a.nameStack.set($),!0}return L("E031",i=e,void 0,void 0,void 0,void 0),!1}function _e(_,$){var e=__(_,"function"==typeof $?$:function(_,$,e){return e.left=$,_7(_,$,e,{allowDestructuring:!0}),e.right=U(_,10),e},20);return e.exps=!0,e.assign=!0,e}function _t(_,$,e){var t=K(_,e);return Q(t),t.infix=!0,t.led="function"==typeof $?$:function(_,$){return a.option.bitwise&&L("W016",this,this.id),this.left=$,this.right=U(_,e),this},t}function _n(_){return K(_,20).exps=!0,__(_,function(_,$,e){return a.option.bitwise&&L("W016",e,e.id),_7(_,$,e),e.right=U(_,10),e},20)}function _r(_){var $=K(_,150);return $.led=function(_,$){return a.option.plusplus&&L("W016",this,this.id),_7(_,$,this),this.left=$,this},$}function _i(_,$,e){if(a.tokens.next.identifier){e||V();var t=a.tokens.curr;return C(_,t)&&!($&&a.inES5())&&L("W024",a.tokens.curr,a.tokens.curr.id),t.value}}function _u(_){if(!_X(a.tokens.next,"..."))return!1;if(a.inES6(!0)||L("W119",a.tokens.next,_+" operator","6"),V(),_X(a.tokens.next,"..."))for(L("E024",a.tokens.next,"...");_X(a.tokens.next,"...");)V();return!0}function _o(_,$){var e,t,n=_i(_,$,!1);if(n)return n;e=a.tokens.next,L("E030",e,t=a.tokens.next.value,void 0,void 0,void 0),";"!==a.tokens.next.id&&V()}function _a(_){var $,e=0;if(";"===a.tokens.next.id&&!_.inBracelessBlock)for(;;){do $=P(e),e+=1;while("(end)"!==$.id&&"(comment)"===$.id);if($.reach)return;if("(endline)"!==$.id){if(t($,e)){!0===a.option.latedef&&L("W026",$);break}L("W027",$,$.value,_.value);break}}function t(_,$){return"function"===_.id||("async"===_.id?"function"===(_=P($)).id:void 0)}}function _s(_){if(";"!==a.tokens.next.id){if(a.tokens.next.isUnclosed)return V();var $=z(a.tokens.curr,a.tokens.next)&&"(end)"!==a.tokens.next.id,e=_X(a.tokens.next,"}");!$||e||"do"===_.id&&a.inES6(!0)?a.option.asi||e&&$&&a.option.lastsemic||M("W033",a.tokens.curr.line,a.tokens.curr.character):j("E058",a.tokens.curr.line,a.tokens.curr.character)}else V(";")}function _c(_){var $,e=h,t=a.tokens.next,n=!1;if(_|=f.initial,";"===t.id){V(";");return}var r=C(_,t);if(r&&t.meta&&t.meta.isFutureReservedWord&&!t.fud&&(L("W024",t,t.id),r=!1),t.identifier&&!r&&":"===P().id&&(V(),V(":"),n=!0,a.funct["(scope)"].stack(),a.funct["(scope)"].block.addLabel(t.value,{token:a.tokens.curr}),a.tokens.next.labelled||"{"===a.tokens.next.value||L("W028",a.tokens.next,t.value,a.tokens.next.value),t=a.tokens.next),"{"===t.id){_d(_,!0,!0,!1,!1,"case"===a.funct["(verb)"]&&":"===a.tokens.curr.value),n&&a.funct["(scope)"].unstack();return}return($=U(_,0))&&!($.identifier&&"function"===$.value)&&!("(punctuator)"===$.type&&$.left&&$.left.identifier&&"function"===$.left.value)&&!a.isStrict()&&a.stmtMissingStrict()&&L("E007"),t.block||(a.option.expr||$&&$.exps?a.option.nonew&&$&&$.left&&"("===$.id&&"new"===$.left.id&&L("W031",t):L("W030",a.tokens.curr),_s(t)),h=e,n&&a.funct["(scope)"].unstack(),$}function _l(_){for(var $,e=[];!a.tokens.next.reach&&"(end)"!==a.tokens.next.id;)";"===a.tokens.next.id?(($=P())&&("("===$.id||"["===$.id)||L("W032"),V(";")):e.push(_c(_));return e}function _f(){for(var _=a.tokens.next;"(string)"===a.tokens.next.id;){var $,e=O();if(!R(0,_,e))break;_=e,V();var t=a.tokens.curr.value;if((a.directive[t]||"use strict"===t&&"implied"===a.option.strict)&&L("W034",a.tokens.curr,t),"use strict"===t&&a.inES7()&&!a.funct["(global)"]&&!1===a.funct["(hasSimpleParams)"]){L("E065",$=a.tokens.curr,void 0,void 0,void 0,void 0)}a.directive[t]=a.tokens.curr,_s(_)}a.isStrict()&&(a.option.undef=!0)}function _d(_,$,e,t,n,r){var i,u,o,s,c,l,d,v=p,g=h;p=$,l=a.tokens.next;var m=a.funct["(metrics)"];if(m.nestedBlockDepth+=1,m.verifyMaxNestedBlockDepthPerFunction(),"{"===a.tokens.next.id){if(V("{"),a.funct["(scope)"].stack(),"}"!==a.tokens.next.id){for(h+=a.option.indent;!$&&a.tokens.next.from>h;)h+=a.option.indent;if(t){for(d in c={},a.directive)c[d]=a.directive[d];_f(),a.funct["(isStrict)"]=a.isStrict(),a.option.strict&&a.funct["(context)"]["(global)"]&&!c["use strict"]&&!a.isStrict()&&L("E007")}s=_l(_),m.statementCount+=s.length,h-=a.option.indent}else t&&(a.funct["(isStrict)"]=a.isStrict());V("}",l),t&&(a.funct["(scope)"].validateParams(n),c&&(a.directive=c)),a.funct["(scope)"].unstack(),h=g}else if($){a.funct["(scope)"].stack(),(!e||a.option.curly)&&L("W116",a.tokens.next,"{",a.tokens.next.value);var y,k,x="if"===a.funct["(verb)"]||"else"===a.tokens.curr.id;if(a.tokens.next.inBracelessBlock=!0,h+=a.option.indent,s=[_c(_)],h-=a.option.indent,s[0]&&s[0].declaration&&!(x&&"function"===s[0].id)){y=s[0],L("E048",y,k=s[0].id[0].toUpperCase()+s[0].id.slice(1),void 0,void 0,void 0)}a.funct["(scope)"].unstack()}else if(t){if(a.funct["(scope)"].stack(),e&&!n&&!a.inMoz()){L("W118",i=a.tokens.curr,"function closure expressions",void 0,void 0,void 0)}n&&a.funct["(scope)"].validateParams(!0);var b=U(_,10);a.option.noreturnawait&&_&f.async&&b.identifier&&"await"===b.value&&L("W146",b),a.option.strict&&a.funct["(context)"]["(global)"]&&!a.isStrict()&&L("E007"),a.funct["(scope)"].unstack()}else{u=a.tokens.next,L("E021",u,"{",o=a.tokens.next.value,void 0,void 0)}switch(a.funct["(verb)"]){case"break":case"continue":case"return":case"throw":if(r)break;default:a.funct["(verb)"]=null}return p=v,$&&a.option.noempty&&(!s||0===s.length)&&L("W035",a.tokens.prev),m.nestedBlockDepth-=1,s}function _p(_){y&&"boolean"!=typeof y[_]&&L("W036",a.tokens.curr,_),"number"==typeof m[_]?m[_]+=1:m[_]=1}_4.legacy=["xml","unknown"],_4.es3=["undefined","boolean","number","string","function","object",],_4.es3=_4.es3.concat(_4.legacy),_4.es6=_4.es3.concat("symbol","bigint"),_2("(number)",function(){return"."===a.tokens.next.id&&L("W005",this),this}),_2("(string)",function(){return this}),a.syntax["(identifier)"]={type:"(identifier)",lbp:0,identifier:!0,nud:function(_){var $=this.value,e="=>"===a.tokens.next.id;return C(_,this)?L("W024",this,$):e||a.funct["(comparray)"].check($)||a.funct["(scope)"].block.use($,a.tokens.curr),this},led:function(){var _,$;_=a.tokens.next,L("E033",_,$=a.tokens.next.value,void 0,void 0,void 0)}};var _h,_v,_g={identifier:!1,template:!0};a.syntax["(template)"]=t.extend({lbp:155,type:"(template)",nud:_A,led:_A,noSubst:!1},_g),a.syntax["(template middle)"]=t.extend({lbp:0,type:"(template middle)",noSubst:!1},_g),a.syntax["(template tail)"]=t.extend({lbp:0,type:"(template tail)",tail:!0,noSubst:!1},_g),a.syntax["(no subst template)"]=t.extend({lbp:155,type:"(template)",nud:_A,led:_A,noSubst:!0,tail:!0},_g),_2("(regexp)",function(){return this}),Z("(endline)"),(_h=Z("(begin)")).line=_h.from=0,Z("(end)").reach=!0,Z("(error)").reach=!0,Z("}").reach=!0,Z(")"),Z("]"),Z('"').reach=!0,Z("'").reach=!0,Z(";"),Z(":").reach=!0,Z("#"),_1("else"),_1("case").reach=!0,_1("catch"),_1("default").reach=!0,_1("finally"),_1("true",function(){return this}),_1("false",function(){return this}),_1("null",function(){return this}),_1("this",function(){return a.isStrict()&&!a.funct["(method)"]&&!a.option.validthis&&(a.funct["(statement)"]&&a.funct["(name)"].charAt(0)>"Z"||a.funct["(global)"])&&L("W040",this),this}),(_v=_1("super",function(){return _N.call(a.tokens.curr,this),this})).rbp=161,_e("=","assign"),_e("+=","assignadd"),_e("-=","assignsub"),_e("*=","assignmult"),_e("/=","assigndiv").nud=function(){L("E014",void 0,void 0,void 0,void 0,void 0)},_e("%=","assignmod"),_e("**=",function(_,$,e){return a.inES7()||L("W119",e,"Exponentiation operator","7"),e.left=$,_7(_,$,e),e.right=U(_,10),e}),_n("&="),_n("|="),_n("^="),_n("<<="),_n(">>="),_n(">>>="),__(",",function(_,$,e){return a.option.nocomma&&L("W127",e),e.left=$,q()?e.right=U(_,10):e.right=null,e},10,!0),__("?",function(_,$,e){return _F(),e.left=$,e.right=U(_&~f.noin,10),V(":"),U(_,10),e},30),__("||",function(_,$,e){return _F(),e.left=$,e.right=U(_,40),e},40),__("&&",function(_,$,e){return _F(),e.left=$,e.right=U(_,50),e},50),__("??",function(_,$,e){if(!$.paren&&("||"===$.id||"&&"===$.id)){L("E024",t=e,"??",void 0,void 0,void 0)}a.inES11()||L("W119",e,"nullish coalescing","11"),_F(),e.left=$;var t,n,r,i,u,o=e.right=U(_,39);if(o){if(!o.paren&&("||"===o.id||"&&"===o.id)){n=e.right,L("E024",n,r=e.right.id,void 0,void 0,void 0)}}else{i=a.tokens.next,L("E024",i,u=a.tokens.next.id,void 0,void 0,void 0)}return e},39),__("**",function(_,$,e){if(a.inES7()||L("W119",e,"Exponentiation operator","7"),!$.paren&&_$($)){var t;L("E024",t=e,"**",void 0,void 0,void 0)}return e.left=$,e.right=U(_,e.rbp),e},150),a.syntax["**"].rbp=140,_t("|","bitor",70),_t("^","bitxor",80),_t("&","bitand",90),_3("==",function(_,$,e){var t=a.option.eqnull&&("null"===($&&$.value)||"null"===(e&&e.value));switch(!0){case!t&&a.option.eqeqeq:this.from=this.character,L("W116",this,"===","==");break;case _5(e,$,a):L("W122",this,e.value);break;case _5($,e,a):L("W122",this,$.value)}return this}),_3("===",function(_,$,e){return _5(e,$,a)?L("W122",this,e.value):_5($,e,a)&&L("W122",this,$.value),this}),_3("!=",function(_,$,e){return!(a.option.eqnull&&("null"===($&&$.value)||"null"===(e&&e.value)))&&a.option.eqeqeq?(this.from=this.character,L("W116",this,"!==","!=")):_5(e,$,a)?L("W122",this,e.value):_5($,e,a)&&L("W122",this,$.value),this}),_3("!==",function(_,$,e){return _5(e,$,a)?L("W122",this,e.value):_5($,e,a)&&L("W122",this,$.value),this}),_3("<"),_3(">"),_3("<="),_3(">="),_t("<<","shiftleft",120),_t(">>","shiftright",120),_t(">>>","shiftrightunsigned",120),__("in","in",120),__("instanceof",function(_,$,e){var t,n=a.funct["(scope)"];return(e.left=$,e.right=t=U(_,120),t)?(("(number)"===t.id||"(string)"===t.id||"null"===t.value||"undefined"===t.value&&!n.has("undefined")||"unary"===t.arity||"{"===t.id||"["===t.id&&!t.right||"(regexp)"===t.id||"(template)"===t.id&&!t.tag)&&L("E060",void 0,void 0,void 0,void 0,void 0),"function"===t.id&&L("W139"),e):e},120),__("+",function(_,$,e){var t,n=a.tokens.next;return(e.left=$,e.right=t=U(_,130),$&&t&&"(string)"===$.id&&"(string)"===t.id)?($.value+=t.value,$.character=t.character,!a.option.scripturl&&o.javascriptURL.test($.value)&&L("W050",$),$):(("+"===n.id||"++"===n.id)&&L("W007",e.right),e)},130),Y("+",function(_){var $=a.tokens.next;return this.arity="unary",this.right=U(_,150),("+"===$.id||"++"===$.id)&&L("W007",this.right),this}),__("-",function(_,$,e){var t=a.tokens.next;return e.left=$,e.right=U(_,130),("-"===t.id||"--"===t.id)&&L("W006",e.right),e},130),Y("-",function(_){var $=a.tokens.next;return this.arity="unary",this.right=U(_,150),("-"===$.id||"--"===$.id)&&L("W006",this.right),this}),__("*","mult",140),__("/","div",140),__("%","mod",140),_r("++"),Y("++","preinc"),a.syntax["++"].exps=!0,_r("--"),Y("--","predec"),a.syntax["--"].exps=!0,Y("delete",function(_){this.arity="unary";var $=U(_,150);return $&&("."!==$.id&&"["!==$.id&&L("W051"),this.first=$,$.identifier&&!a.isStrict()&&($.forgiveUndef=!0)),this}).exps=!0,Y("~",function(_){return a.option.bitwise&&L("W016",this,"~"),this.arity="unary",this.right=U(_,150),this}),__("..."),Y("!",function(_){return this.arity="unary",this.right=U(_,150),this.right||D("E041",this),!0===x[this.right.id]&&L("W018",this,"!"),this}),Y("typeof",function(_){this.arity="unary";var $=U(_,150);return this.first=this.right=$,$||D("E041",this),$.identifier&&($.forgiveUndef=!0),this}),Y("new",function(_){var $,e,t,n=_D(_,"target",function(){a.inES6(!0)||L("W119",a.tokens.prev,"new.target","6");for(var _,$=a.funct;$&&(_=!$["(global)"],$["(arrow)"]);)$=$["(context)"];_||L("W136",a.tokens.prev,"new.target")});if(n)return n;var r,i=a.tokens.next,u=U(_,155);if(!u)return this;if(!u.paren&&u.rbp>160){$=i,L("E024",$,e=i.value,void 0,void 0,void 0)}if("function"!==u.id){if(u.identifier)switch(u.value){case"Number":case"String":case"Boolean":case"Math":case"JSON":L("W053",a.tokens.prev,u.value);break;case"Symbol":a.inES6()&&L("W053",a.tokens.prev,u.value);break;case"Function":a.option.evil||L("W054");break;case"Date":case"RegExp":case"this":break;default:r=u.value.substr(0,1),a.option.newcap&&(r<"A"||r>"Z")&&!a.funct["(scope)"].isPredefined(u.value)&&L("W055",a.tokens.curr)}else if("?."!==u.id||u.paren)"."!==u.id&&"["!==u.id&&"("!==u.id&&L("W056",a.tokens.curr);else{t=u,L("E024",t,"?.",void 0,void 0,void 0)}}else a.option.supernew||L("W057",this);return"("===a.tokens.next.id||a.option.supernew||L("W058",a.tokens.curr,a.tokens.curr.value),this.first=this.right=u,this}),a.syntax.new.exps=!0;var _m=X("class",function(_){var $,e;return a.inES6()||L("W104",a.tokens.curr,"class","6"),a.inClassBody=!0,a.tokens.next.identifier&&"extends"!==a.tokens.next.value&&($=(e=a.tokens.next).value,_o(_),a.funct["(scope)"].addbinding($,{type:"class",initialized:!1,token:e})),"extends"===a.tokens.next.value&&(V("extends"),U(_,0)),e?(this.name=e,a.funct["(scope)"].initialize($)):this.name=null,a.funct["(scope)"].stack(),_y(this,_),this});function _y(_,$){var e,t,n,r,i,u,o,s,c,l,d,p,h,v,g,m,y,k,x,b,E,A,S=Object.create(null);for("{"===a.tokens.next.value?V("{"):(L("W116",a.tokens.curr,"identifier",a.tokens.next.type),V());"}"!==a.tokens.next.value;){if(b=!1,E=!1,$&=~f.preAsync,"static"!==a.tokens.next.value||_X(P(),"(")||(b=!0,V()),"async"!==a.tokens.next.value||_X(P(),"(")||($|=f.preAsync,V(),N(a.tokens.curr),_X(a.tokens.next,"*")&&(E=!0,V("*"),a.inES9()||L("W119",a.tokens.next,"async generators","9")),a.inES8()||L("W119",a.tokens.curr,"async functions","8")),"*"===a.tokens.next.value&&(E=!0,V()),"set"!==(x=a.tokens.next).value&&"get"!==x.value||_X(P(),"("))k=null;else{if(E){e=x,L("E024",e,t=x.value,void 0,void 0,void 0)}if(k=x.value,V(),x=a.tokens.next,b||"constructor"!==x.value){if(b&&"prototype"===x.value){n=x,r="static class "+k+"ter method",L("E049",n,r,i=x.value,void 0,void 0)}}else{u=x,o="class "+k+"ter method",L("E049",u,o,s=x.value,void 0,void 0)}}switch(x.value){case";":L("W032",x),V();break;case"constructor":if(b)y=_b($),_9(S,y,x,!0,b),_k(_,$,y,E);else{if(E||$&f.preAsync){c=x,L("E024",c,l=x.value,void 0,void 0,void 0)}else if(A){d=x,L("E024",d,p=x.value,void 0,void 0,void 0)}else A=!k&&!b;V(),_k(_,$,a.nameStack.infer())}break;case"[":y=_Z($),_k(_,$,y,E);break;default:if(void 0===(y=_b($))){h=x,L("E024",h,v=x.value,void 0,void 0,void 0),V();break}if(k)_K(k,S,y,x,!0,b),y=a.nameStack.infer();else{if(b&&"prototype"===y){g=x,L("E049",g,"static class method",m=y,void 0,void 0)}_9(S,y,x,!0,b)}_k(_,$,y,E)}}V("}"),_W(S),a.inClassBody=!1,a.funct["(scope)"].unstack()}function _k(_,$,e,t){if(t&&!a.inES6()&&L("W119",a.tokens.curr,"function*","6"),"("!==a.tokens.next.value){var n,r;if(n=a.tokens.next,L("E054",n,r=a.tokens.next.value,void 0,void 0,void 0),V(),"{"===a.tokens.next.value){V(),"}"===a.tokens.next.value&&(L("W116",a.tokens.next,"(",a.tokens.next.value),V(),_o($),V());return}for(;"("!==a.tokens.next.value;)V()}_S($,{name:e,type:t?"generator":null,isMethod:!0,statement:_})}function _x(_){var $,e=a.tokens.next,t=-1;do"("===e.value?_+=1:")"===e.value&&(_-=1),t+=1,$=e,e=P(t);while(!(0===_&&")"===$.value)&&"(end)"!==e.type);return e}function _b(_){var $=_i(_,!0);return $||("(string)"===a.tokens.next.id?($=a.tokens.next.value,V()):"(number)"===a.tokens.next.id&&($=a.tokens.next.value.toString(),V())),"hasOwnProperty"===$&&L("W001"),$}function _E(_,$,e){var n={"(name)":_,"(breakage)":0,"(loopage)":0,"(isStrict)":"unknown","(global)":!1,"(line)":null,"(character)":null,"(metrics)":null,"(statement)":null,"(context)":null,"(scope)":null,"(comparray)":null,"(yielded)":null,"(arrow)":null,"(async)":null,"(params)":null};return $&&t.extend(n,{"(line)":$.line,"(character)":$.character,"(metrics)":_C($)}),t.extend(n,e),n["(context)"]&&(n["(scope)"]=n["(context)"]["(scope)"],n["(comparray)"]=n["(context)"]["(comparray)"]),n}function _8(_){return _["(global)"]&&!_["(verb)"]}function _A(_,$){var e=this.context,t=this.noSubst,n=this.depth;if(!t)for(;!r();)!a.tokens.next.template||a.tokens.next.depth>n?U(_,0):V();return{id:"(template)",type:"(template)",tag:"number"==typeof $?null:$};function r(){if(a.tokens.curr.template&&a.tokens.curr.tail&&a.tokens.curr.context===e)return!0;var _=a.tokens.next.template&&a.tokens.next.tail&&a.tokens.next.context===e;return _&&V(),_||a.tokens.next.isUnclosed}}function _S(_,$){var n,r,i,u,o,s,c,l,d,p=a.option,h=a.ignored,v=_&f.preAsync;$&&(i=$.name,u=$.statement,o=$.classExprBinding,s="generator"===$.type,c="arrow"===$.type,l=$.isMethod,d=$.ignoreLoopFunc),_&=~f.noin,_&=~f.tryClause,v?_|=f.async:_&=~f.async,s?_|=f.yield:c||(_&=~f.yield),_&=~f.preAsync,a.option=Object.create(a.option),a.ignored=Object.create(a.ignored),a.funct=_E(i||a.nameStack.infer(),a.tokens.next,{"(statement)":u,"(context)":a.funct,"(arrow)":c,"(method)":l,"(async)":v}),n=a.funct,r=a.tokens.curr,e.push(a.funct),a.funct["(scope)"].stack("functionouter");var g=!l&&(i||o);g&&a.funct["(scope)"].block.add(g,o?"class":"function",a.tokens.curr,!1),c||a.funct["(scope)"].funct.add("arguments","var",r,!1),a.funct["(scope)"].stack("functionparams");var m=function _($,e){var n,r,i,u=[],o=[],s=!1,c=!1,l=0,f=e&&e.loneArg,d=!1;if(f&&!0===f.identifier)return a.funct["(scope)"].addParam(f.value,f),{arity:1,params:[f.value],isSimple:!0};if(n=a.tokens.next,e&&e.parsedOpening||V("("),")"===a.tokens.next.id){V(")");return}function p(_){a.funct["(scope)"].addParam.apply(a.funct["(scope)"],_)}for(;;){l++;var h,v,g=[];if(c=_u("rest"),t.includes(["{","["],a.tokens.next.id))for(i in d=!0,o=_B($))(i=o[i]).id&&(u.push(i.id),g.push([i.id,i.token]));else if(r=_o($))u.push(r),g.push([r,a.tokens.curr]);else for(;!_J(a.tokens.next,[",",")"]);)V();if(s&&"="!==a.tokens.next.id){L("W138",h=a.tokens.curr,void 0,void 0,void 0,void 0)}if("="===a.tokens.next.id){if(a.inES6()||L("W119",a.tokens.next,"default parameters","6"),c){L("E062",v=a.tokens.next,void 0,void 0,void 0,void 0)}V("="),s=!0,U($,10)}if(g.forEach(p),","===a.tokens.next.id&&(c&&L("W131",a.tokens.next),V(","),q({allowTrailing:!0})),")"===a.tokens.next.id)return","!==a.tokens.curr.id||a.inES8()||L("W119",a.tokens.curr,"Trailing comma in function parameters","8"),V(")",n),{arity:l,params:u,isSimple:!d&&!c&&!s}}}(_,$);return m?(a.funct["(params)"]=m.params,a.funct["(hasSimpleParams)"]=m.isSimple,a.funct["(metrics)"].arity=m.arity,a.funct["(metrics)"].verifyMaxParametersPerFunction()):(a.funct["(params)"]=[],a.funct["(metrics)"].arity=0,a.funct["(hasSimpleParams)"]=!0),c&&(_&=~f.yield,a.inES6(!0)||L("W119",a.tokens.curr,"arrow function syntax (=>)","6"),$.loneArg||V("=>")),_d(_,!1,!0,!0,c),a.option.noyield||!s||a.funct["(yielded)"]||L("W124",a.tokens.curr),a.funct["(metrics)"].verifyMaxStatementsPerFunction(),a.funct["(metrics)"].verifyMaxComplexityPerFunction(),a.funct["(unusedOption)"]=a.option.unused,a.option=p,a.ignored=h,a.funct["(last)"]=a.tokens.curr.line,a.funct["(lastcharacter)"]=a.tokens.curr.character,a.funct["(scope)"].unstack(),a.funct["(scope)"].unstack(),a.funct=a.funct["(context)"],!d&&!a.option.loopfunc&&a.funct["(loopage)"]&&n["(outerMutables)"]&&L("W083",r,n["(outerMutables)"].join(", ")),n}function _C(_){return{statementCount:0,nestedBlockDepth:-1,ComplexityCount:1,arity:0,verifyMaxStatementsPerFunction:function(){a.option.maxstatements&&this.statementCount>a.option.maxstatements&&L("W071",_,this.statementCount)},verifyMaxParametersPerFunction:function(){t.isNumber(a.option.maxparams)&&this.arity>a.option.maxparams&&L("W072",_,this.arity)},verifyMaxNestedBlockDepthPerFunction:function(){a.option.maxdepth&&this.nestedBlockDepth>0&&this.nestedBlockDepth===a.option.maxdepth+1&&L("W073",null,this.nestedBlockDepth)},verifyMaxComplexityPerFunction:function(){var $=a.option.maxcomplexity,e=this.ComplexityCount;$&&e>$&&L("W074",_,e)}}}function _F(){a.funct["(metrics)"].ComplexityCount+=1}function _w(_){if(_&&!_.paren){if(","===_.id){_w(_.right);return}switch(_.id){case"=":case"+=":case"-=":case"*=":case"%=":case"&=":case"|=":case"^=":case"/=":a.option.boss||L("W084",_)}}}function _W(_){if(a.inES5())for(var $ in _)_[$]&&_[$].setterToken&&!_[$].getterToken&&!_[$].static&&L("W078",_[$].setterToken)}function _D(_,$,e){if(_X(a.tokens.next,".")){var t,n,r,i=a.tokens.curr.id;V(".");var u=_o(_);if(a.tokens.curr.isMetaProperty=!0,$!==u){t=a.tokens.prev,n=i,r=u,L("E057",t,n,r,void 0,void 0)}else e();return a.tokens.curr}}function _B(_,$){var e=$&&$.assignment;return _&=~f.noin,a.inES6()||L("W104",a.tokens.curr,e?"destructuring assignment":"destructuring binding","6"),function _($,e){var t,n,r,i,u=[],o=e&&e.openingParsed,s=e&&e.assignment,c=s?{assignment:s}:null,l=o?a.tokens.curr:a.tokens.next,f=function(){var e;if(_J(a.tokens.next,["[","{"]))for(n=0,t=_($,c);n"===t.id||"("===t.id||"&&"===t.id||"||"===t.id||"?"===t.id||"async"===t.id||"?."===t.id||a.inES6()&&t["(name)"])&&L("W067",e)),e.left=$,e},155,!0).exps=!0,Y("(",function(_,$){var e,t,n,r,i,u=a.tokens.curr,o=a.tokens.prev,s=!a.option.singleGroups,c=_x(1);if("function"===a.tokens.next.id&&(n=a.tokens.next.immed=!0),"=>"===c.value)return c.funct=_S(_,{type:"arrow",parsedOpening:!0}),c;if(")"===a.tokens.next.id){V(")");return}if(t=U(_,0),V(")",this),t){if(t.paren=!0,a.option.immed&&t&&"function"===t.id&&"("!==a.tokens.next.id&&"."!==a.tokens.next.id&&"["!==a.tokens.next.id&&L("W068",this),","===t.id){for(r=t.left;","===r.id;)r=r.left;i=t.right}else r=i=t,s||(n||(n="async"===t.id),s=u.beginsStmt&&("{"===t.id||n)||n&&(!R()||"}"!==a.tokens.prev.id)||"=>"===t.id&&!R()||"{"===t.id&&"=>"===o.id||_$(t)&&"**"===a.tokens.next.id||"??"===o.id&&("&&"===t.id||"||"===t.id)||"(number)"===t.type&&_X(c,".")&&/^\d+$/.test(t.value)||u.beginsStmt&&"="===t.id&&"{"===t.left.id||"?."===t.id&&("new"===o.id||"(template)"===a.tokens.next.type));return!s&&((e=r).first||e.right||e.left||"yield"===e.id||"await"===e.id||r!==i)&&(s=$>r.lbp||$>0&&$===r.lbp||!R()&&i.rbp",42)).infix=!0,_G.led=function(_,$){return H(a.tokens.prev,a.tokens.curr),this.left=$,this.right=_S(_,{type:"arrow",loneArg:$}),this},_G).rbp=161,__("[",function(_,$,e){var t,n,r;return a.option.asi&&_J(a.tokens.prev,[")","]"])&&!z(a.tokens.prev,a.tokens.curr)&&L("W014",a.tokens.curr,a.tokens.curr.id),(t=U(_&~f.noin,0))&&"(string)"===t.type&&(!a.option.evil&&("eval"===t.value||"execScript"===t.value)&&_6($,a)&&L("W061"),_p(t.value),!a.option.sub&&o.identifier.test(t.value)&&(r=(n=a.syntax[t.value])?!C(_,n):"eval"!==t.value&&"arguments"!==t.value)&&L("W069",a.tokens.prev,t.value)),V("]",e),t&&"hasOwnProperty"===t.value&&"="===a.tokens.next.value&&L("W001"),e.left=$,e.right=t,e},160,!0),Y("[",function(_){var $=_q();if($.isCompArray)return a.option.esnext||a.inMoz()||L("W118",a.tokens.curr,"array comprehension"),function _($){var e,n={};n.exps=!0,a.funct["(comparray)"].stack();var r=!1;if("for"!==a.tokens.next.value&&(r=!0,a.inMoz()||L("W116",a.tokens.next,"for",a.tokens.next.value),a.funct["(comparray)"].setState("use"),n.right=U($,10)),V("for"),"each"!==a.tokens.next.value||(V("each"),a.inMoz()||L("W118",a.tokens.curr,"for each")),V("("),a.funct["(comparray)"].setState("define"),n.left=U($,130),t.includes(["in","of"],a.tokens.next.value))V();else{L("E045",e=a.tokens.curr,void 0,void 0,void 0,void 0)}return a.funct["(comparray)"].setState("generate"),U($,10),V(")"),"if"===a.tokens.next.value&&(V("if"),V("("),a.funct["(comparray)"].setState("filter"),U($,10),V(")")),r||(a.funct["(comparray)"].setState("use"),n.right=U($,10)),V("]"),a.funct["(comparray)"].unstack(),n}(_);if($.isDestAssign)return this.destructAssign=_B(_,{openingParsed:!0,assignment:!0}),this;var e=!z(a.tokens.curr,a.tokens.next);for(this.first=[],e&&(h+=a.option.indent,a.tokens.next.from===h+a.option.indent&&(h+=a.option.indent));"(end)"!==a.tokens.next.id;){for(;","===a.tokens.next.id;){if(!a.option.elision){if(a.inES5()){L("W128");do V(",");while(","===a.tokens.next.id);continue}L("W070")}V(",")}if("]"===a.tokens.next.id)break;if(_u("spread"),this.first.push(U(_,10)),","===a.tokens.next.id){if(V(","),q({allowTrailing:!0}),"]"===a.tokens.next.id&&!a.inES5()){L("W070",a.tokens.curr);break}}else{a.option.trailingcomma&&a.inES5()&&M("W140",a.tokens.curr.line,a.tokens.curr.character);break}}return e&&(h-=a.option.indent),V("]",this),this}),(_I=Z("{")).nud=function(_){var $,e,t,n,r,i,u=!1,o=Object.create(null),s=!1;if(($=!z(a.tokens.curr,a.tokens.next))&&(h+=a.option.indent,a.tokens.next.from===h+a.option.indent&&(h+=a.option.indent)),_q().isDestAssign)return this.destructAssign=_B(_,{openingParsed:!0,assignment:!0}),this;for(a.inObjectBody=!0;"}"!==a.tokens.next.id;){if(i=a.tokens.next.value,a.tokens.next.identifier&&(","===O().id||"}"===O().id))a.inES6()||L("W104",a.tokens.next,"object short notation","6"),t=(r=U(_,10))&&r.value,r&&_9(o,t,r);else if(":"!==P().id&&("get"===i||"set"===i))V(i),!a.inES5()&&L("E034",void 0,void 0,void 0,void 0,void 0),"["===a.tokens.next.id?t=_Z(_):!(t=_b(_))&&!a.inES6()&&L("E035",void 0,void 0,void 0,void 0,void 0),t&&_K(i,o,t,a.tokens.curr),r=a.tokens.next,n=(e=_S(_,{isMethod:!0}))["(params)"],"get"===i&&t&&n.length?L("W076",r,n[0],t):"set"===i&&t&&1!==e["(metrics)"].arity&&L("W077",r,t);else if(_u("spread"))a.inES9()||L("W119",a.tokens.next,"object spread property","9"),U(_,10);else{if("async"!==a.tokens.next.id||_J(P(),["(",":"])?s=!1:(a.inES8()||L("W119",a.tokens.next,"async functions","8"),s=!0,V(),N(a.tokens.curr)),"*"===a.tokens.next.value&&"(punctuator)"===a.tokens.next.type?(s&&!a.inES9()?L("W119",a.tokens.next,"async generators","9"):a.inES6()||L("W104",a.tokens.next,"generator functions","6"),V("*"),u=!0):u=!1,"["===a.tokens.next.id)t=_Z(_),a.nameStack.set(t);else if(a.nameStack.set(a.tokens.next),t=_b(_),_9(o,t,a.tokens.next),"string"!=typeof t)break;"("===a.tokens.next.value?(a.inES6()||L("W104",a.tokens.curr,"concise methods","6"),_S(s?_|f.preAsync:_,{isMethod:!0,type:u?"generator":null})):(V(":"),U(_,10))}if(_p(t),","===a.tokens.next.id)V(","),q({allowTrailing:!0,property:!0}),","===a.tokens.next.id?L("W070",a.tokens.curr):"}"!==a.tokens.next.id||a.inES5()||L("W070",a.tokens.curr);else{a.option.trailingcomma&&a.inES5()&&M("W140",a.tokens.curr.line,a.tokens.curr.character);break}}return $&&(h-=a.option.indent),V("}",this),_W(o),a.inObjectBody=!1,this},_I.fud=function(){var _;L("E036",_=a.tokens.curr,void 0,void 0,void 0,void 0)};var _T=J("const",function(_){return _M("const",this,_)});function _j(){return"("===a.tokens.next.id&&a.inMoz()}_T.exps=!0,_T.declaration=!0;var _G,_I,_P,_O,_V,_R=J("let",function(_){return _M("let",this,_)});_R.nud=function(_,$){if(!_j())return this.exps=!1,a.syntax["(identifier)"].nud.apply(this,arguments);a.funct["(scope)"].stack(),V("("),a.tokens.prev.fud(_),V(")"),U(_,$),a.funct["(scope)"].unstack()},_R.meta={es5:!0,isFutureReservedWord:!1,strictOnly:!0},_R.exps=!0,_R.declaration=!0,_R.useFud=function(_){var $,e=a.tokens.next;return!!(this.line===e.line||a.inES6())&&(($=e.identifier&&(!C(_,e)||"let"===e.id))||_J(e,["{","["])||_j())},J("var",function(_){var $,e,n,i,u=_&f.noin;for(this.first=[];;){var o=[];for(var s in t.includes(["{","["],a.tokens.next.value)?($=_B(_),e=!1):($=[],(i=_o(_))&&$.push({id:i,token:a.tokens.curr}),e=!0),a.option.varstmt&&L("W132",this),$)$.hasOwnProperty(s)&&(s=$[s],!a.funct["(global)"]||a.impliedClosure()||(!1===k[s.id]?L("W079",s.token,s.id):!1!==a.option.futurehostile||(a.inES5()||!1!==r.ecmaIdentifiers[5][s.id])&&(a.inES6()||!1!==r.ecmaIdentifiers[6][s.id])||L("W129",s.token,s.id)),s.id&&(a.funct["(scope)"].addbinding(s.id,{type:"var",token:s.token}),o.push(s.token)));if("="===a.tokens.next.id&&(this.hasInitializer=!0,a.nameStack.set(a.tokens.curr),V("="),"="!==P(0).id||!a.tokens.next.identifier||(u||a.funct["(params)"])&&-1!==a.funct["(params)"].indexOf(a.tokens.next.value)||L("W120",a.tokens.next,a.tokens.next.value),i=a.tokens.prev,(n=U(_,10))&&(!a.funct["(loopage)"]&&n.identifier&&"undefined"===n.value&&L("W080",i,i.value),e||_L(o,n))),this.first=this.first.concat(o),","!==a.tokens.next.id)break;this.hasComma=!0,V(","),q()}return this}).exps=!0,X("function",function(_){var $,e=_&f.export,t=!1,n=_&f.preAsync,r="";n&&(r="async "),"*"===a.tokens.next.value&&(n&&!a.inES9()?L("W119",a.tokens.prev,"async generators","9"):n||a.inES6(!0)||L("W119",a.tokens.next,"function*","6"),V("*"),r+="generator ",t=!0),r+="function",p&&L("W082",a.tokens.curr),this.name=_i(_)?a.tokens.curr:null,this.name?a.funct["(scope)"].addbinding(this.name.value,{type:r,token:a.tokens.curr,initialized:!0}):e||L("W025");var i=_S(_,{name:this.name&&this.name.value,statement:this,type:t?"generator":null,ignoreLoopFunc:p}),u=i["(isStrict)"]&&!a.isStrict();if(this.name&&("arguments"===i["(name)"]||"eval"===i["(name)"])&&u){L("E008",$=this.name,void 0,void 0,void 0,void 0)}return"("===a.tokens.next.id&&")"===P().id&&"=>"!==P(1).id&&a.tokens.next.line===a.tokens.curr.line&&L("E039",void 0,void 0,void 0,void 0,void 0),this}).declaration=!0,Y("function",function(_){var $,e,t=!1,n=_&f.preAsync;"*"===a.tokens.next.value&&(n&&!a.inES9()?L("W119",a.tokens.prev,"async generators","9"):n||a.inES6(!0)||L("W119",a.tokens.curr,"function*","6"),V("*"),t=!0),this.name=_i(n?_|f.async:_)?a.tokens.curr:null;var r=_S(_,{name:this.name&&this.name.value,type:t?"generator":null});if(t&&this.name&&"yield"===this.name.value){L("E024",$=this.name,"yield",void 0,void 0,void 0)}if(this.name&&("arguments"===r["(name)"]||"eval"===r["(name)"])&&r["(isStrict)"]){L("E008",e=this.name,void 0,void 0,void 0,void 0)}return this}),X("if",function(_){var $=a.tokens.next;_F(),V("(");var e=U(_,0);e||D("E041",this),_w(e);var t=null;a.option.forin&&a.forinifcheckneeded&&(a.forinifcheckneeded=!1,t=a.forinifchecks[a.forinifchecks.length-1],"(punctuator)"===e.type&&"!"===e.value?t.type="(negative)":t.type="(positive)"),V(")",$);var n=_d(_,!0,!0);return t&&"(negative)"===t.type&&n&&n[0]&&"(identifier)"===n[0].type&&"continue"===n[0].value&&(t.type="(negative-with-continue)"),"else"===a.tokens.next.id&&(V("else"),"if"===a.tokens.next.id||"switch"===a.tokens.next.id?_c(_):_d(_,!0,!0)),this}),X("try",function(_){var $,e,n,r=!1;function i(){if(V("("),_J(a.tokens.next,["[","{"])){var $=_B(_);t.each($,function(_){_.id&&a.funct["(scope)"].addParam(_.id,_.token,"exception")})}else"(identifier)"!==a.tokens.next.type?L("E030",a.tokens.next,a.tokens.next.value):a.funct["(scope)"].addParam(_o(_),a.tokens.curr,"exception");"if"===a.tokens.next.value&&(a.inMoz()||L("W118",a.tokens.curr,"catch filter"),V("if"),U(_,0)),V(")")}for(_d(_|f.tryClause,!0);"catch"===a.tokens.next.id;)_F(),n&&!a.inMoz()&&L("W118",a.tokens.next,"multiple catch blocks"),V("catch"),"{"!==a.tokens.next.id?(a.funct["(scope)"].stack("catchparams"),r=!0,i()):a.inES10()||L("W119",a.tokens.curr,"optional catch binding","10"),_d(_,!1),r&&(a.funct["(scope)"].unstack(),r=!1),n=!0;if("finally"===a.tokens.next.id){V("finally"),_d(_,!0);return}if(!n){$=a.tokens.next,L("E021",$,"catch",e=a.tokens.next.value,void 0,void 0)}return this}),X("while",function(_){var $=a.tokens.next;return a.funct["(breakage)"]+=1,a.funct["(loopage)"]+=1,_F(),V("("),_w(U(_,0)),V(")",$),_d(_,!0,!0),a.funct["(breakage)"]-=1,a.funct["(loopage)"]-=1,this}).labelled=!0,X("with",function(_){var $,e=a.tokens.next;if(a.isStrict()){L("E010",$=a.tokens.curr,void 0,void 0,void 0,void 0)}else a.option.withstmt||L("W085",a.tokens.curr);return V("("),U(_,0),V(")",e),_d(_,!0,!0),this}),X("switch",function(_){var $,e,t,n,r,i=a.tokens.next,u=!1,o=!1,s=!1;for(a.funct["(breakage)"]+=1,V("("),_w(U(_,0)),V(")",i),i=a.tokens.next,V("{"),a.funct["(scope)"].stack(),a.tokens.next.from===h&&(o=!0),o||(h+=a.option.indent);;)switch(a.tokens.next.id){case"case":switch(a.funct["(verb)"]){case"yield":case"break":case"case":case"continue":case"return":case"switch":case"throw":break;case"default":a.option.leanswitch&&L("W145",a.tokens.next);break;default:a.tokens.curr.caseFallsThrough||L("W086",a.tokens.curr,"case")}V("case"),U(_,0),s=!0,_F(),u=!0,V(":"),a.funct["(verb)"]="case";break;case"default":switch(a.funct["(verb)"]){case"yield":case"break":case"continue":case"return":case"throw":break;case"case":a.option.leanswitch&&L("W145",a.tokens.curr);break;default:s&&!a.tokens.curr.caseFallsThrough&&L("W086",a.tokens.curr,"default")}V("default"),u=!0,V(":"),a.funct["(verb)"]="default";break;case"}":o||(h-=a.option.indent),V("}",i),a.funct["(scope)"].unstack(),a.funct["(breakage)"]-=1,a.funct["(verb)"]=void 0;return;case"(end)":L("E023",$=a.tokens.next,"}",void 0,void 0,void 0);return;default:if(h+=a.option.indent,u)switch(a.tokens.curr.id){case",":L("E040",void 0,void 0,void 0,void 0,void 0);return;case":":u=!1,_l(_);break;default:L("E025",e=a.tokens.curr,void 0,void 0,void 0,void 0);return}else if(":"===a.tokens.curr.id){V(":"),L("E024",t=a.tokens.curr,":",void 0,void 0,void 0),_l(_)}else{n=a.tokens.next,L("E021",n,"case",r=a.tokens.next.value,void 0,void 0);return}h-=a.option.indent}}).labelled=!0,J("debugger",function(){return a.option.debug||L("W087",this),this}).exps=!0,(_P=J("do",function(_){a.funct["(breakage)"]+=1,a.funct["(loopage)"]+=1,_F(),this.first=_d(_,!0,!0),V("while");var $=a.tokens.next;return V("("),_w(U(_,0)),V(")",$),a.funct["(breakage)"]-=1,a.funct["(loopage)"]-=1,this})).labelled=!0,_P.exps=!0,X("for",function(_){var $,e,n,r,i,u,o,s,c,l,d=a.tokens.next,p=!1,h=!1,v=null;if("each"!==d.value||(v=d,V("each"),a.inMoz()||L("W118",a.tokens.curr,"for each")),a.tokens.next.identifier&&"await"===a.tokens.next.value){if(V("await"),h=!0,_&f.async)a.inES9()||L("W119",a.tokens.curr,"asynchronous iteration","9");else{L("E024",y=a.tokens.curr,"await",void 0,void 0,void 0)}}_F(),V("(");var g=P(),m=_|f.noin;if("var"===a.tokens.next.id)V("var"),r=(c=a.tokens.curr.fud(m)).hasComma?c:null,i=c.hasInitializer?c:null;else if("const"===a.tokens.next.id||"let"===a.tokens.next.id&&(g.identifier&&"in"!==g.id||_J(g,["{","["])))V(a.tokens.next.id),p=!0,a.funct["(scope)"].stack(),r=(c=a.tokens.curr.fud(m)).hasComma?c:null,i=c.hasInitializer?c:null;else if(!_X(a.tokens.next,";")){for(o=[];"in"!==a.tokens.next.value&&"of"!==a.tokens.next.value&&!_X(a.tokens.next,";");)_J(a.tokens.next,["{","["])?(_B(m,{assignment:!0}).forEach(function(_){this.push(_.token)},o),_X(a.tokens.next,"=")&&(V("="),i=a.tokens.curr,U(m,10))):(s=U(m,10))&&("(identifier)"===s.type?o.push(s):_X(s,"=")&&(i=s,o.push(s))),_X(a.tokens.next,",")&&(V(","),r||(r=a.tokens.curr));i||r||o.forEach(function(_){a.funct["(scope)"].has(_.value)||L("W088",_,_.value)})}if(n=a.tokens.next,h&&"of"!==n.value){L("E066",k=n,void 0,void 0,void 0,void 0)}if(t.includes(["in","of"],n.value)){if("of"===n.value?(u=20,a.inES6()||L("W104",n,"for of","6")):u=0,r){x=r,L("W133",x,b=n.value,"more than one ForBinding",void 0,void 0)}if(i){E=i,L("W133",E,A=n.value,"initializer is forbidden",void 0,void 0)}if(!s||r||i||_7(_,s,n),V(n.value),U(_,u),V(")",d),"in"===n.value&&a.option.forin&&(a.forinifcheckneeded=!0,void 0===a.forinifchecks&&(a.forinifchecks=[]),a.forinifchecks.push({type:"(none)"})),a.funct["(breakage)"]+=1,a.funct["(loopage)"]+=1,l=_d(_,!0,!0),"in"===n.value&&a.option.forin){if(a.forinifchecks&&a.forinifchecks.length>0){var y,k,x,b,E,A,S=a.forinifchecks.pop();(l&&l.length>0&&("object"!=typeof l[0]||"if"!==l[0].value)||"(positive)"===S.type&&l.length>1||"(negative)"===S.type)&&L("W089",this)}a.forinifcheckneeded=!1}a.funct["(breakage)"]-=1,a.funct["(loopage)"]-=1}else{if(v){L("E045",$=v,void 0,void 0,void 0,void 0)}if(V(";"),c&&c.first&&c.first[0]&&("const"!==c.value||c.hasInitializer||L("E012",c,c.first[0].value),c.first.forEach(function(_){a.funct["(scope)"].initialize(_.value)})),a.funct["(loopage)"]+=1,";"!==a.tokens.next.id&&_w(U(_,0)),V(";"),";"===a.tokens.next.id){L("E021",e=a.tokens.next,")",";",void 0,void 0)}if(")"!==a.tokens.next.id)for(;U(_,0),","===a.tokens.next.id;)V(","),q();V(")",d),a.funct["(breakage)"]+=1,_d(_,!0,!0),a.funct["(breakage)"]-=1,a.funct["(loopage)"]-=1}return p&&a.funct["(scope)"].unstack(),this}).labelled=!0,J("break",function(){var _=a.tokens.next.value;return a.tokens.next.identifier&&z(a.tokens.curr,a.tokens.next)?(a.funct["(scope)"].funct.hasLabel(_)||L("W090",a.tokens.next,_),this.first=a.tokens.next,V()):0===a.funct["(breakage)"]&&L("W052",a.tokens.next,this.value),_a(this),this}).exps=!0,J("continue",function(){var _=a.tokens.next.value;return 0!==a.funct["(breakage)"]&&a.funct["(loopage)"]||L("W052",a.tokens.next,this.value),a.tokens.next.identifier&&z(a.tokens.curr,a.tokens.next)&&(a.funct["(scope)"].funct.hasLabel(_)||L("W090",a.tokens.next,_),this.first=a.tokens.next,V()),_a(this),this}).exps=!0,J("return",function(_){return z(this,a.tokens.next)?";"!==a.tokens.next.id&&!a.tokens.next.reach&&(this.first=U(_,0),!this.first||"(punctuator)"!==this.first.type||"="!==this.first.value||this.first.paren||a.option.boss||L("W093",this.first),a.option.noreturnawait&&_&f.async&&!(_&f.tryClause)&&this.first.identifier&&"await"===this.first.value&&L("W146",this.first)):"(punctuator)"===a.tokens.next.type&&["[","{","+","-"].indexOf(a.tokens.next.value)>-1&&N(this),_a(this),this}).exps=!0,Y("await",function(_){if(!(_&f.async))return this.exps=!1,a.syntax["(identifier)"].nud.apply(this,arguments);if(!a.funct["(params)"]){var $;$=this,L("E024",$,"await",void 0,void 0,void 0)}return U(_,10),this}).exps=!0,(_O=Y("async",function(_,$){return this.isFunc(_)?(a.inES8()||L("W119",this,"async functions","8"),_|=f.preAsync,this.func=U(_,$),this.identifier=!1,this):(this.exps=!1,a.syntax["(identifier)"].nud.apply(this,arguments))})).meta={es5:!0,isFutureReservedWord:!0,strictOnly:!0},_O.isFunc=function(){var _,$=a.tokens.next;return this.line===$.line&&("function"===$.id||("("===$.id?"=>"===(_=_x(0)).id:!!$.identifier&&"=>"===P().id))},_O.useFud=_O.isFunc,_O.fud=function(_){return a.inES8()||L("W119",this,"async functions","8"),_|=f.preAsync,_|=f.initial,this.func=U(_,0),this.block=this.func.block,this.exps=this.func.exps,this},_O.exps=!0,delete _O.reserved,(_V=Y("yield",function(_){if(a.inMoz())return _U.call(this,_);if(!(_&f.yield))return this.exps=!1,a.syntax["(identifier)"].nud.apply(this,arguments);var $,e,t,n=a.tokens.prev;if(!a.funct["(params)"]){$=this,L("E024",$,"yield",void 0,void 0,void 0)}if(!this.beginsStmt&&n.lbp>30&&!_J(n,["("])){e=this,L("E061",e,void 0,void 0,void 0,void 0)}if(a.inES6()||L("W104",a.tokens.curr,"yield","6"),a.funct["(yielded)"]=!0,"*"===a.tokens.next.value&&V("*"),"*"===a.tokens.curr.value||z(a.tokens.curr,a.tokens.next)){if(a.tokens.next.nud)H(a.tokens.curr,a.tokens.next),this.first=U(_,10),"(punctuator)"!==this.first.type||"="!==this.first.value||this.first.paren||a.option.boss||L("W093",this.first);else if(a.tokens.next.led&&","!==a.tokens.next.id){L("W017",t=a.tokens.next,void 0,void 0,void 0,void 0)}}return this})).rbp=_V.lbp=25,_V.exps=!0;var _U=function(_){var $,e,t=a.tokens.prev;if(a.inES6(!0)&&!(_&f.yield)){L("E046",$=a.tokens.curr,"yield",void 0,void 0,void 0)}a.funct["(yielded)"]=!0;var n=!1;if("*"===a.tokens.next.value&&(n=!0,V("*")),z(this,a.tokens.next)){if(!n&&(";"===a.tokens.next.id||a.option.asi||a.tokens.next.reach||!a.tokens.next.nud)||(H(a.tokens.curr,a.tokens.next),this.first=U(_,10),"(punctuator)"!==this.first.type||"="!==this.first.value||this.first.paren||a.option.boss||L("W093",this.first)),")"!==a.tokens.next.id&&(t.lbp>30||!t.assign&&!R())){e=this,L("E050",e,void 0,void 0,void 0,void 0)}}else a.option.asi||N(this);return this};J("throw",function(_){return N(this),this.first=U(_,20),_a(this),this}).exps=!0,Y("import",function(_){var $=_D(_,"meta",function(){if(a.inES11(!0)||L("W119",a.tokens.prev,"import.meta","11"),!a.option.module){var _;L("E070",_=a.tokens.prev,void 0,void 0,void 0,void 0)}});return $||(_X(a.tokens.next,"(")?(a.inES11()||L("W119",a.tokens.curr,"dynamic import","11"),V("("),U(_,10),V(")"),this):a.syntax["(identifier)"].nud.call(this,_))});var _z=J("import",function(_){var $,e,t,n;if(!a.funct["(scope)"].block.isGlobal()){L("E053",$=a.tokens.curr,"Import",void 0,void 0,void 0)}if(a.inES6()||L("W119",a.tokens.curr,"import","6"),"(string)"===a.tokens.next.type)return V("(string)"),this;if(a.tokens.next.identifier){if(this.name=_o(_),a.funct["(scope)"].addbinding(this.name,{type:"import",initialized:!0,token:a.tokens.curr}),","!==a.tokens.next.value)return V("from"),V("(string)"),this;V(",")}if("*"===a.tokens.next.id)V("*"),V("as"),a.tokens.next.identifier&&(this.name=_o(_),a.funct["(scope)"].addbinding(this.name,{type:"import",initialized:!0,token:a.tokens.curr}));else for(V("{");;){if("}"===a.tokens.next.value){V("}");break}if("as"===P().value&&(_o(_,!0),V("as")),e=_o(_),a.funct["(scope)"].addbinding(e,{type:"import",initialized:!0,token:a.tokens.curr}),","===a.tokens.next.value)V(",");else if("}"===a.tokens.next.value){V("}");break}else{t=a.tokens.next,L("E024",t,n=a.tokens.next.value,void 0,void 0,void 0);break}}return V("from"),V("(string)"),this});function _H(_,$){return("call"!==_||!$["(async)"])&&("property"===_&&!!$["(method)"]||"call"===_&&!!$["(statement)"]&&"class"===$["(statement)"].id||!!$["(arrow)"]&&_H(_,$["(context)"]))}_z.exps=!0,_z.reserved=!0,_z.meta={isFutureReservedWord:!0,es5:!0},_z.useFud=function(){return!_J(a.tokens.next,[".","("])},_z.rbp=161,J("export",function(_){var $,e,t,n,r=!0;if(_|=f.export,a.inES6()||(L("W119",a.tokens.curr,"export","6"),r=!1),!a.funct["(scope)"].block.isGlobal()){L("E053",o=a.tokens.curr,"Export",void 0,void 0,void 0),r=!1}if("*"===a.tokens.next.value)return V("*"),"as"===a.tokens.next.value&&(a.inES11()||L("W119",a.tokens.curr,"export * as ns from","11"),V("as"),_o(_,!0),a.funct["(scope)"].setExported(null,a.tokens.curr)),V("from"),V("(string)"),this;if("default"===a.tokens.next.type){a.nameStack.set(a.tokens.next),V("default");var i=a.tokens.curr,u=a.tokens.next.id;return"function"===u?(this.block=!0,V("function"),t=a.syntax.function.fud(_),a.funct["(scope)"].setExported(t.name,i)):"async"===u&&"function"===P().id?(this.block=!0,V("async"),V("function"),t=a.syntax.function.fud(_|f.preAsync),a.funct["(scope)"].setExported(t.name,i)):"class"===u?(this.block=!0,V("class"),t=a.syntax.class.fud(_),a.funct["(scope)"].setExported(t.name,i)):(U(_,10),a.funct["(scope)"].setExported(null,i)),this}if("{"===a.tokens.next.value){V("{");for(var o,s,c,l,d,p=[];!_X(a.tokens.next,"}");){if(!a.tokens.next.identifier){s=a.tokens.next,L("E030",s,c=a.tokens.next.value,void 0,void 0,void 0)}if(V(),"as"===a.tokens.next.value){if(V("as"),!a.tokens.next.identifier){l=a.tokens.next,L("E030",l,d=a.tokens.next.value,void 0,void 0,void 0)}p.push({local:a.tokens.prev,export:a.tokens.next}),V()}else p.push({local:a.tokens.curr,export:a.tokens.curr});_X(a.tokens.next,"}")||V(",")}V("}"),"from"===a.tokens.next.value?(V("from"),n=a.tokens.next,V("(string)")):r&&p.forEach(function(_){a.funct["(scope)"].setExported(_.local,_.export)}),0===p.length&&(n?L("W142",this,"export",n.value):L("W141",this,"export"))}else if("var"===a.tokens.next.id)V("var"),(t=a.tokens.curr.fud(_)).first.forEach(function(_){a.funct["(scope)"].setExported(_,_)});else if("let"===a.tokens.next.id)V("let"),(t=a.tokens.curr.fud(_)).first.forEach(function(_){a.funct["(scope)"].setExported(_,_)});else if("const"===a.tokens.next.id)V("const"),(t=a.tokens.curr.fud(_)).first.forEach(function(_){a.funct["(scope)"].setExported(_,_)});else if("function"===a.tokens.next.id)this.block=!0,V("function"),t=a.syntax.function.fud(_),a.funct["(scope)"].setExported(t.name,t.name);else if("async"===a.tokens.next.id&&"function"===P().id)this.block=!0,V("async"),V("function"),t=a.syntax.function.fud(_|f.preAsync),a.funct["(scope)"].setExported(t.name,t.name);else if("class"===a.tokens.next.id)this.block=!0,V("class"),t=a.syntax.class.fud(_),a.funct["(scope)"].setExported(t.name,t.name);else{$=a.tokens.next,L("E024",$,e=a.tokens.next.value,void 0,void 0,void 0)}return this}).exps=!0;var _N=function(){var _,$,e,t,n=a.tokens.next;if(_J(n,["[","."])){if(!_H("property",a.funct)){_=this,L("E063",_,void 0,void 0,void 0,void 0)}}else if(_X(n,"(")){if(!_H("call",a.funct)){$=this,L("E064",$,void 0,void 0,void 0,void 0)}}else{e=n,L("E024",e,t=n.value||n.id,void 0,void 0,void 0)}return this};_0("abstract"),_0("boolean"),_0("byte"),_0("char"),_0("double"),_0("enum",{es5:!0}),_0("export",{es5:!0}),_0("extends",{es5:!0}),_0("final"),_0("float"),_0("goto"),_0("implements",{es5:!0,strictOnly:!0}),_0("int"),_0("interface",{es5:!0,strictOnly:!0}),_0("long"),_0("native"),_0("package",{es5:!0,strictOnly:!0}),_0("private",{es5:!0,strictOnly:!0}),_0("protected",{es5:!0,strictOnly:!0}),_0("public",{es5:!0,strictOnly:!0}),_0("short"),_0("static",{es5:!0,strictOnly:!0}),_0("synchronized"),_0("transient"),_0("volatile");var _q=function(){var _,$,e,t=-1,n=0,r={};_J(a.tokens.curr,["[","{"])&&(n+=1);do{if(e=-1===t?a.tokens.curr:_,_=-1===t?a.tokens.next:P(t),$=P(t+1),t+=1,_J(_,["[","{"])?n+=1:_J(_,["]","}"])&&(n-=1),1===n&&_.identifier&&"for"===_.value&&!_X(e,".")){r.isCompArray=!0,r.notJson=!0;break}if(0===n&&_J(_,["}","]"])){if("="===$.value){r.isDestAssign=!0,r.notJson=!0;break}if("."===$.value){r.notJson=!0;break}}_X(_,";")&&(r.notJson=!0)}while(n>0&&"(end)"!==_.id);return r};function _9(_,$,e,t,n,r){e.identifier&&($=e.value);var i=$;if(t&&n&&(i="static "+$),_[i]&&"__proto__"!==$&&!r){var u=["key","class method","static class method"];u=u[(t||!1)+(n||!1)],L("W075",a.tokens.next,u,$)}else _[i]=Object.create(null);_[i].basic=!0,_[i].basictkn=e}function _K(_,$,e,t,n,r){var i="get"===_?"getterToken":"setterToken",u=e;if(a.tokens.curr.accessorType=_,a.nameStack.set(t),n&&r&&(u="static "+e),$[u]){if(($[u].basic||$[u][i])&&"__proto__"!==e){var o="";n?(r&&(o+="static "),o+=_+"ter method"):o="key",L("W075",a.tokens.next,o,e)}}else $[u]=Object.create(null);$[u][i]=t,r&&($[u].static=!0)}function _Z(_){V("["),a.tokens.curr.delim=!0,a.tokens.curr.lbp=0,a.inES6()||L("W119",a.tokens.curr,"computed property names","6");var $=U(_&~f.noin,10);return V("]"),$}function _J(_,$){return"(punctuator)"===_.type&&t.includes($,_.value)}function _X(_,$){return"(punctuator)"===_.type&&_.value===$}var _Q=function(){var _,$=function(){this.mode="use",this.variables=[]},e=[];function n($){return 0===_.variables.filter(function(_){if(_.value===$&&!_.undef)return!0===_.unused&&(_.unused=!1),$}).length}return{stack:function(){_=new $,e.push(_)},unstack:function(){_.variables.filter(function(_){_.unused&&L("W098",_.token,_.token.raw_text||_.value),_.undef&&a.funct["(scope)"].block.use(_.value,_.token)}),e.splice(-1,1),_=e[e.length-1]},setState:function($){t.includes(["use","define","generate","filter"],$)&&(_.mode=$)},check:function($){if(_){if(_&&"use"===_.mode)return n($)&&_.variables.push({token:a.tokens.curr,value:$,undef:!0,unused:!1}),!0;if(_&&"define"===_.mode){var e;return e=$,0===_.variables.filter(function(_){if(_.value===e)return _.undef=!1,e}).length&&_.variables.push({token:a.tokens.curr,value:$,undef:!1,unused:!0}),!0}return _&&"generate"===_.mode?(a.funct["(scope)"].block.use($,a.tokens.curr),!0):!!_&&"filter"===_.mode&&(n($)&&a.funct["(scope)"].block.use($,a.tokens.curr),!0)}}}},_Y=function(_){return _.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")},$2=function(n,i,o){i=t.clone(i),a.reset(),C=a.option,F=a.ignored,i&&i.scope?d.scope=i.scope:(d.errors=[],d.internals=[],d.blacklist={},d.scope="(main)"),w(k=Object.create(null),r.ecmaIdentifiers[3]),w(k,r.reservedVars),$=Object.create(null);var s,c,f,x,S,C,F,B,G=Object.create(null);function I(_,$){_&&(Array.isArray(_)||"object"!=typeof _||(_=Object.keys(_)),_.forEach($))}if(i)for(I([i.predef,i.globals],function(_){I(_,function($){var e,t;"-"===$[0]?(e=$.slice(1),d.blacklist[e]=e,delete k[e]):(t=Object.getOwnPropertyDescriptor(_,$),k[$]=!!t&&t.value)})}),I(i.exported||null,function(_){G[_]=!0}),delete i.predef,delete i.exported,S=Object.keys(i),c=0;c0&&(i.implieds=u),(r=a.funct["(scope)"].getUsedOrDefinedGlobals()).length>0&&(i.globals=r),t=1;t0&&(i.unused=o),m)if("number"==typeof m[n]){i.member=m;break}return i},$2.jshint=$2,$2}();"object"==typeof e&&e&&(e.JSHINT=d)},{"./lex.js":17,"./messages.js":18,"./options.js":20,"./prod-params.js":21,"./reg.js":22,"./scope-manager.js":23,"./state.js":24,"./style.js":25,"./vars.js":27,"console-browserify":9,events:11,lodash:12}]},{},[]))("jshint").JSHINT,"object"==typeof exports&&exports&&(exports.JSHINT=JSHINT)}(); \ No newline at end of file diff --git a/scripts/build-ext-demo.js b/scripts/build-ext-demo.js new file mode 100644 index 00000000..e614fff8 --- /dev/null +++ b/scripts/build-ext-demo.js @@ -0,0 +1,42 @@ +/** + * @file Build extension pages demonstration using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + */ + +import { build } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import * as Utils from "./utils.js"; + +/** @type {import("vite").InlineConfig} */ +const sharedConfig = { + ...Utils.baseConfig, + mode: "development", + + define: { + ...Utils.baseConfig.define, + "import.meta.env.SAFARI_VERSION": JSON.stringify(15), + "import.meta.env.EXT_DEMO_BUILD": JSON.stringify(true), + }, +}; + +/** + * Empty resources directory + * Copy public static assets + */ +await Utils.emptyBuildDir("dist"); +Utils.cp("public/ext/shared", "dist"); + +/** Build shared modules */ +build({ + ...sharedConfig, + plugins: [svelte()], + publicDir: "public/ext/vendor/", + build: { + outDir: `dist/`, + emptyOutDir: false, + rollupOptions: { + input: ["entry-ext-action-popup.html", "entry-ext-extension-page.html"], + }, + target: "esnext", // top-level await + }, +}); diff --git a/scripts/build-safari-15.js b/scripts/build-safari-15.js new file mode 100644 index 00000000..1466bf15 --- /dev/null +++ b/scripts/build-safari-15.js @@ -0,0 +1,108 @@ +/** + * @file Build App-WebView and Safari-Extension resources using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + * + * Safari supports for modules in background since 16.4 + * @see {@link https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes#Safari-Extensions} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background#browser_compatibility} + * To ensure forward compatibility, background script use independent builds for safari 15 + * + * Content scripts not support import modules, and due to their privileges and the + * speed of injecting user scripts, use a independent build currently + * + * All build processes start at the same time due to asynchronous calls + * The assets name is irrelevant, just need to determine the entry path + */ + +import { build } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import pluginMarkedDivest from "./vite-plugin-marked-divest.js"; +import * as Utils from "./utils.js"; + +/** @type {import("vite").InlineConfig} */ +const sharedConfig = { + ...Utils.baseConfig, + define: { + ...Utils.baseConfig.define, + "import.meta.env.SAFARI_VERSION": JSON.stringify(15), + }, + build: { + sourcemap: process.env.BETA ? true : false, + target: "safari15", + }, +}; + +/** + * Build App-Shared WebView resources to xcode dist + */ +build({ + ...Utils.baseConfig, + plugins: [svelte(), pluginMarkedDivest()], + build: { + ...sharedConfig.build, + outDir: `${Utils.APP_SHARED_RESOURCES}/dist/`, + copyPublicDir: false, + rollupOptions: { + input: "entry-app-webview.html", + }, + }, +}); + +/** + * Empty resources directory + * Copy public static assets + */ +await Utils.emptyBuildDir(Utils.EXT_SAFARI_RESOURCES); +Utils.cp("public/ext/shared", Utils.EXT_SAFARI_RESOURCES); +Utils.cp("public/ext/safari-15", Utils.EXT_SAFARI_RESOURCES); + +/** Build content scripts */ +[ + { userscripts: "src/ext/content-scripts/entry-userscripts.js" }, + { "dot-user-js": "src/ext/content-scripts/entry-dot-user-js.js" }, + { "script-market": "src/ext/content-scripts/entry-script-market.js" }, +].forEach((input) => { + build({ + ...sharedConfig, + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/content-scripts/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input, + output: { entryFileNames: "[name].js" }, + }, + }, + }); +}); + +/** Build background scripts */ +build({ + ...sharedConfig, + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input: { background: "src/ext/background/main.js" }, + output: { entryFileNames: "[name].js" }, + }, + }, +}); + +/** Build shared modules */ +build({ + ...sharedConfig, + plugins: [svelte()], + publicDir: "public/ext/vendor/", + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/`, + emptyOutDir: false, + rollupOptions: { + input: ["entry-ext-action-popup.html", "entry-ext-extension-page.html"], + }, + }, +}); diff --git a/scripts/build-safari-16.4.js b/scripts/build-safari-16.4.js new file mode 100644 index 00000000..866b844d --- /dev/null +++ b/scripts/build-safari-16.4.js @@ -0,0 +1,116 @@ +/** + * @file Build App-WebView and Safari-Extension resources using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + * + * Safari supports for modules in background since 16.4 + * @see {@link https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes#Safari-Extensions} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/background#browser_compatibility} + * + * Content scripts not support import modules, and due to their privileges and the + * speed of injecting user scripts, use a independent build currently + * + * All build processes start at the same time due to asynchronous calls + * The assets name is irrelevant, just need to determine the entry path + */ + +import { build } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import pluginMarkedDivest from "./vite-plugin-marked-divest.js"; +import * as Utils from "./utils.js"; + +/** @type {import("vite").InlineConfig} */ +const sharedConfig = { + ...Utils.baseConfig, + define: { + ...Utils.baseConfig.define, + "import.meta.env.SAFARI_VERSION": JSON.stringify(16.4), + }, + build: { + sourcemap: process.env.BETA ? true : false, + target: "safari16.4", + }, +}; + +/** + * Build App-Shared WebView resources to xcode dist + */ +build({ + ...Utils.baseConfig, + plugins: [svelte(), pluginMarkedDivest()], + build: { + ...sharedConfig.build, + outDir: `${Utils.APP_SHARED_RESOURCES}/dist/`, + copyPublicDir: false, + rollupOptions: { + input: "entry-app-webview.html", + }, + }, +}); + +/** + * Empty resources directory + * Copy public static assets + */ +await Utils.emptyBuildDir(Utils.EXT_SAFARI_RESOURCES); +Utils.cp("public/ext/shared", Utils.EXT_SAFARI_RESOURCES); +Utils.cp("public/ext/safari-16.4", Utils.EXT_SAFARI_RESOURCES); + +/** Build content scripts */ +[ + { userscripts: "src/ext/content-scripts/entry-userscripts.js" }, + { "dot-user-js": "src/ext/content-scripts/entry-dot-user-js.js" }, + { "script-market": "src/ext/content-scripts/entry-script-market.js" }, +].forEach((input) => { + build({ + ...sharedConfig, + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/content-scripts/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input, + output: { entryFileNames: "[name].js" }, + }, + }, + }); +}); + +/** + * Build background scripts + * Modular background may not load correctly on Safari startup + * Currently build classic script separately to avoid this error + */ +build({ + ...sharedConfig, + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input: { background: "src/ext/background/main.js" }, + output: { entryFileNames: "[name].js" }, + }, + }, +}); + +/** Build shared modules */ +build({ + ...sharedConfig, + plugins: [svelte()], + publicDir: "public/ext/vendor/", + build: { + ...sharedConfig.build, + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/`, + emptyOutDir: false, + rollupOptions: { + input: { + // background: "src/ext/background/main.js", + "action-popup": "entry-ext-action-popup.html", + "extension-page": "entry-ext-extension-page.html", + }, + output: { entryFileNames: "[name].js" }, + }, + }, +}); diff --git a/scripts/dev-safari.js b/scripts/dev-safari.js new file mode 100644 index 00000000..c9236b9f --- /dev/null +++ b/scripts/dev-safari.js @@ -0,0 +1,211 @@ +/** + * @file Develop Safari extension resources using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + * This development build and server requires a valid https certificate to support remote real-time development + * Typically using a self-signed certificate, it needs to be installed and trusted by the device or simulator + * @see {@link https://developer.apple.com/library/archive/qa/qa1948/} + * @see {@link https://developer.apple.com/library/archive/technotes/tn2326/} + */ + +import { build, createServer } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import * as Utils from "./utils.js"; +import https from "node:https"; + +/** @type {import("vite").InlineConfig} */ +const sharedConfig = { + ...Utils.baseConfig, + mode: "development", + define: { + ...Utils.baseConfig.define, + "import.meta.env.SAFARI_VERSION": JSON.stringify(16.4), + }, +}; + +/** + * Build resources for remote real-time development + * @param {import("vite").ViteDevServer} server + * @param {string} origin + */ +async function buildResources(server, origin) { + /** + * empty resources directory + * copy public static assets + */ + await Utils.emptyBuildDir("dist"); + await Utils.emptyBuildDir(Utils.EXT_SAFARI_RESOURCES); + Utils.cp("public/ext/shared", Utils.EXT_SAFARI_RESOURCES); + Utils.cp("public/ext/shared-dev", Utils.EXT_SAFARI_RESOURCES); + if (process.env.SAFARI_PLATFORM === "ios") { + Utils.cp( + "public/ext/safari-dev/manifest-ios.json", + `${Utils.EXT_SAFARI_RESOURCES}/manifest.json`, + ); + } else { + Utils.cp( + "public/ext/safari-dev/manifest-mac.json", + `${Utils.EXT_SAFARI_RESOURCES}/manifest.json`, + ); + } + + /** build content scripts */ + [ + { userscripts: "src/ext/content-scripts/entry-userscripts.js" }, + { "dot-user-js": "src/ext/content-scripts/entry-dot-user-js.js" }, + { "script-market": "src/ext/content-scripts/entry-script-market.js" }, + ].forEach((input) => { + /** build proxy content scripts replace actual code */ + build({ + ...sharedConfig, + plugins: [ + { + name: "generate-content-proxy", + load(id) { + const name = id.replace(/.+entry-/, ""); + const url = `${origin}/dist/content-scripts/${name}`; + return `// proxy content + (function () { + if (window["${id}"]) return; + window["${id}"] = 1; + const xhr = new XMLHttpRequest(); + xhr.open("GET", "${url}", false); + xhr.send(); + const code = xhr.responseText; + try { + Function(code + "//# sourceURL=proxy-${name}")(); + } catch (error) { + console.error(error); + } + })();`; + }, + }, + ], + build: { + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/content-scripts/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input, + output: { entryFileNames: "[name].js" }, + }, + }, + }); + /** build content scripts for dev server and watch changes */ + build({ + ...sharedConfig, + build: { + outDir: `dist/content-scripts/`, + emptyOutDir: false, + copyPublicDir: false, + rollupOptions: { + input, + output: { entryFileNames: "[name].js" }, + }, + watch: {}, + minify: false, + }, + }); + }); + + /** generate entrance dist */ + build({ + ...sharedConfig, + publicDir: "public/ext/vendor/", + plugins: [ + /** + * @see {@link https://github.com/vitejs/vite/issues/14263} + * Dev only requires entrances, so order `pre` without transform modules + */ + { + name: "generate-dev-entrance", + transformIndexHtml: { + order: "pre", + async handler(html, ctx) { + const str = await server.transformIndexHtml(ctx.path, html); + return str.replaceAll(`src="/`, `src="${origin}/`); + }, + }, + }, + ], + build: { + outDir: `${Utils.EXT_SAFARI_RESOURCES}/dist/`, + emptyOutDir: false, + rollupOptions: { + input: { + background: "entry-ext-background.html", + "action-popup": "entry-ext-action-popup.html", + "extension-page": "entry-ext-extension-page.html", + }, + }, + }, + }); +} + +/** + * Define shared constants + * Developing in native machine and simulator can just use `https://localhost:port/` as the origin + * But using a fixed domain name on non-native devices such as real iOS devices is a better choice + * The local domain name needs to be resolved to the LAN IP address on LAN DNS or the target device + * When the origin specified here is unreachable, localhost will be automatically used as the alternate + * The origin requires as an exemption to be added to the `manifest.json` due to default CSP limitations + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_Security_Policy} + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/content_security_policy} + */ +const serverOptions = await Utils.sharedServerOptions(); +const SERVER_PORT = 55173; +const SERVER_ORIGIN = `https://userscripts.test:${SERVER_PORT}`; + +/** + * Check if the server is reachable with self-signed certificate + * @param {string | URL} url + * @returns {Promise} + */ +async function serverCheck(url) { + const options = { + method: "HEAD", + ca: serverOptions.ca, + headers: { Accept: "*/*" }, + }; + return new Promise((resolve) => { + const req = https.request(url, options, (res) => { + if (res.headers.server === SERVER_ORIGIN) { + resolve(true); + } + resolve(false); + }); + req.on("error", (e) => { + console.error(`${req.host}: ${e.message}`); + resolve(false); + }); + req.end(); + }); +} + +/** main process */ +(async () => { + /** run development server */ + const server = await createServer({ + ...sharedConfig, + publicDir: "public/ext/vendor/", + plugins: [svelte()], + server: { + host: true, + port: SERVER_PORT, + strictPort: true, + https: { key: serverOptions.key, cert: serverOptions.cert }, + headers: { server: SERVER_ORIGIN }, + }, + }); + await server.listen(); + server.printUrls(); + + // Check available origins and build resources + for (let url of [SERVER_ORIGIN, ...server.resolvedUrls.local]) { + if (await serverCheck(url)) { + const origin = url.at(-1) === "/" ? url.slice(0, -1) : url; + console.info(`build with origin: ${origin}`); + buildResources(server, origin); + break; + } + } +})(); diff --git a/scripts/jsconfig.json b/scripts/jsconfig.json new file mode 100644 index 00000000..f0633242 --- /dev/null +++ b/scripts/jsconfig.json @@ -0,0 +1,26 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + +{ + "compilerOptions": { + "target": "ESNext", + "module": "NodeNext", + "moduleResolution": "nodenext", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true + }, + "include": ["**/*.d.ts", "**/*.js"] +} diff --git a/scripts/preview-app.js b/scripts/preview-app.js new file mode 100644 index 00000000..1c1ad4eb --- /dev/null +++ b/scripts/preview-app.js @@ -0,0 +1,24 @@ +/** + * @file Preview App WebView resources using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + */ + +import { preview } from "vite"; +import { baseConfig } from "./utils.js"; + +/** + * Preview App-Shared WebView resources from xcode dist + */ +(async () => { + const previewServer = await preview({ + ...baseConfig, + preview: { + // port: 4173, + open: "entry-app-webview.html", + }, + build: { + outDir: "xcode/App-Shared/Resources/dist/", + }, + }); + previewServer.printUrls(); +})(); diff --git a/scripts/preview-ext-demo.js b/scripts/preview-ext-demo.js new file mode 100644 index 00000000..a14b0a9a --- /dev/null +++ b/scripts/preview-ext-demo.js @@ -0,0 +1,24 @@ +/** + * @file Preview App WebView resources using the Vite JavaScript API + * @see {@link https://vitejs.dev/guide/api-javascript.html JavaScript API} + */ + +import { preview } from "vite"; +import { baseConfig } from "./utils.js"; + +/** + * Preview App-Shared WebView resources from xcode dist + */ +(async () => { + const previewServer = await preview({ + ...baseConfig, + preview: { + // port: 4173, + open: "entry-ext-extension-page.html", + }, + build: { + outDir: "dist/", + }, + }); + previewServer.printUrls(); +})(); diff --git a/scripts/utils.js b/scripts/utils.js new file mode 100644 index 00000000..e0069126 --- /dev/null +++ b/scripts/utils.js @@ -0,0 +1,201 @@ +/** + * node:process + * @see {@link https://nodejs.org/api/process.html#processchdirdirectory chdir} + * @see {@link https://nodejs.org/api/process.html#processcwd cwd} + * node:child_process + * @see {@link https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback execFile} + * node:util + * @see {@link https://nodejs.org/api/util.html#utilpromisifyoriginal promisify} + * node:url + * @see {@link https://nodejs.org/api/url.html#urlfileurltopathurl fileURLToPath} + * node:fs/promises + * @see {@link https://nodejs.org/api/fs.html#fspromisescopyfilesrc-dest-mode copyFile} + * @see {@link https://nodejs.org/api/fs.html#fspromisesmkdirpath-options mkdir} + * @see {@link https://nodejs.org/api/fs.html#fspromisesreaddirpath-options readdir} + * @see {@link https://nodejs.org/api/fs.html#fspromisesreadfilepath-options readFile} + * @see {@link https://nodejs.org/api/fs.html#fspromisesrealpathpath-options realpath} + * @see {@link https://nodejs.org/api/fs.html#fspromisesrmpath-options rm} + * @see {@link https://nodejs.org/api/fs.html#fspromisesstatpath-options stat} + */ +import { chdir, cwd } from "node:process"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { fileURLToPath } from "node:url"; +import { + copyFile, + mkdir, + readdir, + readFile, + realpath, + rm, + stat, +} from "node:fs/promises"; + +/** Promisified execFile */ +const execFileAsync = promisify(execFile); + +/** Define shared constants */ +export const APP_SHARED_RESOURCES = "xcode/App-Shared/Resources"; +export const EXT_SAFARI_RESOURCES = "xcode/Ext-Safari/Resources"; + +/** + * If not then cd to root dir and returns the path + * @see {@link https://nodejs.org/api/esm.html#importmetaurl} + * @returns {Promise} project root directory + */ +export async function rootDir() { + const root = fileURLToPath(new URL("..", import.meta.url)); + if (cwd() !== (await realpath(root))) { + chdir(root); + console.info("cd:", root); + } + return root; +} + +/** + * Empty the build directory safely + * @see {@link https://nodejs.org/api/errors.html#common-system-errors ENOENT} + * @param {string} dir + * @returns {Promise} + */ +export async function emptyBuildDir(dir) { + const buildPaths = ["dist", EXT_SAFARI_RESOURCES]; + if (!buildPaths.includes(dir)) { + console.error("Non-build path, cannot be empty."); + return false; + } + const root = await rootDir(); + try { + for (const sub of await readdir(dir)) { + const path = `${root}/${dir}/${sub}`; + // console.log("rm:", path); // DEBUG + await rm(path, { force: true, recursive: true }); + } + return true; + } catch (error) { + if (error.code === "ENOENT") { + console.info("[SKIPPED] emptyBuildDir:", error.message); + return true; + } + console.error("emptyBuildDir:", error); + return false; + } +} + +/** + * Copy a file or folder recursively + * @param {string} src Source file or directory + * @param {string} dest Destination file or directory + */ +export async function cp(src, dest) { + // console.log("cp:", src, "->", dest); // DEBUG + await rootDir(); + const srcStat = await stat(src); + if (srcStat.isFile()) return copyFile(src, dest); + if (!srcStat.isDirectory()) return; + await mkdir(dest, { recursive: true }); + for (const sub of await readdir(src)) { + if ([".DS_Store"].includes(sub)) continue; + await cp(`${src}/${sub}`, `${dest}/${sub}`); + } +} + +/** + * Read file and return text + * @param {string} file file path + * @returns {Promise} file content + */ +export async function read(file) { + const path = fileURLToPath(new URL(file, import.meta.url)); + return readFile(path, "utf8"); +} + +/** + * @see {@link https://vitejs.dev/config/server-options.html#server-https} + * @see {@link https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener} + * @returns {Promise} https.ServerOptions + */ +export const sharedServerOptions = async () => ({ + ca: await read("./local/ca.pem"), + key: await read("./local/key.pem"), + cert: await read("./local/cert.pem"), +}); + +/** + * @see {@link https://git-scm.com/docs/git-describe} + * @returns {Promise} git tag, eg: v5.0.0-beta.1 + */ +async function getGitTag() { + try { + const { stdout, stderr } = await execFileAsync("git", [ + "describe", + "--tags", + "--abbrev=0", + ]); + stderr && console.log(stderr); + if (stdout) return stdout.trim(); + } catch (error) { + console.error(error); + process.exit(error.code); + } +} + +/** + * @see {@link https://git-scm.com/docs/git-rev-parse} + * @returns {Promise} git commit, eg: 700c978ccbd387921084ee185f495aa1f795d2e0 + */ +async function getGitCommit() { + try { + const { stdout, stderr } = await execFileAsync("git", [ + "rev-parse", + "HEAD", + ]); + stderr && console.log(stderr); + if (stdout) return stdout.trim(); + } catch (error) { + console.error(error); + process.exit(error.code); + } +} + +const gitTag = await getGitTag(); +const gitCommit = await getGitCommit(); +if (!gitTag || !gitCommit) { + console.error("Failed to get git infos, please check."); + process.exit(1); +} + +/** + * Define default vite config options + * Disable auto resolving {@link vite.config.js} + * @see {@link https://vitejs.dev/config/ Config} + * @see {@link https://vitejs.dev/guide/api-javascript.html#inlineconfig InlineConfig} + * @type {import("vite").InlineConfig} + */ +export const baseConfig = { + configFile: false, + envFile: false, + root: await rootDir(), + base: "./", + define: process.env.SAFARI_PLATFORM + ? { + "import.meta.env.BROWSER": JSON.stringify("Safari"), + "import.meta.env.NATIVE_APP": JSON.stringify("app"), + "import.meta.env.SAFARI_PLATFORM": JSON.stringify( + process.env.SAFARI_PLATFORM, + ), + "import.meta.env.GIT_TAG": JSON.stringify(gitTag), + "import.meta.env.GIT_COMMIT": JSON.stringify(gitCommit), + } + : { + "import.meta.env.GIT_TAG": JSON.stringify(gitTag), + "import.meta.env.GIT_COMMIT": JSON.stringify(gitCommit), + }, + resolve: { + alias: { + // NOTE: Don't forget to modify the `paths` in `jsconfig.json` also + "@shared": fileURLToPath(new URL("../src/shared", import.meta.url)), + "@ext": fileURLToPath(new URL("../src/ext/shared", import.meta.url)), + }, + }, +}; diff --git a/scripts/vite-plugin-marked-divest.js b/scripts/vite-plugin-marked-divest.js new file mode 100644 index 00000000..27093567 --- /dev/null +++ b/scripts/vite-plugin-marked-divest.js @@ -0,0 +1,89 @@ +import { fileURLToPath } from "node:url"; +import { readFile } from "node:fs/promises"; +import { marked } from "marked"; + +/** + * Exclude files that do not need to be processed + * @param {string} id + * @returns {boolean} + */ +function isExclude(id) { + const res = [/\/src\/app\/_locales\/[A-Za-z0-9_]+\/messages\.js/]; + for (const re of res) { + if (re.test(id)) return false; + } + return true; +} + +/** + * Preprocess `marked` calls and `.md?raw` transformations at build time + * to avoid including `marked` modules and runtime transformations + * @returns {import('vite').Plugin} + */ +function preprocessMarked() { + return { + name: "preprocess-marked", + resolveId: { + order: "pre", + handler(source, importer) { + if (isExclude(importer)) return; + if (source.endsWith(".md?raw")) { + const importerUrl = new URL(importer, import.meta.url); + const sourceUrl = new URL(source, importerUrl); + return fileURLToPath(sourceUrl) + "?marked"; + } + }, + }, + load: { + order: "pre", + async handler(id) { + if (!id.endsWith(".md?marked")) return; + const url = new URL(id, import.meta.url); + const text = await readFile(fileURLToPath(url), "utf8"); + const html = await marked.parse(text); + return `export default ${JSON.stringify(html)}`; + }, + }, + transform(code, id) { + if (isExclude(id)) return; + if (!code.includes("marked.parse")) return; + const node = this.parse(code); + if (node.sourceType !== "module") return; + for (const module of node.body) { + if (module.type !== "ImportDeclaration") continue; + if (!module.source.value.toString().endsWith(".md?raw")) continue; + const specifier = module.specifiers.find( + (specifier) => specifier.type === "ImportDefaultSpecifier", + ); + const varName = specifier.local.name; + code = code.replace(`await marked.parse(${varName})`, varName); + // console.debug(`optimizing 'await marked.parse(${varName})'`, id); // DEBUG + } + return { code }; + }, + }; +} + +/** + * Turn off `Marked` module side effects for better tree-shaking + * @returns {import('vite').Plugin} + */ +function disableMarkedSideEffects() { + return { + name: "disable-marked-side-effects", + transform(code, id) { + if (id.includes("/node_modules/marked")) { + return { code, moduleSideEffects: false }; + } + }, + }; +} + +/** + * @returns {import('vite').Plugin[]} + */ +function markedDivest() { + return [disableMarkedSideEffects(), preprocessMarked()]; +} + +export default markedDivest; diff --git a/src/App.svelte b/src/App.svelte deleted file mode 100644 index 937e58ef..00000000 --- a/src/App.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - -
-
- - - -
-

Userscripts dev

- -

- page, - popup -

- -

- This is the development preview environment -

-
- - diff --git a/src/app.css b/src/app.css deleted file mode 100644 index 1fedad38..00000000 --- a/src/app.css +++ /dev/null @@ -1,82 +0,0 @@ -/* stylelint-disable */ -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -.card { - padding: 2em; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/app/App.svelte b/src/app/App.svelte new file mode 100644 index 00000000..bbfe8df8 --- /dev/null +++ b/src/app/App.svelte @@ -0,0 +1,407 @@ + + + + +
+ {#await initialize() then app} + {#if OOBE} +
+
{gl("quick_start_guide_title")}
+ + {@html OOBEText} + +
+ {/if} + {#if promptLogger} + + {/if} + {#if enableLogger} +
+ + + + +
+ {/if} +
+
+
+
+ {#if import.meta.env.GIT_TAG && import.meta.env.GIT_COMMIT} + + {import.meta.env.GIT_TAG} + + ( + {import.meta.env.GIT_COMMIT.slice(0, 7)} + ) + {:else} + v{app.version} + ({app.build}) + {/if} +
+ {#if platform === "mac"} +
+ + {gl("safari_extension_status")}: + {gl(extStatus)} +
+ + {/if} +
+
+ +
+
+ +
{gl("current_directory")}
+ +
+ + {:catch error} +
+ {error} +
+ {/await} +
+ + diff --git a/src/app/_locales/en/messages.js b/src/app/_locales/en/messages.js new file mode 100644 index 00000000..4cee3c24 --- /dev/null +++ b/src/app/_locales/en/messages.js @@ -0,0 +1,85 @@ +import nativeLoggerCaveat from "./native-logger-caveat.md?raw"; +import quickStartGuideMac from "./quick-start-guide-mac.md?raw"; +import quickStartGuideIos from "./quick-start-guide-ios.md?raw"; + +import { marked } from "marked"; + +/** @satisfies {Types.I18nMessages} */ +export const markdown = { + native_logger_caveat: { + message: await marked.parse(nativeLoggerCaveat), + }, + quick_start_guide_mac: { + message: await marked.parse(quickStartGuideMac), + }, + quick_start_guide_ios: { + message: await marked.parse(quickStartGuideIos), + }, +}; + +/** @satisfies {Types.I18nMessages} */ +export const messages = { + enabled: { + message: "Enabled", + }, + disabled: { + message: "Disabled", + }, + unknown: { + message: "Unknown", + }, + error: { + message: "Error", + }, + button_ok: { + message: "OK", + }, + button_dismiss: { + message: "Dismiss", + }, + button_disable: { + message: "Disable", + }, + safari_extension_status: { + message: "Safari Extension Status", + }, + native_logger_enabled_title: { + message: "Native Logger Enabled", + }, + quick_start_guide_title: { + message: "Quick Start Guide", + }, + open_safari_settings: { + message: "Open Safari Settings", + }, + open_safari_preferences: { + message: "Open Safari Preferences", + }, + show_usage_guide: { + message: "Show Usage Guide", + }, + change_directory: { + message: "Change Userscripts Directory", + }, + current_directory: { + message: "CURRENT DIRECTORY:", + }, + documentation: { + message: "Documentation", + }, + discussions: { + message: "Discussions", + }, + report_an_issue: { + message: "Report an Issue", + }, + privacy_policy: { + message: "Privacy Policy", + }, + export_log_files: { + message: "Export log files", + }, + disable_logger: { + message: "Disable Logger", + }, +}; diff --git a/src/app/_locales/en/native-logger-caveat.md b/src/app/_locales/en/native-logger-caveat.md new file mode 100644 index 00000000..f1ba6b62 --- /dev/null +++ b/src/app/_locales/en/native-logger-caveat.md @@ -0,0 +1,5 @@ +- The logger is for local debugging only and can be streamed through the macOS console while writing to a local file when enabled. +- You can repeat the steps to reproduce the issue after enabling it, then export the log files and inspect them in the app. +- There is no built-in upload function. If you need to submit it to developers, please manually attach the exported log files to your issue report. +- To reduce energy consumption and disk reading and writing, please disbale the logger promptly after debugging. +- If the log file exceeds $1, the logger will be automatically disbaled and the log files will be cleared. diff --git a/src/app/_locales/en/quick-start-guide-ios.md b/src/app/_locales/en/quick-start-guide-ios.md new file mode 100644 index 00000000..4bc2316b --- /dev/null +++ b/src/app/_locales/en/quick-start-guide-ios.md @@ -0,0 +1,27 @@ +### Welcome, follow these two main steps to get the extension up and running: + +- **Open the app and set a directory** + + - When you first launch the app, a local directory will be set by default. + - If needed, you can select a new directory to suit your preferences. + - This directory is used for saving and loading your user scripts. + - Click the current directory to jump to the default directory or the one you set. + +- **Enable the extension in Safari** + + - Manage extensions from the Settings App (Settings > Safari > Extensions) or Safari App (`AA` button on iPhone, `extension-icon` on iPad, the same location where you open the extension's `popup` interface). + - For an optimal experience it's recommended that you "Always Allow" Userscripts for "All Websites", but you could also allow only the sites you want, but please don't forget your settings if your script doesn't work. + +### How to install and inject a user script and some things to note: + +- The mobile extension does not currently have a built-in script editor, but you could still write your own scripts and install them. +- You could save files that extension with `.user.js` directly to the Userscripts directory. + - The script file must contain a valid metadata block to match the web pages and be displayed in the extension popup. +- When you visit a `.user.js` URL in Safari, open the extension popup and you will see an installation prompt, or it will pop up automatically if you have enhanced prompts turned on. + - The URL should end with `.user.js` in `/PATH` part, not the `?QUERY` or `#HASH` parts, otherwise it will not be considered a valid user script URL by the extension. +- When you enabled the extension and write the metadata correctly especially like `@match`, it will automatically complete the injection when you visit a matching web page. + - You could temporarily disable individual user scripts or all of them, just open the extension popup interface to toggle them. + +### That’s it, start improving your quality of life now! + +_Hey, don't forget to read our detailed documentation to learn more._ diff --git a/src/app/_locales/en/quick-start-guide-mac.md b/src/app/_locales/en/quick-start-guide-mac.md new file mode 100644 index 00000000..c6ae6ef7 --- /dev/null +++ b/src/app/_locales/en/quick-start-guide-mac.md @@ -0,0 +1,27 @@ +### Welcome to this project, you will need two main steps to make the extension work: + +- **Open the app and set a directory** + + - When you first launch the app, a local directory will be set by default. + - If needed, you can select a new directory to suit your preferences. + - This directory is used for saving and loading your user scripts. + - Click the current directory to jump to the default directory or the one you set. + +- **Enable the extension in Safari** + + - Enable Userscripts in Safari > Settings... > Extensions list. + - For an optimal experience it's recommended that you "Always Allow" Userscripts for "All Websites", but you could also allow only the sites you want, but please don't forget your settings if your script doesn't work. + +### How to install and inject a user script and some things to note: + +- The preferred method is of course to create your own scripts, open the extension page and click on the plus sign in the editor to create a new JS, write code and save it. +- You could also save files that extension with `.user.js` directly to the Userscripts directory. + - The script file must contain a valid metadata block to match the web pages and be displayed in the extension popup. +- When you visit a `.user.js` URL in Safari, open the extension popup and you will see an installation prompt, or it will pop up automatically if you have enhanced prompts turned on. + - The URL should end with `.user.js` in `/PATH` part, not the `?QUERY` or `#HASH` parts, otherwise it will not be considered a valid user script URL by the extension. +- When you enabled the extension and write the metadata correctly especially like `@match`, it will automatically complete the injection when you visit a matching web page. + - You could temporarily disable individual user scripts or all of them, just open the extension popup interface to toggle them. + +### That’s it, start improving your quality of life now! + +_Hey, don't forget to read our detailed documentation to learn more._ diff --git a/src/app/_locales/zh/messages.js b/src/app/_locales/zh/messages.js new file mode 100644 index 00000000..e69057b0 --- /dev/null +++ b/src/app/_locales/zh/messages.js @@ -0,0 +1,85 @@ +import nativeLoggerCaveat from "./native-logger-caveat.md?raw"; +import quickStartGuideMac from "./quick-start-guide-mac.md?raw"; +import quickStartGuideIos from "./quick-start-guide-ios.md?raw"; + +import { marked } from "marked"; + +/** @satisfies {typeof import("../en/messages.js").markdown} */ +export const markdown = { + native_logger_caveat: { + message: await marked.parse(nativeLoggerCaveat), + }, + quick_start_guide_mac: { + message: await marked.parse(quickStartGuideMac), + }, + quick_start_guide_ios: { + message: await marked.parse(quickStartGuideIos), + }, +}; + +/** @satisfies {typeof import("../en/messages.js").messages} */ +export const messages = { + enabled: { + message: "已启用", + }, + disabled: { + message: "已停用", + }, + unknown: { + message: "未知", + }, + error: { + message: "错误", + }, + button_ok: { + message: "确定", + }, + button_dismiss: { + message: "关闭", + }, + button_disable: { + message: "停用", + }, + safari_extension_status: { + message: "Safari 扩展状态", + }, + native_logger_enabled_title: { + message: "本地日志记录器已启用", + }, + quick_start_guide_title: { + message: "快速入门指南", + }, + open_safari_settings: { + message: "打开 Safari 设置", + }, + open_safari_preferences: { + message: "打开 Safari 偏好", + }, + show_usage_guide: { + message: "显示使用指南", + }, + change_directory: { + message: "更改用户脚本目录", + }, + current_directory: { + message: "当前目录:", + }, + documentation: { + message: "详细文档", + }, + discussions: { + message: "交流讨论", + }, + report_an_issue: { + message: "报告问题", + }, + privacy_policy: { + message: "隐私政策", + }, + export_log_files: { + message: "导出日志文件", + }, + disable_logger: { + message: "停用日志记录", + }, +}; diff --git a/src/app/_locales/zh/native-logger-caveat.md b/src/app/_locales/zh/native-logger-caveat.md new file mode 100644 index 00000000..005723d0 --- /dev/null +++ b/src/app/_locales/zh/native-logger-caveat.md @@ -0,0 +1,5 @@ +- 日志记录器仅用于本地调试,开启期间可通过 macOS 控制台流式传输,同时写入本地文件。 +- 您可以在开启后重复复现问题的相关操作,然后在 App 中导出日志文件和检查它们。 +- 没有内建的上传功能,如需提交给开发者,请手动将导出的日志文件附加到您的问题报告中。 +- 为减少能耗和磁盘读写,请调试结束后及时关闭日志记录器。 +- 如日志文件超过 $1 将自动关闭日志记录器并清除日志。 diff --git a/src/app/_locales/zh/quick-start-guide-ios.md b/src/app/_locales/zh/quick-start-guide-ios.md new file mode 100644 index 00000000..d24bda16 --- /dev/null +++ b/src/app/_locales/zh/quick-start-guide-ios.md @@ -0,0 +1,26 @@ +### 欢迎来到这个项目,您需要两个主要步骤来使扩展工作: + +- **打开 App 并设置目录** + + - 该目录用于保存和加载您的用户脚本。 + - 首次启动 App 时已为您设置默认本地目录。 + - 点击当前目录可跳转到默认目录或您设置的目录。 + +- **在 Safari 中启用扩展** + + - 从 `设置` 应用(设置 > Safari > 扩展)或 `Safari` 应用(iPhone 上的“AA”按钮,iPad 上的“扩展图标”,也是您打开扩展的“弹出界面”的位置)管理扩展。 + - 为了获得最佳体验,建议您“始终允许” Userscripts 为“所有网站”,但您也可以仅允许您想要的网站,但当您的脚本不起作用时,请不要忘记您的设置。 + +### 那么,如何安装和注入用户脚本,以及一些注意事项? + +- 移动版扩展目前不包含内置的脚本编辑器,但您仍然可以编写您自己的脚本并安装它们。 +- 您可以直接保存扩展名为 `.user.js` 的文件到您上面设置的 Userscripts 目录中。 + - 您的脚本文件必须包含有效的元数据块以匹配网站页面并正确显示在扩展的弹出界面中。 +- 当您在 Safari 中访问一个 `.user.js` 网址,然后打开扩展弹出界面,您将看到安装提示;如果您启用了增强提示,它会自动弹出。 + - 请注意该网址的 `/路径` 部分应该以 `.user.js` 结尾,而不是 `?查询` 或 `#锚点` 部分,否则该网址将不会被视为有效的用户脚本。 +- 当您启用该扩展并正确编写元数据尤其是像 `@match` 后,当您访问匹配的网页时,它将自动完成注入。 + - 您可以暂时禁用单个用户脚本或所有用户脚本,只需打开扩展弹出界面即可切换它们。 + +### 就这样,现在就开始改善您的生活质量吧! + +_嘿,不要忘记阅读我们的详细文档以了解更多信息。_ diff --git a/src/app/_locales/zh/quick-start-guide-mac.md b/src/app/_locales/zh/quick-start-guide-mac.md new file mode 100644 index 00000000..f9983ced --- /dev/null +++ b/src/app/_locales/zh/quick-start-guide-mac.md @@ -0,0 +1,26 @@ +### 欢迎来到这个项目,您需要两个主要步骤来使扩展工作: + +- **打开 App 并设置目录** + + - 该目录用于保存和加载您的用户脚本。 + - 首次启动 App 时已为您设置默认本地目录。 + - 点击当前目录可跳转到默认目录或您设置的目录。 + +- **在 Safari 中启用扩展** + + - 在 Safari > 设置... > 扩展列表中启用 Userscripts 条目。 + - 为了获得最佳体验,建议您“始终允许” Userscripts 为“所有网站”,但您也可以仅允许您想要的网站,但当您的脚本不起作用时,请不要忘记您的设置。 + +### 那么,如何安装和注入用户脚本,以及一些注意事项? + +- 首选的方法当然是撰写您自己的用户脚本,打开扩展页面点击加号创建新脚本,编辑和保存它。 +- 您还可以直接保存扩展名为 `.user.js` 的文件到您上面设置的 Userscripts 目录中。 + - 您的脚本文件必须包含有效的元数据块以匹配网站页面并正确显示在扩展的弹出界面中。 +- 当您在 Safari 中访问一个 `.user.js` 网址,然后打开扩展弹出界面,您将看到安装提示;如果您启用了增强提示,它会自动弹出。 + - 请注意该网址的 `/路径` 部分应该以 `.user.js` 结尾,而不是 `?查询` 或 `#锚点` 部分,否则该网址将不会被视为有效的用户脚本。 +- 当您启用该扩展并正确编写元数据尤其是像 `@match` 后,当您访问匹配的网页时,它将自动完成注入。 + - 您可以暂时禁用单个用户脚本或所有用户脚本,只需打开扩展弹出界面即可切换它们。 + +### 就这样,现在就开始改善您的生活质量吧! + +_嘿,不要忘记阅读我们的详细文档以了解更多信息。_ diff --git a/src/app/_locales/zh_Hant/messages.js b/src/app/_locales/zh_Hant/messages.js new file mode 100644 index 00000000..e0fadede --- /dev/null +++ b/src/app/_locales/zh_Hant/messages.js @@ -0,0 +1,85 @@ +import nativeLoggerCaveat from "./native-logger-caveat.md?raw"; +import quickStartGuideMac from "./quick-start-guide-mac.md?raw"; +import quickStartGuideIos from "./quick-start-guide-ios.md?raw"; + +import { marked } from "marked"; + +/** @satisfies {typeof import("../en/messages.js").markdown} */ +export const markdown = { + native_logger_caveat: { + message: await marked.parse(nativeLoggerCaveat), + }, + quick_start_guide_mac: { + message: await marked.parse(quickStartGuideMac), + }, + quick_start_guide_ios: { + message: await marked.parse(quickStartGuideIos), + }, +}; + +/** @satisfies {typeof import("../en/messages.js").messages} */ +export const messages = { + enabled: { + message: "已啟用", + }, + disabled: { + message: "已停用", + }, + unknown: { + message: "未知", + }, + error: { + message: "錯誤", + }, + button_ok: { + message: "確定", + }, + button_dismiss: { + message: "關閉", + }, + button_disable: { + message: "停用", + }, + safari_extension_status: { + message: "Safari 延伸功能狀態", + }, + native_logger_enabled_title: { + message: "本地日誌記錄器已啟用", + }, + quick_start_guide_title: { + message: "快速入門指南", + }, + open_safari_settings: { + message: "開啟 Safari 設定", + }, + open_safari_preferences: { + message: "開啟 Safari 偏好", + }, + show_usage_guide: { + message: "顯示使用指南", + }, + change_directory: { + message: "更改使用者腳本目錄", + }, + current_directory: { + message: "當前目錄:", + }, + documentation: { + message: "詳細文件", + }, + discussions: { + message: "交流討論", + }, + report_an_issue: { + message: "報告問題", + }, + privacy_policy: { + message: "隱私政策", + }, + export_log_files: { + message: "匯出日誌檔案", + }, + disable_logger: { + message: "停用日誌記錄", + }, +}; diff --git a/src/app/_locales/zh_Hant/native-logger-caveat.md b/src/app/_locales/zh_Hant/native-logger-caveat.md new file mode 100644 index 00000000..0c9b4baa --- /dev/null +++ b/src/app/_locales/zh_Hant/native-logger-caveat.md @@ -0,0 +1,5 @@ +- 日誌記錄器僅用於本地除錯,開啟期間可透過 macOS 控制檯流式傳輸,同時寫入本地檔案。 +- 您可以在開啟後重複復現問題的相關操作,然後在 App 中匯出日誌檔案和檢查它們。 +- 沒有內建的上傳功能,如需提交給開發者,請手動將匯出的日誌檔案附加到您的問題報告中。 +- 為減少能耗和磁碟讀寫,請除錯結束後及時關閉日誌記錄器。 +- 如日誌檔案超過 $1 將自動關閉日誌記錄器並清除日誌。 diff --git a/src/app/_locales/zh_Hant/quick-start-guide-ios.md b/src/app/_locales/zh_Hant/quick-start-guide-ios.md new file mode 100644 index 00000000..0d7f5e51 --- /dev/null +++ b/src/app/_locales/zh_Hant/quick-start-guide-ios.md @@ -0,0 +1,26 @@ +### 歡迎來到這個專案,您需要兩個主要步驟來使延伸功能工作: + +- **開啟 App 並設定目錄** + + - 該目錄用於儲存和載入您的使用者腳本。 + - 首次啟動 App 時已為您設定預設本地目錄。 + - 點選當前目錄可跳轉到預設目錄或您設定的目錄。 + +- **在 Safari 中啟用延伸功能** + + - 從 `設定` 應用(設定 > Safari > 延伸功能)或 `Safari` 應用(iPhone 上的“AA”按鈕,iPad 上的“延伸功能圖示”,也是您開啟延伸功能的“彈出介面”的位置)管理延伸功能。 + - 為了獲得最佳體驗,建議您“始終允許” Userscripts 為“所有網站”,但您也可以僅允許您想要的網站,但當您的腳本不起作用時,請不要忘記您的設定。 + +### 那麼,如何安裝和注入使用者腳本,以及一些注意事項? + +- 移動版延伸功能目前不包含內建的腳本編輯器,但您仍然可以編寫您自己的腳本並安裝它們。 +- 您可以直接儲存副檔名為 `.user.js` 的檔案到您上面設定的 Userscripts 目錄中。 + - 您的腳本檔案必須包含有效的元資料塊以匹配網站頁面並正確顯示在延伸功能的彈出介面中。 +- 當您在 Safari 中訪問一個 `.user.js` 網址,然後開啟延伸功能彈出介面,您將看到安裝提示;如果您啟用了增強提示,它會自動彈出。 + - 請注意該網址的 `/路徑` 部分應該以 `.user.js` 結尾,而不是 `?查詢` 或 `#錨點` 部分,否則該網址將不會被視為有效的使用者腳本。 +- 當您啟用該延伸功能並正確編寫元資料尤其是像 `@match` 後,當您訪問匹配的網頁時,它將自動完成注入。 + - 您可以暫時禁用單個使用者腳本或所有使用者腳本,只需開啟延伸功能彈出介面即可切換它們。 + +### 就這樣,現在就開始改善您的生活質量吧! + +_嘿,不要忘記閱讀我們的詳細文件以瞭解更多資訊。_ diff --git a/src/app/_locales/zh_Hant/quick-start-guide-mac.md b/src/app/_locales/zh_Hant/quick-start-guide-mac.md new file mode 100644 index 00000000..3331a109 --- /dev/null +++ b/src/app/_locales/zh_Hant/quick-start-guide-mac.md @@ -0,0 +1,26 @@ +### 歡迎來到這個專案,您需要兩個主要步驟來使延伸功能工作: + +- **開啟 App 並設定目錄** + + - 該目錄用於儲存和載入您的使用者腳本。 + - 首次啟動 App 時已為您設定預設本地目錄。 + - 點選當前目錄可跳轉到預設目錄或您設定的目錄。 + +- **在 Safari 中啟用延伸功能** + + - 在 Safari > 設定... > 延伸功能列表中啟用 Userscripts 條目。 + - 為了獲得最佳體驗,建議您“始終允許” Userscripts 為“所有網站”,但您也可以僅允許您想要的網站,但當您的腳本不起作用時,請不要忘記您的設定。 + +### 那麼,如何安裝和注入使用者腳本,以及一些注意事項? + +- 首選的方法當然是撰寫您自己的使用者腳本,開啟延伸功能頁面點選加號建立新腳本,編輯和儲存它。 +- 您還可以直接儲存副檔名為 `.user.js` 的檔案到您上面設定的 Userscripts 目錄中。 + - 您的腳本檔案必須包含有效的元資料塊以匹配網站頁面並正確顯示在延伸功能的彈出介面中。 +- 當您在 Safari 中訪問一個 `.user.js` 網址,然後開啟延伸功能彈出介面,您將看到安裝提示;如果您啟用了增強提示,它會自動彈出。 + - 請注意該網址的 `/路徑` 部分應該以 `.user.js` 結尾,而不是 `?查詢` 或 `#錨點` 部分,否則該網址將不會被視為有效的使用者腳本。 +- 當您啟用該延伸功能並正確編寫元資料尤其是像 `@match` 後,當您訪問匹配的網頁時,它將自動完成注入。 + - 您可以暫時禁用單個使用者腳本或所有使用者腳本,只需開啟延伸功能彈出介面即可切換它們。 + +### 就這樣,現在就開始改善您的生活質量吧! + +_嘿,不要忘記閱讀我們的詳細文件以瞭解更多資訊。_ diff --git a/src/app/app.css b/src/app/app.css new file mode 100644 index 00000000..40c7cfa2 --- /dev/null +++ b/src/app/app.css @@ -0,0 +1,121 @@ +html { + height: auto; + overflow: visible; + overscroll-behavior: none; +} + +body { + background-color: var(--color-bg-primary); + color: var(--text-color-primary); + font: var(--text-default); + letter-spacing: var(--letter-spacing-medium); + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; +} + +/* ios */ +@supports (-webkit-touch-callout: none) { + /** + * Dynamic Type + * https://support.apple.com/102453 + * https://webkit.org/blog/3709/using-the-system-font-in-web-content/ + * https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Articles/Safari_7_0.html#//apple_ref/doc/uid/TP40014305-CH5-SW10 + * https://github.com/w3c/csswg-drafts/issues/3708 + */ + @supports (font: -apple-system-body) { + html { + /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ + font: -apple-system-body; /* default 17px */ + } + } + + /* Avoid page zoom */ + input, + textarea, + select { + font-size: max(1rem, 16px); + } +} + +#app { + flex-direction: column; +} + +address, +article, +aside, +button, +div, +footer, +header, +hgroup, +main, +nav, +search, +section { + display: flex; +} + +button { + border: none; + cursor: pointer; + padding: 0; + user-select: none; +} + +button:disabled { + opacity: var(--opacity-disabled); + pointer-events: none; +} + +a, +.link { + background: none; + color: var(--color-blue); + cursor: pointer; + text-decoration: underline; +} + +.truncate { + flex-grow: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.flexible_space { + flex-grow: 1; +} + +x-markdown { + display: flex; + flex-direction: column; + overflow: auto; + text-align: left; + gap: var(--gap); + padding: 0 3rem; + --gap: 0.75rem; +} + +@media (width < 500px) { + x-markdown { + padding: 0 1.5rem; + } +} + +x-markdown :is(h1, h2, h3, h4, h5, h6) { + padding: 0; + margin: 0; +} + +x-markdown ul { + display: flex; + flex-direction: column; + gap: var(--gap); + padding-left: 1.5rem; +} + +x-markdown > ul ul { + margin-top: var(--gap); +} diff --git a/src/app/global.d.ts b/src/app/global.d.ts new file mode 100644 index 00000000..b3f9487f --- /dev/null +++ b/src/app/global.d.ts @@ -0,0 +1,25 @@ +// https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html + +/// +/// + +declare global { + interface Window { + webapp: { + updateDirectory: (directory: string) => void; + updateExtStatus: (extStatus: ExtensionStatus) => void; + switchLogger: (enableLogger: boolean, promptLogger: boolean) => void; + }; + webkit: { + messageHandlers: { + controller: { + postMessage: ( + message: T, + ) => Promise>; + }; + }; + }; + } +} + +export {}; diff --git a/src/app/i18n.js b/src/app/i18n.js new file mode 100644 index 00000000..389042d8 --- /dev/null +++ b/src/app/i18n.js @@ -0,0 +1,58 @@ +/** + * @typedef {typeof import("./_locales/en/messages.js").messages} MessagesT + * @typedef {typeof import("./_locales/en/messages.js").markdown} MarkdownT + */ + +/** + * @param {Types.I18nMessages} messages + * @param {string} messageName + * @param {string | string[]} substitutions + * @returns {string} + */ +function getLangFrom(messages, messageName, substitutions = undefined) { + if (!(messageName in messages)) { + console.warn(`i18n - "${messageName}" not found`, messages); + return ""; + } + let text = messages[messageName].message; + // handle substitutions + if (typeof substitutions === "string") { + text = text.replace("$1", substitutions); + } else if (Array.isArray(substitutions)) { + for (let i = 0; i < substitutions.length; i++) { + text = text.replace(`$${i + 1}`, substitutions[i]); + } + } + if (import.meta.env.MODE === "development") { + console.debug("i18n", messages[messageName].message, text, substitutions); + } + return text; +} + +export async function i18nInit() { + const languages = [navigator.language.replace("-", "_")]; + if (["zh-HK", "zh-MO", "zh-TW"].includes(navigator.language)) { + languages.unshift("zh_Hant"); + } + languages.push(navigator.language.split("-")[0]); + languages.push("en"); // fallback + if (import.meta.env.MODE === "development") { + // languages.unshift("en"); // DEBUG + } + for (const language of languages) { + try { + /** @type {{messages: MessagesT, markdown: MarkdownT}} */ + const module = await import(`./_locales/${language}/messages.js`); + const { messages, markdown } = module; + return { + /** @type {Types.GetLang} */ + gl: (n, s) => getLangFrom(messages, n, s), + /** @type {Types.GetLang} */ + md: (n, s) => getLangFrom(markdown, n, s), + }; + } catch (error) { + console.debug("Will fall back to `en`", error); + } + } + throw Error("I18n initialization failed"); +} diff --git a/src/app/jsconfig.json b/src/app/jsconfig.json new file mode 100644 index 00000000..95a69da9 --- /dev/null +++ b/src/app/jsconfig.json @@ -0,0 +1,30 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true, + + "paths": { + "@shared/*": ["../shared/*"] + } + }, + "include": ["**/*.d.ts", "**/*.js", "**/*.svelte"] +} diff --git a/src/app/main.js b/src/app/main.js new file mode 100644 index 00000000..8a28fe48 --- /dev/null +++ b/src/app/main.js @@ -0,0 +1,42 @@ +import "./reset.css"; +import "@shared/variables.css"; +import "./app.css"; +import { mount } from "svelte"; +import App from "./App.svelte"; + +if (import.meta.env.MODE === "development") { + // Simulation in non-WkWebView dev environment + if (import.meta.env.SAFARI_PLATFORM === undefined) { + console.warn("Simulation webkit..."); + window.webkit = { + messageHandlers: { + controller: { + postMessage: async (message) => { + switch (message) { + case "INIT": + return { + build: "2", + directory: "Userscripts App Documents", + enableLogger: true, + extStatus: "unknown", + firstRunTime: 1, + maxLogFileSize: 500_000_000, + platform: "mac", + promptLogger: true, + useSettingsInsteadOfPreferences: true, + version: "2.0.0", + }; + default: + console.debug("Simulation.webkit.messageHandler:", message); + break; + } + }, + }, + }, + }; + } +} + +const app = mount(App, { target: document.getElementById("app") }); + +export default app; diff --git a/src/app/reset.css b/src/app/reset.css new file mode 100644 index 00000000..0b8335ad --- /dev/null +++ b/src/app/reset.css @@ -0,0 +1,42 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +body, +p { + margin: 0; +} + +ul { + margin: 0; +} + +button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: inherit; + margin: 0; +} + +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; +} + +::-webkit-search-decoration, +::-webkit-search-cancel-button, +::-webkit-search-results-button, +::-webkit-search-results-decoration { + -webkit-appearance: none; +} + +img { + border-style: none; +} diff --git a/src/app/types.d.ts b/src/app/types.d.ts new file mode 100644 index 00000000..f282812b --- /dev/null +++ b/src/app/types.d.ts @@ -0,0 +1,43 @@ +declare namespace Types { + /** @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n/Locale-Specific_Message_reference} */ + type I18nMessages = { + [x: string]: { + message: string; + description?: string; + placeholders?: Object; + }; + }; + + type GetLang = ( + messageName: T, + substitutions?: string | string[], + ) => string; + + type SystemPlatform = "mac" | "ios"; + + type ExtensionStatus = "unknown" | "disabled" | "enabled" | "error"; + + type MessageBody = + | "INIT" + | "CHANGE_DIRECTORY" + | "OPEN_DIRECTORY" + | "SHOW_PREFERENCES" + | "EXPORT_LOG_FILES" + | "DISABLE_LOGGER" + | "DISMISS_LOGGER_PROMPT"; + + type MessageReply = T extends "INIT" + ? { + build: string; + version: string; + platform: SystemPlatform; + directory: string; + extStatus: ExtensionStatus; + useSettingsInsteadOfPreferences: boolean; + enableLogger: boolean; + promptLogger: boolean; + maxLogFileSize: number; + firstRunTime: number; + } + : void; +} diff --git a/src/dev/App.svelte b/src/dev/App.svelte new file mode 100644 index 00000000..602dae45 --- /dev/null +++ b/src/dev/App.svelte @@ -0,0 +1,47 @@ + + +
+
+ + + +
+

Userscripts dev

+ +

+ + native-app + , + + action-popup + , + + extension-page + +

+ +

This is the development preview environment

+
+ + diff --git a/src/dev/app.css b/src/dev/app.css new file mode 100644 index 00000000..19d0543e --- /dev/null +++ b/src/dev/app.css @@ -0,0 +1,82 @@ +/* stylelint-disable */ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/assets/logo.png b/src/dev/assets/logo.png similarity index 100% rename from src/assets/logo.png rename to src/dev/assets/logo.png diff --git a/src/dev/jsconfig.json b/src/dev/jsconfig.json new file mode 100644 index 00000000..899038cd --- /dev/null +++ b/src/dev/jsconfig.json @@ -0,0 +1,26 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true + }, + "include": ["**/*.d.ts", "**/*.js", "**/*.svelte"] +} diff --git a/src/dev/main.js b/src/dev/main.js new file mode 100644 index 00000000..85631fee --- /dev/null +++ b/src/dev/main.js @@ -0,0 +1,7 @@ +import "./app.css"; +import { mount } from "svelte"; +import App from "./App.svelte"; + +const app = mount(App, { target: document.getElementById("app") }); + +export default app; diff --git a/src/vite-env.d.ts b/src/dev/vite-env.d.ts similarity index 100% rename from src/vite-env.d.ts rename to src/dev/vite-env.d.ts diff --git a/src/ext/action-popup/App.svelte b/src/ext/action-popup/App.svelte new file mode 100644 index 00000000..6d593530 --- /dev/null +++ b/src/ext/action-popup/App.svelte @@ -0,0 +1,761 @@ + + +{#if showUpdates} + (showUpdates = false)} + showLoaderOnDisabled={true} + abortClick={abortUpdates} + abort={showUpdates} + > + + +{:else if showInstall} + (showInstall = false)} + showLoaderOnDisabled={true} + > + (showInstall = false)} + installConfirmClick={installConfirm} + /> + +{:else if showAll} + { + showAll = false; + refreshView(); + }} + showLoaderOnDisabled={false} + > + + +{:else} +
+
+ + (showUpdates = true)} + title="Show updates" + {disabled} + /> + + +
+ {#if !active} + + {/if} + {#if showBetaNews && platform !== "macos"} +
+ NEW: + is now available! + (showBetaNews = false)} + title="Close" + /> +
+ {/if} + {#if showInstallPrompt} +
+ Userscript + {#if scriptChecking} + {showInstallPrompt} + {:else} + {scriptInstalled ? "Installed" : "Detected"}: + + {/if} +
+ {/if} + {#if errorNotification} +
+ {errorNotification} + (errorNotification = undefined)} + title="Clear error" + /> +
+ {/if} +
+
+ {#if loading} + + {:else if inactive} +
Popup inactive on extension page
+ {:else if firstGuide} +
+

Welcome, first use please: 

+ +

to complete the initialization

+
+ {:else if initError} +
+ Something went wrong:  + +
+ {:else if items.length < 1} +
No matched userscripts
+ {:else} +
+ {#each list as item, i (item.filename)} + toggleItem(item)} + /> + {/each} +
+ {/if} +
+ {#if !inactive && platform === "macos"} + + {/if} +{/if} + + diff --git a/src/ext/action-popup/Components/PopupItem.svelte b/src/ext/action-popup/Components/PopupItem.svelte new file mode 100644 index 00000000..0afba81c --- /dev/null +++ b/src/ext/action-popup/Components/PopupItem.svelte @@ -0,0 +1,97 @@ + + +
+ +
{name}
+ {#if subframe}
SUB
{/if} + +
+ + diff --git a/src/ext/action-popup/Components/View.svelte b/src/ext/action-popup/Components/View.svelte new file mode 100644 index 00000000..1bd7ad7c --- /dev/null +++ b/src/ext/action-popup/Components/View.svelte @@ -0,0 +1,89 @@ + + +
+
+ {headerTitle} + +
+
+ {#if loading && showLoaderOnDisabled} + + {:else} +
Slot content is required...
+ {/if} +
+
+ + diff --git a/src/ext/action-popup/Components/Views/AllItemsView.svelte b/src/ext/action-popup/Components/Views/AllItemsView.svelte new file mode 100644 index 00000000..20aea85c --- /dev/null +++ b/src/ext/action-popup/Components/Views/AllItemsView.svelte @@ -0,0 +1,56 @@ + + +{#if allItems.length} +
+ {#each list as item (item.filename)} + allItemsToggleItem(item)} + /> + {/each} +
+{:else} +
No valid files found in directory
+{/if} + + diff --git a/src/ext/action-popup/Components/Views/InstallView.svelte b/src/ext/action-popup/Components/Views/InstallView.svelte new file mode 100644 index 00000000..8c54b6cc --- /dev/null +++ b/src/ext/action-popup/Components/Views/InstallView.svelte @@ -0,0 +1,176 @@ + + +
+ {#if installError} +
+ + {@html iconError} +
Couldn't install userscript
+

{installError}

+
+ {:else if userscript} +
    +
  • {userscript.name}
  • + {#if userscript.description} +
  • {userscript.description}
  • + {/if} + {#if userscript.match} +
  • +
    @match
    + {#each userscript.match as match (match)} +
    {match}
    + {/each} +
  • + {/if} + {#if userscript.include} +
  • +
    @include
    + {#each userscript.include as include (include)} +
    {include}
    + {/each} +
  • + {/if} + {#if userscript.require} +
  • +
    @require
    + {#each userscript.require as require (require)} +
    {require}
    + {/each} +
  • + {/if} + {#if userscript.grant} +
  • +
    @grant
    + {#each userscript.grant as grant (grant)} +
    {grant}
    + {/each} +
  • + {/if} +
+
+ +
{@html iconWarn}
+
+ Be sure you trust the author before installing. Nefarious code can + exploit your security and privacy. +
+
+
+ + +
+ {/if} +
+ + diff --git a/src/ext/action-popup/Components/Views/UpdateView.svelte b/src/ext/action-popup/Components/Views/UpdateView.svelte new file mode 100644 index 00000000..6da55f46 --- /dev/null +++ b/src/ext/action-popup/Components/Views/UpdateView.svelte @@ -0,0 +1,83 @@ + + +{#if updates.length} + {#each updates as item (item.name)} + +
+
{item.name}
+ Source + +
+ {/each} +

Be sure you trust the author before saving remote code to your device.

+ +{:else} +
+ + {@html iconUpdate} +
+ There are no file updates available +
+ +
+
+{/if} + + diff --git a/src/ext/action-popup/app.css b/src/ext/action-popup/app.css new file mode 100644 index 00000000..88f6c725 --- /dev/null +++ b/src/ext/action-popup/app.css @@ -0,0 +1,97 @@ +html { + font-size: 100%; + overflow: hidden; +} + +body { + background-color: var(--color-bg-secondary); + color: var(--text-color-primary); + font: var(--text-medium); + letter-spacing: var(--letter-spacing-medium); + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; + position: relative; + width: 18rem; + height: 26rem; +} + +/* ios */ +@supports (-webkit-touch-callout: none) { + html { + font-size: 125%; + overflow: visible; + overscroll-behavior: none; + } + + /** + * Dynamic Type + * https://support.apple.com/102453 + * https://webkit.org/blog/3709/using-the-system-font-in-web-content/ + * https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Articles/Safari_7_0.html#//apple_ref/doc/uid/TP40014305-CH5-SW10 + * https://github.com/w3c/csswg-drafts/issues/3708 + */ + @supports (font: -apple-system-body) { + html { + /* stylelint-disable-next-line declaration-property-value-no-unknown,font-family-no-missing-generic-family-keyword */ + font: -apple-system-title3; /* default 20px */ + } + } + + body { + width: auto; + min-width: 16rem; + height: auto; + } + + /* non-fixed width need to stretch */ + @media (width < 320px) { + body { + width: 18rem; + min-height: 16rem; + } + } + + /* Avoid page zoom */ + input, + textarea, + select { + font-size: max(1rem, 16px); + } +} + +noscript { + display: block; +} + +#app { + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; +} + +button { + border: none; + cursor: pointer; + padding: 0; + user-select: none; +} + +button:disabled { + opacity: var(--opacity-disabled); + pointer-events: none; +} + +a, +.link { + background: none; + color: var(--color-blue); + cursor: pointer; + text-decoration: underline; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/ext/action-popup/main.js b/src/ext/action-popup/main.js new file mode 100644 index 00000000..a5190622 --- /dev/null +++ b/src/ext/action-popup/main.js @@ -0,0 +1,38 @@ +import "@shared/reset.css"; +import "@shared/variables.css"; +import "./app.css"; +import { mount } from "svelte"; +import App from "./App.svelte"; + +// vite feat that only import in dev mode +if (import.meta.env.MODE === "development") { + try { + const modules = import.meta.glob("@ext/dev.js", { eager: true }); + const browser = modules["/src/ext/shared/dev.js"]["browser"]; + console.debug("DEV-ENV", import.meta.env, modules, browser); + if (!window?.browser?.extension && browser) { + // assign to window simulation WebExtension APIs + window.browser = browser; + // macos popup simulation + const style = document.createElement("style"); + style.textContent = ` +body { + top: 20px; + left: 20px; + box-sizing: content-box; + border: 1px solid light-dark(#bebebe, #585c5f); + border-radius: 10px; + box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.2); + overflow: hidden; +} +`; + browser.platform === "macos" && document.head.append(style); + } + } catch (error) { + console.error(error); + } +} + +const app = mount(App, { target: document.getElementById("app") }); + +export default app; diff --git a/src/ext/background/main.js b/src/ext/background/main.js new file mode 100644 index 00000000..761eda72 --- /dev/null +++ b/src/ext/background/main.js @@ -0,0 +1,608 @@ +import { contentScriptRegistration, openExtensionPage } from "@ext/utils.js"; +import * as settingsStorage from "@ext/settings.js"; +import { connectNative, sendNativeMessage } from "@ext/native.js"; + +// first sorts files by run-at value, then by weight value +function userscriptSort(a, b) { + // map the run-at values to numeric values + const runAtValues = { + "document-start": 1, + "document-end": 2, + "document-idle": 3, + }; + const runAtA = a.scriptObject["run-at"]; + const runAtB = b.scriptObject["run-at"]; + if (runAtA !== runAtB && runAtValues[runAtA] && runAtValues[runAtB]) { + return runAtValues[runAtA] > runAtValues[runAtB]; + } + return Number(a.scriptObject.weight) < Number(b.scriptObject.weight); +} + +async function getPlatform() { + let platform = localStorage.getItem("platform"); + if (!platform) { + const message = { name: "REQ_PLATFORM" }; + const response = await sendNativeMessage(message); + if (!response.platform) { + console.error("Failed to get platform"); + return ""; + } + platform = response.platform; + localStorage.setItem("platform", platform); + } + return platform; +} + +function setClipboard(data, type = "text/plain") { + // future enhancement? + // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write + // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText + const onCopy = (e) => { + e.stopImmediatePropagation(); + e.preventDefault(); + e.clipboardData.setData(type, data); + document.removeEventListener("copy", onCopy, true); + }; + + const textarea = document.createElement("textarea"); + textarea.textContent = ""; + document.body.appendChild(textarea); + textarea.select(); + document.addEventListener("copy", onCopy, true); + try { + return document.execCommand("copy"); + } catch (error) { + console.warn("setClipboard failed", error); + document.removeEventListener("copy", onCopy, true); + return false; + } finally { + document.body.removeChild(textarea); + } +} + +async function setBadgeCount() { + const clearBadge = () => browser.browserAction.setBadgeText({ text: null }); + // @todo until better introduce in ios, only set badge on macOS + // set a text badge or an empty string in visionOS will cause the extension's icon to no longer be displayed + const platform = await getPlatform(); + if (platform !== "macos") return clearBadge(); + // @todo settingsStorage.get("global_exclude_match") + const settings = await settingsStorage.get([ + "global_active", + "toolbar_badge_count", + ]); + if (settings["global_active"] === false) return clearBadge(); + if (settings["toolbar_badge_count"] === false) return clearBadge(); + + const currentTab = await browser.tabs.getCurrent(); + // no active tabs exist (user closed all windows) + if (!currentTab) return clearBadge(); + const url = currentTab.url; + // if url doesn't exist, stop + if (!url) return clearBadge(); + // only check for http/s pages + if (!url.startsWith("http://") && !url.startsWith("https://")) + return clearBadge(); + // @todo if url match in global exclude list, clear badge + const frameUrls = new Set(); + const frames = await browser.webNavigation.getAllFrames({ + tabId: currentTab.id, + }); + for (let i = 0; i < frames.length; i++) { + const frameUrl = frames[i].url; + if (frameUrl !== url && frameUrl.startsWith("http")) { + frameUrls.add(frameUrl); + } + } + const message = { + name: "POPUP_BADGE_COUNT", + url, + frameUrls: Array.from(frameUrls), + }; + const response = await sendNativeMessage(message); + if (response?.error) return console.error(response.error); + if (response?.count > 0) { + browser.browserAction.setBadgeText({ text: response.count.toString() }); + } else { + const _url = new URL(url); + if (_url.pathname.endsWith(".user.js")) { + browser.browserAction.setBadgeText({ text: "JS" }); + } else { + clearBadge(); + } + } +} + +// on startup set declarativeNetRequest rulesets +// should also check and refresh when: +// 1. dnr item save event in the page occurs +// 2. dnr item toggle event in the page occurs +// 3. external editor changes script file content +async function setDNRRulesets() { + // not supported below safari 15.4 + if (!browser.declarativeNetRequest.updateDynamicRules) return; + const message = { name: "REQ_REQUESTS" }; + const response = await sendNativeMessage(message); + if (response.error) { + console.error(response.error); + return; + } + // loop through response, parse the rules, push to array and log + /** @type {import("webextension-polyfill").DeclarativeNetRequest.Rule[]} */ + const addRules = []; + let ruleId = 1; + for (let i = 0; i < response.length; i++) { + if ( + ruleId > + browser.declarativeNetRequest.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES + ) { + console.warn( + "Rules exceed the maximum number, some rules will be ignored", + ); + break; + } + const ruleset = response[i]; + /** @type {Array} */ + let rules; + try { + const res = JSON.parse(ruleset.code); + // check if an array or single rule + if (Array.isArray(res)) { + rules = res; + } else if (typeof res === "object") { + rules = [res]; + } else { + console.warn(`Not a valid DNR ruleset: ${ruleset.name}`); + continue; + } + console.info(`Setting DNR ruleset: ${ruleset.name} (${rules.length})`); + } catch (error) { + console.warn( + `Failed parsed into a valid DNR ruleset: ${ruleset.name}`, + error, + ); + continue; + } + for (const rule of rules) { + // simple check if it is a rule object + if (!rule.action || !rule.condition || !rule.id) { + console.warn("Not a valid DNR rule:", rule); + continue; + } + // set unique ids for all rules to ensure no repeats + rule.id = ruleId++; + addRules.push(rule); + } + } + // remove all then add declarativeNetRequest rules + try { + const oldRules = await browser.declarativeNetRequest.getDynamicRules(); + const removeRuleIds = oldRules.map((rule) => rule.id); + await browser.declarativeNetRequest.updateDynamicRules({ + addRules, + removeRuleIds, + }); + } catch (error) { + return console.error(error); + } + console.info(`Finished setting ${addRules.length} DNR rules`); +} + +// the current update logic is similar to setDNRRulesets() +// this feature needs a more detailed redesign in the future +// https://github.com/quoid/userscripts/issues/453 +async function getContextMenuItems() { + // macos exclusive feature + const platform = await getPlatform(); + if (platform !== "macos") return; + // since it's not possible to get a list of currently active menu items + // on update, all context-menu items are cleared, then re-added + // this is done to ensure fresh code changes appear + await browser.menus.removeAll(); + // get the context-menu scripts + const message = { name: "REQ_CONTEXT_MENU_SCRIPTS" }; + const response = await sendNativeMessage(message); + if (response.error) { + console.error(response.error); + return; + } + // add menus items + const items = response.files?.menu || []; + if (items.length) { + console.info(`Setting ${items.length} context-menu userscripts`); + } + for (let i = 0; i < items.length; i++) { + const item = items[i]; + // context-menu scripts require @match value + // @include values are ignored + if (!item.scriptObject.matches.length) continue; + addContextMenuItem(item); + } +} + +async function addContextMenuItem(userscript) { + const patterns = userscript.scriptObject.matches; + browser.menus.create( + { + contexts: ["all"], + documentUrlPatterns: patterns, + id: userscript.scriptObject.filename, + title: userscript.scriptObject.name, + }, + () => { + // add event listener if needed + if (!browser.menus.onClicked.hasListener(contextClick)) { + browser.menus.onClicked.addListener(contextClick); + } + }, + ); +} + +async function contextClick(info, tab) { + // when any created context-menu item is clicked, send message to tab + // the content script for that tag will have the context-menu code + // which will get send back in the response if/when found + const message = { name: "CONTEXT_RUN", menuItemId: info.menuItemId }; + const response = await browser.tabs.sendMessage(tab.id, message); + // if code is returned, execute on that tab + if (!response.code) return; + browser.tabs.executeScript(tab.id, { code: response.code }); +} + +async function nativeChecks() { + const response = await sendNativeMessage({ + name: "NATIVE_CHECKS", + }); + if (response.error) { + settingsStorage.set({ error_native: response }); + return false; + } + settingsStorage.reset("error_native"); + return true; +} + +/** + * Handles messages sent with `browser.runtime.sendMessage` + * Make sure not to return `undefined` or `rejection`, otherwise the reply may never be delivered + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage#listener} + * @type {Parameters[0]} + * @returns {Promise<{status: "pending"|"fulfilled"|"rejected", result?: any}>} + */ +async function handleMessage(message, sender) { + switch (message.name) { + case "REQ_USERSCRIPTS": { + // get the page url from the content script that sent request + const url = sender.url; + // use frameId to determine if request came from top level window + // if @noframes true, and isTop false, swift layer won't return code + const isTop = sender.frameId === 0; + // send request to swift layer to provide code for page url + const message = { name: "REQ_USERSCRIPTS", url, isTop }; + try { + const response = await sendNativeMessage(message); + if (import.meta.env.MODE === "development") { + console.debug("REQ_USERSCRIPTS", message, response); + } + // if request failed, send error to content script for logging + if (response.error) return response; + // sort files + response.files.js.sort(userscriptSort); + response.files.css.sort((a, b) => Number(a.weight) < Number(b.weight)); + // return sorted files for injection + return response; + } catch (error) { + console.error(error); + // @ts-ignore -- ignore for now and will reconstruct this in the future. + return { error: String(error) }; + } + } + case "API_CLOSE_TAB": { + try { + await browser.tabs.remove(message.tabId || sender.tab.id); + return { status: "fulfilled" }; + } catch (error) { + console.error(message, sender, error); + return { status: "rejected", result: String(error) }; + } + } + case "API_OPEN_TAB": { + try { + const tab = await browser.tabs.create({ + active: message.active, + index: sender.tab.index + 1, + url: message.url, + }); + return { status: "fulfilled", result: tab }; + } catch (error) { + console.error(message, sender, error); + return { status: "rejected", result: String(error) }; + } + } + case "API_ADD_STYLE": { + try { + await browser.tabs.insertCSS(sender.tab.id, { + code: message.css, + cssOrigin: "user", + }); + return { status: "fulfilled" }; + } catch (error) { + console.error(message, sender, error); + return { status: "rejected", result: String(error) }; + } + } + case "API_GET_TAB": { + if (typeof sender.tab === "undefined") { + const error = "unable to deliver tab due to empty tab id"; + return { status: "rejected", result: error }; + } + try { + const tabData = sessionStorage.getItem(`tab-${sender.tab.id}`); + // if tabData is null, can still parse it and return that + const tabObj = JSON.parse(tabData); + return { status: "fulfilled", result: tabObj }; + } catch (error) { + console.error("failed to parse tab data for getTab", error); + return { status: "rejected", result: String(error) }; + } + } + case "API_SAVE_TAB": { + if (sender.tab != null && sender.tab.id) { + const key = `tab-${sender.tab.id}`; + sessionStorage.setItem(key, JSON.stringify(message.tabObj)); + return { status: "fulfilled" }; + } else { + const error = "unable to save tab, empty tab id"; + return { status: "rejected", result: String(error) }; + } + } + case "API_SET_CLIPBOARD": { + const result = setClipboard(message.clipboardData, message.type); + return { status: "fulfilled", result }; + } + case "API_XHR": { + try { + // initializing an xhr instance + const xhr = new XMLHttpRequest(); + // establish a long-lived port connection to content script + /** @type {import("../global.d.ts").TypeBackground.XHRPort} */ + const port = browser.tabs.connect(sender.tab.id, { + name: message.xhrPortName, + }); + // receive messages from content script and process them + port.onMessage.addListener((msg) => { + if (msg.name === "ABORT") xhr.abort(); + if (msg.name === "DISCONNECT") port.disconnect(); + }); + // handle port disconnect and clean tasks + port.onDisconnect.addListener((p) => { + if (p?.error) { + console.error( + `port disconnected due to an error: ${p.error.message}`, + ); + } + }); + // parse details and set up for xhr instance + /** @type {TypeExtMessages.XHRTransportableDetails} */ + const details = message.details; + /** @type {Parameters[0]} */ + const method = details.method || "GET"; + /** @type {Parameters[1]} */ + const url = details.url; + /** @type {Parameters[3]} */ + const user = details.user || null; + /** @type {Parameters[4]} */ + const password = details.password || null; + /** @type {Parameters[0]} */ + let body = null; + if (typeof details.data === "object") { + /** @type {TypeExtMessages.XHRProcessedData} */ + const data = details.data; + if (typeof data.data === "string") { + if (data.type === "Text") { + // deprecate once body supports more data types + // the `binary` key will no longer needed + if (details.binary) { + const binaryString = data.data; + const view = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + view[i] = binaryString.charCodeAt(i); + } + body = view; + } else { + body = data.data; + } + } + if (data.type === "Document") { + body = data.data; + if (!("content-type" in details.headers)) { + details.headers["content-type"] = data.mime; + } + } + if (data.type === "URLSearchParams") { + body = new URLSearchParams(data.data); + } + } + if (Array.isArray(data.data)) { + if ( + data.type === "ArrayBuffer" || + data.type === "ArrayBufferView" + ) { + body = new Uint8Array(data.data); + } + if (data.type === "Blob") { + body = new Uint8Array(data.data); + if (!("content-type" in details.headers)) { + details.headers["content-type"] = data.mime; + } + } + if (data.type === "FormData") { + body = new FormData(); + for (const [k, v] of data.data) { + if (typeof v === "string") { + body.append(k, v); + } else { + const view = new Uint8Array(v.data); + body.append( + k, + new File([view], v.name, { + type: v.mime, + lastModified: v.lastModified, + }), + ); + } + } + } + } + } + // xhr instances automatically filter out unexpected user values + xhr.timeout = details.timeout; + xhr.responseType = details.responseType; + // record parsed values for subsequent use + const responseType = xhr.responseType; + // avoid unexpected behavior of legacy defaults such as parsing XML + if (responseType === "") xhr.responseType = "text"; + // transfer to content script via arraybuffer and then parse to blob + if (responseType === "blob") xhr.responseType = "arraybuffer"; + // transfer to content script via text and then parse to document + if (responseType === "document") xhr.responseType = "text"; + // add required listeners and send result back to the content script + if (details.hasUploadHandlers) { + for (const handler of Object.keys(details.hasUploadHandlers)) { + /** @param {ProgressEvent} event */ + xhr.upload[handler] = async (event) => { + /** @type {TypeExtMessages.XHRProgress} */ + const progress = { + lengthComputable: event.lengthComputable, + loaded: event.loaded, + total: event.total, + }; + port.postMessage({ handler, progress }); + }; + } + } + const handlers = details.hasHandlers ?? {}; + for (const handler of Object.keys(handlers)) { + xhr[handler] = async () => { + // can not send xhr through postMessage + // construct new object to be sent as "response" + /** @type {TypeExtMessages.XHRTransportableResponse} */ + const response = { + contentType: undefined, // non-standard + readyState: xhr.readyState, + response: xhr.response, + responseHeaders: xhr.getAllResponseHeaders(), + responseType, + responseURL: xhr.responseURL, + status: xhr.status, + statusText: xhr.statusText, + timeout: xhr.timeout, + }; + // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response#value + if (xhr.readyState < xhr.DONE && xhr.responseType !== "text") { + response.response = null; + } + // get content-type when headers received + if (xhr.readyState >= xhr.HEADERS_RECEIVED) { + response.contentType = xhr.getResponseHeader("Content-Type"); + } + // only process when xhr is complete and data exist + // note the status of the last `progress` event in Safari is DONE/4 + // exclude this event to avoid unnecessary processing and transmission + if ( + xhr.readyState === xhr.DONE && + xhr.response !== null && + handler !== "onprogress" + ) { + // need to convert arraybuffer data to postMessage + if ( + xhr.responseType === "arraybuffer" && + xhr.response instanceof ArrayBuffer + ) { + const buffer = xhr.response; + response.response = Array.from(new Uint8Array(buffer)); + } + } + port.postMessage({ handler, response }); + }; + } + // if onloadend not set in xhr details + // onloadend event won't be passed to content script + // if that happens port DISCONNECT message won't be posted + // so if details lacks onloadend then attach the listener + if (!handlers.onloadend) { + xhr.onloadend = () => { + port.postMessage({ handler: "onloadend" }); + }; + } + if (details.overrideMimeType) { + xhr.overrideMimeType(details.overrideMimeType); + } + xhr.open(method, url, true, user, password); + // must set headers after `xhr.open()`, but before `xhr.send()` + if (typeof details.headers === "object") { + for (const [key, val] of Object.entries(details.headers)) { + xhr.setRequestHeader(key, val); + } + } + xhr.send(body); + } catch (error) { + console.error(error); + } + return { status: "fulfilled" }; + } + case "REFRESH_DNR_RULES": { + setDNRRulesets(); + break; + } + case "REFRESH_CONTEXT_MENU_SCRIPTS": { + getContextMenuItems(); + break; + } + case "WEB_USERJS_POPUP": { + const currentTab = await browser.tabs.getCurrent(); + if (currentTab.id === sender.tab.id) { + browser.browserAction.openPopup(); + } + break; + } + } +} +browser.runtime.onInstalled.addListener(async () => { + await nativeChecks(); + const enable = await settingsStorage.get("augmented_userjs_install"); + await contentScriptRegistration(enable); +}); +browser.runtime.onStartup.addListener(async () => { + setDNRRulesets(); + getContextMenuItems(); +}); +// listens for messages from content script, popup and page +browser.runtime.onMessage.addListener(handleMessage); +// set the badge count +browser.tabs.onActivated.addListener(setBadgeCount); +browser.windows.onFocusChanged.addListener(async (windowId) => { + if (windowId < 1) { + // lose focus + return; + } + nativeChecks(); + setBadgeCount(); + setDNRRulesets(); + getContextMenuItems(); +}); +browser.webNavigation.onCompleted.addListener(setBadgeCount); + +// handle native app messages +const port = connectNative(); +port.onMessage.addListener(async (message) => { + // console.info(message); // DEBUG + if (message.name === "SAVE_LOCATION_CHANGED") { + await openExtensionPage(); + if (message?.userInfo?.returnApp === true) { + sendNativeMessage({ name: "OPEN_APP" }); + } + } + // if (message.name === "OPEN_EXTENSION_PAGE") { + // openExtensionPage(); + // } +}); diff --git a/src/ext/content-scripts/api.js b/src/ext/content-scripts/api.js new file mode 100644 index 00000000..e2bf4cdb --- /dev/null +++ b/src/ext/content-scripts/api.js @@ -0,0 +1,503 @@ +async function setValue(key, value) { + if (typeof key !== "string" || !key.length) { + return Promise.reject(new Error("setValue invalid key arg")); + } + const sid = this.US_filename; + if (typeof sid !== "string" || !sid.length) { + return Promise.reject(new Error("setValue invalid call")); + } + const item = {}; + item[`${sid}---${key}`] = value; + return browser.storage.local.set(item); +} + +async function getValue(key, defaultValue) { + if (typeof key !== "string" || !key.length) { + return Promise.reject(new Error("getValue invalid key arg")); + } + const sid = this.US_filename; + if (typeof sid !== "string" || !sid.length) { + return Promise.reject(new Error("getValue invalid call")); + } + const prefixedKey = `${sid}---${key}`; + const results = await browser.storage.local.get(prefixedKey); + if (prefixedKey in results) return results[prefixedKey]; + if (defaultValue !== undefined) return defaultValue; + return undefined; +} + +async function deleteValue(key) { + if (typeof key !== "string" || !key.length) { + return Promise.reject(new Error("deleteValue missing key arg")); + } + const sid = this.US_filename; + if (typeof sid !== "string" || !sid.length) { + return Promise.reject(new Error("deleteValue invalid call")); + } + const prefixedKey = `${sid}---${key}`; + return browser.storage.local.remove(prefixedKey); +} + +async function listValues() { + const sid = this.US_filename; + if (typeof sid !== "string" || !sid.length) { + return Promise.reject(new Error("listValues invalid call")); + } + const prefix = `${sid}---`; + const results = await browser.storage.local.get(); + const keys = []; + for (const key in results) { + key.startsWith(prefix) && keys.push(key.slice(prefix.length)); + } + return keys; +} + +async function sendMessageProxy(message) { + try { + /** @type {{status: "fulfilled"|"rejected", result: any}} */ + const response = await browser.runtime.sendMessage(message); + if (response.status === "fulfilled") { + return response.result; + } else { + return Promise.reject(response.result); + } + } catch (error) { + console.error(error); + return Promise.reject(error); + } +} + +async function openInTab(url, openInBackground = false) { + try { + new URL(url); + } catch (error) { + return Promise.reject(error); + } + return sendMessageProxy({ + name: "API_OPEN_TAB", + url, + active: !openInBackground, + }); +} + +async function closeTab(tabId) { + return sendMessageProxy({ name: "API_CLOSE_TAB", tabId }); +} + +async function getTab() { + return sendMessageProxy({ name: "API_GET_TAB" }); +} + +async function saveTab(tabObj) { + if (tabObj == null) return Promise.reject(new Error("saveTab invalid arg")); + return sendMessageProxy({ name: "API_SAVE_TAB", tabObj }); +} + +async function addStyle(css) { + if (typeof css !== "string" || !css.length) { + return Promise.reject(new Error("addStyle invalid css arg")); + } + return sendMessageProxy({ name: "API_ADD_STYLE", css }); +} + +async function setClipboard(clipboardData, type) { + return sendMessageProxy({ + name: "API_SET_CLIPBOARD", + clipboardData, + type, + }); +} + +/** + * Restore `response.response` to required `responseType` + * @param {TypeExtMessages.XHRTransportableResponse} msgResponse + * @param {TypeExtMessages.XHRResponse} response + */ +function xhrResponseProcessor(msgResponse, response) { + const res = msgResponse; + /** + * only include responseXML when needed + * NOTE: Only add implementation at this time, not enable, to avoid + * unnecessary calculations, and this legacy default behavior is not + * recommended, users should explicitly use `responseType: "document"` + * to obtain it. + if (res.responseType === "" && typeof res.response === "string") { + const mimeTypes = [ + "text/xml", + "application/xml", + "application/xhtml+xml", + "image/svg+xml", + ]; + for (const mimeType of mimeTypes) { + if (res.contentType.includes(mimeType)) { + const parser = new DOMParser(); + res.responseXML = parser.parseFromString(res.response, "text/xml"); + break; + } + } + } + */ + if (res.responseType === "arraybuffer" && Array.isArray(res.response)) { + // arraybuffer responses had their data converted in background + // convert it back to arraybuffer + try { + response.response = new Uint8Array(res.response).buffer; + } catch (err) { + console.error("error parsing xhr arraybuffer", err); + } + } + if (res.responseType === "blob" && Array.isArray(res.response)) { + // blob responses had their data converted in background + // convert it back to blob + try { + const typedArray = new Uint8Array(res.response); + const type = res.contentType ?? ""; + response.response = new Blob([typedArray], { type }); + } catch (err) { + console.error("error parsing xhr blob", err); + } + } + if (res.responseType === "document" && typeof res.response === "string") { + // document responses had their data converted in background + // convert it back to document + try { + const parser = new DOMParser(); + const mimeType = res.contentType.includes("text/html") + ? "text/html" + : "text/xml"; + response.response = parser.parseFromString(res.response, mimeType); + response.responseXML = response.response; + } catch (err) { + console.error("error parsing xhr document", err); + } + } +} + +/** + * Process data into a transportable object + * @param {Parameters[0]} data + * @returns {Promise} + */ +async function xhrDataProcessor(data) { + if (typeof data === "undefined") return undefined; + if (typeof data === "string") { + return { data, type: "Text" }; + } + if (data instanceof Document) { + if (data instanceof XMLDocument) { + try { + return { + data: new XMLSerializer().serializeToString(data), + type: "Document", + mime: data.contentType || "text/xml", + }; + } catch (error) { + console.error( + "XML serialization failed, the data will be omitted", + error, + ); + } + } else { + let html = data.documentElement.outerHTML; + if (data.doctype) { + html = `` + html; + } + return { + data: html, + type: "Document", + mime: data.contentType || "text/html", + }; + } + } + if (data instanceof Blob) { + try { + const buffer = await data.arrayBuffer(); + return { + data: Array.from(new Uint8Array(buffer)), + type: "Blob", + mime: data.type, + }; + } catch (error) { + throw Error("Document serialization failed, the data will be omitted", { + cause: error, + }); + } + } + if (data instanceof ArrayBuffer) { + return { + data: Array.from(new Uint8Array(data)), + type: "ArrayBuffer", + }; + } + if (ArrayBuffer.isView(data)) { + return { + data: Array.from(new Uint8Array(data.buffer)), + type: "ArrayBufferView", + }; + } + if (data instanceof FormData) { + /** @type {TypeExtMessages.XHRProcessedFormData} */ + const entries = []; + for (const [k, v] of data.entries()) { + if (typeof v === "string") { + entries.push([k, v]); + continue; + } else { + const buffer = await v.arrayBuffer(); + entries.push([ + k, + { + data: Array.from(new Uint8Array(buffer)), + lastModified: v.lastModified, + name: v.name, + mime: v.type, + }, + ]); + } + } + return { data: entries, type: "FormData" }; + } + if (data instanceof URLSearchParams) { + return { data: data.toString(), type: "URLSearchParams" }; + } + throw Error("Unexpected data type, the data will be omitted"); +} + +/** + * @param {Object} details + * @param {Object} control + * @param {{resolve: Function, reject: Function}=} promise + * @returns {Promise} + */ +async function xhr(details, control, promise) { + if (details == null) return console.error("xhr invalid details arg"); + if (!details.url) return console.error("xhr details missing url key"); + // define control method, will be replaced after port is established + control.abort = () => console.error("xhr has not yet been initialized"); + // depreciation notice + if (details.binary) { + console.warn( + "Please make sure your xhr `data` is a binary-string since you have set the `binary` true, however this legacy format is no longer recommended.", + "The `binary` key is deprecated and will be removed in the future, use binary data objects such as `Blob`, `ArrayBuffer`, `TypedArray`, etc. instead.", + ); + } + // can not send details (func, blob, etc.) through message + // construct a new processed object send to background page + /** @type {TypeExtMessages.XHRTransportableDetails} */ + const detailsParsed = { + binary: Boolean(details.binary), + data: undefined, + headers: {}, + method: String(details.method), + overrideMimeType: String(details.overrideMimeType), + password: String(details.password), + responseType: details.responseType, + timeout: Number(details.timeout), + url: String(details.url), + user: String(details.user), + hasHandlers: {}, + hasUploadHandlers: {}, + }; + // preprocess data key + try { + detailsParsed.data = await xhrDataProcessor(details.data); + } catch (error) { + console.error(error); + } + // preprocess headers key + if (typeof details.headers === "object") { + for (const [k, v] of Object.entries(details.headers)) { + detailsParsed.headers[k.toLowerCase()] = v; + } + } + // preprocess handlers + /** + * Record the handlers existing in details to a new object + * to avoid modifying the original object, and to prevent + * the original object from being changed by user scripts + * @type {TypeExtMessages.XHRHandlersObj} + */ + const handlers = {}; + /** @type {TypeExtMessages.XHRHandlers} */ + const XHRHandlers = [ + "onreadystatechange", + "onloadstart", + "onprogress", + "onabort", + "onerror", + "onload", + "ontimeout", + "onloadend", + ]; + for (const handler of XHRHandlers) { + // check which handlers are included in the original details object + if ( + handler in XMLHttpRequest.prototype && + typeof details[handler] === "function" + ) { + // add a bool to indicate if event listeners should be attached + detailsParsed.hasHandlers[handler] = true; + // record to the new object + handlers[handler] = details[handler]; + } + } + // preprocess upload handlers + /** @type {TypeExtMessages.XHRUploadHandlersObj} */ + const uploadHandlers = {}; + /** @type {TypeExtMessages.XHRUploadHandlers} */ + const XHRUploadHandlers = [ + "onabort", + "onerror", + "onload", + "onloadend", + "onloadstart", + "onprogress", + "ontimeout", + ]; + if (typeof details.upload === "object") { + for (const handler of XHRUploadHandlers) { + if ( + handler in XMLHttpRequestEventTarget.prototype && + typeof details.upload[handler] === "function" + ) { + detailsParsed.hasUploadHandlers[handler] = true; + uploadHandlers[handler] = details.upload[handler]; + } + } + } + // resolving asynchronous xmlHttpRequest + if (promise) { + detailsParsed.hasHandlers.onloadend = true; + const _onloadend = handlers.onloadend; + handlers.onloadend = (response) => { + promise.resolve(response); + _onloadend?.(response); + }; + } + // make sure to listen to XHR.DONE events only once, to avoid processing + // and transmitting the same response data multiple times + if (detailsParsed.hasHandlers.onreadystatechange) { + delete detailsParsed.hasHandlers.onload; + delete detailsParsed.hasHandlers.onloadend; + } + if (detailsParsed.hasHandlers.onload) { + delete detailsParsed.hasHandlers.onloadend; + } + // generate random port name for single xhr + const xhrPortName = Math.random().toString(36).substring(1, 9); + /** + * port listener, most of the messaging logic goes here + * @type {Parameters[0]} + * @param {import("../global.d.ts").TypeContentScripts.XHRPort} port + */ + const listener = (port) => { + if (port.name !== xhrPortName) return; + // handle port messages + port.onMessage.addListener(async (msg) => { + const handler = msg.handler; + // handle upload progress + if ( + "progress" in msg && + detailsParsed.hasUploadHandlers[handler] && + typeof uploadHandlers[handler] === "function" + ) { + // call userscript handler + uploadHandlers[handler](msg.progress); + return; + } + // handle download events + if ( + "response" in msg && + detailsParsed.hasHandlers[handler] && + typeof handlers[handler] === "function" + ) { + // process xhr response + /** @type {TypeExtMessages.XHRTransportableResponse} */ + const msgResponse = msg.response; + /** @type {TypeExtMessages.XHRResponse} */ + const response = msgResponse; + // only include responseText when needed + if (["", "text"].includes(response.responseType)) { + response.responseText = response.response; + } + // only process when xhr is complete and data exist + if (response.readyState === 4 && response.response !== null) { + xhrResponseProcessor(msgResponse, response); + } + // call userscript handler + handlers[handler](response); + // call the deleted XHR.DONE handlers above + if (response.readyState === 4) { + if (handler === "onreadystatechange") { + if (typeof handlers.onload === "function") { + handlers.onload(response); + } + if (typeof handlers.onloadend === "function") { + handlers.onloadend(response); + } + } else if (handler === "onload") { + if (typeof handlers.onloadend === "function") { + handlers.onloadend(response); + } + } + } + } + // all messages received + if (handler === "onloadend") { + // tell background it's safe to close port + port.postMessage({ name: "DISCONNECT" }); + } + }); + // handle port disconnect and clean tasks + port.onDisconnect.addListener((p) => { + if (p?.error) { + console.error(`port disconnected due to an error: ${p.error.message}`); + } + browser.runtime.onConnect.removeListener(listener); + }); + // fill the method returned to the user script + control.abort = () => port.postMessage({ name: "ABORT" }); + }; + // wait for the background to establish a port connection + browser.runtime.onConnect.addListener(listener); + // pass the basic information to the background through a common message + const message = { + name: "API_XHR", + details: detailsParsed, + xhrPortName, + }; + sendMessageProxy(message); +} + +function xmlHttpRequest(details) { + let promise; + const control = new Promise((resolve, reject) => { + promise = { resolve, reject }; + }); + xhr(details, control, promise); + return control; +} + +function GM_xmlhttpRequest(details) { + const control = {}; + xhr(details, control); + return control; +} + +export default { + setValue, + getValue, + listValues, + deleteValue, + openInTab, + getTab, + saveTab, + closeTab, + addStyle, + setClipboard, + // notification, + // registerMenuCommand, + // getResourceUrl, + xmlHttpRequest, + GM_xmlhttpRequest, +}; diff --git a/src/ext/content-scripts/entry-dot-user-js.js b/src/ext/content-scripts/entry-dot-user-js.js new file mode 100644 index 00000000..66757ff0 --- /dev/null +++ b/src/ext/content-scripts/entry-dot-user-js.js @@ -0,0 +1,12 @@ +async function initialize() { + // avoid duplicate injection of content scripts + if (window["CS_ENTRY_DOT_USER_JS"]) return; + window["CS_ENTRY_DOT_USER_JS"] = 1; + // check user settings + const key = "US_AUGMENTED_USERJS_INSTALL"; + if ((await browser.storage.local.get(key))[key] === false) return; + // actual execution content + browser.runtime.sendMessage({ name: "WEB_USERJS_POPUP" }); +} + +initialize(); diff --git a/src/ext/content-scripts/entry-script-market.js b/src/ext/content-scripts/entry-script-market.js new file mode 100644 index 00000000..45ba17d6 --- /dev/null +++ b/src/ext/content-scripts/entry-script-market.js @@ -0,0 +1,63 @@ +let url; + +async function injection() { + const tabUrl = new URL(location.href); + let links; + if (tabUrl.hostname.endsWith("greasyfork.org")) { + links = document.querySelectorAll( + '#install-area a.install-link[data-install-format="js"]', + ); + } + for (const link of links) { + if (link["href"]) url = link["href"]; + link.addEventListener( + "click", + (e) => { + e.stopImmediatePropagation(); + e.preventDefault(); + browser.runtime.sendMessage({ name: "WEB_USERJS_POPUP" }); + }, + true, + ); + } +} + +async function listeners() { + /** + * handle messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = async (message) => { + if (import.meta.env.MODE === "development") { + console.debug(message, url); + } + if (message === "TAB_CLICK_USERJS") { + return url; + } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); + } + }); +} + +async function initialize() { + // avoid duplicate injection of content scripts + if (window["CS_ENTRY_SCRIPT_MARKET"]) return; + window["CS_ENTRY_SCRIPT_MARKET"] = 1; + // check user settings + const key = "US_AUGMENTED_USERJS_INSTALL"; + if ((await browser.storage.local.get(key))[key] === false) return; + // actual execution content + injection(); + listeners(); +} + +initialize(); diff --git a/src/ext/content-scripts/entry-userscripts.js b/src/ext/content-scripts/entry-userscripts.js new file mode 100644 index 00000000..362bfacb --- /dev/null +++ b/src/ext/content-scripts/entry-userscripts.js @@ -0,0 +1,294 @@ +import USAPI from "./api.js"; +import { colors } from "@shared/colors.js"; + +// code received from background page will be stored in this variable +// code referenced again when strict CSPs block initial injection attempt +let data; +// determines whether strict csp injection has already run (JS only) +let cspFallbackAttempted = false; + +// label used to distinguish frames in console +const label = randomLabel(); +const usTag = window.self === window.top ? "" : `(${label})`; + +function randomLabel() { + const a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const r = Math.random(); + return a[Math.floor(r * a.length)] + r.toString().slice(5, 6); +} + +function triageJS(userscript) { + const runAt = userscript.scriptObject["run-at"]; + if (runAt === "document-start") { + injectJS(userscript); + } else if (runAt === "document-end") { + if (document.readyState !== "loading") { + injectJS(userscript); + } else { + document.addEventListener( + "DOMContentLoaded", + () => injectJS(userscript), + { once: true }, + ); + } + } else if (runAt === "document-idle") { + if (document.readyState === "complete") { + injectJS(userscript); + } else { + const handle = () => { + if (document.readyState === "complete") { + injectJS(userscript); + document.removeEventListener("readystatechange", handle); + } + }; + document.addEventListener("readystatechange", handle); + } + } +} + +function injectJS(userscript) { + const filename = userscript.scriptObject.filename; + const name = userscript.scriptObject.name; + const code = `\ +(async () => { + try { +// ===UserScript===start=== +${userscript.code} +// ===UserScript====end==== + } catch (error) { + console.error(\`${filename.replaceAll("`", "\\`")}\`, error); + } +})(); //# sourceURL=${filename.replace(/[\s"']/g, "-") + usTag}`; + let injectInto = userscript.scriptObject["inject-into"]; + // change scope to content since strict CSP event detected + if (injectInto === "auto" && (userscript.fallback || cspFallbackAttempted)) { + injectInto = "content"; + console.warn(`Attempting fallback injection for ${name}`); + } + const world = injectInto === "content" ? "content" : "page"; + if (window.self === window.top) { + console.info(`Injecting: ${name} %c(js/${world})`, colors.yellow); + } else { + console.info( + `Injecting: ${name} %c(js/${world})%c - %cframe(${label})(${window.location})`, + colors.yellow, + colors.inherit, + colors.blue, + ); + } + if (world === "page") { + const div = document.createElement("div"); + div.style.display = "none"; + const shadowRoot = div.attachShadow({ mode: "closed" }); + const tag = document.createElement("script"); + tag.textContent = code; + shadowRoot.append(tag); + (document.body ?? document.head ?? document.documentElement).append(div); + } else { + try { + Function( + `{${Object.keys(userscript.apis).join(",")}}`, + code, + )(userscript.apis); + } catch (error) { + console.error(`${filename}`, error); + } + return; + } +} + +function injectCSS(name, code) { + if (window.self === window.top) { + console.info(`Injecting ${name} %c(css)`, colors.green); + } else { + console.info( + `Injecting ${name} %c(css)%c - %cframe(${label})(${window.location})`, + colors.green, + colors.inherit, + colors.blue, + ); + } + // Safari lacks full support for tabs.insertCSS + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/insertCSS + // specifically frameId and cssOrigin + // if support for those details keys arrives, the method below can be used + // NOTE: manifest V3 does support frameId, but not origin + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/insertCSS + + // write the css code to head of the document + const tag = document.createElement("style"); + tag.textContent = code; + document.head.appendChild(tag); +} + +function cspFallback(e) { + // if a security policy violation event has occurred + // and the directive is script-src or script-src-elem + // it's fair to assume that there is a strict CSP for javascript + // and that injection was blocked for all userscripts + // when any script-src violation is detected, re-attempt injection + if ( + e.effectiveDirective === "script-src" || + e.effectiveDirective === "script-src-elem" + ) { + // get all "auto" code + // since other code can trigger a security policy violation event + // make sure data var is not undefined before attempting fallback + if (!data || cspFallbackAttempted) return; + // update global that tracks security policy violations + cspFallbackAttempted = true; + // for all userscripts with @inject-into: auto, attempt re-injection + for (let i = 0; i < data.files.js.length; i++) { + const userscript = data.files.js[i]; + if (userscript.scriptObject["inject-into"] !== "auto") continue; + userscript.fallback = 1; + triageJS(userscript); + } + } +} + +async function injection() { + const response = await browser.runtime.sendMessage({ + name: "REQ_USERSCRIPTS", + }); + if (import.meta.env.MODE === "development") { + console.debug("REQ_USERSCRIPTS", response); + } + // cancel injection if errors detected + if (!response || response.error) { + console.error(response?.error || "REQ_USERSCRIPTS returned undefined"); + return; + } + // save response locally in case CSP events occur + data = response; + // combine regular and context-menu scripts + const scripts = [...data.files.js, ...data.files.menu]; + // loop through each userscript and prepare for processing + for (let i = 0; i < scripts.length; i++) { + const userscript = scripts[i]; + const filename = userscript.scriptObject.filename; + const grants = userscript.scriptObject.grant; + const injectInto = userscript.scriptObject["inject-into"]; + // create GM.info object, all userscripts get access to GM.info + userscript.apis = { GM: {} }; + userscript.apis.GM.info = { + script: userscript.scriptObject, + scriptHandler: data.scriptHandler, + scriptHandlerVersion: data.scriptHandlerVersion, + scriptMetaStr: userscript.scriptMetaStr, + version: data.scriptHandlerVersion, + }; + // add GM_info + userscript.apis.GM_info = userscript.apis.GM.info; + // if @grant explicitly set to none, empty grants array + if (grants.includes("none")) grants.length = 0; + // @grant values exist for page scoped userscript + if (grants.length && injectInto === "page") { + // remove grants + grants.length = 0; + // log warning + console.warn( + `${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`, + ); + } + // @grant exist for auto scoped userscript + if (grants.length && injectInto === "auto") { + // change scope + userscript.scriptObject["inject-into"] = "content"; + // log warning + console.warn( + `${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`, + ); + } + // loop through each userscript @grant value, add methods as needed + for (let j = 0; j < grants.length; j++) { + const grant = grants[j]; + const method = grant.startsWith("GM.") ? grant.slice(3) : grant; + // ensure API method exists in USAPI object + if (!Object.keys(USAPI).includes(method)) continue; + // add granted methods + switch (method) { + case "info": + case "GM_info": + continue; + case "getValue": + case "setValue": + case "deleteValue": + case "listValues": + userscript.apis.GM[method] = USAPI[method].bind({ + US_filename: filename, + }); + break; + case "GM_xmlhttpRequest": + userscript.apis[method] = USAPI[method]; + break; + default: + userscript.apis.GM[method] = USAPI[method]; + } + } + // triage userjs item for injection + triageJS(userscript); + } + // loop through each usercss and inject + for (let i = 0; i < data.files.css.length; i++) { + const userstyle = data.files.css[i]; + injectCSS(userstyle.name, userstyle.code); + } +} + +function listeners() { + /** listen for CSP violations */ + document.addEventListener("securitypolicyviolation", cspFallback, { + once: true, + }); + /** + * listens for messages from background, popup, etc... + * @type {import("webextension-polyfill").Runtime.OnMessageListener} + */ + const handleMessage = (message) => { + const name = message.name; + if (name === "CONTEXT_RUN") { + // from bg script when context-menu item is clicked + // double check to ensure context-menu scripts only run in top windows + if (window !== window.top) return; + // loop through context-menu scripts saved to data object and find match + // if no match found, nothing will execute and error will log + const filename = message.menuItemId; + for (let i = 0; i < data.files.menu.length; i++) { + const item = data.files.menu[i]; + if (item.scriptObject.filename === filename) { + console.info(`Injecting ${filename} %c(js)`, colors.yellow); + injectJS(item); + return; + } + } + console.error(`Couldn't find ${filename} code!`); + } + }; + /** Dynamically remove listeners to avoid memory leaks */ + if (document.visibilityState === "visible") { + browser.runtime.onMessage.addListener(handleMessage); + } + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + browser.runtime.onMessage.removeListener(handleMessage); + } else { + browser.runtime.onMessage.addListener(handleMessage); + } + }); +} + +async function initialize() { + // avoid duplicate injection of content scripts + if (window["CS_ENTRY_USERSCRIPTS"]) return; + window["CS_ENTRY_USERSCRIPTS"] = 1; + // check user settings + const key = "US_GLOBAL_ACTIVE"; + const results = await browser.storage.local.get(key); + if (results[key] === false) return console.info("Userscripts off"); + // start the injection process and add the listeners + injection(); + listeners(); +} + +initialize(); diff --git a/src/ext/extension-page/App.svelte b/src/ext/extension-page/App.svelte new file mode 100644 index 00000000..1c4ce4d3 --- /dev/null +++ b/src/ext/extension-page/App.svelte @@ -0,0 +1,214 @@ + + + + +{#if $v4state.includes("init")} +
+ + {#if $v4state.includes("init-error")} + Failed to initialize app, check the browser console + {:else} + Initializing app... + {/if} +
+{/if} +
+ {#if !sidebarHidden} + +
+ {/if} + +
+{#if splitterActive} +
+{/if} +
    + {#each $notifications as item (item.id)} + notifications.remove(item.id)} {item} /> + {/each} +
+{#if $v4state.includes("settings")} + v4state.remove("settings")} let:navRegister> + + +{/if} + + diff --git a/src/ext/extension-page/Appios.svelte b/src/ext/extension-page/Appios.svelte new file mode 100644 index 00000000..048def7d --- /dev/null +++ b/src/ext/extension-page/Appios.svelte @@ -0,0 +1,88 @@ + + +{#if $v4state.includes("init")} +
+ + {#if $v4state.includes("init-error")} + Failed to initialize app, check the browser console + {:else} + Initializing app... + {/if} +
+{/if} +
    + {#each $notifications as item (item.id)} + notifications.remove(item.id)} {item} /> + {/each} +
+{#if $v4state.includes("settings")} + +{/if} + + diff --git a/src/ext/extension-page/Components/Editor/CodeMirror.svelte b/src/ext/extension-page/Components/Editor/CodeMirror.svelte new file mode 100644 index 00000000..b2cbacda --- /dev/null +++ b/src/ext/extension-page/Components/Editor/CodeMirror.svelte @@ -0,0 +1,436 @@ + + + + + + +{#if initialized} + (searchActive = false)} + {instance} + /> +{/if} + + diff --git a/src/ext/extension-page/Components/Editor/Editor.svelte b/src/ext/extension-page/Components/Editor/Editor.svelte new file mode 100644 index 00000000..e0b7c719 --- /dev/null +++ b/src/ext/extension-page/Components/Editor/Editor.svelte @@ -0,0 +1,358 @@ + + +
+ {#if $v4state.includes("editor-loading") || $v4state.includes("fetching")} + + {/if} + {#if !activeItem} +
No Item Selected
+ {/if} +
+ {#if sidebarHidden} + + {/if} +
+
+ +
{name}
+
+
+
+ {#if $v4state.includes("saving")} + Saving... + {:else if $v4state.includes("trashing")} + (◞‸◟) + {:else if $v4state.includes("updating")} + Updating code, + {:else if remote} + Code was remotely fetched, + check carefully before saving! + {:else if temp} + Ready for code! + {:else} + Last modified: {lastModified} + {/if} +
+
+
+
+ + + +
+
+
+ +
+ +
+ + diff --git a/src/ext/extension-page/Components/Editor/EditorSearch.svelte b/src/ext/extension-page/Components/Editor/EditorSearch.svelte new file mode 100644 index 00000000..6c76e4a6 --- /dev/null +++ b/src/ext/extension-page/Components/Editor/EditorSearch.svelte @@ -0,0 +1,203 @@ + + +{#if active} + +{/if} + + diff --git a/src/ext/extension-page/Components/ModalWrapper.svelte b/src/ext/extension-page/Components/ModalWrapper.svelte new file mode 100644 index 00000000..9cb4c5aa --- /dev/null +++ b/src/ext/extension-page/Components/ModalWrapper.svelte @@ -0,0 +1,150 @@ + + +
+ + + +
+ +
+ + diff --git a/src/ext/extension-page/Components/Notification.svelte b/src/ext/extension-page/Components/Notification.svelte new file mode 100644 index 00000000..afdb56b9 --- /dev/null +++ b/src/ext/extension-page/Components/Notification.svelte @@ -0,0 +1,154 @@ + + +
  • + +
    + +
    {@html icon}
    + {item.message} + +
    +
  • + + diff --git a/src/ext/extension-page/Components/Settings.svelte b/src/ext/extension-page/Components/Settings.svelte new file mode 100644 index 00000000..8ef12c4c --- /dev/null +++ b/src/ext/extension-page/Components/Settings.svelte @@ -0,0 +1,845 @@ + + +
    + {#each groups as group (group)} +
    +
    +
    {gl(`settings_section_${group}`)}
    + {#if indicators.resetting} + + {/if} +
    + {#each groupItems(group) as item (item.name)} + {@const ariaAttributes = { + "aria-labelledby": `${item.name}_label`, + "aria-describedby": `${item.name}_desc`, + }} +
    +
    +
    + {gl(`settings_${item.name}`)} +
    +
    + {gl(`settings_${item.name}_desc`)} +
    +
    + {#if item.nodeType === "Toggle"} + + settings.updateSingleSetting( + item.name, + !$settings[item.name], + )} + /> + {/if} + {#if item.nodeType === "select"} + + {/if} +
    +
    + {#if indicators.resetting && !indicators.saving[item.name] && !item.protect && (item.name !== "global_exclude_match" || !gemFocused)} + + {/if} + {#if item.nodeType === "textarea"} + {#if indicators.saving[item.name]} +
    + + {@html iconLoader} + {gl(`settings_${item.name}_saving`)} +
    + {/if} + {#if gemFocused} + + + {/if} + {/if} +
    +
    + {#if item.nodeType === "textarea" && item.name === "global_exclude_match"} +
    + +
    + {#each gemParsed.items as p (p)} + + {p.start}{#if p.warn || p.error}{p.value}{:else}{p.value}{/if}{p.separ} + {/each} +
    +
    +
    + {gl(`settings_${item.name}_refer`)} + +
    + {/if} +
    + {/each} +
    + {/each} +
    +
    +
    {gl(`settings_section_native`)}
    +
    +
    +
    +
    {gl("settings_scripts_directory")}
    +
    {gl("settings_scripts_directory_desc")}
    +
    +
    + {#if indicators.loading.changeSaveLocation} + + {@html iconLoader} + {/if} + +
    +
    +
    + +
    +
    +
    +
    +
    {gl(`settings_section_tools`)}
    +
    +
    +
    + {#if indicators.resetting} + + + {:else} + + + + {/if} +
    + +
    {gl("settings_scripts_tools_desc")}
    +
    +
    +
    +
    + {gl(`settings_section_about`)} +
    +
    +
    +

    + Userscripts {import.meta.env.BROWSER ?? ""} + {#if import.meta.env.GIT_TAG && import.meta.env.GIT_COMMIT} + + () + {:else} + v{$settings["version"]} + ({$settings["build"]}) + {/if} +

    +

    + {gl("settings_about_text1")} + + | + + | + +

    +

    + {gl("settings_about_text2")} + + | + +

    +
    +
    +
    +
    + + diff --git a/src/ext/extension-page/Components/Sidebar/Sidebar.svelte b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte new file mode 100644 index 00000000..c5af6b55 --- /dev/null +++ b/src/ext/extension-page/Components/Sidebar/Sidebar.svelte @@ -0,0 +1,307 @@ + + + + + diff --git a/src/ext/extension-page/Components/Sidebar/SidebarFilter.svelte b/src/ext/extension-page/Components/Sidebar/SidebarFilter.svelte new file mode 100644 index 00000000..d63e8048 --- /dev/null +++ b/src/ext/extension-page/Components/Sidebar/SidebarFilter.svelte @@ -0,0 +1,116 @@ + + +
    + + + {#each orders as order (order)} + + {/each} + + {#if query} + (query = "")} + {disabled} + title="Clear" + /> + {/if} +
    + + diff --git a/src/ext/extension-page/Components/Sidebar/SidebarItem.svelte b/src/ext/extension-page/Components/Sidebar/SidebarItem.svelte new file mode 100644 index 00000000..c7c27bc7 --- /dev/null +++ b/src/ext/extension-page/Components/Sidebar/SidebarItem.svelte @@ -0,0 +1,104 @@ + + +
    +
    + +
    {data.name}
    + +
    + {#if description} +
    + {description} +
    + {/if} +
    + + diff --git a/src/ext/extension-page/app.css b/src/ext/extension-page/app.css new file mode 100644 index 00000000..4ae66302 --- /dev/null +++ b/src/ext/extension-page/app.css @@ -0,0 +1,81 @@ +html { + font-size: 100%; + height: 100vh; + height: 100svh; /* safari 15.4 */ + overflow: hidden; + overscroll-behavior: none; +} + +/* ios */ +@supports (-webkit-touch-callout: none) { + html { + height: auto; + overflow: visible; + } + + /** + * Dynamic Type + * https://support.apple.com/102453 + * https://webkit.org/blog/3709/using-the-system-font-in-web-content/ + * https://developer.apple.com/library/archive/releasenotes/General/WhatsNewInSafari/Articles/Safari_7_0.html#//apple_ref/doc/uid/TP40014305-CH5-SW10 + * https://github.com/w3c/csswg-drafts/issues/3708 + */ + @supports (font: -apple-system-body) { + html { + /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ + font: -apple-system-body; /* default 17px */ + } + } + + /* Avoid page zoom */ + input, + textarea, + select { + font-size: max(1rem, 16px); + } +} + +body { + background-color: var(--color-bg-primary); + color: var(--text-color-primary); + font: var(--text-default); + height: 100%; + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; +} + +noscript { + display: block; +} + +#app { + display: flex; + flex-direction: column; + height: 100%; +} + +a, +.link { + background: none; + color: var(--color-blue); + cursor: pointer; + text-decoration: underline; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +button { + border: none; + cursor: pointer; + padding: 0; + user-select: none; +} + +button:disabled { + opacity: var(--opacity-disabled); + pointer-events: none; +} diff --git a/src/ext/extension-page/codemirror.css b/src/ext/extension-page/codemirror.css new file mode 100644 index 00000000..bd3e5c40 --- /dev/null +++ b/src/ext/extension-page/codemirror.css @@ -0,0 +1,214 @@ +.CodeMirror { + background-color: transparent; + color: var(--editor-default); + font-family: var(--editor-font); + font-size: var(--editor-font-size); + font-weight: 400; + height: 100%; + line-height: var(--editor-line-height); + text-rendering: unset; + -webkit-font-smoothing: antialiased; +} + +.CodeMirror-gutters { + background-color: transparent; + border-right: 0; +} + +.CodeMirror-activeline-background, +.CodeMirror-activeline-gutter { + background-color: var(--editor-active-line); +} + +.CodeMirror-linenumber { + color: var(--text-color-primary); + font-size: var(--editor-font-size); + opacity: 0.25; +} + +.CodeMirror-activeline .CodeMirror-linenumber { + opacity: 0.5; +} + +.CodeMirror-cursor { + border-left: 2px solid var(--text-color-secondary); /* check */ +} + +.CodeMirror-hints { + background-color: var(--color-bg-theme); + border: 1px solid var(--border-color); + box-shadow: var(--box-shadow); + font: var(--text-small); + font-family: var(--editor-font); + line-height: 1.5rem; +} + +.CodeMirror-hint { + color: var(--text-color-secondary); +} + +li.CodeMirror-hint-active { + background-color: var(--color-blue); + color: light-dark(var(--color-white), var(--color-black)); +} + +.cm-s-default .CodeMirror-selected { + background-color: var(--editor-selected-bg); +} + +div.CodeMirror span.CodeMirror-matchingbracket { + color: var(--editor-matching-bracket-color); + border-bottom: 2px solid var(--editor-matching-bracket-border); +} + +div.CodeMirror span.CodeMirror-nonmatchingbracket { + color: var(--editor-non-matching-bracket); +} + +.CodeMirror .CodeMirror-placeholder, +.cm-s-default .cm-comment { + color: var(--editor-comment); +} + +.cm-s-default .cm-keyword { + color: var(--editor-keyword); +} + +.cm-s-default .cm-def { + color: var(--editor-def); +} + +.cm-s-default .cm-variable, +.cm-s-default .cm-variable-2 { + color: inherit; +} + +.cm-s-default .cm-operator { + color: var(--editor-operator); +} + +.cm-s-default .cm-property { + color: var(--editor-property); +} + +.cm-s-default .cm-string { + color: var(--editor-string); +} + +.cm-s-default .cm-string-2 { + color: var(--editor-string-2); +} + +.cm-s-default .cm-number { + color: var(--editor-number); +} + +.cm-s-default .cm-atom { + color: var(--editor-atom); +} + +.cm-s-default .cm-error { + color: var(--color-red); +} + +.CodeMirror .cm-whitespace::before, +.CodeMirror .CodeMirror-line > span::after, +.CodeMirror .CodeMirror-code > div > pre > span::after { + color: var(--editor-invisible); +} + +.cm-s-default .cm-variable-3, +.cm-s-default .cm-builtin, +.cm-s-default .cm-qualifier, +.cm-s-default .cm-tag { + color: var(--editor-def); +} + +.cm-s-default .cm-searching { + background-color: var(--editor-search-highlight); + padding: 3px 0; +} + +.cm-s-default .cm-search-mark { + border-bottom: 2px solid var(--editor-matching-bracket-border); +} + +/* +.cm-s-default .cm-matchhighlight { + background-color: var(--editor-matched-highlight); +} +*/ + +.CodeMirror-lint-markers { + width: 8px; +} + +.CodeMirror-lint-marker { + border-radius: 50%; + cursor: default; + height: 0.375rem; + left: 0.25rem; + position: relative; + width: 0.375rem; +} + +.CodeMirror-lint-marker-error, +.CodeMirror-lint-marker-warning { + background: var(--color-yellow); + top: -1px; +} + +.CodeMirror-lint-marker-error { + background-color: var(--editor-error); +} + +.CodeMirror-lint-marker-multiple { + background: var(--editor-def); + left: 0; + position: absolute; + top: 0; +} + +.CodeMirror-lint-mark-warning { + background: none; + border-bottom: 2px solid var(--color-yellow); +} + +.CodeMirror-lint-mark-error { + background: none; + border-bottom: 2px solid var(--editor-error); +} + +.CodeMirror-lint-tooltip { + background-color: var(--color-bg-theme); + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + color: var(--text-color-secondary); + font: var(--text-small); + padding: 0.5rem; + transition: opacity 200ms; +} + +.CodeMirror-lint-message-error, +.CodeMirror-lint-message-warning { + background: none; + padding-left: 1rem; + position: relative; +} + +.CodeMirror-lint-message-error::before, +.CodeMirror-lint-message-warning::before { + background-color: var(--color-yellow); + border-radius: 50%; + content: ""; + height: 0.5rem; + left: 0; + position: absolute; + top: 0.25rem; + width: 0.5rem; +} + +.CodeMirror-lint-message-error::before { + background-color: var(--editor-error); +} diff --git a/src/page/codemirror.js b/src/ext/extension-page/codemirror.js similarity index 100% rename from src/page/codemirror.js rename to src/ext/extension-page/codemirror.js diff --git a/src/ext/extension-page/main.js b/src/ext/extension-page/main.js new file mode 100644 index 00000000..fd0a7abe --- /dev/null +++ b/src/ext/extension-page/main.js @@ -0,0 +1,64 @@ +import "@shared/reset.css"; +import "@shared/variables.css"; +import "./app.css"; +import { mount } from "svelte"; +import App from "./App.svelte"; +import Appios from "./Appios.svelte"; + +// vite feat that only import in dev mode +if (import.meta.env.MODE === "development") { + try { + const modules = import.meta.glob("@ext/dev.js", { eager: true }); + const browser = modules["/src/ext/shared/dev.js"]["browser"]; + console.debug("DEV-ENV", import.meta.env, modules, browser); + // basic dev mode relies on extension environment simulation + if (!window?.browser?.extension && browser) { + // assign to window simulation WebExtension APIs + window.browser = browser; + // pre-fetch i18n resource registration to window + const lang = window.navigator.language.replace("-", "_"); + let url = "/public/ext/shared/_locales/en/messages.json"; + if (lang.startsWith("zh")) { + url = "/public/ext/shared/_locales/zh/messages.json"; + } + // demo build for non-extension environment (gh-pages) + if (import.meta.env.EXT_DEMO_BUILD) { + url = "/_locales/en/messages.json"; + if (["zh_HK", "zh_MO", "zh_TW"].includes(lang)) { + url = `/_locales/${lang}/messages.json`; + } else if (lang.startsWith("zh")) { + url = `/_locales/zh/messages.json`; + } + } + try { + const response = await fetch(url); + const messages = await response.json(); + window["i18nMessages"] = messages; + } catch (error) { + console.error("Fetch i18n faild", error); + } + } + } catch (error) { + console.error(error); + } +} + +let app; +const target = document.getElementById("app"); +if (import.meta.env.MODE === "development") { + const platform = await browser.runtime.getPlatformInfo(); + // @ts-ignore -- incomplete polyfill types + if (platform.os === "ios") { + app = mount(Appios, { target }); + } else { + app = mount(App, { target }); + } +} else { + if (import.meta.env.SAFARI_PLATFORM === "ios") { + app = mount(Appios, { target }); + } else { + app = mount(App, { target }); + } +} + +export default app; diff --git a/src/ext/extension-page/store.js b/src/ext/extension-page/store.js new file mode 100644 index 00000000..0b2959c2 --- /dev/null +++ b/src/ext/extension-page/store.js @@ -0,0 +1,176 @@ +import { writable } from "svelte/store"; +import { uniqueId, contentScriptRegistration } from "@ext/utils.js"; +import * as settingsStorage from "@ext/settings.js"; +import { sendNativeMessage } from "@ext/native.js"; + +function notificationStore() { + const { subscribe, update } = writable([]); + const add = (item) => { + update((a) => { + a.push(item); + return a; + }); + }; + const remove = (id) => update((a) => a.filter((b) => b.id !== id)); + return { subscribe, add, remove }; +} +export const notifications = notificationStore(); + +function logStore() { + const { subscribe, set, update } = writable([]); + const add = (message, type, notify) => { + const item = { id: uniqueId(), message, time: Date.now(), type }; + if (notify || type === "error") notifications.add(item); // always notify on error + update((a) => { + a.push(item); + return a; + }); + }; + const remove = (id) => update((a) => a.filter((b) => b.id !== id)); + const reset = () => set([]); + return { subscribe, add, remove, reset }; +} +export const log = logStore(); + +function stateStore() { + const { subscribe, update } = writable(["init"]); + // store oldState to see how state transitioned + // ex. if (newState === foo && oldState === bar) baz(); + let oldState = []; + const add = (stateModifier) => { + update((state) => { + // list of acceptable states, mostly for state definition tracking + const states = [ + "init", // the uninitialized app state (start screen) + "init-error", // unique error when initialization fails, shows error on load screen + "settings", // when the settings modal is shown + "items-loading", // when the sidebar items are loading + "saving", // when a file in the editor is being saved + "fetching", // when a new remote file has been added and it being fetched + "trashing", // when deleting the file in the editor + "updating", // when the file in the editor is being updating + ]; + // disallow adding undefined states + if (!states.includes(stateModifier)) { + console.error("invalid state"); + return state; + } + // save pre-changed state to oldState var + oldState = [...state]; + // if current state modifier not present, add it to state array + if (!state.includes(stateModifier)) state.push(stateModifier); + // ready state only when no other states present, remove it if present + if (state.includes("ready")) state.splice(state.indexOf("ready"), 1); + log.add( + `App state updated to: ${state} from: ${oldState}`, + "info", + false, + ); + return state; + }); + // URL hash handle + const params = new URLSearchParams(location.hash.slice(1)); + if (["settings"].includes(stateModifier)) { + params.set("state", stateModifier); + location.hash = params.toString(); + } + }; + const remove = (stateModifier) => { + update((state) => { + // save pre-changed state to oldState var + oldState = [...state]; + // if current state modifier present, remove it from state array + if (state.includes(stateModifier)) + state.splice(state.indexOf(stateModifier), 1); + // if no other states, push ready state + if (state.length === 0) state.push("ready"); + log.add( + `App state updated to: ${state} from: ${oldState}`, + "info", + false, + ); + return state; + }); + // URL hash handle + const params = new URLSearchParams(location.hash.slice(1)); + const state = params.get("state"); + if (state === stateModifier) { + params.delete("state"); + location.hash = params.toString(); + } + }; + const getOldState = () => oldState; + // URL hash handle + const loadUrlState = () => { + const params = new URLSearchParams(location.hash.slice(1)); + const state = params.get("state"); + state && add(state); + }; + return { subscribe, add, getOldState, remove, loadUrlState }; +} +export const v4state = stateStore(); + +function settingsStore() { + const { subscribe, update, set } = writable({}); + let platform; + const init = async (initData) => { + platform = initData.platform; + if (import.meta.env.SAFARI_PLATFORM === "mac") { + // import legacy settings data just one-time + await settingsStorage.legacyImport(); + } + // read settings from persistence storage + const settings = await settingsStorage.get(undefined, { platform }); + if (import.meta.env.MODE === "development") { + console.debug("store.js settingsStore init", initData, settings); + } + set({ ...initData, ...settings }); + // sync popup, backgound, etc... settings changes + settingsStorage.onChangedSettings((sets, area) => { + console.info(`store.js storage.${area}.onChanged`, sets); + update((obj) => Object.assign(obj, sets)); + }); + }; + /** @param {string|string[]} keys */ + const reset = async (keys = undefined) => { + await settingsStorage.reset(keys); + const settings = await settingsStorage.get(undefined, { platform }); + if (import.meta.env.MODE === "development") { + console.debug("store.js settingsStore reset", settings); + } + update((obj) => Object.assign(obj, settings)); + // legacy updates + ["global_active", "global_exclude_match"].forEach((s) => { + updateSingleSettingOld(s, settings[s]); + }); + }; + // temporarily keep the old storage method until it is confirmed that all dependencies are removed + const updateSingleSettingOld = (key, value) => { + // following settings logics is still handled in the native swift layer + if (key === "global_exclude_match") { + const message = { name: "PAGE_UPDATE_BLACKLIST", blacklist: value }; + sendNativeMessage(message).then((response) => { + response.error && log.add(response.error, "error", true); + }); + } + if (key === "global_active") { + sendNativeMessage({ name: "TOGGLE_EXTENSION", active: String(value) }); + } + }; + /** @param {string} key @param {any} value */ + const updateSingleSetting = (key, value) => { + update((settings) => ((settings[key] = value), settings)); + // save settings to persistence storage + settingsStorage.set({ [key]: value }); + // make specific changes take effect + if (key === "augmented_userjs_install") { + contentScriptRegistration(value); + } + // legacy updates + updateSingleSettingOld(key, value); + }; + return { subscribe, set, init, reset, updateSingleSetting }; +} +export const settings = settingsStore(); + +export const items = writable([]); diff --git a/src/ext/global.d.ts b/src/ext/global.d.ts new file mode 100644 index 00000000..8b626e08 --- /dev/null +++ b/src/ext/global.d.ts @@ -0,0 +1,41 @@ +// https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html + +/// +/// + +import type Browser from "webextension-polyfill"; + +declare global { + const browser: Browser.Browser; + interface Window { + browser: Browser.Browser; + } +} + +declare namespace TypeBackground { + interface XHRMessage { + handler: string; + progress?: TypeExtMessages.XHRProgress; + response?: TypeExtMessages.XHRTransportableResponse; + } + + interface XHRPort extends Browser.Runtime.Port { + onMessage: Browser.Events.Event< + (message: TypeContentScripts.XHRMessage, port: XHRPort) => void + >; + postMessage: (message: XHRMessage) => void; + } +} + +declare namespace TypeContentScripts { + interface XHRMessage { + name: string; + } + + interface XHRPort extends Browser.Runtime.Port { + onMessage: Browser.Events.Event< + (message: TypeBackground.XHRMessage, port: XHRPort) => void + >; + postMessage(message: XHRMessage): void; + } +} diff --git a/src/ext/jsconfig.json b/src/ext/jsconfig.json new file mode 100644 index 00000000..26d5891a --- /dev/null +++ b/src/ext/jsconfig.json @@ -0,0 +1,31 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true, + + "paths": { + "@shared/*": ["../shared/*"], + "@ext/*": ["./shared/*"] + } + }, + "include": ["**/*.d.ts", "**/*.js", "**/*.svelte"] +} diff --git a/src/ext/shared/dev.js b/src/ext/shared/dev.js new file mode 100644 index 00000000..23633a5e --- /dev/null +++ b/src/ext/shared/dev.js @@ -0,0 +1,533 @@ +import { parse, uniqueId, wait } from "./utils"; + +/** + * + * @param {("css"|"js")} type + * @param {boolean?} updates + * @param {boolean?} longName + * @returns + */ +function generateFile( + type, + updates = false, + longName = false, + request = false, +) { + let canUpdate = updates; + const uid = uniqueId(); + let name = request ? `${uid}-request-${type}` : `${uid}-example-${type}`; + if (longName) name = `${uid}${uid}${uid}-example-${type}`; + const randomDate = +( + Number(new Date()) - Math.floor(Math.random() * 10000000000) + ); + let content = + "// ==UserScript==" + + `\n// @name ${name}` + + `\n// @description Custom description for userscript with name "${name}"` + + "\n// @match *://*.*" + + "\n// @exclude-match https://github.com/quoid/userscripts" + + "\n// @version 1.0" + + "\n// @noframes" + + "\n// ==/UserScript==" + + `\n\nconsole.log("I am ${name}");`; + if (type === "css") { + content = content.replace("// ==UserScript==", "/* ==UserStyle=="); + content = content.replace("// ==/UserScript==", "==/UserStyle== */"); + content = content.replaceAll("// @", "@"); + content = content.replace( + `console.log("I am ${name}");`, + "#id,\n.class\n.pseudo-element::after {\n color: red\n}", + ); + canUpdate = false; + } + if (request) { + content = content.replace( + "// @noframes", + "// @noframes\n// @run-at request", + ); + } + if (canUpdate) { + content = content.replace( + "1.0", + "1.0\n// @updateURL https://www.k21p.com/example.user.js", + ); + content = content.replace( + `console.log("I am ${name}");`, + `console.log("I am ${name} and you can update me!");`, + ); + } + return { + content, + filename: `${name}.${type}`, + lastModified: randomDate, + name, + request, + type, + }; +} + +/** + * @typedef {Object} File + * @property {string} content - ... + * @property {string} filename - ... + * @property {number} lastModified - ... + * @property {string} [name] - ... + * @property {boolean} [request] - ... + * @property {"css" | "js"} [type] - ... + */ +/** @type {File[]} */ +const files = [ + generateFile("js", true, true), + generateFile("css"), + generateFile("js", false, false, true), + ...Array.from({ length: 7 }, () => generateFile("js")), +]; + +/** @type {"ios"|"macos"} */ +const platform = "macos"; + +const _browser = { + delay: 200, + platform, + runtime: { + getURL() { + return "https://www.example.com/"; + }, + async getPlatformInfo() { + return { os: platform }; + }, + async sendMessage(message, responseCallback) { + const name = message.name; + console.info(`Got message: ${name}`); + let response = {}; + if (name === "REFRESH_DNR_RULES") { + response = { success: true }; + } + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + async sendNativeMessage(application, message, responseCallback) { + const name = message.name; + console.info(`Got message: ${name}`); + /** @type {any} */ + let response = {}; + if (name === "PAGE_INIT_DATA") { + response = { + saveLocation: "/Users/someone/Library/Directory", + version: "5.0.0", + build: "55", + }; + } else if (name === "PAGE_LEGACY_IMPORT") { + response = { + active: "true", + autoCloseBrackets: "true", + autoHint: "true", + blacklist: [], + descriptions: "true", + languageCode: "en", + lint: "true", + log: "false", + showCount: "true", + showInvisibles: "true", + sortOrder: "lastModifiedDesc", + tabSize: "4", + }; + } else if (name === "PAGE_ALL_FILES") { + response = []; + files.forEach((file) => { + const content = file.content; + const parsed = parse(content); + const metadata = parsed.metadata; + const canUpdate = !!(metadata.version && metadata.updateURL); + const scriptData = { + canUpdate, + content, + description: metadata.description ? metadata.description[0] : "", + disabled: false, + filename: file.filename, + lastModified: file.lastModified, + metadata, + name: metadata.name[0], + request: file.request, + // type: file.filename.substring(file.filename.lastIndexOf(".") + 1) + type: file.type, + }; + response.push(scriptData); + }); + } else if (name === "TOGGLE_ITEM") { + response = { success: true }; + // response = {error: true}; + } else if (name === "PAGE_UPDATE_SETTINGS") { + response = { success: true }; + } else if (name === "PAGE_UPDATE_BLACKLIST") { + response = { success: true }; + } else if (name === "PAGE_NEW_REMOTE") { + const result = await getRemoteFileContents(message.url); + response = result; + } else if (name === "PAGE_SAVE") { + const newContent = message.content; + const oldFilename = message.item.filename; + const parsed = parse(newContent); + const lastModified = Date.now(); + let canUpdate = false; + // script failed to parse + if (!parsed) { + return { error: "save failed, file has invalid metadata" }; + } + const metaName = parsed.metadata.name[0]; + const newFilename = `${metaName}.${message.item.type}`; + // filename length too long + if (newFilename.length > 255) { + return { error: "save failed, filename too long!" }; + } + + // check if file can be remotely updated + if (parsed.metadata.version && parsed.metadata.updateURL) { + canUpdate = true; + } + + const success = { + canUpdate, + content: newContent, + filename: newFilename, + lastModified, + name, + }; + + // add description if in file metadata + if (parsed.metadata.description) { + // const foo = parsed.metadata; + success.description = parsed.metadata.description[0]; + } + // check if declarative network request + if ( + parsed.metadata["run-at"] && + parsed.metadata["run-at"][0] === "request" + ) { + success.request = true; + } + // overwriting + if (newFilename.toLowerCase() === oldFilename.toLowerCase()) { + saveFile(newContent, lastModified, newFilename, oldFilename); + return success; + } + + // not overwriting, check if filename for that type is taken + if ( + files.find( + (a) => a.filename.toLowerCase() === newFilename.toLowerCase(), + ) + ) { + return { error: "save failed, name already taken!" }; + } + + // not overwriting but all validation passed + saveFile(newContent, lastModified, newFilename, oldFilename); + return success; + } else if (name === "PAGE_UPDATE") { + const url = parse(message.content).metadata.updateURL; + await wait(500); + const result = await getRemoteFileContents(url); + response = result; + // response.error = "Something went wrong!"; + // response.info = "No updates found"; + } else if (name === "TOGGLE_EXTENSION") { + // response = {error: "Failed toggle extension"}; + response = { success: true }; + } else if ( + name === "POPUP_UPDATE_ALL" || + name === "POPUP_UPDATE_SINGLE" + ) { + // response = {error: "Failed refresh scripts"}; + response = { + items: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js", + }, + { + name: "Wikipedia Mobile Redirect", + filename: "Wikipedia Mobile Redirect.js", + disabled: true, + type: "js", + }, + { + name: "A Special Userscript", + filename: "A Special Userscript.js", + disabled: false, + type: "js", + }, + { + name: "CSS Adblock", + filename: "CSS Adblock.css", + disabled: false, + type: "css", + }, + { + name: "New Userscript With a Really Really Long Name", + filename: "New Userscript With a Really Really Long Name.css", + disabled: true, + type: "css", + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + subframe: true, + type: "css", + }, + ], + updates: [], + }; + } else if (name === "POPUP_CHECK_UPDATES") { + response = { + updates: [ + { + filename: "Google Images Restored.js", + name: "Google Images Restored", + url: "https://www.k21p.com", + }, + { + filename: "New Userscript With a Really Really Long Name.js", + name: "New Userscript With a Really Really Long Name", + url: "https://www.filmgarb.com", + }, + ], + }; + } else if (name === "POPUP_MATCHES") { + response = { + matches: [ + ...files, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + subframe: true, + type: "js", + }, + ], + }; + } else if (name === "POPUP_UPDATES") { + response = { + updates: [ + { + filename: "Google Images Restored.js", + name: "Google Images Restored", + url: "https://www.k21p.com", + }, + { + filename: "New Userscript With a Really Really Long Name.js", + name: "New Userscript With a Really Really Long Name", + url: "https://www.filmgarb.com", + }, + ], + }; + response.updates = []; + } else if (name === "REQ_PLATFORM") { + response = { platform: _browser.platform }; + } else if (name === "POPUP_OPEN_EXTENSION_PAGE") { + response = { error: "Failed to get page url" }; + window.open("https://github.com/quoid/userscripts"); + } else if (name === "OPEN_SAVE_LOCATION") { + if (_browser.platform === "macos") { + response = { success: true }; + } else { + response = { + items: [ + { + name: "Google Images Restored", + filename: "Google Images Restored.js", + disabled: false, + type: "js", + metadata: [], + }, + { + name: "Subframe Script Managerial Staffing Company", + filename: "Subframe Script.js", + disabled: false, + type: "css", + metadata: [], + }, + { + name: "Another Script from Managerial Staffing Company", + filename: "Cool Script.js", + disabled: false, + type: "js", + metadata: [], + }, + ], + }; + } + } else if (name === "POPUP_INSTALL_CHECK") { + response = random([ + { success: "Click to install", installed: false }, + { success: "Click to re-install", installed: true }, + ]); + response.metadata = { + description: + 'This userscript re-implements the "View Image" and "Search by image" buttons into google images.', + grant: ["GM.getValue", "GM.setValue", "GM.xmlHttpRequest"], + match: [ + "https://www.example.com/*", + "https://www.example.com/somethingReallylong/goesRightHere", + ], + name: "Test Install Userscript", + require: [ + "https://code.jquery.com/jquery-3.5.1.min.js", + "https://code.jquery.com/jquery-1.7.1.min.js", + ], + source: + "https://greasyforx.org/scripts/00000-something-something-long-name/code/Something%20something%20long20name.user.js", + }; + // response.error = "something went wrong (dev)"; + } + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + connectNative: () => ({ + onMessage: { + addListener: () => console.info("connectNative - addListener"), + }, + }), + }, + tabs: { + getCurrent(/* responseCallback */) { + const response = random([ + { url: "https://www.filmgarb.com/foo.user.js", id: 101 }, + { + url: `${window.location.origin}/src/ext/shared/dev/DEMO.Alert-URL.user.js`, + id: 102, + }, + // increase probability + { + url: `${window.location.origin}/src/ext/shared/dev/DEMO.Alert-URL.user.js`, + id: 103, + }, + { url: window.location.href, id: 10 }, + ]); + console.info("browser.tabs.getCurrent", response); + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + }, + query(message, responseCallback) { + const response = [ + { url: "https://www.filmgarb.com/foo.user.js", id: 101 }, + ]; + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + sendMessage(tabId, message, responseCallback) { + let response = {}; + if (message.name === "DEMO_MSG") { + response = {}; + // response.error = "something went wrong (dev)"; + } + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + }, + webNavigation: { + getAllFrames(message, responseCallback) { + const response = []; + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + }, + storage: { + local: { + get(items, responseCallback) { + const response = {}; + if (!responseCallback) { + return new Promise((resolve) => { + setTimeout(() => resolve(response), _browser.delay); + }); + } + setTimeout(() => responseCallback(response), _browser.delay); + }, + set() { + return new Promise((resolve) => { + setTimeout(() => resolve(), _browser.delay); + }); + }, + remove: () => + new Promise((resolve) => { + setTimeout(() => resolve(), _browser.delay); + }), + }, + onChanged: { + addListener: () => undefined, + }, + }, + i18n: { + getMessage(n, s = undefined) { + if (!window["i18nMessages"]) return; + return window["i18nMessages"]?.[n]?.message.replace("$1", s); + }, + }, +}; + +async function getRemoteFileContents(url) { + const result = {}; + await fetch(url) + .then((response) => { + if (!response.ok) throw Error(response.statusText); + return response.text(); + }) + .then((text) => { + result.content = text; + }) + .catch((error) => { + console.error(error); + result.error = "Remote url bad response!"; + }); + return result; +} + +function saveFile(content, lastMod, newFilename, oldName) { + const ind = files.findIndex((f) => f.filename === oldName); + const s = { + content, + filename: newFilename, + lastModified: lastMod, + }; + if (ind !== -1) { + // overwrite at index + files[ind] = s; + } else { + // add to beginning of array + files.unshift(s); + } +} + +function random(array) { + return array[Math.floor(Math.random() * array.length)]; +} + +export const browser = _browser; + +export default { + browser: _browser, +}; diff --git a/src/shared/dev/DEMO.Alert-URL.user.js b/src/ext/shared/dev/DEMO.Alert-URL.user.js similarity index 78% rename from src/shared/dev/DEMO.Alert-URL.user.js rename to src/ext/shared/dev/DEMO.Alert-URL.user.js index 97868afb..51ee5902 100644 --- a/src/shared/dev/DEMO.Alert-URL.user.js +++ b/src/ext/shared/dev/DEMO.Alert-URL.user.js @@ -9,8 +9,7 @@ // @run-at document-start // ==/UserScript== -/* eslint-disable */ (function () { - 'use strict'; - alert("DEBUG.Alert-URL:\n\n" + location); -})(); \ No newline at end of file + "use strict"; + alert("DEBUG.Alert-URL:\n\n" + location); +})(); diff --git a/src/ext/shared/native.js b/src/ext/shared/native.js new file mode 100644 index 00000000..82566825 --- /dev/null +++ b/src/ext/shared/native.js @@ -0,0 +1,28 @@ +/** + * @file exchange messages between `web extension` and `native application` + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging} + * @see {@link https://developer.apple.com/documentation/safariservices/safari_web_extensions/messaging_between_the_app_and_javascript_in_a_safari_web_extension} + */ + +/** + * @see {@link https://vitejs.dev/guide/env-and-mode.html} + * @returns {string} + */ +const application = () => import.meta.env.NATIVE_APP ?? "application"; + +/** + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/connectNative} + * @returns {import("webextension-polyfill").Runtime.Port} A `runtime.Port` object + */ +export function connectNative() { + return browser.runtime.connectNative(application()); +} + +/** + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendNativeMessage} + * @param {any} message + * @returns {Promise} A response of JSON object or no arguments + */ +export function sendNativeMessage(message) { + return browser.runtime.sendNativeMessage(application(), message); +} diff --git a/src/ext/shared/settings.js b/src/ext/shared/settings.js new file mode 100644 index 00000000..6154d6bd --- /dev/null +++ b/src/ext/shared/settings.js @@ -0,0 +1,679 @@ +/** + * @file wrap a relatively independent settings storage with its own functions + */ + +/** @type {string} */ +const storagePrefix = "US_"; + +/** + * Convert name to storage key + * @param {string} name + * @returns prefixed storage key + */ +const storageKey = (name) => storagePrefix + name.toUpperCase(); + +/** + * @typedef {"sync"|"local"|"managed"|"session"} PossibleAreas + * @typedef {"sync"|"local"} ParamArea + * + * @param {ParamArea} area - storage area + * @returns Dynamic storage reference + */ +const storageRef = async (area) => { + const storages = { + sync: { + area: "sync", + ref: browser.storage.sync, + }, + local: { + area: "local", + ref: browser.storage.local, + }, + }; + // https://developer.apple.com/documentation/safariservices/safari_web_extensions/assessing_your_safari_web_extension_s_browser_compatibility#3584139 + // since storage sync is not implemented in Safari, currently only returns using local storage + if (import.meta.env.BROWSER === "Safari") { + return storages.local; + } + if (area in storages) { + return storages[area]; + } else if (area === undefined) { + const key = storageKey("settings_sync"); + const result = await browser.storage.local.get(key); + return result?.[key] ? storages.sync : storages.local; + } else { + return Promise.reject(new Error(`invalid area ${area}`)); + } +}; + +/** + * @typedef {Object} Platforms platform availability + * @property {any=} macos - overriding defaults + * @property {any=} ipados - overriding defaults + * @property {any=} ios - overriding defaults + * + * @typedef {"INTERNAL"|"general"|"editor"} Group group name + * + * @typedef {Object} Setting The setting item + * @property {string} name - setting's name + * @property {"string"|"number"|"boolean"|"object"|"array"} type - setting's value type + * @property {boolean=} local - local settings will not be synced + * @property {Array=} values - setting's values list + * @property {any} default - setting's default value + * @property {boolean=} disable - disabled settings not be displayed + * @property {boolean=} protect - protected settings cannot be reset + * @property {boolean=} confirm - double confirmation is required when change + * @property {Platforms=} platforms - platform availability and overriding defaults + * @property {Group} group - setting's group name + * @property {string=} legacy - setting's legacy name + * @property {"Toggle"|"select"|"textarea"=} nodeType - setting's node type + * @property {Object=} nodeClass - node class name with setting's value + * + * @typedef {Setting & {key: string}} SettingWithKey The setting item with storage key + */ + +/** @type {Readonly} - Read-only setting template and fallback defaults */ +const settingDefault = deepFreeze({ + name: "setting_default", + type: undefined, + local: false, + values: [], + default: undefined, + disable: false, + protect: false, + confirm: false, + platforms: { macos: undefined, ipados: undefined, ios: undefined }, + group: "INTERNAL", + legacy: "", + nodeType: undefined, + nodeClass: {}, +}); + +/** @type {Readonly} - Read-only settings definition */ +const settingsDefinition = /** @type {const} */ [ + { + name: "error_native", + type: "object", + local: true, + default: { error: undefined }, + group: "INTERNAL", + }, + { + name: "legacy_imported", + type: "number", + local: true, + default: 0, + protect: true, + platforms: { macos: undefined }, + group: "INTERNAL", + }, + { + name: "language_code", + type: "string", + default: "en", + group: "INTERNAL", + legacy: "languageCode", + }, + { + name: "settings_sync", + type: "boolean", + local: true, + default: false, + disable: true, + protect: true, + group: "general", + nodeType: "Toggle", + }, + { + name: "theme_mode", + type: "string", + values: ["auto", "dark", "light"], + default: "auto", + group: "general", + nodeType: "select", + }, + { + name: "global_active", + type: "boolean", + local: true, + default: true, + group: "general", + legacy: "active", + nodeType: "Toggle", + nodeClass: { warn: false }, + }, + { + name: "augmented_userjs_install", + type: "boolean", + default: true, + group: "general", + nodeType: "Toggle", + }, + { + name: "toolbar_badge_count", + type: "boolean", + default: true, + platforms: { macos: true, ipados: false, ios: false }, + group: "general", + legacy: "showCount", + nodeType: "Toggle", + }, + { + name: "scripts_settings", + type: "object", + default: {}, + disable: true, + group: "INTERNAL", + }, + { + name: "scripts_update_check_interval", + type: "number", + values: [0, 1, 3, 7, 15, 30], + default: 0, + group: "general", + nodeType: "select", + }, + { + name: "scripts_update_check_lasttime", + type: "number", + default: 0, + group: "INTERNAL", + legacy: "lastUpdateCheck", + }, + { + name: "scripts_update_automation", + type: "boolean", + default: false, + disable: true, + confirm: true, + group: "general", + nodeType: "Toggle", + nodeClass: { warn: true }, + }, + { + name: "global_exclude_match", + type: "object", + default: [], + group: "general", + legacy: "blacklist", + nodeType: "textarea", + }, + { + name: "editor_list_sort", + type: "string", + values: ["nameAsc", "nameDesc", "lastModifiedAsc", "lastModifiedDesc"], + default: "lastModifiedDesc", + platforms: { macos: undefined }, + group: "editor", + legacy: "sortOrder", + nodeType: "select", + }, + { + name: "editor_list_descriptions", + type: "boolean", + default: true, + platforms: { macos: undefined }, + group: "editor", + legacy: "descriptions", + nodeType: "Toggle", + }, + { + name: "editor_close_brackets", + type: "boolean", + default: true, + platforms: { macos: undefined }, + group: "editor", + legacy: "autoCloseBrackets", + nodeType: "Toggle", + }, + { + name: "editor_auto_hint", + type: "boolean", + default: true, + platforms: { macos: undefined }, + group: "editor", + legacy: "autoHint", + nodeType: "Toggle", + }, + { + name: "editor_javascript_lint", + type: "boolean", + default: false, + platforms: { macos: undefined }, + group: "editor", + legacy: "lint", + nodeType: "Toggle", + }, + { + name: "editor_show_whitespace", + type: "boolean", + default: true, + platforms: { macos: undefined }, + group: "editor", + legacy: "showInvisibles", + nodeType: "Toggle", + }, + { + name: "editor_tab_size", + type: "number", + values: [1, 2, 3, 4, 5, 6, 8, 10, 12], + default: 4, + platforms: { macos: undefined }, + group: "editor", + legacy: "tabSize", + nodeType: "select", + }, +]; + +/** @type {Readonly<{[key: string]: SettingWithKey}>} - Read-only settings dictionary */ +export const settingsDictionary = deepFreeze( + settingsDefinition.reduce(settingsDefinitionReduceCallbackFn, {}), +); + +/** + * populate the settings-define with setting-default + * and convert settings-define to storage-key object + * @param {{[key: string]: SettingWithKey}} settings settings dictionary + * @param {Setting} setting each setting define + * @returns // {US_GLOBAL_ACTIVE: {key: US_GLOBAL_ACTIVE, name: global_active, ... }, ...} + */ +function settingsDefinitionReduceCallbackFn(settings, setting) { + const key = storageKey(setting.name); + settings[key] = { ...settingDefault, ...setting, key }; + return settings; +} + +/** + * prevent settings define from being modified in any case + * otherwise user settings may be lost in the worst case + * @type {(o: T) => Readonly} + * @param {object} object any object + * @returns {object} deep frozen object + */ +function deepFreeze(object) { + for (const p in object) { + if (typeof object[p] == "object") { + deepFreeze(object[p]); + } + } + return Object.freeze(object); +} + +/** + * compatibility polyfill for Safari < 15.4 + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn#browser_compatibility} + * @todo remove this polyfill when set safari strict_min_version 15.4 + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/browser_specific_settings#safari_properties} + */ +if (Object.hasOwn === undefined) { + Object.hasOwn = (obj, prop) => + Object.prototype.hasOwnProperty.call(obj, prop); +} + +// export and define the operation method of settings storage +// they are similar to browser.storage but slightly different + +/** + * settings.get + * @param {string|string[]} keys key | array of keys | undefined for all + * @typedef FnGetOptions + * @property {ParamArea=} area + * @property {keyof Platforms=} platform + * @param {FnGetOptions} options + * @returns settings object + */ +export async function get(keys = undefined, options = {}) { + let { area, platform } = options; + if (![undefined, "local", "sync"].includes(area)) { + return console.error("Unexpected storage area:", area); + } + if (![undefined, "macos", "ios", "ipados"].includes(platform)) { + return console.error("Unexpected platform:", platform); + } + // validate setting value and fix surprises to default + /** @param {string} key @param {any} val */ + const valueFix = (key, val) => { + if (!key || !Object.hasOwn(settingsDictionary, key)) return; + const def = + settingsDictionary[key].platforms[platform] ?? + settingsDictionary[key].default; + // check if value type conforms to settings-dictionary + const type = settingsDictionary[key].type; + if (typeof val != type) { + console.warn( + `Unexpected ${key} value type '${typeof val}' should '${type}', fix to default`, + ); + return def; + } + // check if value conforms to settings-dictionary + const values = settingsDictionary[key].values; + if (values.length && !values.includes(val)) { + console.warn( + `Unexpected ${key} value '${val}' should one of '${values}', fix to default`, + ); + return def; + } + // verified, pass original value + return val; + }; + // [single setting] + if (typeof keys == "string") { + const key = storageKey(keys); + // check if key exist in settings-dictionary + if (!Object.hasOwn(settingsDictionary, key)) { + return console.error("unexpected settings key:", key); + } + // check if only locally stored setting + settingsDictionary[key].local === true && (area = "local"); + const storage = await storageRef(area); + const result = await storage.ref.get(key); + if (Object.hasOwn(result, key)) return valueFix(key, result[key]); + return ( + settingsDictionary[key].platforms[platform] ?? + settingsDictionary[key].default + ); + } + const complexGet = async (settingsDefault, areaKeys) => { + const storage = await storageRef(area); + let local = {}, + sync = {}; + if (storage.area === "sync") { + if (areaKeys.sync.length) { + sync = await storage.ref.get(areaKeys.sync); + } + if (areaKeys.local.length) { + local = await browser.storage.local.get(areaKeys.local); + } + } else { + local = await storage.ref.get(areaKeys.all); + } + const result = Object.assign(settingsDefault, local, sync); + // revert settings object property name + return Object.entries(result).reduce((p, c) => { + p[settingsDictionary[c[0]].name] = valueFix(...c); + return p; + }, {}); + }; + // [muilt settings] + if (Array.isArray(keys)) { + if (!keys.length) { + return console.error("Settings keys empty:", keys); + } + const settingsDefault = {}; + const areaKeys = { local: [], sync: [], all: [] }; + for (const k of keys) { + const key = storageKey(k); + // check if key exist in settings-dictionary + if (!Object.hasOwn(settingsDictionary, key)) { + return console.error("unexpected settings key:", key); + } + settingsDefault[key] = + settingsDictionary[key].platforms[platform] ?? + settingsDictionary[key].default; + // detach only locally stored settings + settingsDictionary[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + // record all keys in case sync storage is not enabled + areaKeys.all.push(key); + } + return complexGet(settingsDefault, areaKeys); + } + // [all settings] + if (typeof keys == "undefined" || keys === null) { + const settingsDefault = {}; + const areaKeys = { local: [], sync: [], all: [] }; + for (const key of Object.keys(settingsDictionary)) { + settingsDefault[key] = + settingsDictionary[key].platforms[platform] ?? + settingsDictionary[key].default; + // detach only locally stored settings + settingsDictionary[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + // record all keys in case sync storage is not enabled + areaKeys.all.push(key); + } + return complexGet(settingsDefault, areaKeys); + } + return console.error("Unexpected keys type:", keys); +} + +/** + * settings.set + * @param {object} keys settings object + * @typedef FnSetOptions + * @property {ParamArea=} area + * @param {FnSetOptions} options + */ +export async function set(keys, options = {}) { + const { area } = options; + if (![undefined, "local", "sync"].includes(area)) { + return console.error("unexpected storage area:", area); + } + if (typeof keys != "object") { + return console.error("Unexpected keys type:", keys); + } + if (!Object.keys(keys).length) { + return console.error("Settings object empty:", keys); + } + const areaKeys = { local: {}, sync: {}, all: {} }; + for (const k of Object.keys(keys)) { + const key = storageKey(k); + // check if key exist in settings-dictionary + if (!Object.hasOwn(settingsDictionary, key)) { + return console.error("Unexpected settings keys:", key); + } + // check if value type conforms to settings-dictionary + const type = settingsDictionary[key].type; + if (typeof keys[k] != type) { + if (type === "number" && !Number.isNaN(Number(keys[k]))) { + // compatible with string numbers + keys[k] = Number(keys[k]); // still store it as a number type + } else { + return console.error( + `Unexpected ${k} value type '${typeof keys[k]}' should '${type}'`, + ); + } + } + // check if value conforms to settings-dictionary + const values = settingsDictionary[key].values; + if (values.length && !values.includes(keys[k])) { + return console.error( + `Unexpected ${k} value '${keys[k]}' should one of '${values}'`, + ); + } + // detach only locally stored settings + settingsDictionary[key].local === true + ? (areaKeys.local[key] = keys[k]) + : (areaKeys.sync[key] = keys[k]); + // record all keys in case sync storage is not enabled + areaKeys.all[key] = keys[k]; + } + const storage = await storageRef(area); + // complexSet + try { + if (storage.area === "sync") { + if (Object.keys(areaKeys.sync).length) { + await storage.ref.set(areaKeys.sync); + } + if (Object.keys(areaKeys.local).length) { + await browser.storage.local.set(areaKeys.local); + } + } else { + await storage.ref.set(areaKeys.all); + } + return true; + } catch (error) { + return console.error(error); + } +} + +/** + * settings.reset + * reset to default + * @param {string|string[]} keys key | array of keys | undefined for all + * @typedef FnResetOptions + * @property {ParamArea=} area + * @param {FnResetOptions} options + */ +export async function reset(keys = undefined, options = {}) { + let { area } = options; + if (![undefined, "local", "sync"].includes(area)) { + return console.error("unexpected storage area:", area); + } + // [single setting] + if (typeof keys == "string") { + const key = storageKey(keys); + // check if key exist in settings-dictionary + if (!Object.hasOwn(settingsDictionary, key)) { + return console.error("unexpected settings key:", key, keys); + } + // check if key is protected + if (settingsDictionary[key].protect === true) { + return console.error("protected settings key:", key, keys); + } + settingsDictionary[key].local === true && (area = "local"); + const storage = await storageRef(area); + return storage.ref.remove(key); + } + const complexRemove = async (areaKeys) => { + const storage = await storageRef(area); + try { + if (storage.area === "sync") { + if (areaKeys.sync.length) { + await storage.ref.remove(areaKeys.sync); + } + if (areaKeys.local.length) { + await browser.storage.local.remove(areaKeys.local); + } + } else { + await storage.ref.remove(areaKeys.all); + } + return true; + } catch (error) { + return console.error(error); + } + }; + // [muilt settings] + if (Array.isArray(keys)) { + if (!keys.length) { + return console.error("Settings keys empty:", keys); + } + const areaKeys = { local: [], sync: [], all: [] }; + for (const k of keys) { + const key = storageKey(k); + // check if key exist in settings-dictionary + if (!Object.hasOwn(settingsDictionary, key)) { + return console.error("unexpected settings key:", key, k); + } + // check if key is protected + if (settingsDictionary[key].protect === true) { + return console.error("protected settings key:", key, k); + } + // detach only locally stored settings + settingsDictionary[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + // record all keys in case sync storage is not enabled + areaKeys.all.push(key); + } + return complexRemove(areaKeys); + } + // [all settings] + if (typeof keys == "undefined" || keys === null) { + const areaKeys = { local: [], sync: [], all: [] }; + for (const key in settingsDictionary) { + // skip protected keys + if (settingsDictionary[key].protect === true) continue; + // detach only locally stored settings + settingsDictionary[key].local === true + ? areaKeys.local.push(key) + : areaKeys.sync.push(key); + // record all keys in case sync storage is not enabled + areaKeys.all.push(key); + // clean up the legacy keys at the same time + const legacyKey = settingsDictionary[key].legacy; + legacyKey && areaKeys.all.push(legacyKey); + } + return complexRemove(areaKeys); + } + return console.error("Unexpected keys type:", keys); +} + +/** + * complex onChanged + * this function is convenient for the svelte store to update the state + * @callback onChangedSettingsCallback + * @param {{[key: string]: any}} settings - changed settings + * @param {PossibleAreas} area - storage area + * @returns {void} + * @param {onChangedSettingsCallback} callback + */ +export function onChangedSettings(callback) { + if (typeof callback != "function") { + return console.error("Unexpected callback:", callback); + } + console.info("storage onChanged addListener"); + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/onChanged#listener} + * @param {object} changes + * @param {PossibleAreas} area + */ + const listener = (changes, area) => { + // console.log(`storage.${area}.onChanged`, changes); // DEBUG + const settings = {}; + for (const key in changes) { + if (!Object.hasOwn(settingsDictionary, key)) continue; + settings[settingsDictionary[key].name] = changes[key].newValue; + } + try { + callback(settings, area); + } catch (error) { + console.error("onChanged callback:", error); + } + }; + browser.storage.onChanged.addListener(listener); +} + +// the following functions are used only for compatibility transition periods +// these functions will be removed in the future, perhaps in version 5.0 + +export async function legacyImport() { + // if legacy data has already been imported, skip this process + const imported = await get("legacy_imported"); + if (imported) return console.info("Legacy settings has already imported"); + // start the one-time import process + const result = await browser.runtime.sendNativeMessage("app", { + name: "PAGE_LEGACY_IMPORT", + }); + if (!result) return console.error("PAGE_LEGACY_IMPORT not response"); + if (result.error) return console.error(result.error); + console.info("Import settings data from legacy manifest file"); + const settings = {}; + for (const key of Object.keys(settingsDictionary)) { + const legacy = settingsDictionary[key].legacy; + if (legacy in result) { + let value = result[legacy]; + switch (settingsDictionary[key].type) { + case "boolean": + value = JSON.parse(value); + break; + case "number": + value = Number(value); + break; + } + console.info(`Importing legacy setting: ${legacy}`, value); + settings[settingsDictionary[key].name] = value; + } + } + // import complete tag, to ensure will only be import once + Object.assign(settings, { legacy_imported: Date.now() }); + if (await set(settings, { area: "local" })) { + console.info("Import legacy settings complete"); + // send a message to the Swift layer to safely clean up legacy data + // browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORTED"}); + return true; + } + return console.error("Import legacy settings abort"); +} diff --git a/src/ext/shared/utils.js b/src/ext/shared/utils.js new file mode 100644 index 00000000..63875afd --- /dev/null +++ b/src/ext/shared/utils.js @@ -0,0 +1,515 @@ +/** + * @param {number} ms millisecond timestamp + * @returns {string} + */ +export function formatDate(ms) { + const d = new Date(ms); + const yr = new Intl.DateTimeFormat("en", { year: "numeric" }).format(d); + const mo = new Intl.DateTimeFormat("en", { month: "short" }).format(d); + const dd = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(d); + const hr = d.getHours(); + const mn = d.getMinutes(); + return `${mo} ${dd}, ${yr} at ${hr}:${mn}`; +} + +export function uniqueId() { + return Math.random().toString(36).substring(2, 10); +} + +/** + * awaitable function for waiting an arbitrary amount of time + * @param {number} ms the amount of time to wait in milliseconds + * @returns {Promise} + */ +export function wait(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +// TODO: describe the items array that should get passed to this function +/** + * @param {Array} array + * @param {("lastModifiedAsc"|"lastModifiedDesc"|"nameAsc"|"nameDesc")} order + * @returns + */ +export function sortBy(array, order) { + if (order === "nameAsc") { + array.sort((a, b) => a.name.localeCompare(b.name)); + } else if (order === "nameDesc") { + array.sort((a, b) => b.name.localeCompare(a.name)); + } else if (order === "lastModifiedAsc") { + array.sort((a, b) => (a.lastModified < b.lastModified ? -1 : 1)); + } else if (order === "lastModifiedDesc") { + array.sort((a, b) => (a.lastModified > b.lastModified ? -1 : 1)); + } + // always keep temp file pinned to the top, should only ever have one temp script + // if (array.find(f => f.temp)) array.sort((a, b) => a.temp ? -1 : b.temp ? 1 : 0); + return array; +} + +/** + * + * @param {string} description + * @param {string} name + * @param {("css"|"js")} type + * @returns {string} + */ +export function newScriptDefault(description, name, type) { + if (type === "css") { + return `/* ==UserStyle==\n@name ${name}\n@description ${description}\n@match \n==/UserStyle== */`; + } + return `// ==UserScript==\n// @name ${name}\n// @description ${description}\n// @match *://*/*\n// ==/UserScript==`; +} + +/** + * @param {string} str + * @returns {?{code: string, content: str, metablock: string, metadata: object}} + */ +export function parse(str) { + if (typeof str != "string") return null; + const blocksReg = + /(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))/; + const blocks = str.match(blocksReg); + + if (!blocks) return null; + + const metablock = blocks[1] != null ? blocks[1] : blocks[4]; + const metas = blocks[2] != null ? blocks[2] : blocks[5]; + const code = blocks[3] != null ? blocks[3].trim() : blocks[6].trim(); + + const metadata = {}; + const metaArray = metas.split("\n"); + metaArray.forEach((m) => { + const parts = m + .trim() + .match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)/); + const parts2 = m + .trim() + .match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$/); + if (parts) { + metadata[parts[1]] = metadata[parts[1]] || []; + metadata[parts[1]].push(parts[2]); + } else if (parts2) { + metadata[parts2[1]] = metadata[parts2[1]] || []; + metadata[parts2[1]].push(true); + } + }); + // fail if @name is missing or name is empty + if (!metadata.name || metadata.name[0].length < 2) return; + + return { + code, + content: str, + metablock, + metadata, + }; +} + +/** + * @param {string} text editor code + * @returns {{match: boolean, meta: boolean} | {key: string, value: string, text: string}[]} + */ +export function parseMetadata(text) { + const groupsRe = + /(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)/; + const groups = text.match(groupsRe); + // userscript code doesn't match the regex expression + // could be missing opening/closing tags, malformed + // or missing metadata between opening/closing tags (group 2 in regex exp) + if (!groups) { + return { match: false, meta: false }; + } + + // userscript code matches but content between opening/closing tag missing + // ex. opening/closing tags present, but newline characters between the tags + const metas = groups[2]; + if (!metas) return { match: true, meta: false }; + + const metadata = []; + const metaArray = metas.split("\n"); + + for (let i = 0; i < metaArray.length; i++) { + const metaRegex = + /^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]*([^\s]+[^\r\n\t\v\f]*)?/; + const meta = metaArray[i]; + const parts = meta.match(metaRegex); + if (parts) + metadata.push({ key: parts[1], value: parts[2], text: parts[0] }); + } + + // if there is content between the opening/closing tags, match will be found + // this additionally checks that there's at least one properly formed key + // if not keys found, assume metadata is missing + // checking that required keys are present will happen elsewhere + if (!Object.keys(metadata).length) return { match: true, meta: false }; + + return metadata; +} + +/** + * @param {string} input a match pattern + * @typedef {string} value - the match pattern + * @typedef {Object} MatchGroups - regexp match groups + * @property {string} start - leading whitespace + * @property {string} separ - separator whitespace + * @property {value} value - the match pattern + * @typedef {MatchGroups & CheckedItem} ParsedItem - parsed result + * @returns {{warn: boolean, error: boolean, items: ParsedItem[], values: value[]}} + */ +export function parseMatchPatterns(input) { + const result = { + warn: false, // global warn + error: false, // global error + items: [], + values: [], + }; + if (typeof input !== "string") return result; + // match the separated values from input string + const matches = input.matchAll( + /(?^\s*|)(?\S+?)(?\s+|$)/g, + ); + for (const match of matches) { + const item = checkMatchPatterns(match.groups.value); + // setting the global error indicator + if (item.warn === true) result.warn = true; + if (item.error === true) result.error = true; + result.items.push({ ...match.groups, ...item }); + result.values.push(match.groups.value.toLowerCase()); + } + return result; +} + +/** + * @param {string} input whitespace separated list of match patterns + * @typedef {Object} CheckedItem - checked result + * @property {boolean} warn - the match pattern has warning + * @property {boolean} error - the match pattern valid or not + * @property {string} point - invalid point or error message + * @returns {CheckedItem} + */ +export function checkMatchPatterns(input) { + if (typeof input !== "string") return; + const result = { + warn: false, + error: true, + point: "", + }; + if (["", "*://*/*"].includes(input)) { + result.warn = true; + result.error = false; + result.point = gl("utils_check_match_patterns_0"); + return result; + } + let scheme, host, path; + /** check scheme component */ + if (input.slice(0, 5).toLowerCase() === "https") { + scheme = input.slice(0, 5); + } else if (input.slice(0, 4).toLowerCase() === "http") { + scheme = input.slice(0, 4); + } else if (input.startsWith("*")) { + scheme = "*"; + } else { + // The scheme component should one of *, https, http + result.point = gl("utils_check_match_patterns_1"); + return result; + } + /** check :// separator */ + if (input.slice(scheme.length, scheme.length + 3) !== "://") { + // The scheme and host should separated by `://` + result.point = gl("utils_check_match_patterns_2"); + return result; + } + // separate host and path + const array = input.slice(scheme.length + 3).split("/"); + if (array.length < 2) { + // The match pattern has no path component + result.point = gl("utils_check_match_patterns_3"); + return result; + } + host = array[0]; + path = "/" + array.slice(1).join("/"); + /** check host component */ + if (host === "*.") { + // The `*.` should followed by part of the hostname + result.point = gl("utils_check_match_patterns_4"); + return result; + } + // allow fully qualified domain name (FQDN) + if (host.at(-1) === ".") host = host.slice(0, -1); + if (host.length < 1 || 255 - 1 < host.length) { + // The host component length should be 1-255 + result.point = gl("utils_check_match_patterns_5"); + return result; + } + let labels = []; // domain labels + let hostPart = ""; // rest part that exclude wildcard + if (host.startsWith("*.")) { + hostPart = host.slice(2); + labels = hostPart.split("."); + } else if (host !== "*") { + hostPart = host; + labels = host.split("."); + } + if (hostPart.includes("*")) { + // The `*` in host component should be independent or `*.` at the start + result.point = gl("utils_check_match_patterns_6"); + return result; + } + for (const label of labels) { + if (label.length === 0) { + // The host component contains empty label(s) + result.point = gl("utils_check_match_patterns_7"); + return result; + } + if (label.startsWith("-") || label.endsWith("-")) { + // The label cannot start or end with `-` character + result.point = gl("utils_check_match_patterns_8"); + return result; + } + if (label.length > 63) { + // The maximum length of the label cannot exceed 63 + result.point = gl("utils_check_match_patterns_9"); + return result; + } + } + // allowed character set of host component + const hostInvalidMatches = hostPart.match(/[^A-Za-z0-9.-]/g); + if (hostInvalidMatches) { + const characters = hostInvalidMatches.join(""); + // The host component contains invalid character(s): ${c} + result.point = gl("utils_check_match_patterns_10", characters); + return result; + } + /** check path component */ + // allowed character set of path component + const pathInvalidMatches = path.match( + /[^\w\]\\!$%&'()*+,./:;=?@[^`{|}~-]/g, // toolsGetValidPathCharacters + ); + if (pathInvalidMatches) { + const characters = pathInvalidMatches.join(""); + // The path component contains invalid character(s): ${c} + result.point = gl("utils_check_match_patterns_11", characters); + return result; + } + result.error = false; + return result; +} + +/** Generate valid path characters in browser js runtime */ +export function toolsGetValidPathCharacters() { + const set = new Set(); + for (let i = 32; i < 128; i++) set.add(String.fromCharCode(i)); + set.delete("?"); + set.delete("#"); + const p = [...set].join(""); + set.delete("="); + set.delete("&"); + const s = [...set].join(""); + const url = new URL(`https://host/${p}?1=${s}&${s}=2`); + const possibles = [...url.pathname, ...url.search]; + const characters = [...new Set(possibles)].sort().join(""); + const symbols = characters.match(/[^\w]/g).join(""); + return { + characters, + symbols, + restr: `/\\w${symbols.replace(/[\^[\]-]/g, "\\$&")}]/`, + }; +} + +/** + * get lang + * @param {string} n messageName + * @param {string | string[]} s substitutions + * const gl = browser.i18n.getMessage; // issue: safari return `undefined` + */ +export const gl = (n, s = undefined) => browser.i18n.getMessage(n, s); + +export const validGrants = new Set([ + "GM.info", + "GM_info", + "GM.addStyle", + "GM.openInTab", + "GM.closeTab", + "GM.setValue", + "GM.getValue", + "GM.deleteValue", + "GM.listValues", + "GM.setClipboard", + "GM.getTab", + "GM.saveTab", + "GM_xmlhttpRequest", + "GM.xmlHttpRequest", + "none", +]); + +export const validMetaKeys = new Set([ + "author", + "description", + "downloadURL", + "exclude", + "exclude-match", + "grant", + "icon", + "include", + "inject-into", + "match", + "name", + "noframes", + "require", + "run-at", + "updateURL", + "version", + "weight", +]); + +export const extensionPaths = { + popup: "/dist/entry-ext-action-popup.html", + page: "/dist/entry-ext-extension-page.html", +}; + +export async function openExtensionPage() { + const url = browser.runtime.getURL(extensionPaths.page); + const tabs = await browser.tabs.query({ url }); + const tab = tabs.find((e) => e.url.startsWith(url)); + if (!tab) return browser.tabs.create({ url }); + await browser.tabs.update(tab.id, { active: true }); + if (!browser.windows.update) return; + await browser.windows.update(tab.windowId, { focused: true }); +} + +/** + * Safari currently does not honor the target attribute of elements in extension contexts + * @param {string} url + */ +export async function openInBlank(url) { + browser.tabs.create({ url }); +} + +/** + * Safari macos currently does not honor the download attribute of elements in extension contexts + * Also not support https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/download + * @param {string} filename + * @param {string} content + * @param {string} type + */ +export async function downloadToFile(filename, content, type = "text/plain") { + let a; + // created only once in the current page so that it can be used repeatedly + if (!window["_download_helper"]) { + if (import.meta.env.SAFARI_PLATFORM === "mac") { + // bypassing safari issues through iframe + const iframe = document.createElement("iframe"); + iframe.style.display = "none"; + document.body.append(iframe); + a = iframe.contentDocument.createElement("a"); + } else { + a = document.createElement("a"); + } + window["_download_helper"] = a; + } else { + a = window["_download_helper"]; + } + const blob = URL.createObjectURL(new Blob([content], { type })); + setTimeout(() => URL.revokeObjectURL(blob), 30000); + a.download = filename; + a.href = blob; + a.click(); +} + +// Safari macos currently does not honor the download attribute of elements in extension contexts +// Also not support https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/download +// eslint-disable-next-line no-unused-vars -- the old implementation is left for reference +async function downloadToFile_old(filename, content, type = "text/plain") { + // there must be an accessible url, otherwise the script cannot be successfully injected + const url = "https://quoid.github.io/userscripts/serve/download.html"; + const tab = await browser.tabs.create({ url }); + const exchange = { filename, content, type }; + const exscript = (o) => { + // make sure executed only once + // @ts-ignore + if (window.US_DOWNLOAD === 1) return; + // @ts-ignore + window.US_DOWNLOAD = 1; + window.stop(); + document.body.textContent = "Download is starting..."; + const a = document.createElement("a"); + a.download = o.filename; + a.href = URL.createObjectURL(new Blob([o.content], { type: o.type })); + a.click(); + document.body.innerHTML += "
    The download should have started.
    "; + a.textContent = o.filename; + document.body.append(a); + }; + // Safari currently unable to stably executeScript on tab loading status + try { + await browser.tabs.executeScript(tab.id, { + code: `(${exscript})(${JSON.stringify(exchange)});`, + }); + } catch { + const handleUpdated = async (tabId) => { + if (tabId !== tab.id) return; + try { + await browser.tabs.executeScript(tabId, { + code: `(${exscript})(${JSON.stringify(exchange)});`, + }); + console.info(`[${filename}] Download is starting...`); + } catch { + console.info(`[${filename}] Start download failed, retrying...`); + } + }; + browser.tabs.onUpdated.addListener(handleUpdated); + // Remove the listener when tab closing + const handleRemoved = (tabId) => { + if (tabId !== tab.id) return; + browser.tabs.onUpdated.removeListener(handleUpdated); + browser.tabs.onRemoved.removeListener(handleRemoved); + }; + browser.tabs.onRemoved.addListener(handleRemoved); + } +} + +/** + * Dynamic registration of content scripts [Safari >= 16.4] + * @param {boolean} enable - whether to register content scripts + */ +export async function contentScriptRegistration(enable) { + if (import.meta.env.SAFARI_VERSION < 16.4) return; + /** + * @see {@link https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/RegisteredContentScript} + * @type {Parameters[0]} + */ + const scripts = [ + { + id: "dot-user-js", + js: ["dist/content-scripts/dot-user-js.js"], + matches: ["*://*/*.user.js", "*://*/*.user.js?*"], + runAt: "document_start", + }, + { + id: "script-market", + js: ["dist/content-scripts/script-market.js"], + matches: ["*://*.greasyfork.org/*"], + excludeMatches: ["*://*/*.user.js", "*://*/*.user.js?*"], + runAt: "document_end", + }, + ]; + const regScripts = await browser.scripting.getRegisteredContentScripts(); + const ids = regScripts.map((script) => script.id); + for (const script of scripts) { + if (enable && !ids.includes(script.id)) { + await browser.scripting.registerContentScripts([script]); + if (import.meta.env.MODE === "development") { + console.debug("registerContentScripts", script.id); + } + } + if (!enable && ids.includes(script.id)) { + await browser.scripting.unregisterContentScripts({ + ids: [script.id], + }); + if (import.meta.env.MODE === "development") { + console.debug("unregisterContentScripts", script.id); + } + } + } +} diff --git a/src/ext/types.d.ts b/src/ext/types.d.ts new file mode 100644 index 00000000..1ad82689 --- /dev/null +++ b/src/ext/types.d.ts @@ -0,0 +1,102 @@ +declare namespace TypeExtMessages { + interface XHRProcessedFile { + data: Array; + lastModified: number; + name: string; + mime: string; + } + + type XHRProcessedFormData = [string, string | XHRProcessedFile][]; + + type XHRProcessedData = + | { + data: string; + type: "Text" | "URLSearchParams"; + } + | { + data: string; + mime: string; + type: "Document"; + } + | { + data: Array; + type: "ArrayBuffer" | "ArrayBufferView"; + } + | { + data: Array; + mime: string; + type: "Blob"; + } + | { + data: XHRProcessedFormData; + type: "FormData"; + }; + + type XHRHandlers = [ + "onreadystatechange", + "onloadstart", + "onprogress", + "onabort", + "onerror", + "onload", + "ontimeout", + "onloadend", + ]; + + type XHRUploadHandlers = [ + "onabort", + "onerror", + "onload", + "onloadend", + "onloadstart", + "onprogress", + "ontimeout", + ]; + + type XHRHandlersObj = { + [handler in XHRHandlers[number]]?: (response: XHRResponse) => void; + }; + + type XHRUploadHandlersObj = { + [handler in XHRUploadHandlers[number]]?: (response: XHRProgress) => void; + }; + + interface XHRTransportableDetails { + binary: boolean; + data: XHRProcessedData; + headers: { [x: Lowercase]: string }; + method: string; + overrideMimeType: string; + password: string; + responseType: XMLHttpRequestResponseType; + timeout: number; + url: string; + user: string; + hasHandlers: { [handler in XHRHandlers[number]]?: boolean }; + hasUploadHandlers: { [handler in XHRUploadHandlers[number]]?: boolean }; + } + + interface XHRTransportableResponse { + contentType: string; // non-standard + readyState: number; + response: string | number[]; + responseHeaders: string; + responseType: XMLHttpRequestResponseType; + responseURL: string; + status: number; + statusText: string; + timeout: number; + } + + interface XHRResponse extends Omit { + response: any; + responseText?: string; + responseXML?: Document; + } + + interface XHRProgress { + lengthComputable: boolean; + loaded: number; + total: number; + } +} diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 37103c68..00000000 --- a/src/main.js +++ /dev/null @@ -1,8 +0,0 @@ -import "./app.css"; -import App from "./App.svelte"; - -const app = new App({ - target: document.getElementById("app") -}); - -export default app; diff --git a/src/page/App.svelte b/src/page/App.svelte deleted file mode 100644 index 62e46a66..00000000 --- a/src/page/App.svelte +++ /dev/null @@ -1,133 +0,0 @@ - - - - -{#if $state.includes("init")} -
    - {@html logo} - {#if $state.includes("init-error")} - Failed to initialize app, check the browser console - {:else} - Initializing app... - {/if} -
    -{/if} -
    - - -
    -
      - {#each $notifications as item (item.id)} - notifications.remove(item.id)} {item}/> - {/each} -
    -{#if $state.includes("settings")}{/if} - - diff --git a/src/page/Components/Editor/CodeMirror.svelte b/src/page/Components/Editor/CodeMirror.svelte deleted file mode 100644 index 9fce5705..00000000 --- a/src/page/Components/Editor/CodeMirror.svelte +++ /dev/null @@ -1,402 +0,0 @@ - - - - -{#if instance} - searchActive = false} - instance={instance} - /> -{/if} diff --git a/src/page/Components/Editor/Editor.svelte b/src/page/Components/Editor/Editor.svelte deleted file mode 100644 index 029a0876..00000000 --- a/src/page/Components/Editor/Editor.svelte +++ /dev/null @@ -1,337 +0,0 @@ - - -
    - {#if $state.includes("editor-loading") || $state.includes("fetching")} - - {/if} - {#if !activeItem} -
    No Item Selected
    - {/if} -
    -
    -
    - -
    {name}
    -
    -
    -
    - {#if $state.includes("saving")} - Saving... - {:else if $state.includes("trashing")} - (◞‸◟) - {:else if $state.includes("updating")} - Updating code, cancel request - {:else if remote} - Code was remotely fetched, check carefully before saving! - {:else if temp} - Ready for code! - {:else} - Last modified: {lastModified} - {/if} -
    -
    -
    -
    - - - -
    -
    -
    - -
    - -
    - - diff --git a/src/page/Components/Editor/EditorSearch.svelte b/src/page/Components/Editor/EditorSearch.svelte deleted file mode 100644 index ff1833cb..00000000 --- a/src/page/Components/Editor/EditorSearch.svelte +++ /dev/null @@ -1,199 +0,0 @@ - - -{#if active} - -{/if} - - diff --git a/src/page/Components/Notification.svelte b/src/page/Components/Notification.svelte deleted file mode 100644 index 0bac0589..00000000 --- a/src/page/Components/Notification.svelte +++ /dev/null @@ -1,146 +0,0 @@ - - -
  • - -
    -
    {@html icon}
    - {item.message} - -
    -
  • - - diff --git a/src/page/Components/Settings.svelte b/src/page/Components/Settings.svelte deleted file mode 100644 index 75e64086..00000000 --- a/src/page/Components/Settings.svelte +++ /dev/null @@ -1,313 +0,0 @@ - - -
    - - diff --git a/src/page/Components/Sidebar/Sidebar.svelte b/src/page/Components/Sidebar/Sidebar.svelte deleted file mode 100644 index b5c93f37..00000000 --- a/src/page/Components/Sidebar/Sidebar.svelte +++ /dev/null @@ -1,255 +0,0 @@ - - - - - diff --git a/src/page/Components/Sidebar/SidebarFilter.svelte b/src/page/Components/Sidebar/SidebarFilter.svelte deleted file mode 100644 index ae6f7bb0..00000000 --- a/src/page/Components/Sidebar/SidebarFilter.svelte +++ /dev/null @@ -1,117 +0,0 @@ - - -
    - - -
  • updateSortOrder("lastModifiedAsc")} - > - Last Modified: Asc{sortOrder === "lastModifiedAsc" ? " *" : ""} -
  • -
  • updateSortOrder("lastModifiedDesc")} - > - Last Modified: Desc{sortOrder === "lastModifiedDesc" ? " *" : ""} -
  • -
  • updateSortOrder("nameAsc")} - > - Name: Asc{sortOrder === "nameAsc" ? " *" : ""} -
  • -
  • updateSortOrder("nameDesc")} - > - Name: Desc{sortOrder === "nameDesc" ? " *" : ""} -
  • -
    - {#if query} - query = ""} {disabled}/> - {/if} -
    - - diff --git a/src/page/Components/Sidebar/SidebarItem.svelte b/src/page/Components/Sidebar/SidebarItem.svelte deleted file mode 100644 index b9e93c82..00000000 --- a/src/page/Components/Sidebar/SidebarItem.svelte +++ /dev/null @@ -1,99 +0,0 @@ - - -
    -
    - -
    {data.name}
    - -
    - {#if description} -
    - {description} -
    - {/if} -
    - - diff --git a/src/page/app.css b/src/page/app.css deleted file mode 100644 index 8c39f72a..00000000 --- a/src/page/app.css +++ /dev/null @@ -1,49 +0,0 @@ -html { - font-size: 100%; - height: 100vh; - overflow: hidden; -} - -body { - background-color: var(--color-bg-primary); - color: var(--text-color-primary); - font: var(--text-default); - height: 100%; - text-rendering: optimizelegibility; - -webkit-font-smoothing: antialiased; -} - -noscript { - display: block; -} - -#app { - display: flex; - flex-direction: column; - height: 100%; -} - -a, -.link { - color: var(--color-blue); - cursor: pointer; - text-decoration: underline; -} - -.truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -button { - border: none; - cursor: pointer; - padding: 0; - user-select: none; -} - -button:disabled { - opacity: var(--opacity-disabled); - pointer-events: none; -} diff --git a/src/page/cm.css b/src/page/cm.css deleted file mode 100644 index 27b7210b..00000000 --- a/src/page/cm.css +++ /dev/null @@ -1,214 +0,0 @@ -.CodeMirror { - background-color: transparent; - color: var(--editor-default); - font-family: var(--editor-font); - font-size: var(--editor-font-size); - font-weight: 400; - height: 100%; - line-height: var(--editor-line-height); - text-rendering: unset; - -webkit-font-smoothing: antialiased; -} - -.CodeMirror-gutters { - background-color: transparent; - border-right: 0; -} - -.CodeMirror-activeline-background, -.CodeMirror-activeline-gutter { - background-color: var(--editor-active-line); -} - -.CodeMirror-linenumber { - color: var(--text-color-primary); - font-size: var(--editor-font-size); - opacity: 0.25; -} - -.CodeMirror-activeline .CodeMirror-linenumber { - opacity: 0.5; -} - -.CodeMirror-cursor { - border-left: 2px solid var(--text-color-secondary); /* check */ -} - -.CodeMirror-hints { - background-color: var(--color-black); - border: 1px solid black; - box-shadow: var(--box-shadow); - font: var(--text-small); - font-family: var(--editor-font); - line-height: 1.5rem; -} - -.CodeMirror-hint { - color: var(--text-color-secondary); -} - -li.CodeMirror-hint-active { - background-color: var(--color-blue); - color: var(--color-black); -} - -.cm-s-default .CodeMirror-selected { - background-color: var(--editor-selected-bg); -} - -div.CodeMirror span.CodeMirror-matchingbracket { - color: var(--editor-matching-bracket-color); - border-bottom: 2px solid var(--editor-matching-bracket-border); -} - -div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: var(--editor-non-matching-bracket); -} - -.CodeMirror .CodeMirror-placeholder, -.cm-s-default .cm-comment { - color: var(--editor-comment); -} - -.cm-s-default .cm-keyword { - color: var(--editor-keyword); -} - -.cm-s-default .cm-def { - color: var(--editor-def); -} - -.cm-s-default .cm-variable, -.cm-s-default .cm-variable-2 { - color: inherit; -} - -.cm-s-default .cm-operator { - color: var(--editor-operator); -} - -.cm-s-default .cm-property { - color: var(--editor-property); -} - -.cm-s-default .cm-string { - color: var(--editor-string); -} - -.cm-s-default .cm-string-2 { - color: var(--editor-string-2); -} - -.cm-s-default .cm-number { - color: var(--editor-number); -} - -.cm-s-default .cm-atom { - color: var(--editor-atom); -} - -.cm-s-default .cm-error { - color: var(--color-red); -} - -.CodeMirror .cm-whitespace::before, -.CodeMirror .CodeMirror-line > span::after, -.CodeMirror .CodeMirror-code > div > pre > span::after { - color: var(--editor-invisible); -} - -.cm-s-default .cm-variable-3, -.cm-s-default .cm-builtin, -.cm-s-default .cm-qualifier, -.cm-s-default .cm-tag { - color: var(--editor-def); -} - -.cm-s-default .cm-searching { - background-color: var(--editor-search-highlight); - padding: 3px 0; -} - -.cm-s-default .cm-search-mark { - border-bottom: 2px solid var(--editor-matching-bracket-border); -} - -/* -.cm-s-default .cm-matchhighlight { - background-color: var(--editor-matched-highlight); -} -*/ - -.CodeMirror-lint-markers { - width: 8px; -} - -.CodeMirror-lint-marker { - border-radius: 50%; - cursor: default; - height: 0.375rem; - left: 0.25rem; - position: relative; - width: 0.375rem; -} - -.CodeMirror-lint-marker-error, -.CodeMirror-lint-marker-warning { - background: var(--color-yellow); - top: -1px; -} - -.CodeMirror-lint-marker-error { - background-color: var(--editor-error); -} - -.CodeMirror-lint-marker-multiple { - background: var(--editor-def); - left: 0; - position: absolute; - top: 0; -} - -.CodeMirror-lint-mark-warning { - background: none; - border-bottom: 2px solid var(--color-yellow); -} - -.CodeMirror-lint-mark-error { - background: none; - border-bottom: 2px solid var(--editor-error); -} - -.CodeMirror-lint-tooltip { - background-color: var(--color-black); - border: 1px solid black; - border-radius: var(--border-radius); - box-shadow: var(--box-shadow); - color: var(--text-color-secondary); - font: var(--text-small); - padding: 0.5rem; - transition: opacity 200ms; -} - -.CodeMirror-lint-message-error, -.CodeMirror-lint-message-warning { - background: none; - padding-left: 1rem; - position: relative; -} - -.CodeMirror-lint-message-error::before, -.CodeMirror-lint-message-warning::before { - background-color: var(--color-yellow); - border-radius: 50%; - content: ""; - height: 0.5rem; - left: 0; - position: absolute; - top: 0.25rem; - width: 0.5rem; -} - -.CodeMirror-lint-message-error::before { - background-color: var(--editor-error); -} diff --git a/src/page/main.js b/src/page/main.js deleted file mode 100644 index 66ad9d11..00000000 --- a/src/page/main.js +++ /dev/null @@ -1,17 +0,0 @@ -import "./app.css"; -import "../shared/reset.css"; -import "../shared/variables.css"; -import App from "./App.svelte"; -import "./cm.css"; - -if (import.meta.env.DEV) { // vite feat that only import in dev mode - const modules = import.meta.glob("../shared/dev.js", {eager: true}); - window.browser = modules["../shared/dev.js"].browser; - console.info("DEV-ENV", import.meta.env, modules, browser); -} - -const app = new App({ - target: document.getElementById("app") -}); - -export default app; diff --git a/src/page/store.js b/src/page/store.js deleted file mode 100644 index a19f83a6..00000000 --- a/src/page/store.js +++ /dev/null @@ -1,132 +0,0 @@ -import {writable} from "svelte/store"; -import {uniqueId} from "../shared/utils.js"; -import * as settingsStorage from "../shared/settings.js"; - -function notificationStore() { - const {subscribe, update} = writable([]); - const add = item => { - update(a => { - a.push(item); - return a; - }); - }; - const remove = id => update(a => a.filter(b => b.id !== id)); - return {subscribe, add, remove}; -} -export const notifications = notificationStore(); - -function logStore() { - const {subscribe, set, update} = writable([]); - const add = (message, type, notify) => { - const item = {id: uniqueId(), message, time: Date.now(), type}; - if (notify || type === "error") notifications.add(item); // always notify on error - update(a => { - a.push(item); - return a; - }); - }; - const remove = id => update(a => a.filter(b => b.id !== id)); - const reset = () => set([]); - return {subscribe, add, remove, reset}; -} -export const log = logStore(); - -function stateStore() { - const {subscribe, update} = writable(["init"]); - // store oldState to see how state transitioned - // ex. if (newState === foo && oldState === bar) baz(); - let oldState = []; - const add = stateModifier => update(state => { - // list of acceptable states, mostly for state definition tracking - const states = [ - "init", // the uninitialized app state (start screen) - "init-error", // unique error when initialization fails, shows error on load screen - "settings", // when the settings modal is shown - "items-loading", // when the sidebar items are loading - "saving", // when a file in the editor is being saved - "fetching", // when a new remote file has been added and it being fetched - "trashing", // when deleting the file in the editor - "updating" // when the file in the editor is being updating - ]; - // disallow adding undefined states - if (!states.includes(stateModifier)) return console.error("invalid state"); - // save pre-changed state to oldState var - oldState = [...state]; - // if current state modifier not present, add it to state array - if (!state.includes(stateModifier)) state.push(stateModifier); - // ready state only when no other states present, remove it if present - if (state.includes("ready")) state.splice(state.indexOf("ready"), 1); - log.add(`App state updated to: ${state} from: ${oldState}`, "info", false); - return state; - }); - const remove = stateModifier => update(state => { - // save pre-changed state to oldState var - oldState = [...state]; - // if current state modifier present, remove it from state array - if (state.includes(stateModifier)) state.splice(state.indexOf(stateModifier), 1); - // if no other states, push ready state - if (state.length === 0) state.push("ready"); - log.add(`App state updated to: ${state} from: ${oldState}`, "info", false); - return state; - }); - const getOldState = () => oldState; - return {subscribe, add, getOldState, remove}; -} -export const state = stateStore(); - -function settingsStore() { - const {subscribe, update, set} = writable({}); - const init = async initData => { - // import legacy settings data just one-time - await settingsStorage.legacyImport(); - // for compatibility with legacy getting names only - // once all new name is used, use settingsStorage.get() - const settings = await settingsStorage.legacyGet(); - console.info("store.js settingsStore init", initData, settings); - set(Object.assign({}, initData, settings)); - // sync popup, backgound, etc... settings changes - settingsStorage.onChanged((s, area) => { - console.log(`store.js storage.${area}.onChanged`, s); - update(obj => Object.assign(obj, s)); - }); - }; - const reset = async keys => { - await settingsStorage.reset(keys); - // once all new name is used, use settingsStorage.get() - const settings = await settingsStorage.legacyGet(); - console.info("store.js settingsStore reset", settings); - update(obj => Object.assign(obj, settings)); - }; - - const updateSingleSettingOld = (key, value) => { - // blacklist not stored in normal setting object in manifest, so handle differently - if (key === "blacklist") { - // update blacklist on swift side - const message = {name: "PAGE_UPDATE_BLACKLIST", blacklist: value}; - browser.runtime.sendNativeMessage(message, response => { - if (response.error) { - log.add("Failed to save blacklist to disk", "error", true); - } - }); - } - if (key === "active") { - browser.runtime.sendNativeMessage({name: "TOGGLE_EXTENSION", active: String(value)}); - } - }; - const updateSingleSetting = (key, value) => { - // update(settings => (settings[key] = value, settings)); - update(settings => { - settings[key] = value; - return settings; - }); - // for compatibility with legacy setting names only - // once all new name is used, use settingsStorage.set() - settingsStorage.legacySet({[key]: value}); // Durable Storage - // temporarily keep the old storage method until it is confirmed that all dependencies are removed - updateSingleSettingOld(key, value); - }; - return {subscribe, set, init, reset, updateSingleSetting}; -} -export const settings = settingsStore(); - -export const items = writable([]); diff --git a/src/popup/App.svelte b/src/popup/App.svelte deleted file mode 100644 index f12154c7..00000000 --- a/src/popup/App.svelte +++ /dev/null @@ -1,728 +0,0 @@ - - - -
    - - showUpdates = true} - title={"Show updates"} - {disabled} - /> - - -
    -{#if !active} - -{/if} -{#if showInstallPrompt} -
    - Userscript - {#if scriptChecking} - {showInstallPrompt} - {:else} - {scriptInstalled ? "Installed" : "Detected"}: - {showInstallPrompt} - {/if} -
    -{/if} -{#if errorNotification} -
    - {errorNotification} - errorNotification = undefined} - title={"Clear error"} - /> -
    -{/if} -
    - {#if loading} - - {:else} - {#if inactive} -
    Popup inactive on extension page
    - {:else if initError} -
    - Something went wrong:  - window.location.reload()} - > - click to retry - -
    - {:else if items.length < 1} -
    No matched userscripts
    - {:else} -
    - {#each list as item, i (item.filename)} - toggleItem(item)} - /> - {/each} -
    - {/if} - {/if} -
    -{#if !inactive && platform === "macos"} - -{/if} -{#if showUpdates} - showUpdates = false} - showLoaderOnDisabled={true} - abortClick={abortUpdates} - abort={showUpdates} - > - - -{:else if showInstall} - showInstall = false} - showLoaderOnDisabled={true} - > - showInstall = false} - installConfirmClick={installConfirm} - /> - -{:else if showAll} - { - showAll = false; - refreshView(); - }} - showLoaderOnDisabled={false} - > - - -{/if} - - diff --git a/src/popup/Components/PopupItem.svelte b/src/popup/Components/PopupItem.svelte deleted file mode 100644 index 2a3e64a3..00000000 --- a/src/popup/Components/PopupItem.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - -
    - -
    {name}
    - {#if subframe}
    SUB
    {/if} - -
    - - diff --git a/src/popup/Components/View.svelte b/src/popup/Components/View.svelte deleted file mode 100644 index 71115f7b..00000000 --- a/src/popup/Components/View.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - -
    -
    - {headerTitle} - -
    -
    - {#if loading && showLoaderOnDisabled} - - {:else} -
    Slot content is required...
    - {/if} -
    -
    - - diff --git a/src/popup/Components/Views/AllItemsView.svelte b/src/popup/Components/Views/AllItemsView.svelte deleted file mode 100644 index a5a59553..00000000 --- a/src/popup/Components/Views/AllItemsView.svelte +++ /dev/null @@ -1,58 +0,0 @@ - - -{#if allItems.length} -
    - {#each list as item (item.filename)} - allItemsToggleItem(item)} - /> - {/each} -
    -{:else} -
    No valid files found in directory
    -{/if} - - diff --git a/src/popup/Components/Views/InstallView.svelte b/src/popup/Components/Views/InstallView.svelte deleted file mode 100644 index e1dca2cb..00000000 --- a/src/popup/Components/Views/InstallView.svelte +++ /dev/null @@ -1,180 +0,0 @@ - - -
    - {#if installError} -
    - {@html iconError} -
    Couldn't install userscript
    -

    {installError}

    -
    - {:else if userscript} -
      -
    • {userscript.name}
    • - {#if userscript.description} -
    • {userscript.description}
    • - {/if} - {#if userscript.match} -
    • -
      @match
      - {#each userscript.match as match} -
      {match}
      - {/each} -
    • - {/if} - {#if userscript.include} -
    • -
      @include
      - {#each userscript.include as include} -
      {include}
      - {/each} -
    • - {/if} - {#if userscript.require} -
    • -
      @require
      - {#each userscript.require as require} -
      {require}
      - {/each} -
    • - {/if} - {#if userscript.grant} -
    • -
      @grant
      - {#each userscript.grant as grant} -
      {grant}
      - {/each} -
    • - {/if} -
    -
    -
    {@html iconWarn}
    -
    Be sure you trust the author before installing. Nefarious code can exploit your security and privacy.
    -
    -
    - - -
    - {/if} -
    - - diff --git a/src/popup/Components/Views/UpdateView.svelte b/src/popup/Components/Views/UpdateView.svelte deleted file mode 100644 index e291ddd4..00000000 --- a/src/popup/Components/Views/UpdateView.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - -{#if updates.length} - {#each updates as item (item.name)} - -
    -
    {item.name}
    - - Source - - updateSingleClick(item)}> - Update - -
    - {/each} -

    Be sure you trust the author before saving remote code to your device.

    - -{:else} -
    - {@html iconUpdate} -
    - There are no file updates available -
    - -
    -
    -{/if} - - diff --git a/src/popup/app.css b/src/popup/app.css deleted file mode 100644 index a2df9b8e..00000000 --- a/src/popup/app.css +++ /dev/null @@ -1,85 +0,0 @@ -html { - font-size: 100%; - overflow: hidden; -} - -body { - background-color: var(--color-bg-secondary); - color: var(--text-color-primary); - font: var(--text-medium); - letter-spacing: var(--letter-spacing-medium); - position: relative; - text-rendering: optimizelegibility; - width: 18rem; - -webkit-font-smoothing: antialiased; -} - -/* attempts to detect iOS */ -@media (hover: none) { - html { - font-size: 125%; - } - - body { - width: 100vw; - } -} - -@media screen and (min-device-width: 481px) and (orientation: portrait) and (hover: none) { - body { - width: 18rem; - } -} - -@media screen and (min-device-width: 1024px) and (orientation: landscape) and (hover: none) { - body { - width: 18rem; - } -} - -body.ipados { - width: 18rem; - height: 100vh; -} - -body.ipados #app { - height: 100%; -} - -body.ios { - height: 100vh; - width: 100vw; -} - -body.ios #app { - height: 100%; -} - -noscript { - display: block; -} - -button { - border: none; - cursor: pointer; - padding: 0; - user-select: none; -} - -button:disabled { - opacity: var(--opacity-disabled); - pointer-events: none; -} - -a, -.link { - color: var(--color-blue); - cursor: pointer; - text-decoration: underline; -} - -.truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} diff --git a/src/popup/main.js b/src/popup/main.js deleted file mode 100644 index 03752163..00000000 --- a/src/popup/main.js +++ /dev/null @@ -1,39 +0,0 @@ -import "./app.css"; -import "../shared/reset.css"; -import "../shared/variables.css"; -import App from "./App.svelte"; - -if (import.meta.env.DEV) { // vite feat that only import in dev mode - const modules = import.meta.glob("../shared/dev.js", {eager: true}); - window.browser = modules["../shared/dev.js"].browser; - console.info("DEV-ENV", import.meta.env, modules, browser); - // macos popup simulation - const style = document.createElement("style"); - style.textContent = ` -body { - top: 20px; - left: 20px; - box-sizing: content-box; - border: 2px solid #0c0e0f; - border-radius: 10px; - box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.2); -} -body:before { - content: ""; - position: absolute; - top: -1px; - left: -1px; - right: -1px; - bottom: -1px; - border: 1px solid #54575a; - border-radius: 9px; -} -`; - browser.platform === "macos" && document.head.append(style); -} - -const app = new App({ - target: document.getElementById("app") -}); - -export default app; diff --git a/src/shared/Components/Dropdown.svelte b/src/shared/Components/Dropdown.svelte index 76304f39..f9142c3f 100644 --- a/src/shared/Components/Dropdown.svelte +++ b/src/shared/Components/Dropdown.svelte @@ -1,100 +1,127 @@
    - -
      - -
    • At least one slot is required...
    • -
      -
    + +
      + +
    • At least one slot is required...
    • +
      +
    diff --git a/src/shared/Components/IconButton.svelte b/src/shared/Components/IconButton.svelte index 40197ace..52ec42cc 100644 --- a/src/shared/Components/IconButton.svelte +++ b/src/shared/Components/IconButton.svelte @@ -1,64 +1,78 @@ diff --git a/src/shared/Components/Loader.svelte b/src/shared/Components/Loader.svelte index de410c40..f891a160 100644 --- a/src/shared/Components/Loader.svelte +++ b/src/shared/Components/Loader.svelte @@ -1,41 +1,61 @@ -
    - {@html iconLoader} - {#if abort} -
    - Fetching resources, cancel request -
    - {/if} +
    + + {@html iconLoader} + {#if abort} +
    + Fetching resources, +
    + {/if}
    diff --git a/src/shared/Components/LogoIcon.svelte b/src/shared/Components/LogoIcon.svelte new file mode 100644 index 00000000..7b5f74df --- /dev/null +++ b/src/shared/Components/LogoIcon.svelte @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/shared/Components/LogoText.svelte b/src/shared/Components/LogoText.svelte new file mode 100644 index 00000000..e5ad9068 --- /dev/null +++ b/src/shared/Components/LogoText.svelte @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/src/shared/Components/Tag.svelte b/src/shared/Components/Tag.svelte index 13e80cf2..80e00954 100644 --- a/src/shared/Components/Tag.svelte +++ b/src/shared/Components/Tag.svelte @@ -1,55 +1,55 @@
    diff --git a/src/shared/Components/Toggle.svelte b/src/shared/Components/Toggle.svelte index cc26375d..9b1d338f 100644 --- a/src/shared/Components/Toggle.svelte +++ b/src/shared/Components/Toggle.svelte @@ -1,70 +1,68 @@ - - + + diff --git a/src/shared/colors.js b/src/shared/colors.js new file mode 100644 index 00000000..73d9001f --- /dev/null +++ b/src/shared/colors.js @@ -0,0 +1,10 @@ +/** + * Theme colors for console log css + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output} + */ +export const colors = { + inherit: "color: inherit;", + blue: "color: light-dark( #317eff, #006fff);", + green: "color: light-dark( #2bb239, #60f36c);", + yellow: "color: light-dark( #b8722c, #fff600);", +}; diff --git a/src/shared/dev.js b/src/shared/dev.js deleted file mode 100644 index 9b1037d2..00000000 --- a/src/shared/dev.js +++ /dev/null @@ -1,502 +0,0 @@ -import {parse, uniqueId, wait} from "./utils"; - -/** - * - * @param {("css"|"js")} type - * @param {boolean?} updates - * @param {boolean?} longName - * @returns - */ -function generateFile(type, updates = false, longName = false, request = false) { - let canUpdate = updates; - const uid = uniqueId(); - let name = request ? `${uid}-request-${type}` : `${uid}-example-${type}`; - if (longName) name = `${uid}${uid}${uid}-example-${type}`; - const randomDate = +(new Date() - Math.floor(Math.random() * 10000000000)); - let content = "// ==UserScript==" - + `\n// @name ${name}` - + `\n// @description Custom description for userscript with name "${name}"` - + "\n// @match *://*.*" - + "\n// @exclude-match https://github.com/quoid/userscripts" - + "\n// @version 1.0" - + "\n// @noframes" - + "\n// ==/UserScript==" - + `\n\nconsole.log("I am ${name}");`; - if (type === "css") { - content = content.replace("// ==UserScript==", "/* ==UserStyle=="); - content = content.replace("// ==/UserScript==", "==/UserStyle== */"); - content = content.replaceAll("// @", "@"); - content = content.replace( - `console.log("I am ${name}");`, - "#id,\n.class\n.pseudo-element::after {\n color: red\n}" - ); - canUpdate = false; - } - if (request) { - content = content.replace("// @noframes", "// @noframes\n// @run-at request"); - } - if (canUpdate) { - content = content.replace( - "1.0", - "1.0\n// @updateURL https://www.k21p.com/example.user.js" - ); - content = content.replace( - `console.log("I am ${name}");`, - `console.log("I am ${name} and you can update me!");` - ); - } - return { - content, - filename: `${name}.${type}`, - lastModified: randomDate, - name, - request, - type - }; -} - -const files = [ - generateFile("js", true, true), - generateFile("css"), - generateFile("js", false, false, true), - ...Array.from({length: 7}, () => generateFile("js")) -]; - -const _browser = { - delay: 200, - platform: "macos", // ios || macos - runtime: { - getURL() { - return "https://www.example.com/"; - }, - async sendMessage(message, responseCallback) { - const name = message.name; - console.info(`Got message: ${name}`); - let response = {}; - if (name === "REFRESH_SESSION_RULES") { - response = {success: true}; - } - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - }, - async sendNativeMessage(message, responseCallback) { - const name = message.name; - console.info(`Got message: ${name}`); - let response = {}; - if (name === "PAGE_INIT_DATA") { - response = { - saveLocation: "/Users/someone/Library/Directory", - version: "5.0.0", - build: "55" - }; - } else if (name === "PAGE_LEGACY_IMPORT") { - response = { - active: "true", - autoCloseBrackets: "true", - autoHint: "true", - blacklist: [], - descriptions: "true", - languageCode: "en", - lint: "true", - log: "false", - showCount: "true", - showInvisibles: "true", - sortOrder: "lastModifiedDesc", - tabSize: "4" - }; - } else if (name === "PAGE_ALL_FILES") { - response = []; - files.forEach(file => { - const content = file.content; - const parsed = parse(content); - const metadata = parsed.metadata; - const canUpdate = !!(metadata.version && metadata.updateURL); - const scriptData = { - canUpdate, - content, - description: metadata.description ? metadata.description[0] : "", - disabled: false, - filename: file.filename, - lastModified: file.lastModified, - metadata, - name: metadata.name[0], - request: file.request, - // type: file.filename.substring(file.filename.lastIndexOf(".") + 1) - type: file.type - }; - response.push(scriptData); - }); - } else if (name === "TOGGLE_ITEM") { - response = {success: true}; - // response = {error: true}; - } else if (name === "PAGE_UPDATE_SETTINGS") { - response = {success: true}; - } else if (name === "PAGE_UPDATE_BLACKLIST") { - response = {success: true}; - } else if (name === "PAGE_NEW_REMOTE") { - const result = await getRemoteFileContents(message.url); - response = result; - } else if (name === "PAGE_SAVE") { - const newContent = message.content; - const oldFilename = message.item.filename; - const parsed = parse(newContent); - const lastModified = Date.now(); - let canUpdate = false; - // script failed to parse - if (!parsed) { - return {error: "save failed, file has invalid metadata"}; - } - const metaName = parsed.metadata.name[0]; - const newFilename = `${metaName}.${message.item.type}`; - // filename length too long - if (newFilename.length > 255) { - return {error: "save failed, filename too long!"}; - } - - // check if file can be remotely updated - if (parsed.metadata.version && parsed.metadata.updateURL) { - canUpdate = true; - } - - const success = { - canUpdate, - content: newContent, - filename: newFilename, - lastModified, - name - }; - - // add description if in file metadata - if (parsed.metadata.description) { - // const foo = parsed.metadata; - success.description = parsed.metadata.description[0]; - } - // check if declarative network request - if ( - parsed.metadata["run-at"] - && parsed.metadata["run-at"][0] === "request" - ) { - success.request = true; - } - // overwriting - if (newFilename.toLowerCase() === oldFilename.toLowerCase()) { - saveFile(newContent, lastModified, newFilename, oldFilename); - return success; - } - - // not overwriting, check if filename for that type is taken - if (files.find(a => a.filename.toLowerCase() === newFilename.toLowerCase())) { - return {error: "save failed, name already taken!"}; - } - - // not overwriting but all validation passed - saveFile(newContent, lastModified, newFilename, oldFilename); - return success; - } else if (name === "PAGE_UPDATE") { - const url = parse(message.content).metadata.updateURL; - await wait(500); - const result = await getRemoteFileContents(url); - response = result; - // response.error = "Something went wrong!"; - // response.info = "No updates found"; - } else if (name === "TOGGLE_EXTENSION") { - // response = {error: "Failed toggle extension"}; - response = {success: true}; - } else if (name === "POPUP_UPDATE_ALL" || name === "POPUP_UPDATE_SINGLE") { - // response = {error: "Failed refresh scripts"}; - response = { - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, - { - name: "Wikipedia Mobile Redirect", - filename: "Wikipedia Mobile Redirect.js", - disabled: true, - type: "js" - }, - { - name: "A Special Userscript", - filename: "A Special Userscript.js", - disabled: false, - type: "js" - }, - { - name: "CSS Adblock", - filename: "CSS Adblock.css", - disabled: false, - type: "css" - }, - { - name: "New Userscript With a Really Really Long Name", - filename: "New Userscript With a Really Really Long Name.css", - disabled: true, - type: "css" - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "css" - } - ], - updates: [] - }; - } else if (name === "POPUP_CHECK_UPDATES") { - response = { - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - } else if (name === "POPUP_INIT") { - response.initData = { - active: "true", - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js" - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "css" - } - ], - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - } else if (name === "POPUP_MATCHES") { - response = { - matches: [ - ...files, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - subframe: true, - type: "js" - } - ] - }; - } else if (name === "POPUP_UPDATES") { - response = { - updates: [ - { - filename: "Google Images Restored.js", - name: "Google Images Restored", - url: "https://www.k21p.com" - }, - { - filename: "New Userscript With a Really Really Long Name.js", - name: "New Userscript With a Really Really Long Name", - url: "https://www.filmgarb.com" - } - ] - }; - response.updates = []; - } else if (name === "REQ_PLATFORM") { - response = {platform: _browser.platform}; - } else if (name === "POPUP_OPEN_EXTENSION_PAGE") { - response = {error: "Failed to get page url"}; - window.open("https://github.com/quoid/userscripts"); - } else if (name === "OPEN_SAVE_LOCATION") { - if (_browser.platform === "macos") { - response = {success: true}; - } else { - response = { - items: [ - { - name: "Google Images Restored", - filename: "Google Images Restored.js", - disabled: false, - type: "js", - metadata: [] - }, - { - name: "Subframe Script Managerial Staffing Company", - filename: "Subframe Script.js", - disabled: false, - type: "css", - metadata: [] - }, - { - name: "Another Script from Managerial Staffing Company", - filename: "Cool Script.js", - disabled: false, - type: "js", - metadata: [] - } - ] - }; - } - } else if (name === "POPUP_INSTALL_CHECK") { - response = random([ - {success: "Click to install", installed: false}, - {success: "Click to re-install", installed: true} - ]); - response.metadata = { - description: "This userscript re-implements the \"View Image\" and \"Search by image\" buttons into google images.", - grant: ["GM.getValue", "GM.setValue", "GM.xmlHttpRequest"], - match: ["https://www.example.com/*", "https://www.example.com/somethingReallylong/goesRightHere"], - name: "Test Install Userscript", - require: ["https://code.jquery.com/jquery-3.5.1.min.js", "https://code.jquery.com/jquery-1.7.1.min.js"], - source: "https://greasyforx.org/scripts/00000-something-something-long-name/code/Something%20something%20long20name.user.js" - }; - // response.error = "something went wrong (dev)"; - } - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - }, - connectNative: () => ({ - onMessage: { - addListener: () => console.info("connectNative - addListener") - } - }) - }, - tabs: { - getCurrent(/* responseCallback */) { - const response = random([ - {url: "https://www.filmgarb.com/foo.user.js", id: 101}, - {url: `${window.location.origin}/src/shared/dev/DEMO.Alert-URL.user.js`, id: 102}, - {url: `${window.location.origin}/src/shared/dev/DEMO.Alert-URL.user.js`, id: 103}, // increase probability - {url: window.location.href, id: 10} - ]); - console.info("browser.tabs.getCurrent", response); - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - }, - query(message, responseCallback) { - const response = [{url: "https://www.filmgarb.com/foo.user.js", id: 101}]; - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - }, - sendMessage(tabId, message, responseCallback) { - let response = {}; - if (message.name === "DEMO_MSG") { - response = {}; - // response.error = "something went wrong (dev)"; - } - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - } - }, - webNavigation: { - getAllFrames(message, responseCallback) { - const response = []; - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - } - }, - storage: { - local: { - get(items, responseCallback) { - const response = {}; - if (!responseCallback) { - return new Promise(resolve => { - setTimeout(() => resolve(response), _browser.delay); - }); - } - setTimeout(() => responseCallback(response), _browser.delay); - }, - set() { - return new Promise(resolve => { - setTimeout(() => resolve(), _browser.delay); - }); - }, - remove: () => new Promise(resolve => { - setTimeout(() => resolve(), _browser.delay); - }), - onChanged: { - addListener: () => undefined - } - } - } -}; - -async function getRemoteFileContents(url) { - const result = {}; - await fetch(url).then(response => { - if (!response.ok) throw Error(response.statusText); - return response.text(); - }).then(text => { - result.content = text; - }).catch(error => { - console.error(error); - result.error = "Remote url bad response!"; - }); - return result; -} - -function saveFile(content, lastMod, newFilename, oldName) { - const ind = files.findIndex(f => f.filename === oldName); - const s = { - content, - filename: newFilename, - lastModified: lastMod - }; - if (ind !== -1) { - // overwrite at index - files[ind] = s; - } else { - // add to beginning of array - files.unshift(s); - } -} - -function random(array) { - return array[Math.floor(Math.random() * array.length)]; -} - -export const browser = _browser; - -export default { - browser: _browser -}; diff --git a/src/shared/img/icon-bug.svg b/src/shared/img/icon-bug.svg new file mode 100644 index 00000000..400d41b4 --- /dev/null +++ b/src/shared/img/icon-bug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/shared/img/icon-loader.svg b/src/shared/img/icon-loader.svg index b75290bb..64cc3f45 100644 --- a/src/shared/img/icon-loader.svg +++ b/src/shared/img/icon-loader.svg @@ -1,4 +1,4 @@ - + diff --git a/src/shared/img/icon-sidebar.svg b/src/shared/img/icon-sidebar.svg new file mode 100644 index 00000000..8fe91fb9 --- /dev/null +++ b/src/shared/img/icon-sidebar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/shared/img/logo.svg b/src/shared/img/logo.svg deleted file mode 100644 index 9bf95127..00000000 --- a/src/shared/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/shared/jsconfig.json b/src/shared/jsconfig.json new file mode 100644 index 00000000..eb649856 --- /dev/null +++ b/src/shared/jsconfig.json @@ -0,0 +1,30 @@ +// https://code.visualstudio.com/docs/languages/jsconfig +// https://www.typescriptlang.org/docs/handbook/tsconfig-json.html +// https://www.typescriptlang.org/tsconfig + +// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html +// https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html + +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "sourceMap": true, + + "paths": { + "@shared/*": ["./*"] + } + }, + "include": ["**/*.d.ts", "**/*.js", "**/*.svelte"] +} diff --git a/src/shared/reset.css b/src/shared/reset.css index 9ea2b45a..c3329b07 100644 --- a/src/shared/reset.css +++ b/src/shared/reset.css @@ -1,53 +1,53 @@ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } body, p { - margin: 0; + margin: 0; } ul { - list-style: none; - margin: 0; - padding: 0; + list-style: none; + margin: 0; + padding: 0; } button, input, select, textarea { - font-family: inherit; - font-size: 100%; - line-height: inherit; - margin: 0; + font-family: inherit; + font-size: 100%; + line-height: inherit; + margin: 0; } button, input[type="button"], input[type="reset"], input[type="submit"] { - -webkit-appearance: button; + -webkit-appearance: button; } input[type="search"] { - -webkit-appearance: textfield; + -webkit-appearance: textfield; } ::-webkit-input-placeholder { - color: inherit; - opacity: 0.54; + color: inherit; + opacity: 0.54; } ::-webkit-search-decoration, ::-webkit-search-cancel-button, ::-webkit-search-results-button, ::-webkit-search-results-decoration { - -webkit-appearance: none; + -webkit-appearance: none; } img { - border-style: none; + border-style: none; } diff --git a/src/shared/settings.js b/src/shared/settings.js deleted file mode 100644 index 127f1bdb..00000000 --- a/src/shared/settings.js +++ /dev/null @@ -1,682 +0,0 @@ -// wrap a relatively independent settings storage with its own functions - -const storagePrefix = "US_"; -const storageKey = key => storagePrefix + key.toUpperCase(); -// const storageRef = async area => { // dynamic storage reference -// browser.storage.sync.area = "sync"; -// browser.storage.local.area = "local"; -// if (area === "sync") return browser.storage.sync; -// if (area === "local") return browser.storage.local; -// const key = storageKey("settings_sync"); -// const result = await browser.storage.local.get(key); -// if (result?.[key] === true) { -// return browser.storage.sync; -// } else { -// return browser.storage.local; -// } -// }; - -// https://developer.apple.com/documentation/safariservices/safari_web_extensions/assessing_your_safari_web_extension_s_browser_compatibility#3584139 -// since storage sync is not implemented in Safari, currently only returns using local storage -const storageRef = async () => { - browser.storage.local.area = "local"; - return browser.storage.local; -}; - -const settingDefault = deepFreeze({ - name: "setting_default", - type: undefined, - local: false, - values: [], - default: undefined, - protect: false, - confirm: false, - platforms: ["macos", "ipados", "ios"], - langLabel: {}, - langTitle: {}, - group: "", - legacy: "", - nodeType: "", - nodeClass: {} -}); - -export const settingsDefine = deepFreeze([ - { - name: "legacy_imported", - type: "number", - local: true, - default: 0, - protect: true, - platforms: ["macos"], - group: "Internal" - }, - { - name: "language_code", - type: "string", - default: "en", - platforms: ["macos", "ipados", "ios"], - group: "Internal", - legacy: "languageCode" - }, - { - name: "scripts_settings", - type: "object", - default: {}, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Scripts update check active", - zh_hans: "脚本更新检查激活" - }, - langTitle: { - en: "Whether to enable each single script update check", - zh_hans: "是否开启单个脚本更新检查" - }, - group: "Internal", - nodeType: "Subpage" - }, - // { - // name: "settings_sync", - // type: "boolean", - // local: true, - // default: false, - // protect: true, - // platforms: ["macos", "ipados", "ios"], - // langLabel: { - // en: "Sync settings", - // zh_hans: "同步设置" - // }, - // langTitle: { - // en: "Sync settings across devices", - // zh_hans: "跨设备同步设置" - // }, - // group: "General", - // nodeType: "Toggle" - // }, - { - name: "toolbar_badge_count", - type: "boolean", - default: true, - platforms: ["macos", "ipados"], - langLabel: { - en: "Show Toolbar Count", - zh_hans: "工具栏图标显示计数徽章" - }, - langTitle: { - en: "displays a badge on the toolbar icon with a number that represents how many enabled scripts match the url for the page you are on", - zh_hans: "简体中文描述" - }, - group: "General", - legacy: "showCount", - nodeType: "Toggle" - }, - { - name: "global_active", - type: "boolean", - local: true, - default: true, - platforms: ["macos"], - langLabel: { - en: "Enable Injection", - zh_hans: "启用注入" - }, - langTitle: { - en: "toggle on/off script injection for the pages you visit", - zh_hans: "简体中文描述" - }, - group: "General", - legacy: "active", - nodeType: "Toggle", - nodeClass: {red: false} - }, - { - name: "global_scripts_update_check", - type: "boolean", - default: true, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Global scripts update check", - zh_hans: "全局脚本更新检查" - }, - langTitle: { - en: "Whether to enable global periodic script update check", - zh_hans: "是否开启全局定期脚本更新检查" - }, - group: "General", - nodeType: "Toggle" - }, - { - name: "scripts_update_check_interval", - type: "number", - default: 86400000, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Scripts update check interval", - zh_hans: "脚本更新检查间隔" - }, - langTitle: { - en: "The interval for script update check in background", - zh_hans: "脚本更新检查的间隔时间" - }, - group: "General", - nodeType: "Toggle" - }, - { - name: "scripts_update_check_lasttime", - type: "number", - default: 0, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Scripts update check lasttime", - zh_hans: "脚本更新上次检查时间" - }, - langTitle: { - en: "The lasttime for script update check in background", - zh_hans: "后台脚本更新上次检查时间" - }, - group: "Internal", - nodeType: "Toggle" - }, - { - name: "scripts_auto_update", - type: "boolean", - default: false, - confirm: true, - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Scripts silent auto update", - zh_hans: "脚本后台静默自动更新" - }, - langTitle: { - en: "Script silently auto-updates in the background, which is dangerous and may introduce unconfirmed malicious code", - zh_hans: "脚本在后台静默自动更新,这是危险的,可能引入未经确认的恶意代码" - }, - group: "General", - nodeType: "Toggle", - nodeClass: {warn: true} - }, - { - name: "global_exclude_match", - type: "object", - default: [], - platforms: ["macos", "ipados", "ios"], - langLabel: { - en: "Global exclude match patterns", - zh_hans: "全局排除匹配模式列表" - }, - langTitle: { - en: "this input accepts a comma separated list of @match patterns, a page url that matches against a pattern in this list will be ignored for script injection", - zh_hans: "简体中文描述" - }, - group: "General", - legacy: "blacklist", - nodeType: "textarea", - nodeClass: {red: "blacklistError"} - }, - { - name: "editor_close_brackets", - type: "boolean", - default: true, - platforms: ["macos"], - langLabel: { - en: "Auto Close Brackets", - zh_hans: "自动关闭括号" - }, - langTitle: { - en: "toggles on/off auto closing of brackets in the editor, this affects the following characters: () [] {} \"\" ''", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "autoCloseBrackets", - nodeType: "Toggle" - }, - { - name: "editor_auto_hint", - type: "boolean", - default: true, - platforms: ["macos"], - langLabel: { - en: "Auto Hint", - zh_hans: "自动提示(Hint)" - }, - langTitle: { - en: "automatically shows completion hints while editing", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "autoHint", - nodeType: "Toggle" - }, - { - name: "editor_list_sort", - type: "string", - values: ["nameAsc", "nameDesc", "lastModifiedAsc", "lastModifiedDesc"], - default: "lastModifiedDesc", - platforms: ["macos"], - langLabel: { - en: "Sort order", - zh_hans: "排序顺序" - }, - langTitle: { - en: "Display order of items in sidebar", - zh_hans: "侧栏中项目的显示顺序" - }, - group: "Editor", - legacy: "sortOrder", - nodeType: "Dropdown" - }, - { - name: "editor_list_descriptions", - type: "boolean", - default: true, - platforms: ["macos"], - langLabel: { - en: "Show List Descriptions", - zh_hans: "显示列表项目描述" - }, - langTitle: { - en: "show or hides the item descriptions in the sidebar", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "descriptions", - nodeType: "Toggle" - }, - { - name: "editor_javascript_lint", - type: "boolean", - default: false, - platforms: ["macos"], - langLabel: { - en: "Javascript Linter", - zh_hans: "Javascript Linter" - }, - langTitle: { - en: "toggles basic Javascript linting within the editor", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "lint", - nodeType: "Toggle" - }, - { - name: "editor_show_whitespace", - type: "boolean", - default: true, - platforms: ["macos"], - langLabel: { - en: "Show whitespace characters", - zh_hans: "显示空白字符" - }, - langTitle: { - en: "toggles the display of invisible characters in the editor", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "showInvisibles", - nodeType: "Toggle" - }, - { - name: "editor_tab_size", - type: "number", - values: [2, 4], - default: 4, - platforms: ["macos"], - langLabel: { - en: "Tab Size", - zh_hans: "制表符大小" - }, - langTitle: { - en: "the number of spaces a tab is equal to while editing", - zh_hans: "简体中文描述" - }, - group: "Editor", - legacy: "tabSize", - nodeType: "select" - } -].reduce(settingsDefineReduceCallback, {})); - -// populate the settingsDefine with settingDefault -// and convert settingsDefine to storageKey object -function settingsDefineReduceCallback(settings, setting) { - setting.key = storageKey(setting.name); - settings[setting.key] = {...settingDefault, ...setting}; - return settings; -} - -// prevent settings define from being modified in any case -// otherwise user settings may be lost in the worst case -function deepFreeze(object) { - for (const p in object) { - if (typeof object[p] == "object") { - deepFreeze(object[p]); - } - } - return Object.freeze(object); -} - -// export and define the operation method of settings storage -// they are similar to browser.storage but slightly different - -export async function get(keys, area) { - if (![undefined, "local", "sync"].includes(area)) { - return console.error("Unexpected storage area:", area); - } - // validate setting value and fix surprises to default - const valueFix = (key, val) => { - if (!key || !Object.hasOwn(settingsDefine, key)) return; - const def = settingsDefine[key].default; - // check if value type conforms to settingsDefine - const type = settingsDefine[key].type; - // eslint-disable-next-line valid-typeof -- type known to be valid string literal - if (typeof val != type) { - console.warn(`Unexpected ${key} value type '${typeof val}' should '${type}', fix to default`); - return def; - } - // check if value conforms to settingsDefine - const values = settingsDefine[key].values; - if (values.length && !values.includes(val)) { - console.warn(`Unexpected ${key} value '${val}' should one of '${values}', fix to default`); - return def; - } - // verified, pass original value - return val; - }; - if (typeof keys == "string") { // [single setting] - const key = storageKey(keys); - // check if key exist in settingsDefine - if (!Object.hasOwn(settingsDefine, key)) { - return console.error("unexpected settings key:", key); - } - // check if only locally stored setting - // eslint-disable-next-line no-param-reassign -- change the area is expected - settingsDefine[key].local === true && (area = "local"); - const storage = await storageRef(area); - const result = await storage.get(key); - if (Object.hasOwn(result, key)) return valueFix(key, result[key]); - return settingsDefine[key].default; - } - const complexGet = async (settingsDefault, areaKeys) => { - const storage = await storageRef(area); - let local = {}, sync = {}; - if (storage.area === "sync") { - if (areaKeys.sync.length) { - sync = await storage.get(areaKeys.sync); - } - if (areaKeys.local.length) { - const storageLocal = await storageRef("local"); - local = await storageLocal.get(areaKeys.local); - } - } else { - local = await storage.get(areaKeys.all); - } - const result = Object.assign(settingsDefault, local, sync); - // revert settings object property name - return Object.entries(result).reduce((p, c) => { - p[settingsDefine[c[0]].name] = valueFix(...c); - return p; - }, {}); - }; - if (Array.isArray(keys)) { // [muilt settings] - if (!keys.length) { - return console.error("Settings keys empty:", keys); - } - const settingsDefault = {}; - const areaKeys = {local: [], sync: [], all: []}; - for (const k of keys) { - const key = storageKey(k); - // check if key exist in settingsDefine - if (!Object.hasOwn(settingsDefine, key)) { - return console.error("unexpected settings key:", key); - } - settingsDefault[key] = settingsDefine[key].default; - // detach only locally stored settings - settingsDefine[key].local === true - ? areaKeys.local.push(key) - : areaKeys.sync.push(key); - // record all keys in case sync storage is not enabled - areaKeys.all.push(key); - } - return complexGet(settingsDefault, areaKeys); - } - if (typeof keys == "undefined" || keys === null) { // [all settings] - const settingsDefault = {}; - const areaKeys = {local: [], sync: [], all: []}; - for (const key of Object.keys(settingsDefine)) { - settingsDefault[key] = settingsDefine[key].default; - // detach only locally stored settings - settingsDefine[key].local === true - ? areaKeys.local.push(key) - : areaKeys.sync.push(key); - // record all keys in case sync storage is not enabled - areaKeys.all.push(key); - } - return complexGet(settingsDefault, areaKeys); - } - return console.error("Unexpected keys type:", keys); -} - -export async function set(keys, area) { - if (![undefined, "local", "sync"].includes(area)) { - return console.error("unexpected storage area:", area); - } - if (typeof keys != "object") { - return console.error("Unexpected keys type:", keys); - } - if (!Object.keys(keys).length) { - return console.error("Settings object empty:", keys); - } - const areaKeys = {local: {}, sync: {}, all: {}}; - for (const k of Object.keys(keys)) { - const key = storageKey(k); - // check if key exist in settingsDefine - if (!Object.hasOwn(settingsDefine, key)) { - return console.error("Unexpected settings keys:", key); - } - // check if value type conforms to settingsDefine - const type = settingsDefine[key].type; - // eslint-disable-next-line valid-typeof -- type known to be valid string literal - if (typeof keys[k] != type) { - if (type === "number" && !Number.isNaN(Number(keys[k]))) { // compatible with string numbers - keys[k] = Number(keys[k]); // still store it as a number type - } else { - return console.error(`Unexpected ${k} value type '${typeof keys[k]}' should '${type}'`); - } - } - // check if value conforms to settingsDefine - const values = settingsDefine[key].values; - if (values.length && !values.includes(keys[k])) { - return console.error(`Unexpected ${k} value '${keys[k]}' should one of '${values}'`); - } - // detach only locally stored settings - settingsDefine[key].local === true - ? areaKeys.local[key] = keys[k] - : areaKeys.sync[key] = keys[k]; - // record all keys in case sync storage is not enabled - areaKeys.all[key] = keys[k]; - } - const storage = await storageRef(area); - // complexSet - try { - if (storage.area === "sync") { - if (Object.keys(areaKeys.sync).length) { - await storage.set(areaKeys.sync); - } - if (Object.keys(areaKeys.local).length) { - const storageLocal = await storageRef("local"); - await storageLocal.set(areaKeys.local); - } - } else { - await storage.set(areaKeys.all); - } - return true; - } catch (error) { - return console.error(error); - } -} - -export async function reset(keys, area) { // reset to default - if (![undefined, "local", "sync"].includes(area)) { - return console.error("unexpected storage area:", area); - } - if (typeof keys == "string") { // [single setting] - const key = storageKey(keys); - // check if key exist in settingsDefine - if (!Object.hasOwn(settingsDefine, key)) { - return console.error("unexpected settings key:", key); - } - // check if key is protected - if (settingsDefine[key].protect === true) { - return console.error("protected settings key:", key); - } - // eslint-disable-next-line no-param-reassign -- change the area is expected - settingsDefine[key].local === true && (area = "local"); - const storage = await storageRef(area); - return storage.remove(key); - } - const complexRemove = async areaKeys => { - const storage = await storageRef(area); - try { - if (storage.area === "sync") { - if (areaKeys.sync.length) { - await storage.remove(areaKeys.sync); - } - if (areaKeys.local.length) { - const storageLocal = await storageRef("local"); - await storageLocal.remove(areaKeys.local); - } - } else { - await storage.remove(areaKeys.all); - } - return true; - } catch (error) { - return console.error(error); - } - }; - if (Array.isArray(keys)) { // [muilt settings] - if (!keys.length) { - return console.error("Settings keys empty:", keys); - } - const areaKeys = {local: [], sync: [], all: []}; - for (const k of keys) { - const key = storageKey(k); - // check if key exist in settingsDefine - if (!Object.hasOwn(settingsDefine, key)) { - return console.error("unexpected settings key:", key); - } - // check if key is protected - if (settingsDefine[key].protect === true) { - return console.error("protected settings key:", key); - } - // detach only locally stored settings - settingsDefine[key].local === true - ? areaKeys.local.push(key) - : areaKeys.sync.push(key); - // record all keys in case sync storage is not enabled - areaKeys.all.push(key); - } - return complexRemove(areaKeys); - } - if (typeof keys == "undefined" || keys === null) { // [all settings] - const areaKeys = {local: [], sync: [], all: []}; - for (const key in settingsDefine) { - // skip protected keys - if (settingsDefine[key].protect === true) continue; - // detach only locally stored settings - settingsDefine[key].local === true - ? areaKeys.local.push(key) - : areaKeys.sync.push(key); - // record all keys in case sync storage is not enabled - areaKeys.all.push(key); - } - return complexRemove(areaKeys); - } - return console.error("Unexpected keys type:", keys); -} - -// this function is convenient for the svelte store to update the state -export function onChanged(callback) { // complex onChanged - if (typeof callback != "function") { - return console.error("Unexpected callback:", callback); - } - console.info("storage onChanged addListener"); - const handle = (changes, area) => { - // console.log(`storage.${area}.onChanged`, changes); - try { - const settings = {}; - for (const key in changes) { - if (!Object.hasOwn(settingsDefine, key)) continue; - settings[settingsDefine[key].name] = changes[key].newValue; - } - callback(settings, area); - } catch (error) { - console.error("onChanged callback:", error); - } - }; - // comment for now same reason as `storageRef` function - // browser.storage.sync.onChanged.addListener(c => handle(c, "sync")); - browser.storage.local.onChanged.addListener(c => handle(c, "local")); -} - -// the following functions are used only for compatibility transition periods -// these functions will be removed in the future, perhaps in version 5.0 - -export async function legacyGet(keys) { - const result = await get(keys); - // console.log("legacy_get", keys, result); - for (const key of Object.keys(result)) { - const legacy = settingsDefine[storageKey(key)]?.legacy; - if (legacy) result[legacy] = result[key]; - } - return result; -} - -export async function legacySet(keys) { - if (typeof keys != "object") { - return console.error("Unexpected arg type:", keys); - } - if (!Object.keys(keys).length) { - return console.error("Settings object empty:", keys); - } - const settings = {}; - for (const key of Object.keys(settingsDefine)) { - const setting = settingsDefine[key]; - if (!setting.legacy) continue; - if (setting.legacy in keys) { - settings[setting.name] = keys[setting.legacy]; - } - } - // console.log("legacy_set", keys, settings); - return set(settings); -} - -export async function legacyImport() { - // if legacy data has already been imported, skip this process - const imported = await get("legacy_imported"); - if (imported) return console.info("Legacy settings has already imported"); - // start the one-time import process - const result = await browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORT"}); - if (result.error) return console.error(result.error); - console.info("Import settings data from legacy manifest file"); - const settings = {}; - for (const key of Object.keys(settingsDefine)) { - const legacy = settingsDefine[key].legacy; - if (legacy in result) { - let value = result[legacy]; - switch (settingsDefine[key].type) { - case "boolean": value = JSON.parse(value); break; - case "number": value = Number(value); break; - } - console.info(`Importing legacy setting: ${legacy}`, value); - settings[settingsDefine[key].name] = value; - } - } - // import complete tag, to ensure will only be import once - Object.assign(settings, {legacy_imported: Date.now()}); - if (await set(settings, "local")) { - console.info("Import legacy settings complete"); - // send a message to the Swift layer to safely clean up legacy data - // browser.runtime.sendNativeMessage({name: "PAGE_LEGACY_IMPORTED"}); - return true; - } - return console.error("Import legacy settings abort"); -} diff --git a/src/shared/utils.js b/src/shared/utils.js deleted file mode 100644 index 9f73ff56..00000000 --- a/src/shared/utils.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @param {number} ms millisecond timestamp - * @returns {string} - */ -export function formatDate(ms) { - const d = new Date(ms); - const yr = new Intl.DateTimeFormat("en", {year: "numeric"}).format(d); - const mo = new Intl.DateTimeFormat("en", {month: "short"}).format(d); - const dd = new Intl.DateTimeFormat("en", {day: "2-digit"}).format(d); - const hr = d.getHours(); - const mn = d.getMinutes(); - return `${mo} ${dd}, ${yr} at ${hr}:${mn}`; -} - -export function uniqueId() { - return Math.random().toString(36).substring(2, 10); -} - -/** - * awaitable function for waiting an arbitrary amount of time - * @param {number} ms the amount of time to wait in milliseconds - * @returns {Promise} - */ -export function wait(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); -} - -// TODO: describe the items array that should get passed to this function -/** - * @param {Array} array - * @param {("lastModifiedAsc"|"lastModifiedDesc"|"nameAsc"|"nameDesc")} order - * @returns - */ -export function sortBy(array, order) { - if (order === "nameAsc") { - array.sort((a, b) => a.name.localeCompare(b.name)); - } else if (order === "nameDesc") { - array.sort((a, b) => b.name.localeCompare(a.name)); - } else if (order === "lastModifiedAsc") { - array.sort((a, b) => (a.lastModified < b.lastModified) ? -1 : 1); - } else if (order === "lastModifiedDesc") { - array.sort((a, b) => (a.lastModified > b.lastModified) ? -1 : 1); - } - // always keep temp file pinned to the top, should only ever have one temp script - // if (array.find(f => f.temp)) array.sort((a, b) => a.temp ? -1 : b.temp ? 1 : 0); - return array; -} - -/** - * - * @param {string} description - * @param {string} name - * @param {("css"|"js")} type - * @returns {string} - */ -export function newScriptDefault(description, name, type) { - if (type === "css") { - return `/* ==UserStyle==\n@name ${name}\n@description ${description}\n@match \n==/UserStyle== */`; - } - return `// ==UserScript==\n// @name ${name}\n// @description ${description}\n// @match *://*/*\n// ==/UserScript==`; -} - -/** - * @param {string} str - * @returns {?{code: string, content: str, metablock: string, metadata: {string: string[]}}} - */ -export function parse(str) { - if (typeof str != "string") return null; - const blocksReg = /(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))/; - const blocks = str.match(blocksReg); - - if (!blocks) return null; - - const metablock = (blocks[1] != null) ? blocks[1] : blocks[4]; - const metas = (blocks[2] != null) ? blocks[2] : blocks[5]; - const code = (blocks[3] != null) ? blocks[3].trim() : blocks[6].trim(); - - const metadata = {}; - const metaArray = metas.split("\n"); - metaArray.forEach(m => { - const parts = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)/); - const parts2 = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$/); - if (parts) { - metadata[parts[1]] = metadata[parts[1]] || []; - metadata[parts[1]].push(parts[2]); - } else if (parts2) { - metadata[parts2[1]] = metadata[parts2[1]] || []; - metadata[parts2[1]].push(true); - } - }); - // fail if @name is missing or name is empty - if (!metadata.name || metadata.name[0].length < 2) return; - - return { - code, - content: str, - metablock, - metadata - }; -} - -/** - * @param {string} text editor code - * @returns {{match: boolean, meta: boolean} | {key: string, value: string, text: string}[]} - */ -export function parseMetadata(text) { - const groupsRe = /(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)/; - const groups = text.match(groupsRe); - // userscript code doesn't match the regex expression - // could be missing opening/closing tags, malformed - // or missing metadata between opening/closing tags (group 2 in regex exp) - if (!groups) { - return {match: false}; - } - - // userscript code matches but content between opening/closing tag missing - // ex. opening/closing tags present, but newline characters between the tags - const metas = groups[2]; - if (!metas) return {match: true, meta: false}; - - const metadata = []; - const metaArray = metas.split("\n"); - - for (let i = 0; i < metaArray.length; i++) { - const metaRegex = /^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]*([^\s]+[^\r\n\t\v\f]*)?/; - const meta = metaArray[i]; - const parts = meta.match(metaRegex); - if (parts) metadata.push({key: parts[1], value: parts[2], text: parts[0]}); - } - - // if there is content between the opening/closing tags, match will be found - // this additionally checks that there's at least one properly formed key - // if not keys found, assume metadata is missing - // checking that required keys are present will happen elsewhere - if (!Object.keys(metadata).length) return {match: true, meta: false}; - - return metadata; -} - -export const validGrants = new Set([ - "GM.info", - "GM_info", - "GM.addStyle", - "GM.openInTab", - "GM.closeTab", - "GM.setValue", - "GM.getValue", - "GM.deleteValue", - "GM.listValues", - "GM.setClipboard", - "GM.getTab", - "GM.saveTab", - "GM_xmlhttpRequest", - "GM.xmlHttpRequest", - "none" -]); - -export const validMetaKeys = new Set([ - "author", - "description", - "downloadURL", - "exclude", - "exclude-match", - "grant", - "icon", - "include", - "inject-into", - "match", - "name", - "noframes", - "require", - "run-at", - "updateURL", - "version", - "weight" -]); - -export const extensionPaths = { - page: "/dist/entry-page.html", - popup: "/dist/entry-popup.html" -}; - -export async function openExtensionPage() { - const extensionPageUrl = browser.runtime.getURL(extensionPaths.page); - const tabs = await browser.tabs.query({}); - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].url === extensionPageUrl) { - await browser.windows.update(tabs[i].windowId, {focused: true}); - await browser.tabs.update(tabs[i].id, {active: true}); - return; - } - } - await browser.tabs.create({url: extensionPageUrl}); -} diff --git a/src/shared/variables.css b/src/shared/variables.css index acdc671d..f014eba2 100644 --- a/src/shared/variables.css +++ b/src/shared/variables.css @@ -1,51 +1,75 @@ +/* Base styles */ :root { - --border-radius: 0.188rem; - --box-shadow: 0 0.5rem 1rem 0 rgb(0 0 0 / 0.25); - --color-bg-primary: #323639; - --color-bg-secondary: #2f3337; - --color-script-highlighted: #364049; /* rgba(116, 178, 235, 0.1); */ - --color-black: #1d2023; - --color-blue: #74b1eb; - --color-green: #60f36c; - --color-grey: rgb(255 255 255 / 0.15); - --color-red: #ff453a; - --color-yellow: #e4f360; - --letter-spacing-large: -0.031rem; - --letter-spacing-default: -0.029rem; - --letter-spacing-medium: -0.018rem; - --letter-spacing-small: -0.008rem; - --opacity-disabled: 0.3; - --text-color-primary: rgb(255 255 255); - --text-color-secondary: rgb(255 255 255 / 0.65); - --text-color-disabled: rgb(255 255 255 / 0.4); - --font-family: system-ui, -apple-system, "Helvetica Neue", "Helvetica", sans-serif; - --text-default: 1rem/1.5rem var(--font-family); - --text-large: 1.25rem/1.5rem var(--font-family); - --text-medium: 0.875rem/1.313rem var(--font-family); - --text-small: 0.719rem/1rem var(--font-family); + /* Layout variables */ + --border-radius: 0.188rem; + --font-family: -apple-system, "Helvetica Neue", "Helvetica", sans-serif; + --letter-spacing-default: -0.029rem; + --letter-spacing-large: -0.031rem; + --letter-spacing-medium: -0.018rem; + --letter-spacing-small: -0.008rem; + --opacity-disabled: 0.3; + --text-default: 1rem/1.5rem var(--font-family); + --text-large: 1.25rem/1.5rem var(--font-family); + --text-medium: 0.875rem/1.313rem var(--font-family); + --text-small: 0.719rem/1rem var(--font-family); - /* editor variables */ - --editor-font: monaco, monospace; - --editor-font-size: 14px; - --editor-line-height: 24px; - --editor-invisible: rgb(255 255 255 / 0.15); - --editor-active-line: var(--color-bg-secondary); - --editor-selected-bg: rgb(116 178 235 / 0.35); - --editor-matched-highlight: rgb(116 178 235 / 0.2); - --editor-search-highlight: rgb(255 166 0 / 0.3); - --editor-number: #77e26a; - --editor-comment: rgb(255 255 255 / 0.35); - --editor-def: #efc371; - --editor-default: #cdcfd1; - --editor-keyword: #96c3ed; - --editor-atom: #59ebf5; - --editor-operator: #8c99a7; - --editor-property: #e86c8a; - --editor-string: #f5eea2; - --editor-string-2: #cdabff; - --editor-error: var(--color-red); - --editor-cursor: #e3e7eb; - --editor-matching-bracket-color: #fff; - --editor-matching-bracket-border: var(--editor-number); - --editor-non-matching-bracket: var(--editor-error); + /* Editor layout variables */ + --editor-font: monaco, monospace; + --editor-font-size: 14px; + --editor-line-height: 24px; + --editor-active-line: var(--color-bg-secondary); + --editor-error: var(--color-red); + --editor-non-matching-bracket: var(--editor-error); + --editor-selected-bg: rgb(116 178 235 / 0.35); + --editor-matched-highlight: rgb(116 178 235 / 0.2); + --editor-search-highlight: rgb(255 166 0 / 0.3); + + /* Color Theme */ + color-scheme: light dark; + --color-bg-primary: light-dark(#fafafa, #323639); + --color-bg-secondary: light-dark(#f1f1f1, #2f3337); + --color-bg-theme: light-dark(var(--color-white), var(--color-black)); + --color-script-highlighted: light-dark(#e2ebf3, #364049); + --color-black: light-dark(#333, #1d2023); + --color-white: light-dark(#fff, #fff); + --color-blue: light-dark(#317eff, #74b1eb); + --color-green: light-dark(#2bb239, #60f36c); + --color-grey: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15)); + --color-red: light-dark(#ff453a, #ff453a); + --color-yellow: light-dark(#b8722c, #e4f360); + --text-color-primary: light-dark(rgb(51 51 51), rgb(255 255 255)); + --text-color-secondary: light-dark( + rgb(51 51 51 / 0.65), + rgb(255 255 255 / 0.65) + ); + --text-color-disabled: light-dark( + rgb(51 51 51 / 0.35), + rgb(255 255 255 / 0.4) + ); + --box-shadow: 0 0.5rem 1rem 0 light-dark(rgb(0 0 0 / 0.15), rgb(0 0 0 / 0.25)); + --button-color: light-dark(var(--color-white), var(--color-black)); + --border-color: light-dark(var(--color-grey), var(--color-black)); + + /* Editor colors */ + --editor-invisible: light-dark(rgb(51 51 51 / 0.15), rgb(255 255 255 / 0.15)); + --editor-number: light-dark(var(--color-green), #77e26a); + --editor-comment: light-dark( + var(--text-color-disabled), + rgb(255 255 255 / 0.35) + ); + --editor-def: light-dark(#b86d17, #efc371); + --editor-default: light-dark(#777, #cdcfd1); + --editor-keyword: light-dark(#3a68ff, #96c3ed); + --editor-atom: light-dark(#00adad, #59ebf5); + --editor-operator: light-dark(#8c99a7, #8c99a7); + --editor-property: light-dark(#ff453a, #e86c8a); + --editor-string: light-dark(var(--editor-atom), #f5eea2); + --editor-string-2: light-dark(#a34fe6, #cdabff); + --editor-cursor: light-dark(#7e7e7f, #e3e7eb); + --editor-matching-bracket-color: light-dark(var(--text-color-primary), #fff); + --editor-matching-bracket-border: light-dark(#ff00ea, var(--editor-number)); + --editor-blurred-background: light-dark( + rgb(255 255 255 / 0.65), + rgb(50 54 57 / 0.65) + ); } diff --git a/src/shared/vite-env.d.ts b/src/shared/vite-env.d.ts new file mode 100644 index 00000000..4078e747 --- /dev/null +++ b/src/shared/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/vite.config.js b/vite.config.js index 044b986d..56037d79 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,44 +1,28 @@ -import {resolve} from "path"; -import {defineConfig} from "vite"; -import {svelte} from "@sveltejs/vite-plugin-svelte"; +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { baseConfig } from "./scripts/utils.js"; -// [inline-svg] -// TODO: remove this once vite resolved issue -// https://github.com/vitejs/vite/issues/1204 -// import fs from "fs"; -// import svgpkg from "svg-inline-loader"; -// export function svgInline() { // custom plugin -// return { -// name: "transform-file", -// transform(code, id) { -// if (id.endsWith(".svg")) { -// const svg = fs.readFileSync(id, "utf8"); -// const ret = svgpkg.getExtractedSVG(svg, {}); -// return `export default '${ret}'`; -// } -// } -// }; -// } -// [inline-svg] -// NOW: use `?raw` suffix import svg assets as inline -// https://vitejs.dev/guide/assets.html#importing-asset-as-string +/** + * About `inline-svg` + * use `?raw` suffix import svg assets as inline + * @see {@link https://vitejs.dev/guide/assets.html#importing-asset-as-string} + * Ignore lint errors `{@html}`, since these are deterministic local svg resources, + * there is no risk of Cross-Site Scripting (XSS) attacks and can be safely disabled. + * @see {@link https://sveltejs.github.io/eslint-plugin-svelte/rules/no-at-html-tags/} + */ -// [autoprefixer] -// https://vitejs.dev/guide/features.html#postcss -// have config with `.postcssrc.json` file -// about `missing peer postcss` error, ignore it +/** + * About `autoprefixer` + * have config with `.postcssrc.json` file + * @see {@link https://vitejs.dev/guide/features.html#postcss} + * about `missing peer postcss` error, ignore it + */ -// https://vitejs.dev/config/ +/** @see {@link https://vitejs.dev/config/} */ export default defineConfig({ - plugins: [svelte()], - base: "./", - build: { - outDir: "xcode/Safari-Extension/Resources/dist/", - rollupOptions: { - input: { - page: resolve(__dirname, "entry-page.html"), - popup: resolve(__dirname, "entry-popup.html") - } - } - } + publicDir: "public/ext/vendor/", + plugins: [svelte()], + base: "./", + define: baseConfig.define, + resolve: baseConfig.resolve, }); diff --git a/xcode/Mac-App/App.entitlements b/xcode/App-Mac/App.entitlements similarity index 100% rename from xcode/Mac-App/App.entitlements rename to xcode/App-Mac/App.entitlements diff --git a/xcode/App-Mac/AppDelegate.swift b/xcode/App-Mac/AppDelegate.swift new file mode 100644 index 00000000..79c286dc --- /dev/null +++ b/xcode/App-Mac/AppDelegate.swift @@ -0,0 +1,97 @@ +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + var window: NSWindow! + private var windowForego = false + private var windowLoaded = false + private let logger = USLogger(#fileID) + + @IBOutlet weak var enbaleNativeLogger: NSMenuItem! + + @objc func handleGetURL(event: NSAppleEventDescriptor, replyEvent: NSAppleEventDescriptor) { + // issue: https://developer.apple.com/forums/thread/697217 +// sendExtensionMessage(name: "URL_SCHEME_STARTED") + // if open panel is already open, stop processing the URL scheme + if NSApplication.shared.keyWindow?.accessibilityIdentifier() == "open-panel" { return } + // handle URL scheme + if let urlString = event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))?.stringValue, + let url = URL(string: urlString) { + logger?.info("\(#function, privacy: .public) - \(urlString, privacy: .public)") + if url.host == "changesavelocation" { + // avoid opening the panel repeatedly and playing unnecessary warning sounds + if NSApplication.shared.keyWindow?.identifier?.rawValue == "changeSaveLocation" { return } + if windowLoaded { + let viewController = window.contentViewController as? ViewController + viewController?.changeSaveLocation() + } else { + windowForego = true + schemeChangeSaveLocation() + } + } + } + } + + // https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428623-applicationwillfinishlaunching/ + func applicationWillFinishLaunching(_ notification: Notification) { + // https://developer.apple.com/documentation/foundation/nsappleeventmanager/1416131-seteventhandler + NSAppleEventManager.shared().setEventHandler( + self, + andSelector: #selector(handleGetURL(event:replyEvent:)), + forEventClass: AEEventClass(kInternetEventClass), + andEventID: AEEventID(kAEGetURL) + ) + } + + // https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428385-applicationdidfinishlaunching + func applicationDidFinishLaunching(_ notification: Notification) { + // Initialize menu items + enbaleNativeLogger.state = Preferences.enableLogger ? .on : .off + // Whether to initialize the main window + logger?.debug("\(#function, privacy: .public) - windowForego: \(self.windowForego, privacy: .public)") + if windowForego { return } + // https://developer.apple.com/documentation/appkit/nswindow/ + window = NSWindow() + window.styleMask = [.titled, .closable, .miniaturizable] +// window.styleMask = [.titled, .closable, .resizable, .miniaturizable] // DEBUG + window.titlebarAppearsTransparent = true + // Initialize webview + window.contentViewController = ViewController() + // https://developer.apple.com/documentation/uikit/appearance_customization/supporting_dark_mode_in_your_interface#2993897 + window.backgroundColor = NSColor(named: NSColor.Name("USBackgroundColor")) +// window.backgroundColor = .clear // DEBUG + window.center() + window.setIsVisible(true) + window.makeMain() + window.makeKeyAndOrderFront(nil) +// window.level = NSWindow.Level.floating // DEBUG + windowLoaded = true + } + + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + return true + } + + func applicationWillTerminate(_ notification: Notification) { + // Insert code here to tear down your application + USLogStoreToFile() + } + + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + @IBAction func enableLogger(_ sender: NSMenuItem) { + let enable = sender.state == .on ? false : true + sender.state = enable ? .on : .off + USLoggerSwitch(enable) + } + + @IBAction func applicationHelp(_ sender: NSMenuItem) { + if let url = URL(string: "https://github.com/quoid/userscripts") { + NSWorkspace.shared.open(url) + } + } + +} diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/1024.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/1024.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/1024.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/1024.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/128.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/128.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/128.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/128.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/16.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/16.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/16.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/16.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/256-1.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/256-1.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/256-1.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/256-1.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/256.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/256.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/256.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/256.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/32-1.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/32-1.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/32-1.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/32-1.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/32.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/32.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/32.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/32.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/512-1.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/512-1.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/512-1.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/512-1.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/512.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/512.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/512.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/512.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/64.png b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/64.png similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/64.png rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/64.png diff --git a/xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/Contents.json b/xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from xcode/Mac-App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to xcode/App-Mac/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/xcode/Mac-App/Assets.xcassets/Contents.json b/xcode/App-Mac/Assets.xcassets/Contents.json similarity index 100% rename from xcode/Mac-App/Assets.xcassets/Contents.json rename to xcode/App-Mac/Assets.xcassets/Contents.json diff --git a/xcode/Mac-App/Base.lproj/Main.storyboard b/xcode/App-Mac/Base.lproj/Main.storyboard similarity index 57% rename from xcode/Mac-App/Base.lproj/Main.storyboard rename to xcode/App-Mac/Base.lproj/Main.storyboard index ed7ecf33..859f7dde 100644 --- a/xcode/Mac-App/Base.lproj/Main.storyboard +++ b/xcode/App-Mac/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -30,13 +30,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/xcode/App-Mac/Functions.swift b/xcode/App-Mac/Functions.swift new file mode 100644 index 00000000..a6608954 --- /dev/null +++ b/xcode/App-Mac/Functions.swift @@ -0,0 +1,76 @@ +import AppKit +import SafariServices + +private let logger = USLogger(#fileID) + +func getSaveLocationURL() -> URL { + return Preferences.scriptsDirectoryUrl +} + +func setSaveLocationURL(url: URL) -> Bool { + guard FileManager.default.isReadableFile(atPath: url.path) else { + let alert = NSAlert() + alert.messageText = "Cannot read the specified path; please choose a different one." + alert.runModal() + return false + } + guard FileManager.default.isWritableFile(atPath: url.path) else { + let alert = NSAlert() + alert.messageText = "Cannot write to the specified path; please choose a different one." + alert.runModal() + return false + } + Preferences.scriptsDirectoryUrl = url + return Preferences.scriptsDirectoryUrl == url +} + +func sendExtensionMessage(name: String, userInfo: [String : Any]? = nil, completion: ((Error?) -> Void)? = nil) { + SFSafariApplication.dispatchMessage( + withName: name, + toExtensionWithIdentifier: extIdentifier, + userInfo: userInfo + ) { error in // always be called + if error != nil { + debugPrint("Message attempted. Error info: \(String.init(describing: error))") + } + if let userHandle = completion { userHandle(error) } + } +} + +func changeSaveLocationPanel(directoryURL: URL? = nil) -> NSOpenPanel { + let panel = NSOpenPanel() + panel.allowsMultipleSelection = false + panel.canChooseDirectories = true + panel.canCreateDirectories = true + panel.canChooseFiles = false + if directoryURL != nil { + panel.directoryURL = directoryURL + } + panel.title = "Change Save Location - Userscripts" + panel.identifier = NSUserInterfaceItemIdentifier("changeSaveLocation") + return panel +} + +func schemeChangeSaveLocation() { + let saveLocationURL = getSaveLocationURL() + let panel = changeSaveLocationPanel(directoryURL: saveLocationURL) + // shows the path selection panel + let response = panel.runModal() + // check if clicked open button and there is a valid result + guard response == .OK, let url: URL = panel.urls.first else { return } + // revoke implicitly starts security-scoped access + defer { url.stopAccessingSecurityScopedResource() } + // check if path has indeed changed + if url.absoluteString == saveLocationURL.absoluteString { return } + // try set new save location path to bookmark + guard setSaveLocationURL(url: url) else { return } + // use semaphore to ensure the async func executed before app exits + let semaphore = DispatchSemaphore(value: 0) + // notify browser extension of relevant updates + sendExtensionMessage( + name: "SAVE_LOCATION_CHANGED", + userInfo: ["saveLocation": url.absoluteString.removingPercentEncoding ?? url.absoluteString], + completion: { _ in semaphore.signal() } + ) + semaphore.wait() +} diff --git a/xcode/Mac-App/Info.plist b/xcode/App-Mac/Info.plist similarity index 100% rename from xcode/Mac-App/Info.plist rename to xcode/App-Mac/Info.plist diff --git a/xcode/App-Mac/ViewController.swift b/xcode/App-Mac/ViewController.swift new file mode 100644 index 00000000..e09d909c --- /dev/null +++ b/xcode/App-Mac/ViewController.swift @@ -0,0 +1,82 @@ +import SafariServices.SFSafariApplication + +private let logger = USLogger(#fileID) + +class ViewController: NSViewController { + + @IBOutlet var appName: NSTextField! + @IBOutlet var saveLocation: NSTextField! + @IBOutlet weak var enabledText: NSTextField! + @IBOutlet weak var enabledIcon: NSView! + @IBOutlet weak var openButton: NSButton! + + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" + let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" + + override func viewDidLoad() { + super.viewDidLoad() + self.appName.stringValue = "Userscripts Safari Version \(appVersion) (\(buildNumber))" + setExtensionState() + NotificationCenter.default.addObserver( + self, + selector: #selector(setExtensionState), + name: NSApplication.didBecomeActiveNotification, + object: nil + ) + let url = getSaveLocationURL() + self.saveLocation.stringValue = url.absoluteString + self.saveLocation.toolTip = url.absoluteString + if #available(macOS 13, *) { + self.openButton.title = "Open Safari Settings" + } + } + + @objc func setExtensionState() { + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extIdentifier) { (state, error) in + guard let state = state else { + self.enabledText.stringValue = "Safari Extension State Unknown" + if let error = error { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } else { + logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app") + } + return + } + DispatchQueue.main.async { + self.enabledIcon.layer?.backgroundColor = state.isEnabled ? NSColor.green.cgColor : NSColor.red.cgColor + self.enabledText.stringValue = state.isEnabled ? "Safari Extension Enabled" : "Safari Extension Disabled" + } + } + } + + @IBAction func changeSaveLocation(_ sender: AnyObject?) { + guard let window = self.view.window else { return } + let saveLocationURL = getSaveLocationURL() + let panel = changeSaveLocationPanel(directoryURL: saveLocationURL) + panel.beginSheetModal(for: window, completionHandler: { response in + // check if clicked open button and there is a valid result + guard response == .OK, let url: URL = panel.urls.first else { return } + // revoke implicitly starts security-scoped access + defer { url.stopAccessingSecurityScopedResource() } + // check if path has indeed changed + if url.absoluteString == saveLocationURL.absoluteString { return } + // try set new save location path to bookmark + guard setSaveLocationURL(url: url) else { return } + // update user interface text display + self.saveLocation.stringValue = url.absoluteString + self.saveLocation.toolTip = url.absoluteString + // notify browser extension of relevant updates + sendExtensionMessage( + name: "SAVE_LOCATION_CHANGED", + userInfo: [ + "saveLocation": url.absoluteString.removingPercentEncoding ?? url.absoluteString, + "returnApp": true + ] + ) + }) + } + + @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) { + SFSafariApplication.showPreferencesForExtension(withIdentifier: extIdentifier) + } +} diff --git a/xcode/App-Shared/AppIcon.icon/Assets/icon.svg b/xcode/App-Shared/AppIcon.icon/Assets/icon.svg new file mode 100644 index 00000000..f19d064d --- /dev/null +++ b/xcode/App-Shared/AppIcon.icon/Assets/icon.svg @@ -0,0 +1,9 @@ + + + icon + + + + + + \ No newline at end of file diff --git a/xcode/App-Shared/AppIcon.icon/icon.json b/xcode/App-Shared/AppIcon.icon/icon.json new file mode 100644 index 00000000..a5558deb --- /dev/null +++ b/xcode/App-Shared/AppIcon.icon/icon.json @@ -0,0 +1,94 @@ +{ + "fill-specializations" : [ + { + "value" : "automatic" + }, + { + "appearance" : "dark", + "value" : "automatic" + } + ], + "groups" : [ + { + "blend-mode-specializations" : [ + { + "value" : "plus-darker" + }, + { + "appearance" : "dark", + "value" : "plus-lighter" + }, + { + "appearance" : "tinted", + "value" : "normal" + } + ], + "blur-material" : 0.5, + "hidden" : false, + "layers" : [ + { + "blend-mode" : "normal", + "fill-specializations" : [ + { + "value" : "automatic" + }, + { + "appearance" : "dark", + "value" : { + "linear-gradient" : [ + "extended-gray:1.00000,0.80000", + "extended-gray:1.00000,0.70000" + ] + } + }, + { + "appearance" : "tinted", + "value" : { + "linear-gradient" : [ + "extended-gray:1.00000,0.80000", + "extended-gray:1.00000,0.70000" + ] + } + } + ], + "glass" : true, + "hidden" : false, + "image-name" : "icon.svg", + "name" : "icon", + "opacity" : 1 + } + ], + "lighting" : "individual", + "position" : { + "scale" : 1, + "translation-in-points" : [ + 0, + 0 + ] + }, + "shadow-specializations" : [ + { + "value" : { + "kind" : "neutral", + "opacity" : 0.5 + } + }, + { + "appearance" : "light", + "value" : { + "kind" : "layer-color", + "opacity" : 0.5 + } + } + ], + "specular" : true, + "translucency" : { + "enabled" : true, + "value" : 0.2 + } + } + ], + "supported-platforms" : { + "squares" : "shared" + } +} \ No newline at end of file diff --git a/xcode/iOS-App/Assets.xcassets/Contents.json b/xcode/App-Shared/Shared.xcassets/Contents.json similarity index 100% rename from xcode/iOS-App/Assets.xcassets/Contents.json rename to xcode/App-Shared/Shared.xcassets/Contents.json diff --git a/xcode/App-Shared/Shared.xcassets/USBackgroundColor.colorset/Contents.json b/xcode/App-Shared/Shared.xcassets/USBackgroundColor.colorset/Contents.json new file mode 100644 index 00000000..59ca3d0d --- /dev/null +++ b/xcode/App-Shared/Shared.xcassets/USBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFA", + "green" : "0xFA", + "red" : "0xFA" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x39", + "green" : "0x36", + "red" : "0x32" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xcode/App-Shared/USchemeHandler.swift b/xcode/App-Shared/USchemeHandler.swift new file mode 100644 index 00000000..70bbd601 --- /dev/null +++ b/xcode/App-Shared/USchemeHandler.swift @@ -0,0 +1,66 @@ +import WebKit +import UniformTypeIdentifiers + +let AppWebViewUrlScheme = "userscriptsapp" +let AppWebViewEntryPage = "entry-app-webview.html" +private let logger = USLogger(#fileID) + +// https://developer.apple.com/documentation/webkit/wkurlschemehandler +// https://developer.apple.com/documentation/webkit/wkurlschemetask +class USchemeHandler: NSObject, WKURLSchemeHandler { + + func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + // wrapper of didFailWithError + func failHandler(_ errmsg: String) { + logger?.error("\(#function) - \(errmsg, privacy: .public)") + // redirect to a customized error page +// DispatchQueue.main.async { +// webView.load(URLRequest(url: URL(string: "\(AppWebViewUrlScheme):///")!)) +// } + urlSchemeTask.didFailWithError(NSError(domain: "USchemeHandler", code: 0, userInfo: nil)) + } + + // https://developer.apple.com/documentation/dispatch/dispatchqueue + DispatchQueue.global(qos: .userInteractive).async { + guard let url = urlSchemeTask.request.url else { + failHandler("failed to get request url") + return + } + guard url.scheme == AppWebViewUrlScheme else { + failHandler("unexpected url scheme: \(url)") + return + } + var name: String + if #available(macOS 13.0, iOS 16.0, *) { + name = url.path(percentEncoded: false) + } else { + name = url.path + } + if ["", "/"].contains(name) { + name = AppWebViewEntryPage + } + guard let file = Bundle.main.url(forResource: name, withExtension: nil, subdirectory: "dist") else { + failHandler("file not found: \(url)") + return + } + guard let data = try? Data(contentsOf: file) else { + failHandler("faild to get data from: \(url)") + return + } + + // https://developer.apple.com/documentation/uniformtypeidentifiers + // let mime = UTType.init(filenameExtension: file.pathExtension)?.preferredMIMEType ?? "application/octet-stream" + let mime = UTTypeReference.init(filenameExtension: file.pathExtension)?.preferredMIMEType ?? "application/octet-stream" + + let response = URLResponse(url: url, mimeType: mime, expectedContentLength: data.count, textEncodingName: nil) + urlSchemeTask.didReceive(response) + urlSchemeTask.didReceive(data) + urlSchemeTask.didFinish() + } + } + + func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { + + } + +} diff --git a/xcode/App-Shared/ViewController.swift b/xcode/App-Shared/ViewController.swift new file mode 100644 index 00000000..83e0040d --- /dev/null +++ b/xcode/App-Shared/ViewController.swift @@ -0,0 +1,413 @@ +import WebKit +import SwiftUI + +#if os(iOS) +typealias PlatformViewController = UIViewController +#elseif os(macOS) +import SafariServices.SFSafariApplication +typealias PlatformViewController = NSViewController +#endif + +private let logger = USLogger(#fileID) + +class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandlerWithReply { + + @IBOutlet var webView: WKWebView! + + enum ExtensionState: String { + case unknow = "unknow" + case on = "enabled" + case off = "disabled" + case error = "error" + } + var extensionState: ExtensionState = .unknow +#if os(iOS) + var documentPickerHandler: ((URL) -> Void)? +#elseif os(macOS) + var timerExtensionStateCheck: Timer? +#endif + + // https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview + override func loadView() { + // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration + let configuration = WKWebViewConfiguration() + // https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler + configuration.setURLSchemeHandler(USchemeHandler(), forURLScheme: AppWebViewUrlScheme) + // https://developer.apple.com/documentation/webkit/wkusercontentcontroller + configuration.userContentController.addScriptMessageHandler(self, contentWorld: .page, name: "controller") + // https://developer.apple.com/documentation/webkit/wkwebview + self.webView = WKWebView(frame: .zero, configuration: configuration) +#if os(iOS) + self.webView.isOpaque = false + // https://developer.apple.com/documentation/uikit/appearance_customization/supporting_dark_mode_in_your_interface#2993897 + self.webView.backgroundColor = UIColor(named: "USBackgroundColor") +// self.webView.backgroundColor = .clear // DEBUG +// self.webView.scrollView.isScrollEnabled = false +#elseif os(macOS) + self.webView.frame = NSRect(x: 0, y: 0, width: 600, height: 600) + self.webView.setValue(false, forKey: "drawsBackground") +#endif + // https://developer.apple.com/documentation/webkit/wknavigationdelegate + self.webView.navigationDelegate = self +#if DEBUG + // https://webkit.org/blog/13936/enabling-the-inspection-of-web-content-in-apps/ + if #available(macOS 13.3, iOS 16.4, tvOS 16.4, *) { + // https://developer.apple.com/documentation/webkit/wkwebview/4111163-inspectable/ + self.webView.isInspectable = true + } + logger?.debug("\(#function, privacy: .public) - DEBUG mode: isInspectable = true") +#endif + self.view = self.webView + } + + // https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload + override func viewDidLoad() { + super.viewDidLoad() +#if VITE + let url = URL(string: "https://userscripts.test:55173/entry-app-webview.html")! +#else + let url = URL(string: "\(AppWebViewUrlScheme):///")! +#endif + self.webView.load(URLRequest(url: url)) +#if os(macOS) + NotificationCenter.default.addObserver( + self, + selector: #selector(handleOcclusionStateChange), + name: NSApplication.didChangeOcclusionStateNotification, + object: nil + ) +#endif + } + + // https://developer.apple.com/documentation/webkit/wknavigationdelegate/1455629-webview + // DOMContentLoaded +// func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { +// webView.evaluateJavaScript("") +// } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + if navigationAction.navigationType == .linkActivated { + guard let url = navigationAction.request.url else { + decisionHandler(.allow) + return + } + // allow registration scheme + if url.scheme == AppWebViewUrlScheme { + decisionHandler(.allow) + return + } + // allow specified url prefixes +// let allowPrefixes = [ +// "https://github.com/quoid/userscripts" +// ] +// for prefix in allowPrefixes { +// if url.absoluteString.lowercased().hasPrefix(prefix) { +// decisionHandler(.allow) +// return +// } +// } + // open from external app like safari +#if os(iOS) + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + decisionHandler(.cancel) + return + } +#elseif os(macOS) + if let _ = NSWorkspace.shared.urlForApplication(toOpen: url) { + NSWorkspace.shared.open(url) + decisionHandler(.cancel) + return + } +#endif + decisionHandler(.allow) + } + decisionHandler(.allow) + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) { + guard let name = message.body as? String else { + logger?.error("\(#function, privacy: .public) - Userscripts iOS received a message without a name") + return (nil, "bad message body") + } + logger?.debug("\(#function, privacy: .public) - Got message: \(name)") + switch name { + case "INIT": + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0" + let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "0" + let firstRunTime = Preferences.firstRunTime + if(firstRunTime == 0) { + Preferences.firstRunTime = Int(Date().timeIntervalSince1970 * 1000) + } + var useSettingsInsteadOfPreferences = false +#if os(iOS) + let platform = "ios" +#elseif os(macOS) + let platform = "mac" + do { + let state = try await SFSafariExtensionManager.stateOfSafariExtension(withIdentifier: extIdentifier) + self.extensionState = state.isEnabled ? .on : .off + } catch { + self.extensionState = .error + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } + if #available(macOS 13, *) { + useSettingsInsteadOfPreferences = true + } +#endif + return ([ + "build": buildNumber, + "version": appVersion, + "platform": platform, + "directory": getCurrentScriptsDirectoryString(), + "extStatus": self.extensionState.rawValue, + "useSettingsInsteadOfPreferences": useSettingsInsteadOfPreferences, + "enableLogger": Preferences.enableLogger, + "promptLogger": Preferences.promptLogger, + "maxLogFileSize": Preferences.maxLogFileSize, + "firstRunTime": firstRunTime, + ], nil) + case "CHANGE_DIRECTORY": + changeSaveLocation() + break + case "OPEN_DIRECTORY": +#if os(iOS) + guard let saveLocation = getSaveLocation() else { + return (nil, "Uninitialized save location") + } + guard var components = URLComponents(url: saveLocation, resolvingAgainstBaseURL: true) else { + return (nil, "ScriptsDirectoryUrl malformed") + } + components.scheme = "shareddocuments" + if let url = components.url, UIApplication.shared.canOpenURL(url) { + await UIApplication.shared.open(url) + } +#elseif os(macOS) + guard openSaveLocation() else { + return (nil, "ScriptsDirectoryUrl malformed") + } +#endif + break +#if os(macOS) + case "SHOW_PREFERENCES": + do { + try await SFSafariApplication.showPreferencesForExtension(withIdentifier: extIdentifier) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + return (nil, error.localizedDescription) + } + break +#endif + case "EXPORT_LOG_FILES": + // write the logStore so far to log files (async, no guarantee) + USLogStoreToFile(prioritize: true) + // call the export func of different platforms + exportLogFiles() + break + case "DISABLE_LOGGER": + USLoggerSwitch(false) + break + case "DISMISS_LOGGER_PROMPT": + Preferences.promptLogger = false + break + default: + return (nil, "Unexpected message body") + } + return (nil, nil) + } + + func updateEnableLoggerState() { + self.webView.evaluateJavaScript("webapp.switchLogger(\(Preferences.enableLogger),\(Preferences.promptLogger))") + } + + func updateCurrentDirectory() { + self.webView.evaluateJavaScript("webapp.updateDirectory('\(getCurrentScriptsDirectoryString())')") + } + +#if os(macOS) + deinit { + NotificationCenter.default.removeObserver( + self, + name: NSApplication.didChangeOcclusionStateNotification, + object: nil + ) + + DistributedNotificationCenter.default.removeObserver( + self, + name: NSNotification.Name("AppleInterfaceThemeChangedNotification"), + object: nil + ) + } +#endif + +} + +#if os(iOS) +// https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories +// https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller +extension ViewController: UIDocumentPickerDelegate { + + // UIDocumentPickerDelegate + // https://developer.apple.com/documentation/uikit/uidocumentpickerdelegate/2902364-documentpicker + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { + guard let handler = documentPickerHandler else { + logger?.debug("\(#function, privacy: .public) - documentPickerHandler not set") + return + } + // call handler + handler(url) + // reset handler + documentPickerHandler = nil + } + + func changeSaveLocation() { + logger?.info("\(#function, privacy: .public) - Userscripts iOS has requested to set the readLocation") + // set handler + documentPickerHandler = changeSaveLocationHandler + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) + documentPicker.delegate = self + documentPicker.directoryURL = getDocumentsDirectory() + present(documentPicker, animated: true) + } + + func changeSaveLocationHandler(_ url: URL) { + Preferences.scriptsDirectoryUrl = url + self.updateCurrentDirectory() + } + + func exportLogFiles() { + // set handler + documentPickerHandler = exportLogFilesHandler + let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) + documentPicker.delegate = self + present(documentPicker, animated: true) + } + + func exportLogFilesHandler(_ url: URL) { + guard url.startAccessingSecurityScopedResource() else { + logger?.error("\(#function, privacy: .public) - failed access url: \(url, privacy: .public)") + return + } + defer { url.stopAccessingSecurityScopedResource() } + // export log files and open output directory + do { + let outURL = try USLogFilesExportTo(url) + guard var components = URLComponents(url: outURL, resolvingAgainstBaseURL: true) else { + logger?.error("\(#function, privacy: .public) - url malformed: \(url, privacy: .public)") + return + } + components.scheme = "shareddocuments" + if let url = components.url, UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } catch { + logger?.error("\(#function, privacy: .public) - Export failed: \(error)") + let alert = UIAlertController( + title: "Export failed", + message: "\(error)", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + present(alert, animated: true) + } + } + +} + +#elseif os(macOS) +extension ViewController { + + // Only update the view when the window is visible + @objc func handleOcclusionStateChange() { + let occlusionState = NSApplication.shared.occlusionState + if occlusionState.contains(.visible) { + if timerExtensionStateCheck?.isValid != true { + timerExtensionStateCheck = Timer.scheduledTimer( + timeInterval: 0.2, + target: self, + selector: #selector(handleExtensionStateChange), + userInfo: nil, + repeats: true + ) + } + } else { + timerExtensionStateCheck?.invalidate() + } + } + + // Update webview extension status display via timer + @objc func handleExtensionStateChange() { + SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extIdentifier) { (state, error) in + var status: ExtensionState; + if let state = state { + status = state.isEnabled ? .on : .off + } else { + status = .error + if let error = error { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } else { + logger?.error("\(#function, privacy: .public) - couldn't get safari extension state in containing app") + } + } + if self.extensionState == status { return } + self.extensionState = status + DispatchQueue.main.async { + self.webView.evaluateJavaScript("webapp.updateExtStatus('\(status.rawValue)')") + } + } + } + + func changeSaveLocation() { + guard let window = self.view.window else { return } + let saveLocationURL = getSaveLocationURL() + let panel = changeSaveLocationPanel(directoryURL: saveLocationURL) + panel.beginSheetModal(for: window, completionHandler: { response in + // check if clicked open button and there is a valid result + guard response == .OK, let url: URL = panel.url else { return } + // revoke implicitly starts security-scoped access + defer { url.stopAccessingSecurityScopedResource() } + // check if path has indeed changed + if url.absoluteString == saveLocationURL.absoluteString { return } + // try set new save location path to bookmark + guard setSaveLocationURL(url: url) else { return } + // update user interface text display + self.updateCurrentDirectory() + // notify browser extension of relevant updates + sendExtensionMessage( + name: "SAVE_LOCATION_CHANGED", + userInfo: [ + "saveLocation": url.absoluteString.removingPercentEncoding ?? url.absoluteString, + "returnApp": true + ] + ) + }) + } + + func exportLogFiles() { + guard let window = self.view.window else { return } + let panel = NSOpenPanel() + panel.allowsMultipleSelection = false + panel.canChooseDirectories = true + panel.canChooseFiles = false + panel.title = "Export Log Files" + panel.beginSheetModal(for: window, completionHandler: { response in + panel.close() + // check if clicked save button and there is a valid result + guard response == .OK, let url: URL = panel.url else { return } + // revoke implicitly starts security-scoped access + defer { url.stopAccessingSecurityScopedResource() } + // export log files and open output directory + do { + let outURL = try USLogFilesExportTo(url) + NSWorkspace.shared.selectFile(outURL.path, inFileViewerRootedAtPath: url.path) + } catch { + logger?.error("\(#function, privacy: .public) - Export failed: \(error)") + let alert = NSAlert() + alert.messageText = "Export failed - \(error)" + alert.runModal() + } + }) + } + +} +#endif diff --git a/xcode/iOS-App/App.entitlements b/xcode/App-iOS/App.entitlements similarity index 100% rename from xcode/iOS-App/App.entitlements rename to xcode/App-iOS/App.entitlements diff --git a/xcode/App-iOS/AppDelegate.swift b/xcode/App-iOS/AppDelegate.swift new file mode 100644 index 00000000..1ccbd9fa --- /dev/null +++ b/xcode/App-iOS/AppDelegate.swift @@ -0,0 +1,27 @@ +// +// AppDelegate.swift +// Userscripts-iOS +// +// Created by Justin Wasack on 10/3/21. +// Copyright © 2021 Justin Wasack. All rights reserved. +// + +import UIKit +//import os + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + initializeFirstStart() + return true + } + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + +} diff --git a/xcode/iOS-App/Assets.xcassets/AccentColor.colorset/Contents.json b/xcode/App-iOS/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AccentColor.colorset/Contents.json rename to xcode/App-iOS/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/1024.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/1024.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/1024.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/1024.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/120-1.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/120-1.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/120-1.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/120-1.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/120.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/120.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/120.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/120.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/152.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/152.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/152.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/152.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/167.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/167.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/167.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/167.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/180.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/180.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/180.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/180.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/20.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/20.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/20.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/20.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/29.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/29.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/29.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/29.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40-1.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40-1.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40-1.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40-1.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40-2.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40-2.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40-2.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40-2.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/40.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/40.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/58-1.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/58-1.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/58-1.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/58-1.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/58.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/58.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/58.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/58.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/60.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/60.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/60.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/60.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/76.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/76.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/76.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/76.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/80-1.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/80-1.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/80-1.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/80-1.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/80.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/80.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/80.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/80.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/87.png b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/87.png similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/87.png rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/87.png diff --git a/xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/Contents.json b/xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from xcode/iOS-App/Assets.xcassets/AppIcon.appiconset/Contents.json rename to xcode/App-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/xcode/App-iOS/Assets.xcassets/Contents.json b/xcode/App-iOS/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/xcode/App-iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/xcode/iOS-App/Assets.xcassets/LargeIcon.imageset/Contents.json b/xcode/App-iOS/Assets.xcassets/LargeIcon.imageset/Contents.json similarity index 100% rename from xcode/iOS-App/Assets.xcassets/LargeIcon.imageset/Contents.json rename to xcode/App-iOS/Assets.xcassets/LargeIcon.imageset/Contents.json diff --git a/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/Contents.json b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/Contents.json new file mode 100644 index 00000000..ae847694 --- /dev/null +++ b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "LaunchScreenLightCentered.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchScreenDarkCentered.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenDarkCentered.pdf b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenDarkCentered.pdf new file mode 100644 index 00000000..244ab0ef Binary files /dev/null and b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenDarkCentered.pdf differ diff --git a/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenLightCentered.pdf b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenLightCentered.pdf new file mode 100644 index 00000000..936d8c04 Binary files /dev/null and b/xcode/App-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreenLightCentered.pdf differ diff --git a/xcode/iOS-App/Base.lproj/LaunchScreen.storyboard b/xcode/App-iOS/Base.lproj/LaunchScreen.storyboard similarity index 81% rename from xcode/iOS-App/Base.lproj/LaunchScreen.storyboard rename to xcode/App-iOS/Base.lproj/LaunchScreen.storyboard index f3b08bf5..178ccbc4 100644 --- a/xcode/iOS-App/Base.lproj/LaunchScreen.storyboard +++ b/xcode/App-iOS/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -21,7 +23,7 @@ - + @@ -31,5 +33,8 @@ + + + diff --git a/xcode/iOS-App/Base.lproj/Main.storyboard b/xcode/App-iOS/Base.lproj/Main.storyboard similarity index 100% rename from xcode/iOS-App/Base.lproj/Main.storyboard rename to xcode/App-iOS/Base.lproj/Main.storyboard diff --git a/xcode/iOS-App/Info.plist b/xcode/App-iOS/Info.plist similarity index 63% rename from xcode/iOS-App/Info.plist rename to xcode/App-iOS/Info.plist index a2a30aa2..51a0de5f 100644 --- a/xcode/iOS-App/Info.plist +++ b/xcode/App-iOS/Info.plist @@ -2,6 +2,19 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + $(APP_IDENTIFIER) + CFBundleURLSchemes + + $(APP_URL_SCHEME) + + + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -22,10 +35,16 @@ UIStatusBarStyle - UIStatusBarStyleLightContent + UIStatusBarStyleDefault UIViewControllerBasedStatusBarAppearance + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + US_SHARED_GID $(IOS_SHARED_GID) + US_EXT_IDENTIFIER + $(EXT_IDENTIFIER) diff --git a/xcode/App-iOS/Initialization.swift b/xcode/App-iOS/Initialization.swift new file mode 100644 index 00000000..7585ff7d --- /dev/null +++ b/xcode/App-iOS/Initialization.swift @@ -0,0 +1,49 @@ +import Foundation + +private let logger = USLogger(#fileID) + +private func createDemoScript() { + let demoScript = """ +// ==UserScript== +// @name Demo user script +// @description I am a demo user script that you can safely delete (add any files to this folder and I will no longer automatically generate) +// @author Userscripts +// @version 0.0.1 +// @match *://*/* +// @grant none +// @inject-into content +// ==/UserScript== + +(function () { + 'use strict'; + // here is your code +})(); +""" + let url = getDocumentsDirectory() + // get a list of non-hidden files + guard let contents = try? FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [], options: .skipsHiddenFiles) else { + logger?.error("\(#function, privacy: .public) - failed get contentsOfDirectory") + return + } + if contents.isEmpty { + let fileURL = url.appendingPathComponent("Demo_user_script.user.js") + do { + logger?.info("\(#function, privacy: .public) - try to write demo user script") + try demoScript.write(to: fileURL, atomically: true, encoding: .utf8) + } catch { + logger?.error("\(#function, privacy: .public) - failed to write demo user script") + } + } +} + +func initializeFirstStart() { + // set the scripts directory to the app document on first use + if isCurrentDefaultScriptsDirectory() { + logger?.info("\(#function, privacy: .public) - Initialize default directory") + Preferences.scriptsDirectoryUrl = getDocumentsDirectory() + } + // put a visible file to display the documents directory in files app + if isCurrentInitialScriptsDirectory() { + createDemoScript() + } +} diff --git a/xcode/App-iOS/SceneDelegate.swift b/xcode/App-iOS/SceneDelegate.swift new file mode 100644 index 00000000..d1544f61 --- /dev/null +++ b/xcode/App-iOS/SceneDelegate.swift @@ -0,0 +1,30 @@ +// +// SceneDelegate.swift +// Userscripts-iOS +// +// Created by Justin Wasack on 10/3/21. +// Copyright © 2021 Justin Wasack. All rights reserved. +// + +import UIKit + +// https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneWillResignActive(_ scene: UIScene) { + USLogStoreToFile() + } + + func sceneWillEnterForeground(_ scene: UIScene) { + if let viewController = window?.rootViewController as? ViewController { + viewController.updateEnableLoggerState() + } + } + +} diff --git a/xcode/App-iOS/Settings.bundle/Root.plist b/xcode/App-iOS/Settings.bundle/Root.plist new file mode 100644 index 00000000..b9ad077e --- /dev/null +++ b/xcode/App-iOS/Settings.bundle/Root.plist @@ -0,0 +1,31 @@ + + + + + ApplicationGroupContainerIdentifier + // US_SHARED_GID will be introduced in the custom build script + StringsTable + Root + PreferenceSpecifiers + + + Type + PSGroupSpecifier + Title + GroupDebug + FooterText + FooterText + + + Type + PSToggleSwitchSpecifier + Title + EnableLogger + Key + EnableLogger + DefaultValue + + + + + diff --git a/xcode/App-iOS/Settings.bundle/en.lproj/Root.strings b/xcode/App-iOS/Settings.bundle/en.lproj/Root.strings new file mode 100644 index 00000000..dc826ef0 Binary files /dev/null and b/xcode/App-iOS/Settings.bundle/en.lproj/Root.strings differ diff --git a/xcode/Ext-Safari/Functions.swift b/xcode/Ext-Safari/Functions.swift new file mode 100644 index 00000000..220a380a --- /dev/null +++ b/xcode/Ext-Safari/Functions.swift @@ -0,0 +1,1949 @@ +import SafariServices + +private let logger = USLogger(#fileID) + +// helpers +func getRequireLocation() -> URL { + // simple helper in case required code save directory needs to change + return getDocumentsDirectory().appendingPathComponent("require") +} + +func dateToMilliseconds(_ date: Date) -> Int { + let since1970 = date.timeIntervalSince1970 + return Int(since1970 * 1000) +} + +func sanitize(_ str: String) -> String { + // removes invalid filename characters from strings + var sanitized = str + if sanitized.first == "." { + sanitized = "%2" + str.dropFirst() + } + sanitized = sanitized.replacingOccurrences(of: "/", with: "%2F") + sanitized = sanitized.replacingOccurrences(of: ":", with: "%3A") + sanitized = sanitized.replacingOccurrences(of: "\\", with: "%5C") + return sanitized +} + +func unsanitize(_ str: String) -> String { + var s = str + if s.hasPrefix("%2") && !s.hasPrefix("%2F") { + s = "." + s.dropFirst(2) + } + if s.removingPercentEncoding != s { + s = s.removingPercentEncoding ?? s + } + return s +} + +func normalizeWeight(_ weight: String) -> String { + if let w = Int(weight) { + if w > 999 { + return "999" + } else if w < 1 { + return "1" + } else { + return weight + } + } else { + return "1" + } +} + +func validateUrl(_ urlString: String) -> Bool { + guard + let parts = jsLikeURL(urlString), + let ptcl = parts["protocol"], + let path = parts["pathname"] + else { + logger?.error("\(#function, privacy: .public) - Invalid URL: \(urlString, privacy: .public)") + return false + } + if ptcl != "https:" && ptcl != "http:" { + logger?.error("\(#function, privacy: .public) - Invalid URL protocol: \(urlString, privacy: .public)") + return false + } + if !path.hasSuffix(".css") && !path.hasSuffix(".js") { + logger?.error("\(#function, privacy: .public) - Invalid URL suffix: \(urlString, privacy: .public)") + return false + } + return true +} + +func isVersionNewer(_ oldVersion: String, _ newVersion: String) -> Bool { + let oldVersions = oldVersion.components(separatedBy: ".") + let newVersions = newVersion.components(separatedBy: ".") + for (index, version) in newVersions.enumerated() { + let a = Int(version) ?? 0 + let oldVersionValue = oldVersions.indices.contains(index) ? oldVersions[index] : "0" + let b = Int(oldVersionValue) ?? 0 + if a > b { + return true + } + if a < b { + return false + } + } + return false +} + +func isEncoded(_ str: String) -> Bool { + if let decoded = str.removingPercentEncoding { + return decoded != str + } + return false +} + +// parser REs - force try b/c pattern is known to be valid regex +let re0 = try! NSRegularExpression(pattern: #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"#, options: []) +// RE for meta line +let re1 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)"#, options: []) +// this pattern checks for specific keys that won't have values +let re2 = try! NSRegularExpression(pattern: #"^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$"#, options: []) + +// parser +func parse(_ content: String) -> [String: Any]? { + // returns structured data from content of file + // will fail to parse if metablock or required @name key missing + let range = NSRange(location: 0, length: content.utf16.count) + // return nil/fail if metablock missing + guard let match = re0.firstMatch(in: content, options: [], range: range) else { + logger?.debug("\(#function, privacy: .public) - Non matched content: \(content, privacy: .public)") + return nil + } + + // at this point the text content has passed initial validation, it contains valid metadata + // the metadata can be in userscript or userstyle format, need to check for this and adjust group numbers + // rather than being too strict, text content can precede the opening userscript tag, however it will be ignored + // adjust start index of file content while assigning group numbers to account for any text content preceding opening tag + let contentStartIndex = content.index(content.startIndex, offsetBy: match.range.lowerBound) + let contentEndIndex = content.index(contentStartIndex, offsetBy: 15) + let metaHeadContent = content[contentStartIndex.. Bool { + let content = data + let url = getDocumentsDirectory().appendingPathComponent("manifest.json") + do { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let encoded = try encoder.encode(content) + let fileContent = String(decoding: encoded, as: UTF8.self) + try fileContent.write(to: url, atomically: false, encoding: .utf8) + return true + } catch { + logger?.error("\(#function, privacy: .public) - failed to update manifest: \(error, privacy: .public)") + return false + } +} + +func getManifest() -> Manifest { + let url = getDocumentsDirectory().appendingPathComponent("manifest.json") + if + let content = try? String(contentsOf: url, encoding: .utf8), + let data = content.data(using: .utf8), + let decoded = try? JSONDecoder().decode(Manifest.self, from: Data(data)) + { + return decoded + } else { + // manifest missing, improperly formatted or missing key + // create new manifest with default key/vals + let manifest = Manifest( + blacklist: [], + declarativeNetRequest: [], + disabled: [], + exclude: [:], + excludeMatch: [:], + include: [:], + match: [:], + require: [:], + settings: defaultSettings + ) + _ = updateManifest(with: manifest) + return manifest + } +} + +func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + logger?.info("\(#function, privacy: .public) - started") + // only get all files if files were not provided + var files = [[String: Any]]() + if optionalFilesArray.isEmpty { + guard let getFiles = getAllFiles() else {return false} + files = getFiles + } else { + files = optionalFilesArray + } + var manifest = getManifest() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + let metadata = file["metadata"] as! [String: [String]] + let filename = file["filename"] as! String + // skip request type userscripts + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + continue + } + // populate excludes & matches + var excludeMatched = [String]() + var matched = [String]() + var excluded = [String]() + var included = [String]() + if metadata["exclude-match"] != nil { + excludeMatched.append(contentsOf: metadata["exclude-match"]!) + } + if metadata["match"] != nil { + matched.append(contentsOf: metadata["match"]!) + } + if metadata["include"] != nil { + included.append(contentsOf: metadata["include"]!) + } + if metadata["exclude"] != nil { + excluded.append(contentsOf: metadata["exclude"]!) + } + // if in declarativeNetRequest array, remove it + if manifest.declarativeNetRequest.contains(filename) { + if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { + manifest.declarativeNetRequest.remove(at: index) + } else { + logger?.error("\(#function, privacy: .public) - failed to remove \(filename, privacy: .public) from dNR array") + } + } + + // update manifest values + manifest.excludeMatch = updatePatternDict(filename, excludeMatched, manifest.excludeMatch) + manifest.match = updatePatternDict(filename, matched, manifest.match) + manifest.exclude = updatePatternDict(filename, excluded, manifest.exclude) + manifest.include = updatePatternDict(filename, included, manifest.include) + + if !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - failed to update manifest matches") + return false + } + } + logger?.info("\(#function, privacy: .public) - completed") + return true +} + +func updatePatternDict(_ filename: String, _ filePatterns: [String], _ manifestKeys: [String: [String]]) -> [String: [String]] { + // will hold the exclude/match patterns in manifest that have file name as value + var patternsInManifestForFile = [String]() + // new var from func argument, so it can be manipulated + var returnDictionary = manifestKeys + // patterns from manifest + let keys = returnDictionary.keys + // determine what patterns already have this filename as a value + for key in keys { + // key is an array of filenames + guard let filenames = returnDictionary[key] else { + logger?.error("\(#function, privacy: .public) - failed to get values for manifest key, \(key, privacy: .public)") + continue + } + for name in filenames { + // name is a single filename + // if name is same as filename, file already added for this pattern + // add it to patternsInManifestForFile for later comparison + if name == filename { + patternsInManifestForFile.append(key) + } + } + } + // patterns in file metadata and patterns in manifest that have filename as a value + // filename already present in manifest for these patterns, do nothing with these + // let common = filePatterns.filter{patternsInManifestForFile.contains($0)} + // patterns in file metadata, but don't have the filename as a value within the manifest + // these are the manifest patterns that the filename needs to be added to + let addFilenameTo = filePatterns.filter{!patternsInManifestForFile.contains($0)} + + // the patterns that have the filename as a value, but not present in file metadata + // ie. these are the manifest patterns we need to remove the filename from + let removeFilenameFrom = patternsInManifestForFile.filter{!filePatterns.contains($0)} + + // check if filename needs to be added or new key/val needs to be created + for pattern in addFilenameTo { + if returnDictionary[pattern] != nil { + returnDictionary[pattern]?.append(filename) + } else { + returnDictionary[pattern] = [filename] + } + } + + for pattern in removeFilenameFrom { + // get the index of the filename within the array + let ind = returnDictionary[pattern]?.firstIndex(of: filename) + // remove filename from array by index + returnDictionary[pattern]?.remove(at: ind!) + // if filename was the last item in array, remove the url pattern from dictionary + if returnDictionary[pattern]!.isEmpty { + returnDictionary.removeValue(forKey: pattern) + } + } + + return returnDictionary +} + +func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + logger?.info("\(#function, privacy: .public) - started") + // only get all files if files were not provided + var files = [[String: Any]]() + if optionalFilesArray.isEmpty { + guard let getFiles = getAllFiles() else { + logger?.info("\(#function, privacy: .public) - count not get files") + return false + } + files = getFiles + } else { + files = optionalFilesArray + } + logger?.info("\(#function, privacy: .public) - will loop through \(files.count, privacy: .public)") + var manifest = getManifest() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + let filename = file["filename"] as! String + let metadata = file["metadata"] as! [String: [String]] + let type = file["type"] as! String + let required = metadata["require"] ?? [] + logger?.info("\(#function, privacy: .public) - begin \(filename, privacy: .public)") + // get required resources for file, if fail, skip updating manifest + if !getRequiredCode(filename, required, type) { + logger?.error("\(#function, privacy: .public) - couldn't fetch remote content for \(filename, privacy: .public)") + continue + } + + // create filenames from sanitized resource urls + // getRequiredCode does the same thing when saving to disk + // populate array with entries for manifest + var r = [String]() + for resource in required { + let sanitizedResourceName = sanitize(resource) + r.append(sanitizedResourceName) + } + + // if there are values, write them to manifest + // if failed to write to manifest, continue to next file & log error + if !r.isEmpty && r != manifest.require[filename] { + manifest.require[filename] = r + if !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - couldn't update manifest when getting required resources") + } + } + logger?.info("\(#function, privacy: .public) - end \(filename, privacy: .public)") + } + logger?.info("\(#function, privacy: .public) - completed") + return true +} + +func updateManifestDeclarativeNetRequests(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + logger?.info("\(#function, privacy: .public) - started") + var files = [[String: Any]]() + if optionalFilesArray.isEmpty { + guard let getFiles = getAllFiles() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return false + } + files = getFiles + } else { + files = optionalFilesArray + } + var manifest = getManifest() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + // and getAllFiles always returns the following + let metadata = file["metadata"] as! [String: [String]] + let filename = file["filename"] as! String + let runAt = metadata["run-at"]?[0] ?? "document-end" + // if not a request type, ignore + if runAt != "request" { + continue + } + var update = false + // if filename already in manifest + if !manifest.declarativeNetRequest.contains(filename) { + manifest.declarativeNetRequest.append(filename) + update = true + } + // if filename in another array remove it + for (pattern, filenames) in manifest.match { + for fn in filenames { + if fn == filename, let index = manifest.match[pattern]?.firstIndex(of: filename) { + manifest.match[pattern]?.remove(at: index) + update = true + } + } + } + for (pattern, filenames) in manifest.excludeMatch { + for fn in filenames { + if fn == filename, let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) { + manifest.excludeMatch[pattern]?.remove(at: index) + update = true + } + } + } + for (pattern, filenames) in manifest.include { + for fn in filenames { + if fn == filename, let index = manifest.include[pattern]?.firstIndex(of: filename) { + manifest.include[pattern]?.remove(at: index) + update = true + } + } + } + for (pattern, filenames) in manifest.exclude { + for fn in filenames { + if fn == filename, let index = manifest.exclude[pattern]?.firstIndex(of: filename) { + manifest.exclude[pattern]?.remove(at: index) + update = true + } + } + } + if update, !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return false + } + } + logger?.info("\(#function, privacy: .public) - completed") + return true +} + +func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + logger?.info("\(#function, privacy: .public) - started") + // purge all manifest keys of any stale entries + var update = false, manifest = getManifest(), allSaveLocationFilenames = [String]() + // only get all files if files were not provided + var allFiles = [[String: Any]]() + if optionalFilesArray.isEmpty { + // if getAllFiles fails to return, ignore and pass an empty array + let getFiles = getAllFiles() ?? [] + allFiles = getFiles + } else { + allFiles = optionalFilesArray + } + // populate array with filenames + for file in allFiles { + if let filename = file["filename"] as? String { + allSaveLocationFilenames.append(filename) + } + } + // loop through manifest keys, if no file exists for value, remove value from manifest + // if there are no more filenames in pattern, remove pattern from manifest + for (pattern, filenames) in manifest.match { + for filename in filenames { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.match[pattern]?.firstIndex(of: filename) { + manifest.match[pattern]?.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from match pattern - \(pattern, privacy: .public)") + } + } + } + if let length = manifest.match[pattern]?.count { + if length < 1, let ind = manifest.match.index(forKey: pattern) { + manifest.match.remove(at: ind) + logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) match pattern, removed from manifest") + } + } + } + for (pattern, filenames) in manifest.excludeMatch { + for filename in filenames { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) { + manifest.excludeMatch[pattern]?.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude-match pattern - \(pattern, privacy: .public)") + } + } + } + if let length = manifest.excludeMatch[pattern]?.count { + if length < 1, let ind = manifest.excludeMatch.index(forKey: pattern) { + manifest.excludeMatch.remove(at: ind) + logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude-match pattern, removed from manifest") + } + } + } + for (pattern, filenames) in manifest.exclude { + for filename in filenames { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.exclude[pattern]?.firstIndex(of: filename) { + manifest.exclude[pattern]?.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)") + } + } + } + if let length = manifest.exclude[pattern]?.count { + if length < 1, let ind = manifest.exclude.index(forKey: pattern) { + manifest.exclude.remove(at: ind) + logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest") + } + } + } + for (pattern, filenames) in manifest.include { + for filename in filenames { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.include[pattern]?.firstIndex(of: filename) { + manifest.include[pattern]?.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from exclude pattern - \(pattern, privacy: .public)") + } + } + } + if let length = manifest.include[pattern]?.count { + if length < 1, let ind = manifest.include.index(forKey: pattern) { + manifest.include.remove(at: ind) + logger?.info("\(#function, privacy: .public) - No more files for \(pattern, privacy: .public) exclude pattern, removed from manifest") + } + } + } + // loop through manifest required + for (filename, _) in manifest.require { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.require.index(forKey: filename) { + manifest.require.remove(at: index) + // remove associated resources + if !getRequiredCode(filename, [], (filename as NSString).pathExtension) { + logger?.error("\(#function, privacy: .public) - failed to remove required resources when purging \(filename, privacy: .public) from manifest required records") + } + update = true + logger?.info("\(#function, privacy: .public) - No more required resources for \(filename, privacy: .public), removed from manifest along with resource folder") + } + } + } + // loop through manifest disabled + for filename in manifest.disabled { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.disabled.firstIndex(of: filename) { + manifest.disabled.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from disabled") + } + } + } + // loop through manifest declarativeNetRequest + for filename in manifest.declarativeNetRequest { + if !allSaveLocationFilenames.contains(filename) { + if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { + manifest.declarativeNetRequest.remove(at: index) + update = true + logger?.info("\(#function, privacy: .public) - Could not find \(filename, privacy: .public) in save location, removed from dNR") + } + } + } + // remove obsolete settings + for setting in manifest.settings { + if !defaultSettings.keys.contains(setting.key) { + manifest.settings.removeValue(forKey: setting.key) + update = true + logger?.info("\(#function, privacy: .public) - Removed obsolete setting - \(setting.key, privacy: .public)") + } + } + if update, !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - failed to purge manifest") + return false + } + logger?.info("\(#function, privacy: .public) - completed") + return true +} + +// settings +func checkSettings() -> Bool { + // iterate over default settings and individually check if each present + // if missing add setting to manifest about to be returned + // missing keys will occur when new settings introduced + var manifest = getManifest() + var update = false + for (key, value) in defaultSettings { + if manifest.settings[key] == nil { + manifest.settings[key] = value + update = true + } + } + if update, !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - failed to update manifest settings") + return false + } + return true +} + +func updateSettings(_ settings: [String: String]) -> Bool { + var manifest = getManifest() + manifest.settings = settings + if updateManifest(with: manifest) != true { + logger?.error("\(#function, privacy: .public) - failed to update settings") + return false + } + return true +} + +// files +func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? { + logger?.info("\(#function, privacy: .public) - started") + // returns all files of proper type with filenames, metadata & more + var files = [[String: Any]]() + let fm = FileManager.default + let manifest = getManifest() + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + // get all file urls within save location + guard let urls = try? fm.contentsOfDirectory(at: saveLocation, includingPropertiesForKeys: []) else { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return nil + } + for url in urls { + var fileData = [String: Any]() + // only read contents for css & js files + let filename = url.lastPathComponent + if (!filename.hasSuffix(".css") && !filename.hasSuffix(".js")) { + continue + } + // file will be skipped if metablock is missing + guard + let content = try? String(contentsOf: url, encoding: .utf8), + let dateMod = try? fm.attributesOfItem(atPath: url.path)[.modificationDate] as? Date, + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]], + let type = filename.split(separator: ".").last + else { + logger?.info("\(#function, privacy: .public) - ignoring \(filename, privacy: .public), file missing or metadata missing from file contents") + continue + } + fileData["canUpdate"] = false + fileData["content"] = content + fileData["disabled"] = manifest.disabled.contains(filename) + fileData["filename"] = filename + fileData["lastModified"] = dateToMilliseconds(dateMod) + fileData["metadata"] = metadata + // force unwrap name since parser ensure it exists + fileData["name"] = metadata["name"]![0] + fileData["type"] = "\(type)" + // add extra data if a request userscript + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + fileData["request"] = true + } + if metadata["description"] != nil { + fileData["description"] = metadata["description"]![0] + } + if metadata["version"] != nil && metadata["updateURL"] != nil { + fileData["canUpdate"] = true + } + fileData["noframes"] = metadata["noframes"] != nil ? true : false + // if asked, also return the code string + if (includeCode) { + // can force unwrap because always returned from parser + fileData["code"] = parsed["code"] as! String + } + files.append(fileData) + } + logger?.info("\(#function, privacy: .public) - completed") + return files +} + +func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: String) -> Bool { + let directory = getRequireLocation().appendingPathComponent(filename) + // if file requires no resource but directory exists, remove it + // this resource is not user-generated and can be downloaded again, so just remove it instead of moves to the trash + // also in ios, the volume “Data” has no trash and item can only be removed directly + if resources.isEmpty { + if FileManager.default.fileExists(atPath: directory.path) { + do { + try FileManager.default.removeItem(at: directory) + } catch { + logger?.error("\(#function, privacy: .public) - failed to remove directory: \(error, privacy: .public)") + } + } + return true + } + // record URLs for subsequent processing + var resourceUrls = Set() + // loop through resource urls and attempt to fetch it + for resourceUrlString in resources { + // get the path of the url string + guard let resourceUrlPath = URLComponents(string: resourceUrlString)?.path else { + // if path can not be obtained, skip and log + logger?.info("\(#function, privacy: .public) - failed to get path on \(filename, privacy: .public) for \(resourceUrlString, privacy: .public)") + continue + } + // skip urls pointing to files of different types + if resourceUrlPath.hasSuffix(fileType) { + let resourceFilename = sanitize(resourceUrlString) + let fileURL = directory.appendingPathComponent(resourceFilename) + // insert url to resolve symlink into set + resourceUrls.insert(fileURL.standardizedFileURL) + // only attempt to get resource if it does not yet exist + if FileManager.default.fileExists(atPath: fileURL.path) {continue} + // get the remote file contents + guard let contents = getRemoteFileContents(resourceUrlString) else {continue} + // check if file specific folder exists at requires directory + if !FileManager.default.fileExists(atPath: directory.path) { + guard ((try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false)) != nil) else { + logger?.info("\(#function, privacy: .public) - failed to create required code directory for \(filename, privacy: .public)") + return false + } + } + // finally write file to directory + guard ((try? contents.write(to: fileURL, atomically: false, encoding: .utf8)) != nil) else { + logger?.info("\(#function, privacy: .public) - failed to write content to file for \(filename, privacy: .public) from \(resourceUrlString, privacy: .public)") + return false + } + } + } + // cleanup downloaded files that are no longer required + do { + var downloadedUrls = Set() + // get all downloaded resources url + let fileUrls = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: []) + // insert url to resolve symlink into set + for url in fileUrls { downloadedUrls.insert(url.standardizedFileURL) } + // exclude currently required resources + let abandonedUrls = downloadedUrls.subtracting(resourceUrls) + // loop through abandoned urls and attempt to remove it + for abandonFileUrl in abandonedUrls { + do { + try FileManager.default.removeItem(at: abandonFileUrl) + logger?.info("\(#function, privacy: .public) - cleanup abandoned resource: \(unsanitize(abandonFileUrl.lastPathComponent), privacy: .public)") + } catch { + logger?.error("\(#function, privacy: .public) - failed to remove abandoned resource: \(error, privacy: .public)") + } + } + } catch { + logger?.error("\(#function, privacy: .public) - failed to cleanup resources: \(error, privacy: .public)") + } + return true +} + +func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[String: String]]? { + // only get all files if files were not provided + var files = [[String: Any]]() + if optionalFilesArray.isEmpty { + guard let getFiles = getAllFiles() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + files = getFiles + } else { + files = optionalFilesArray + } + + var hasUpdates = [[String: String]]() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + let filename = file["filename"] as! String + let canUpdate = file["canUpdate"] as! Bool + let metadata = file["metadata"] as! [String: [String]] + let type = file["type"] as! String + let name = metadata["name"]![0] + logger?.info("\(#function, privacy: .public) - Checking for remote updates for \(filename, privacy: .public)") + if canUpdate { + let currentVersion = metadata["version"]![0] + let updateUrl = metadata["updateURL"]![0] + // before fetching remote contents, ensure it points to a file of the same type + if !validateUrl(updateUrl) {continue} + guard + let remoteFileContents = getRemoteFileContents(updateUrl), + let remoteFileContentsParsed = parse(remoteFileContents), + let remoteMetadata = remoteFileContentsParsed["metadata"] as? [String: [String]], + let remoteVersion = remoteMetadata["version"]?[0] + else { + logger?.error("\(#function, privacy: .public) - failed to parse remote file contents") + return nil + } + let remoteVersionNewer = isVersionNewer(currentVersion, remoteVersion) + if remoteVersionNewer { + hasUpdates.append(["name": name, "filename": filename, "type": type, "url": updateUrl]) + } + } + } + logger?.info("\(#function, privacy: .public) - Finished checking for remote updates for \(files.count, privacy: .public) files") + return hasUpdates +} + +func getRemoteFileContents(_ url: String) -> String? { + logger?.info("\(#function, privacy: .public) - started for \(url, privacy: .public)") + // if url is http change to https + var urlChecked = url + if urlChecked.hasPrefix("http:") { + urlChecked = urlChecked.replacingOccurrences(of: "http:", with: "https:") + logger?.info("\(#function, privacy: .public) - \(url, privacy: .public) is using insecure http, attempt to fetch remote content with https") + } + // convert url string to url + guard let solidURL = fixedURL(string: urlChecked) else { + logger?.error("\(#function, privacy: .public) - failed at (1), invalid URL: \(url, privacy: .public)") + return nil + } + var contents = "" + // get remote file contents, synchronously + let semaphore = DispatchSemaphore(value: 0) + var task: URLSessionDataTask? + task = URLSession.shared.dataTask(with: solidURL) { data, response, error in + if let r = response as? HTTPURLResponse, data != nil, error == nil { + if r.statusCode == 200 { + contents = String(data: data!, encoding: .utf8) ?? "" + } else { + logger?.error("\(#function, privacy: .public) - http statusCode (\(r.statusCode, privacy: .public)): \(url, privacy: .public)") + } + } + if let error = error { + logger?.error("\(#function, privacy: .public) - task error: \(error, privacy: .public) (\(url, privacy: .public))") + } + semaphore.signal() + } + task?.resume() + // wait 30 seconds before timing out + if semaphore.wait(timeout: .now() + 30) == .timedOut { + task?.cancel() + logger?.error("\(#function, privacy: .public) - 30 seconds timeout: \(url, privacy: .public)") + } + + // if made it to this point and contents still an empty string, something went wrong with the request + if contents.isEmpty { + logger?.error("\(#function, privacy: .public) - failed at (2), contents empty: \(url, privacy: .public)") + return nil + } + logger?.info("\(#function, privacy: .public) - completed for \(url, privacy: .public)") + return contents +} + +func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool { + // get names of all files with updates available + guard + let filesWithUpdates = checkForRemoteUpdates(optionalFilesArray), + let saveLocation = getSaveLocation() + else { + logger?.error("\(#function, privacy: .public) - failed to update files (1)") + return false + } + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + for file in filesWithUpdates { + // can be force unwrapped because checkForRemoteUpdates didn't return nil + let filename = file["filename"]! + let fileUrl = saveLocation.appendingPathComponent(filename) + guard + let content = try? String(contentsOf: fileUrl, encoding: .utf8), + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]], + let updateUrl = metadata["updateURL"]?[0] + else { + logger?.error("\(#function, privacy: .public) - failed to update files (2)") + continue + } + let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl + guard + let remoteFileContents = getRemoteFileContents(downloadUrl), + ((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil) + else { + logger?.error("\(#function, privacy: .public) - failed to update files (3)") + continue + } + logger?.info("\(#function, privacy: .public) - updated \(filename, privacy: .public) with contents fetched from \(downloadUrl, privacy: .public)") + } + return true +} + +func toggleFile(_ filename: String,_ action: String) -> Bool { + // if file doesn't exist return false + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return false + } + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + let path = saveLocation.appendingPathComponent(filename).path + if !FileManager.default.fileExists(atPath: path) { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return false + } + var manifest = getManifest() + // if file is already disabled + if action == "disable" && manifest.disabled.contains(filename) || action == "enabled" && !manifest.disabled.contains(filename) { + return true + } + // add filename to disabled array if disabling + if (action == "disable") {manifest.disabled.append(filename)} + // remove filename from disabled array if enabling + if (action == "enable") { + guard let index = manifest.disabled.firstIndex(of: filename) else { + logger?.error("\(#function, privacy: .public) - failed at (3)") + return false + } + manifest.disabled.remove(at: index) + } + if !updateManifest(with: manifest) { + logger?.error("\(#function, privacy: .public) - failed at (4)") + return false + } + return true +} + +func checkDefaultDirectories() -> Bool { + let defaultSaveLocation = getDocumentsDirectory().appendingPathComponent("scripts") + let requireLocation = getRequireLocation() + let urls = [defaultSaveLocation, requireLocation] + for url in urls { + if !FileManager.default.fileExists(atPath: url.path) { + do { + try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false) + } catch { + // could not create the save location directory, show error + logger?.error("\(#function, privacy: .public) - failed at (1) - \(url, privacy: .public) - \(error, privacy: .public)") + return false + } + } + } + return true +} + +// matching +func stringToRegex(_ stringPattern: String) -> NSRegularExpression? { + let pattern = #"[\.|\?|\^|\$|\+|\{|\}|\[|\]|\||\\(|\)|\/]"# + var patternReplace = "^\(stringPattern.replacingOccurrences(of: pattern, with: #"\\$0"#, options: .regularExpression))$" + patternReplace = patternReplace.replacingOccurrences(of: "*", with: ".*") + guard let regex = try? NSRegularExpression(pattern: patternReplace, options: .caseInsensitive) else { + return nil + } + return regex +} + +// RE for parse matchPattern +let re3 = try! NSRegularExpression(pattern: #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"#, options: .caseInsensitive) + +func match(_ url: String, _ matchPattern: String) -> Bool { + guard + let parts = jsLikeURL(url), + let ptcl = parts["protocol"], + let host = parts["hostname"], + var path = parts["pathname"] + else { + logger?.error("\(#function, privacy: .public) - invalid url \(url, privacy: .public)") + return false + } + + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#path + // The value for the path matches against the string which is the URL path plus the URL query string + if let search = parts["search"], search.count > 0 { + path += search + } + + // matchPattern is the value from metatdata key @match or @exclude-match + if (matchPattern == "") { + return true + } + // currently only http/s supported + if (ptcl != "http:" && ptcl != "https:") { + return false + } + let range = NSMakeRange(0, matchPattern.utf16.count) + guard let parts = re3.firstMatch(in: matchPattern, options: [], range: range) else { + logger?.error("\(#function, privacy: .public) - malformed regex match pattern - \(matchPattern, privacy: .public)") + return false + } + // ensure url protocol matches pattern protocol + let protocolPattern = matchPattern[Range(parts.range(at: 1), in: matchPattern)!] + if (protocolPattern != "*:" && ptcl != protocolPattern) { + return false + } + // construct host regex from matchPattern + let matchPatternHost = matchPattern[Range(parts.range(at: 2), in: matchPattern)!] + var hostPattern = "^\(matchPatternHost.replacingOccurrences(of: ".", with: "\\."))$" + hostPattern = hostPattern.replacingOccurrences(of: "^*$", with: ".*") + hostPattern = hostPattern.replacingOccurrences(of: "*\\.", with: "(.*\\.)?") + guard let hostRegEx = try? NSRegularExpression(pattern: hostPattern, options: .caseInsensitive) else { + logger?.error("\(#function, privacy: .public) - invalid host regex") + return false + } + // construct path regex from matchPattern + let matchPatternPath = matchPattern[Range(parts.range(at: 3), in: matchPattern)!] + guard let pathRegEx = stringToRegex(String(matchPatternPath)) else { + logger?.error("\(#function, privacy: .public) - invalid path regex") + return false + } + guard + (hostRegEx.firstMatch(in: host, options: [], range: NSMakeRange(0, host.utf16.count)) != nil), + (pathRegEx.firstMatch(in: path, options: [], range: NSMakeRange(0, path.utf16.count)) != nil) + else { + return false + } + + return true +} + +func include(_ url: String,_ pattern: String) -> Bool { + var regex:NSRegularExpression + if pattern.hasPrefix("/") && pattern.hasSuffix("/") { + let p = String(pattern.dropFirst().dropLast()) + guard let exp = try? NSRegularExpression(pattern: p, options: .caseInsensitive) else { + logger?.error("\(#function, privacy: .public) - invalid regex") + return false + } + regex = exp + } else { + guard let exp = stringToRegex(pattern) else { + logger?.error("\(#function, privacy: .public) - coudn't convert string to regex") + return false + } + regex = exp + } + if (regex.firstMatch(in: url, options: [], range: NSMakeRange(0, url.utf16.count)) == nil) { + return false + } + return true +} + +func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlocklist: Bool) -> [String] { + logger?.info("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .private(mask: .hash))") + // logger?.debug("\(#function, privacy: .public) - Getting matched files for \(url, privacy: .public)") + let manifest = optionalManifest ?? getManifest() + + // filenames that should not load for the passed url + // the manifest values from @exclude and @exclude-match populate this set + var excludedFilenames: Set = [] + // filenames that should load for the passed url + // the manifest values from @include and @match populate this set + var matchedFilenames: Set = [] + // all exclude-match patterns from manifest + let excludeMatchPatterns = manifest.excludeMatch.keys + // all match patterns from manifest + let matchPatterns = manifest.match.keys + // all include expressions from manifest + let includeExpressions = manifest.include.keys + // all exclude expressions from manifest + let excludeExpressions = manifest.exclude.keys + + // if url matches a pattern in blocklist, no injection for this url + if (checkBlocklist) { + for pattern in manifest.blacklist { + if match(url, pattern) { + // return empty array + return Array(matchedFilenames) + } + } + } + + // loop through all the @exclude-match patterns + // if any match passed url, push all filenames to excludedFilenames set + for pattern in excludeMatchPatterns { + if match(url, pattern) { + guard let filenames = manifest.excludeMatch[pattern] else { + logger?.error("\(#function, privacy: .public) - failed at (2) for \(pattern, privacy: .public)") + continue + } + excludedFilenames = excludedFilenames.union(filenames) + } + } + for exp in excludeExpressions { + if include(url, exp) { + guard let filenames = manifest.exclude[exp] else { + logger?.error("\(#function, privacy: .public) - failed at (3) for \(exp, privacy: .public)") + continue + } + excludedFilenames = excludedFilenames.union(filenames) + } + } + for pattern in matchPatterns { + if match(url, pattern) { + guard let filenames = manifest.match[pattern] else { + logger?.error("\(#function, privacy: .public) - failed at (4) for \(pattern, privacy: .public)") + continue + } + matchedFilenames = matchedFilenames.union(filenames) + } + } + for exp in includeExpressions { + if include(url, exp) { + guard let filenames = manifest.include[exp] else { + logger?.error("\(#function, privacy: .public) - failed at (5) for \(exp, privacy: .public)") + continue + } + matchedFilenames = matchedFilenames.union(filenames) + } + } + matchedFilenames = matchedFilenames.subtracting(excludedFilenames) + logger?.info("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .private(mask: .hash))") + // logger?.debug("\(#function, privacy: .public) - Got \(matchedFilenames.count) matched files for \(url, privacy: .public)") + return Array(matchedFilenames) +} + +// injection +func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { + var cssFiles = [Any]() + var jsFiles = [Any]() + var menuFiles = [Any]() + + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + + for filename in filenames { + guard + let contents = getFileContentsParsed(saveLocation.appendingPathComponent(filename)), + var code = contents["code"] as? String, + let type = filename.split(separator: ".").last + else { + // if guard fails, log error continue to next file + logger?.error("\(#function, privacy: .public) - failed at (2) for \(filename, privacy: .public)") + continue + } + // can force unwrap b/c getFileContentsParsed ensures metadata exists + let metadata = contents["metadata"] as! [String: [String]] + let name = metadata["name"]![0] + + // if metadata has noframes option and the url is not the top window, don't load + if (metadata["noframes"] != nil && !isTop) { + continue + } + + // get run-at values and set default if missing + // if type request, ignore + var runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + continue + } + + // normalize weight + var weight = metadata["weight"]?[0] ?? "1" + weight = normalizeWeight(weight) + + // get inject-into and set default if missing + var injectInto = metadata["inject-into"]?[0] ?? "auto" + let injectVals: Set = ["auto", "content", "page"] + let runAtVals: Set = ["context-menu", "document-start", "document-end", "document-idle"] + let validGrants: Set = [ + "GM.info", + "GM_info", + "GM.addStyle", + "GM.openInTab", + "GM.closeTab", + "GM.setValue", + "GM.getValue", + "GM.deleteValue", + "GM.listValues", + "GM.setClipboard", + "GM.getTab", + "GM.saveTab", + "GM_xmlhttpRequest", + "GM.xmlHttpRequest" + ] + // if either is invalid use default value + if !injectVals.contains(injectInto) { + injectInto = "auto" + } + if !runAtVals.contains(runAt) { + runAt = "document-end" + } + + // attempt to get all @grant value + var grants = metadata["grant"] ?? [] + + if !grants.isEmpty { + if grants.contains("none") { + // `@grant none` takes precedence + grants = [] + } else { + // remove duplicates + grants = Array(Set(grants)) + // filter out grant values that are not in validGrant set + grants = grants.filter{validGrants.contains($0)} + } + } + + + // set GM.info data + let description = metadata["description"]?[0] ?? "" + let excludes = metadata["exclude"] ?? [] + let excludeMatches = metadata["exclude-match"] ?? [] + let icon = metadata["icon"]?[0] ?? "" + let includes = metadata["include"] ?? [] + let matches = metadata["match"] ?? [] + let requires = metadata["require"] ?? [] + let version = metadata["version"]?[0] ?? "" + let noframes = metadata["noframes"] != nil ? true : false + var scriptObject:[String: Any] = [ + "description": description, + "excludes": excludes, + "exclude-match": excludeMatches, + "filename": filename, + "grant": grants, + "icon": icon, + "includes": includes, + "inject-into": injectInto, + "matches": matches, + "name": name, + "noframes": noframes, + "namespace": "", + "resources": "", + "require": requires, + "run-at": runAt, + "version": version + ] + // certain metadata keys use a different key name then the actual key name + // for compatibility keeping this when applicable, although the rationale is not clear to me + // for unique keys passed to scriptObject, using the same key name that is present in actual userscript + // this key map is used to check for existence of keys in next loop + let keyMap = [ + "exclude": "excludes", + "include": "includes", + "match": "matches", + "resource": "resources", + ] + for metaline in metadata { + let key = keyMap[metaline.key] ?? metaline.key + if !scriptObject.keys.contains(key) { + let value = metaline.value + // metalines without values aren't included in parsed metadata object + // the only exception is @noframes + scriptObject[key] = !value.isEmpty ? value : value[0] + } + } + let scriptMetaStr = contents["metablock"] as? String ?? "??" + + // attempt to get require resource from disk + // if required resource is inaccessible, log error and continue + if let required = metadata["require"] { + // reverse required metadata + // if required is ["A", "B", "C"], C gets added above B which is above A, etc.. + // the reverse of that is desired + for require in required.reversed() { + let sanitizedName = sanitize(require) + let requiredFileURL = getRequireLocation().appendingPathComponent(filename).appendingPathComponent(sanitizedName) + if let requiredContent = try? String(contentsOf: requiredFileURL, encoding: .utf8) { + code = "\(requiredContent)\n\(code)" + } else { + logger?.error("\(#function, privacy: .public) - failed at (3) for \(require, privacy: .public)") + } + } + } + + if type == "css" { + cssFiles.append([ + "code": code, + "filename": filename, + "name": name, + "type": "css", + "weight": weight + ]) + } else if type == "js" { + if runAt == "context-menu" { + #if os(macOS) + menuFiles.append([ + "code": code, + "scriptMetaStr": scriptMetaStr, + "scriptObject": scriptObject, + "type": "js", + "weight": weight + ]) + #endif + } else { + jsFiles.append([ + "code": code, + "scriptMetaStr": scriptMetaStr, + "scriptObject": scriptObject, + "type": "js", + "weight": weight + ]) + } + } + } + let resp = [ + "files": ["css": cssFiles, "js": jsFiles, "menu": menuFiles], + "scriptHandler": "Userscripts", + "scriptHandlerVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" + ] as [String : Any] + return resp +} + +func getFileContentsParsed(_ url: URL) -> [String: Any]? { + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + // check that url is a valid path to a directory or single file + guard + FileManager.default.fileExists(atPath: url.path), + let content = try? String(contentsOf: url, encoding: .utf8), + let parsed = parse(content) + else { + return nil + } + return parsed +} + +func getInjectionFilenames(_ url: String) -> [String]? { + var filenames = [String]() + let manifest = getManifest() + guard let active = manifest.settings["active"] else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // if injection is disabled return empty array + if active != "true" { + return filenames + } + let matched = getMatchedFiles(url, manifest, true) + // filter out all disabled files + filenames = matched.filter{!manifest.disabled.contains($0)} + return filenames +} + +func getRequestScripts() -> [[String: String]]? { + var requestScripts = [[String: String]]() + // check the manifest to see if injection is enabled + let manifest = getManifest() + guard let active = manifest.settings["active"] else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // if not enabled, do not apply any net requests, ie. return empty array + if active != "true" { + return requestScripts + } + guard let files = getAllFiles(includeCode: true) else { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return nil + } + for file in files { + let isRequest = file["request"] as? Bool ?? false + // skip any non-request userscripts + if !isRequest { + continue + } + // can be force unwrapped because getAllFiles always returns these + let name = file["name"] as! String + let code = file["code"] as! String + let filename = file["filename"] as! String + + if !manifest.disabled.contains(filename) { + requestScripts.append(["name": name, "code": code]) + } + } + return requestScripts +} + +func getContextMenuScripts() -> [String: Any]? { + var menuFilenames = [String]() + // check the manifest to see if injection is enabled + let manifest = getManifest() + guard let active = manifest.settings["active"] else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // if not enabled return empty array + if active != "true" { + return ["files": ["menu": []]] + } + // get all files at save location + guard let files = getAllFiles() else { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return nil + } + // loop through files and find @run-at context-menu script filenames + for file in files { + guard let filename = file["filename"] as? String else { + logger?.error("\(#function, privacy: .public) - failed at (3), couldn't get filename") + continue + } + guard let fileMetadata = file["metadata"] as? [String: [String]] else { + logger?.error("\(#function, privacy: .public) - failed at (4), couldn't get metadata for \(filename, privacy: .public)") + continue + } + let runAt = fileMetadata["run-at"]?[0] ?? "document-end" + if runAt != "context-menu" || manifest.disabled.contains(filename) { + continue + } + menuFilenames.append(filename) + } + // get and return script objects for all context-menu scripts + guard let scripts = getCode(menuFilenames, true) else { + logger?.error("\(#function, privacy: .public) - failed at (5)") + return nil + } + return scripts +} + +// popup +func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { + var matches = [[String: Any]]() + // if the url doesn't start with http/s return empty array + if !url.starts(with: "http://") && !url.starts(with: "https://") { + return matches + } + // get all the files saved to manifest that match the passed url + let matched = getMatchedFiles(url, nil, false) + // get all the files at the save location + guard + let files = getAllFiles() + else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // filter out the files that are present in both files and matched + // force unwrap filename to string since getAllFiles always returns it + matches = files.filter{matched.contains($0["filename"] as! String)} + + // get the subframe url matches + var frameUrlsMatched = [[String: Any]]() + var frameUrlsMatches = [String]() + // filter out the top page url from the frame urls + let frameUrls = subframeUrls.filter{$0 != url} + // for each url just pushed to frameUrls, get all the files saved to manifest that match their url + for frameUrl in frameUrls { + let frameMatches = getMatchedFiles(frameUrl, nil, false) + for frameMatch in frameMatches { + // for the match against the frameUrl, see if it has @noframes + // if so, it should not be appended to frameUrlsMatches + // filter all files for the first one that matches the frameMatch filename + // can force unwrap filename b/c getAllFiles always returns it + let frameMatchMetadata = files.filter{$0["filename"] as! String == frameMatch}.first + // can force unwrap noframes b/c getAllFiles always returns it + let noFrames = frameMatchMetadata?["noframes"] as? Bool ?? false + if !matched.contains(frameMatch) && !noFrames { + frameUrlsMatches.append(frameMatch) + } + } + } + + // filter out the files that are present in both files and frameUrlsMatches + // force unwrap filename to string since getAllFiles always returns it + frameUrlsMatched = files.filter{frameUrlsMatches.contains($0["filename"] as! String)} + // loop through frameUrlsMatched and add subframe key/val + for (index, var frameUrlsMatch) in frameUrlsMatched.enumerated() { + frameUrlsMatch["subframe"] = true + frameUrlsMatched[index] = frameUrlsMatch + } + // add frameUrlsMatched to matches array + matches.append(contentsOf: frameUrlsMatched) + return matches +} + +func popupUpdateAll() -> Bool { + guard + let files = getAllFiles(), + updateAllFiles(files), + updateManifestMatches(files), + updateManifestRequired(files), + purgeManifest(files) + else { + return false + } + return true +} + +func getPopupBadgeCount(_ url: String, _ subframeUrls: [String]) -> Int? { + if !url.starts(with: "http://") && !url.starts(with: "https://") { + return 0 + } + let manifest = getManifest() + guard + var matches = getPopupMatches(url, subframeUrls) + else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + for pattern in manifest.blacklist { + if match(url, pattern) { + return 0 + } + } + matches = matches.filter{!manifest.disabled.contains($0["filename"] as! String)} + return matches.count +} + +func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + let fileUrl = saveLocation.appendingPathComponent(filename) + guard + let content = try? String(contentsOf: fileUrl, encoding: .utf8), + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]], + let updateUrl = metadata["updateURL"]?[0] + else { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return nil + } + let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl + guard + let remoteFileContents = getRemoteFileContents(downloadUrl), + ((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil) + else { + logger?.error("\(#function, privacy: .public) - failed at (3)") + return nil + } + guard + let files = getAllFiles(), + updateManifestMatches(files), + updateManifestRequired(files), + purgeManifest(files), + let matches = getPopupMatches(url, subframeUrls) + else { + logger?.error("\(#function, privacy: .public) - failed at (4)") + return nil + } + return matches +} + +// page +func getInitData() -> [String: Any]? { + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return nil + } + return [ + "saveLocation": saveLocation.path, + "platform": getPlatform(), + "scheme": Bundle.main.infoDictionary?["US_URL_SCHEME"] as! String, + "version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??", + "build": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" + ] +} + +func getLegacyData() -> [String: Any]? { + let manifest = getManifest() + var data:[String: Any] = manifest.settings + data["blacklist"] = manifest.blacklist + return data +} + +func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] { + var response = [String: Any]() + let newContent = content + guard let saveLocation = getSaveLocation() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return ["error": "failed to get save location when attempting to save"] + } + guard + let oldFilename = item["filename"] as? String, + let type = item["type"] as? String + else { + return ["error": "invalid argument in save function"] + } + guard + let parsed = parse(newContent), + let metadata = parsed["metadata"] as? [String: [String]] + else { + return ["error": "failed to parse metadata"] + } + guard let n = metadata["name"]?[0] else { + return ["error": "@name not found in metadata"] + } + var name = sanitize(n) + + // construct new file name + let newFilename = "\(name).user.\(type)" + + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + guard + let allFilesUrls = try? FileManager.default.contentsOfDirectory(at: saveLocation, includingPropertiesForKeys: []) + else { + return ["error": "failed to read save urls in save function"] + } + + // validate file before save + var allFilenames:[String] = [] // stores the indv filenames for later comparison + // old and new filenames are equal, overwriting and can skip + if oldFilename.lowercased() != newFilename.lowercased() { + // loop through all the file urls in the save location and save filename to var + for fileUrl in allFilesUrls { + // skip file if it is not of the proper type + let filename = fileUrl.lastPathComponent + if (!filename.hasSuffix(type)) { + continue + } + // if file is of the proper type, add it to the allFilenames array + allFilenames.append(filename.lowercased()) + } + } + + if allFilenames.contains(newFilename.lowercased()) { + return ["error": "filename already taken"] + } + + if newFilename.count > 250 { + return ["error": "filename too long"] + } + + // file passed validation + + // attempt to save to disk + let newFileUrl = saveLocation.appendingPathComponent(newFilename) + do { + try newContent.write(to: newFileUrl, atomically: false, encoding: .utf8) + } catch { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return ["error": "failed to write file to disk"] + } + + // saved to disk successfully + + // get the file last modified date + guard + let dateMod = try? FileManager.default.attributesOfItem(atPath: newFileUrl.path)[.modificationDate] as? Date + else { + logger?.error("\(#function, privacy: .public) - failed at (3)") + return ["error": "failed to read modified date in save function"] + } + + // remove old file and manifest records for old file if they exist + if oldFilename.lowercased() != newFilename.lowercased() { + // if user changed the filename, remove file with old filename + let oldFileUrl = saveLocation.appendingPathComponent(oldFilename) + // however, when creating a new file, if user changes the temp given name by app... + // oldFilename (the temp name in activeItem) and newFilename (@name in file contents) will differ + // the file with oldFilename will not be on the filesystem and can not be deleted + // for that edge case, using try? rather than try(!) to allow failures + try? FileManager.default.trashItem(at: oldFileUrl, resultingItemURL: nil) + } + + // update manifest for new file and purge anything from old file + guard + let allFiles = getAllFiles(), + updateManifestMatches(allFiles), + updateManifestRequired(allFiles), + updateManifestDeclarativeNetRequests(allFiles), + purgeManifest(allFiles) + else { + logger?.error("\(#function, privacy: .public) - failed at (4)") + return ["error": "file save but manifest couldn't be updated"] + } + + // un-santized name + name = unsanitize(name) + + // build response dict + response["canUpdate"] = false + response["content"] = newContent + response["filename"] = newFilename + response["lastModified"] = dateToMilliseconds(dateMod) + response["name"] = name + if metadata["description"] != nil { + response["description"] = metadata["description"]![0] + } + if metadata["version"] != nil && metadata["updateURL"] != nil { + response["canUpdate"] = true + } + // if a request "type" userscript add key/val + let runAt = metadata["run-at"]?[0] ?? "document-end" + if runAt == "request" { + response["request"] = true + } + + return response +} + +func trashFile(_ item: [String: Any]) -> Bool { + guard + let saveLocation = getSaveLocation(), + let filename = item["filename"] as? String + else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return false + } + // security scope + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + let url = saveLocation.appendingPathComponent(filename) + // if file is already removed from path, assume it was removed by user and return true + if (FileManager.default.fileExists(atPath: url.path)) { + do { + try FileManager.default.trashItem(at: url, resultingItemURL: nil) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + return false + } + } + // update manifest + guard updateManifestMatches(), updateManifestRequired(), purgeManifest() else { + logger?.error("\(#function, privacy: .public) - failed at (2)") + return false + } + return true; +} + +func getFileRemoteUpdate(_ content: String) -> [String: String] { + guard + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]] + else { + // can't parse editor contents + return ["error": "Update failed, metadata missing"] + } + // editor contents missing version value + guard let version = metadata["version"]?[0] else { + return ["error": "Update failed, version value required"] + } + // editor contents missing updateURL + guard let updateURL = metadata["updateURL"]?[0] else { + return ["error": "Update failed, update url required"] + } + // set download url + let downloadURL = (metadata["downloadURL"] != nil) ? metadata["downloadURL"]![0] : updateURL + // basic url validation + guard validateUrl(updateURL) else { + return ["error": "Update failed, invalid updateURL"] + } + guard validateUrl(downloadURL) else { + return ["error": "Update failed, invalid downloadURL"] + } + // get the remote file contents for checking version + guard var remoteContent = getRemoteFileContents(updateURL) else { + return ["error": "Update failed, updateURL unreachable"] + } + // parse remote file contents + guard + let remoteParsed = parse(remoteContent), + let remoteMetadata = remoteParsed["metadata"] as? [String: [String]], + let remoteVersion = remoteMetadata["version"]?[0] + else { + // can't parse editor contents + return ["error": "Update failed, couldn't parse remote file contents"] + } + // check if update is needed + if !isVersionNewer(version, remoteVersion) { + return ["info": "No updates found"] + } + // at this point it is known an update is available, get new code from downloadURL + // is there's a specific downloadURL overwrite remoteContents with code from downloadURL + if updateURL != downloadURL { + guard let remoteDownloadContent = getRemoteFileContents(downloadURL) else { + return ["error": "Update failed, downloadURL unreachable"] + } + remoteContent = remoteDownloadContent + } + return ["content": remoteContent] +} + +// background +func nativeChecks() -> [String: String] { + logger?.info("\(#function, privacy: .public) - started") + #if os(iOS) + // check the save location is set + guard (getSaveLocation() != nil) else { + logger?.error("\(#function, privacy: .public) - save location unset (iOS)") + return [ + "error": "Native checks error (0)", + "saveLocation": "unset", + "scheme": Bundle.main.infoDictionary?["US_URL_SCHEME"] as! String + ] + } + #endif + // check the default directories + guard checkDefaultDirectories() else { + logger?.error("\(#function, privacy: .public) - checkDefaultDirectories failed") + return ["error": "Native checks error (1)"] + } + // check the settings + guard checkSettings() else { + logger?.error("\(#function, privacy: .public) - checkSettings failed") + return ["error": "Native checks error (2)"] + } + // get all files to pass as arguments to function below + guard let allFiles = getAllFiles() else { + logger?.error("\(#function, privacy: .public) - getAllFiles failed") + return ["error": "Native checks error (3)"] + } + // purge the manifest of old records + guard purgeManifest(allFiles) else { + logger?.error("\(#function, privacy: .public) - purgeManifest failed") + return ["error": "Native checks error (4)"] + } + // update matches in manifest + guard updateManifestMatches(allFiles) else { + logger?.error("\(#function, privacy: .public) - updateManifestMatches failed") + return ["error": "Native checks error (5)"] + } + // update the required resources + guard updateManifestRequired(allFiles) else { + logger?.error("\(#function, privacy: .public) - updateManifestRequired failed") + return ["error": "Native checks error (6)"] + } + // update declarativeNetRequest + guard updateManifestDeclarativeNetRequests(allFiles) else { + logger?.error("\(#function, privacy: .public) - updateManifestDeclarativeNetRequests failed") + return ["error": "Native checks error (7)"] + } + // pass some info in response + logger?.info("\(#function, privacy: .public) - completed") + return ["success": "Native checks complete"] +} + +// userscript install +func installCheck(_ content: String) -> [String: Any] { + // this func checks a userscript's metadata to determine if it's already installed + + guard let files = getAllFiles() else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return ["error": "installCheck failed at (1)"] + } + + guard + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]], + let newName = metadata["name"]?[0] + else { + return ["error": "userscript metadata is invalid"] + } + + // loop through all files nad get their names and filenames + // we will check the new name/filename to see if this is a unique userscript + // or if it will overwrite an existing userscript + var names = [String]() + for file in files { + // can be force unwrapped because getAllFiles didn't return nil + let name = file["name"] as! String + + // populate array + names.append(name) + } + + var directive = "" + #if os(macOS) + directive = "Click" + #elseif os(iOS) + directive = "Tap" + #endif + + if names.contains(newName) { + return [ + "success": "\(directive) to re-install", + "metadata": metadata, + "installed": true + ] + } + + return [ + "success": "\(directive) to install", + "metadata": metadata, + "installed": false + ]; +} + +func installUserscript(_ url: String, _ type: String, _ content: String) -> [String: Any] { + guard + let parsed = parse(content), + let metadata = parsed["metadata"] as? [String: [String]], + let n = metadata["name"]?[0] + else { + logger?.error("\(#function, privacy: .public) - failed at (1)") + return ["error": "installUserscript failed at (1)"] + } + let name = sanitize(n) + let filename = "\(name).user.\(type)" + + let saved = saveFile(["filename": filename, "type": type], content) + return saved +} diff --git a/xcode/Safari-Extension/Info.plist b/xcode/Ext-Safari/Info.plist similarity index 95% rename from xcode/Safari-Extension/Info.plist rename to xcode/Ext-Safari/Info.plist index eda50006..2cc72fe3 100644 --- a/xcode/Safari-Extension/Info.plist +++ b/xcode/Ext-Safari/Info.plist @@ -33,6 +33,8 @@ $(MAC_SHARED_GID) US_SHARED_GID-iphoneos $(IOS_SHARED_GID) + US_EXT_IDENTIFIER + $(EXT_IDENTIFIER) US_URL_SCHEME $(APP_URL_SCHEME) diff --git a/xcode/Safari-Extension/Mac.entitlements b/xcode/Ext-Safari/Mac.entitlements similarity index 87% rename from xcode/Safari-Extension/Mac.entitlements rename to xcode/Ext-Safari/Mac.entitlements index f123ee4d..aa2f466a 100644 --- a/xcode/Safari-Extension/Mac.entitlements +++ b/xcode/Ext-Safari/Mac.entitlements @@ -8,8 +8,6 @@ $(MAC_SHARED_GID) - com.apple.security.files.bookmarks.app-scope - com.apple.security.files.user-selected.read-write com.apple.security.network.client diff --git a/xcode/Ext-Safari/SafariWebExtensionHandler.swift b/xcode/Ext-Safari/SafariWebExtensionHandler.swift new file mode 100644 index 00000000..84b84dd2 --- /dev/null +++ b/xcode/Ext-Safari/SafariWebExtensionHandler.swift @@ -0,0 +1,294 @@ +import SafariServices + +class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { + func beginRequest(with context: NSExtensionContext) { + defer { USLogStoreToFile() } + let logger = USLogger(#fileID) + let item = context.inputItems[0] as? NSExtensionItem + let message = item?.userInfo?[SFExtensionMessageKey] as? [String: Any] + // if message received without name, ignore + guard let name = message?["name"] as? String else { + logger?.error("\(#function, privacy: .public) - could not get message name from web extension") + return + } + logger?.info("\(#function, privacy: .public) - Got message with name: \(name, privacy: .public)") + // got a valid message, construct response based on message received + let response = NSExtensionItem() + // send standard error when there's an issue parsing inbound message + var inBoundError = false + // these if/else if statement are formatted so that they can be neatly collapsed in Xcode + // typically the "else if" would be on the same line as the preceding statements close bracket + // ie. } else if { + if name == "OPEN_APP" { + if let scheme = Bundle.main.infoDictionary?["US_URL_SCHEME"], + let url = URL(string: "\(scheme):") { + #if os(macOS) + NSWorkspace.shared.open(url) + #endif + } + } + else if name == "NATIVE_CHECKS" { + let result = nativeChecks() + response.userInfo = [SFExtensionMessageKey: result] + } + else if name == "REQ_PLATFORM" { + let platform = getPlatform() + response.userInfo = [SFExtensionMessageKey: ["platform": platform]] + } + else if name == "REQ_USERSCRIPTS" { + if let url = message?["url"] as? String, let isTop = message?["isTop"] as? Bool { + if + let matches = getInjectionFilenames(url), + let code = getCode(matches, isTop) + { + response.userInfo = [SFExtensionMessageKey: code] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "REQ_USERSCRIPTS failed"]] + } + } else { + inBoundError = true + } + } + else if name == "REQ_REQUESTS" { + if let requestScripts = getRequestScripts() { + response.userInfo = [SFExtensionMessageKey: requestScripts] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get requestScripts"]] + } + } + else if name == "REQ_CONTEXT_MENU_SCRIPTS" { + if let contextMenuScripts = getContextMenuScripts() { + response.userInfo = [SFExtensionMessageKey: contextMenuScripts] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get contextMenuScripts"]] + } + } + else if name == "POPUP_BADGE_COUNT" { + if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { + if let matches = getPopupBadgeCount(url, frameUrls) { + response.userInfo = [SFExtensionMessageKey: ["count": matches]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to update badge count"]] + } + } else { + inBoundError = true + } + } + else if name == "POPUP_MATCHES"{ + if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { + if let matches = getPopupMatches(url, frameUrls) { + response.userInfo = [SFExtensionMessageKey: ["matches": matches]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get matches"]] + } + } else { + inBoundError = true + } + } + else if name == "POPUP_UPDATES" { + if let updates = checkForRemoteUpdates() { + response.userInfo = [SFExtensionMessageKey: ["updates": updates]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get updates"]] + } + } + else if name == "POPUP_UPDATE_ALL" { + if popupUpdateAll(), let updates = checkForRemoteUpdates() { + response.userInfo = [SFExtensionMessageKey: ["updates": updates]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to run update sequence"]] + } + } + else if name == "POPUP_UPDATE_SINGLE" { + if + let filename = message?["filename"] as? String, + let url = message?["url"] as? String, + let frameUrls = message?["frameUrls"] as? [String] + { + if let matches = popupUpdateSingle(filename, url, frameUrls) { + response.userInfo = [SFExtensionMessageKey: ["items": matches]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to update file"]] + } + } else { + inBoundError = true + } + } + else if name == "POPUP_CHECK_UPDATES" { + if let updates = checkForRemoteUpdates() { + response.userInfo = [SFExtensionMessageKey: ["updates": updates]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to check for updates"]] + } + } + else if name == "TOGGLE_EXTENSION" { + if let active = message?["active"] as? String, ["true", "false"].contains(active) { + var manifest = getManifest() + manifest.settings["active"] = active + if updateManifest(with: manifest) { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to update injection state"]] + } + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "missing or wrong message content"]] + } + } + else if name == "TOGGLE_ITEM" { + // the current status of the item to toggle comes in as 0 || 1 + if + let item = message?["item"] as? [String: Any], + let filename = item["filename"] as? String, + let current = item["disabled"] as? Int + { + let action = current == 0 ? "disable" : "enable" + if toggleFile(filename, action) { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to toggle file"]] + } + } else { + inBoundError = true + } + } + else if name == "OPEN_SAVE_LOCATION" { + #if os(macOS) + if openSaveLocation() { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } + #elseif os(iOS) + if let files = getAllFiles() { + response.userInfo = [SFExtensionMessageKey: ["items": files]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get all files"]] + } + #endif + } + else if name == "CHANGE_SAVE_LOCATION" { + #if os(macOS) + if let scheme = Bundle.main.infoDictionary?["US_URL_SCHEME"], + let url = URL(string: "\(scheme)://changesavelocation") { + NSWorkspace.shared.open(url) + } + #endif + } + else if name == "POPUP_INSTALL_CHECK" { + if let content = message?["content"] as? String { + response.userInfo = [SFExtensionMessageKey: installCheck(content)] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content"]] + } + } + else if name == "POPUP_INSTALL_SCRIPT" { + if + let url = message?["url"] as? String, + let type = message?["type"] as? String, + let content = message?["content"] as? String + { + response.userInfo = [SFExtensionMessageKey: installUserscript(url, type, content)] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content (2)"]] + } + } + else if name == "PAGE_INIT_DATA" { + if let initData = getInitData(), checkDefaultDirectories() { + response.userInfo = [SFExtensionMessageKey: initData] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get init data"]] + } + } + else if name == "PAGE_LEGACY_IMPORT" { + #if os(macOS) + if let settings = getLegacyData() { + response.userInfo = [SFExtensionMessageKey: settings] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get legacy data"]] + } + #endif + } + else if name == "PAGE_ALL_FILES" { + #if os(macOS) + if let files = getAllFiles() { + response.userInfo = [SFExtensionMessageKey: files] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to get all files"]] + } + #endif + } + else if name == "PAGE_SAVE" { + #if os(macOS) + if + let item = message?["item"] as? [String: Any], + let content = message?["content"] as? String + { + let saveResponse = saveFile(item, content) + response.userInfo = [SFExtensionMessageKey: saveResponse] + } else { + inBoundError = true + } + #endif + } + else if name == "PAGE_TRASH" { + #if os(macOS) + if let item = message?["item"] as? [String: Any] { + if trashFile(item) { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "failed to trash file"]] + } + } else { + inBoundError = true + } + #endif + } + else if name == "PAGE_UPDATE" { + #if os(macOS) + if let content = message?["content"] as? String { + let updateResponse = getFileRemoteUpdate(content) + response.userInfo = [SFExtensionMessageKey: updateResponse] + } else { + inBoundError = true + } + #endif + } + else if name == "CANCEL_REQUESTS" { + URLSession.shared.getAllTasks { tasks in + for task in tasks { + task.cancel() + } + } + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } + else if name == "PAGE_UPDATE_SETTINGS" { + #if os(macOS) + if let settings = message?["settings"] as? [String: String] { + if updateSettings(settings) { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } else { + response.userInfo = [SFExtensionMessageKey: ["error": "Failed to save settings to disk"]] + } + } else { + inBoundError = true + } + #endif + } + else if name == "PAGE_UPDATE_BLACKLIST" { + if let blacklist = message?["blacklist"] as? [String] { + var manifest = getManifest() + manifest.blacklist = blacklist + if !updateManifest(with: manifest) { + response.userInfo = [SFExtensionMessageKey: ["error": "Failed to save blacklist to disk"]] + } else { + response.userInfo = [SFExtensionMessageKey: ["success": true]] + } + } else { + inBoundError = true + } + } + // send inBoundError if found + if inBoundError { + response.userInfo = [SFExtensionMessageKey: ["error": "Failed to parse inbound message"]] + } + context.completeRequest(returningItems: [response], completionHandler: nil) + } +} diff --git a/xcode/Safari-Extension/iOS.entitlements b/xcode/Ext-Safari/iOS.entitlements similarity index 100% rename from xcode/Safari-Extension/iOS.entitlements rename to xcode/Ext-Safari/iOS.entitlements diff --git a/xcode/Mac-App/AppDelegate.swift b/xcode/Mac-App/AppDelegate.swift deleted file mode 100644 index 23e8a127..00000000 --- a/xcode/Mac-App/AppDelegate.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Cocoa - -@NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate { - - private var window: NSWindow! - private var windowForego = false - private var windowLoaded = false - - func application(_ application: NSApplication, open urls: [URL]) { - // if open panel is already open, stop processing the URL scheme - if NSApplication.shared.keyWindow?.accessibilityIdentifier() == "open-panel" { return } - for url in urls { - if url.host == "changesavelocation" { - // avoid opening the panel repeatedly and playing unnecessary warning sounds - if NSApplication.shared.keyWindow?.identifier?.rawValue == "changeSaveLocation" { continue } - if windowLoaded { - let viewController = window.contentViewController as? ViewController - viewController?.changeSaveLocation(nil) - } else { - windowForego = true - schemeChangeSaveLocation() - } - } - } - } - - func applicationDidFinishLaunching(_ aNotification: Notification) { - if windowForego { return } - let storyboard = NSStoryboard(name: "View", bundle: Bundle.main) - let windowController = storyboard.instantiateInitialController() as! NSWindowController -// let viewController = windowController.contentViewController as! ViewController - window = windowController.window - window.setIsVisible(true) - windowLoaded = true - } - - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - return true - } - - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application - } - - func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } - -} diff --git a/xcode/Mac-App/Base.lproj/View.storyboard b/xcode/Mac-App/Base.lproj/View.storyboard deleted file mode 100644 index 7da55c4b..00000000 --- a/xcode/Mac-App/Base.lproj/View.storyboard +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xcode/Mac-App/Functions.swift b/xcode/Mac-App/Functions.swift deleted file mode 100644 index 084ef0ca..00000000 --- a/xcode/Mac-App/Functions.swift +++ /dev/null @@ -1,97 +0,0 @@ -import AppKit -import SafariServices - -let extensionIdentifier = Bundle.main.infoDictionary?["US_EXT_IDENTIFIER"] as! String - -func getSaveLocationURL() -> URL { - var url: URL - // default url - if #available(macOS 13.0, *) { - url = getDocumentsDirectory().appending(path: "scripts") - } else { - url = getDocumentsDirectory().appendingPathComponent("scripts") - } - // if not in safari extension environment, replace with extension path - if let bundleIdentifier = Bundle.main.bundleIdentifier, bundleIdentifier != extensionIdentifier { - let s = url.absoluteString.replacingOccurrences(of: bundleIdentifier, with: extensionIdentifier) - // avoid being encode again, decode first - if let decodePath = s.removingPercentEncoding { - url = URL(fileURLWithPath: decodePath, isDirectory: true) - } - } - // bookmark url - if let data = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) { - if let bookmarkURL = readBookmark(data: data, isSecure: false) { - if directoryExists(path: bookmarkURL.path) { - url = bookmarkURL - } else { - UserDefaults(suiteName: SharedDefaults.suiteName)?.removeObject(forKey: SharedDefaults.keyName) - NSLog("removed shared bookmark because it's directory is non-existent, permanently deleted or in trash") - } - } - } - return url -} - -func setSaveLocationURL(url: URL) -> Bool { - guard FileManager.default.isWritableFile(atPath: url.path) else { - let alert = NSAlert() - alert.messageText = "Can not write to path. Choose a different path." - alert.runModal() - return false - } - guard saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) else { - err("couldn't save new location from host app") - return false - } - return true -} - -func sendExtensionMessage(name: String, userInfo: [String : Any]? = nil, completion: ((Error?) -> Void)? = nil) { - SFSafariApplication.dispatchMessage( - withName: name, - toExtensionWithIdentifier: extensionIdentifier, - userInfo: userInfo - ) { error in // always be called - if error != nil { - debugPrint("Message attempted. Error info: \(String.init(describing: error))") - } - if let userHandle = completion { userHandle(error) } - } -} - -func changeSaveLocationPanel(directoryURL: URL? = nil) -> NSOpenPanel { - let panel = NSOpenPanel() - panel.allowsMultipleSelection = false - panel.canChooseDirectories = true - panel.canCreateDirectories = true - panel.canChooseFiles = false - if directoryURL != nil { - panel.directoryURL = directoryURL - } - panel.title = "Change Save Location - Userscripts" - panel.identifier = NSUserInterfaceItemIdentifier("changeSaveLocation") - return panel -} - -func schemeChangeSaveLocation() { - let saveLocationURL = getSaveLocationURL() - let panel = changeSaveLocationPanel(directoryURL: saveLocationURL) - // shows the path selection panel - let response = panel.runModal() - // check if clicked open button and there is a valid result - guard response == .OK, let url: URL = panel.urls.first else { return } - // check if path has indeed changed - if url.absoluteString == saveLocationURL.absoluteString { return } - // try set new save location path to bookmark - guard setSaveLocationURL(url: url) else { return } - // use semaphore to ensure the async func executed before app exits - let semaphore = DispatchSemaphore(value: 0) - // notify browser extension of relevant updates - sendExtensionMessage( - name: "SAVE_LOCATION_CHANGED", - userInfo: ["saveLocation": url.absoluteString.removingPercentEncoding ?? url.absoluteString], - completion: { _ in semaphore.signal() } - ) - semaphore.wait() -} diff --git a/xcode/Mac-App/ViewController.swift b/xcode/Mac-App/ViewController.swift deleted file mode 100644 index af7b1571..00000000 --- a/xcode/Mac-App/ViewController.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Cocoa -import SafariServices.SFSafariApplication - -class ViewController: NSViewController { - - @IBOutlet var appName: NSTextField! - @IBOutlet var saveLocation: NSTextField! - @IBOutlet weak var enabledText: NSTextField! - @IBOutlet weak var enabledIcon: NSView! - @IBOutlet weak var openButton: NSButton! - - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" - let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" - let extensionID = extensionIdentifier - - override func viewDidLoad() { - super.viewDidLoad() - self.appName.stringValue = "Userscripts Safari Version \(appVersion) (\(buildNumber))" - setExtensionState() - NotificationCenter.default.addObserver( - self, - selector: #selector(setExtensionState), - name: NSApplication.didBecomeActiveNotification, - object: nil - ) - let url = getSaveLocationURL() - self.saveLocation.stringValue = url.absoluteString - self.saveLocation.toolTip = url.absoluteString - if #available(macOS 13, *) { - self.openButton.title = "Open Safari Settings" - } - } - - @objc func setExtensionState() { - SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionID) { (state, error) in - guard let state = state, error == nil else { - self.enabledText.stringValue = "Safari Extension State Unknown" - err(error?.localizedDescription ?? "couldn't get safari extension state in containing app") - return - } - DispatchQueue.main.async { - self.enabledIcon.layer?.backgroundColor = state.isEnabled ? NSColor.green.cgColor : NSColor.red.cgColor - self.enabledText.stringValue = state.isEnabled ? "Safari Extension Enabled" : "Safari Extension Disabled" - } - } - } - - @IBAction func changeSaveLocation(_ sender: AnyObject?) { - guard let window = self.view.window else { return } - let saveLocationURL = getSaveLocationURL() - let panel = changeSaveLocationPanel(directoryURL: saveLocationURL) - panel.beginSheetModal(for: window, completionHandler: { response in - // check if clicked open button and there is a valid result - guard response == .OK, let url: URL = panel.urls.first else { return } - // check if path has indeed changed - if url.absoluteString == saveLocationURL.absoluteString { return } - // try set new save location path to bookmark - guard setSaveLocationURL(url: url) else { return } - // update user interface text display - self.saveLocation.stringValue = url.absoluteString - self.saveLocation.toolTip = url.absoluteString - // notify browser extension of relevant updates - sendExtensionMessage( - name: "SAVE_LOCATION_CHANGED", - userInfo: [ - "saveLocation": url.absoluteString.removingPercentEncoding ?? url.absoluteString, - "returnApp": true - ] - ) - }) - } - - @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) { - SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionID) - } -} diff --git a/xcode/Mac-Tests/UserscriptsTests.swift b/xcode/Mac-Tests/UserscriptsTests.swift deleted file mode 100644 index 12c91bd0..00000000 --- a/xcode/Mac-Tests/UserscriptsTests.swift +++ /dev/null @@ -1,202 +0,0 @@ -// -// UserscriptsTests.swift -// UserscriptsTests -// -// Created by Justin Wasack on 1/23/22. -// Copyright © 2022 Justin Wasack. All rights reserved. -// - -import XCTest -@testable import Userscripts_Mac_Safari_Extension - -class UserscriptsTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testStringSanitization() throws { - // given - let strs = [ - "String", - "https://something.com/?foo=12", - "I have backslashes \\\\", - ".....ok", - ":Akneh.,><>dkie:lm", - "..解锁B站大会员番剧、", - "解锁B站大会员番剧、B站视频解析下载;全网VIP视频免费破解去广告;全网音乐直接下载;油管、Facebook等国外视频解析下载;网盘搜索引擎破解无限下载等", - "5CLksm3AAbb2F2F2f----___--+87363&^#%o%3O3", - "Example Userscript Name" - ] - - // when - var result = [String]() - for str in strs { - let sanitizedString = sanitize(str) - let unsanitizedString = unsanitize(sanitizedString) - result.append(unsanitizedString) - } - - // then - XCTAssert(result.elementsEqual(strs)) - } - - func testEncodedCheck() throws { - let urls = [ - "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", - "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", - "https://cdn.frankerfacez.com/static/ffz_injector.user.js", - "http://www.k21p.com/example.user.js", // add http protocol - "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js" - ] - var result = [String]() - for url in urls { - if isEncoded(url) { - result.append(url) - } - } - // 2 urls already percent encoded - XCTAssert(result.count == 2) - } - - func testGetRemoteFileContents() throws { - let urls = [ - "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", - "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", - "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", - "https://cdn.frankerfacez.com/static/ffz_injector.user.js", - "http://www.k21p.com/example.user.js" // add http protocol - ] - var result = [String]() - for url in urls { - if let contents = getRemoteFileContents(url) { - result.append(contents) - } - } - XCTAssert(result.count == urls.count) - } - - func testFileRemoteUpdate() throws { - let urls = [ - "https://www.k21p.com/example.user.js", - "https://www.k21p.com/example.user.js?foo=bar", // query string - "http://www.k21p.com/example.user.js", // http protocol - "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", // non latin chars - "https://www.k21p.com/example.user.jsx" // should fail - - ] - var result = [Int]() - for url in urls { - let content = """ - // ==UserScript== - // @name test - // @match *://*/* - // @version 0.1 - // @updateURL http://www.k21p.com/example.user.js - // @downloadURL \(url) - // ==/UserScript== - """; - let response = getFileRemoteUpdate(content) - if !response.keys.contains("error") { - result.append(1) - } - } - XCTAssert(result.count == (urls.count - 1)) - } - - func testMatching() throws { - var count = 0 - var result = [String]() - let patternDict = [ - "*://*/*": [ - "https://www.bing.com/", - "https://example.org/foo/bar.html", - "https://a.org/some/path/" - ], - "*://*.mozilla.org/*": [ - "http://mozilla.org/", - "https://mozilla.org/", - "https://b.mozilla.org/path/" - ], - "*://www.google.com/*": [ - "https://www.google.com/://aa", - "https://www.google.com/preferences?prev=https://www.google.com/", - "https://www.google.com/preferences?prev=", - "https://www.google.com/" - ], - "*://localhost/*": [ - "http://localhost:8000/", - "https://localhost:3000/foo.html" - ], - "http://127.0.0.1/*": [ - "http://127.0.0.1/", - "http://127.0.0.1/foo/bar.html" - ], - "*://*.example.com/*?a=1*": [ - "http://example.com/?a=1", - "https://www.example.com/index?a=1&b=2" - ] - ] - let patternDictFails = [ - "https://www.example.com/*": [ - "file://www.example.com/", - "ftp://www.example.com/", - "ws://www.example.com/", - "http://www.example.com/" - ], - "http://www.example.com/index.html": [ - "http://www.example.com/", - "https://www.example.com/index.html" - ], - "*://localhost/*": [ - "https://localhost.com/", - "ftp://localhost:8080/" - ], - "https://www.example*/*": [ - "https://www.example.com/" - ], - "*://*.example.com/*?a=1*": [ - "http://example.com/", - "https://www.example.com/?a=2" - ] - ] - for (pattern, urls) in patternDict { - count = count + urls.count - for url in urls { - if match(url, pattern) { - result.append("1") - } - } - } - for (pattern, urls) in patternDictFails { - // don't increment count since these tests should fail - for url in urls { - if match(url, pattern) { - result.removeLast() - } - } - } - XCTAssert(result.count == count) - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/xcode/Safari-Extension/Functions.swift b/xcode/Safari-Extension/Functions.swift deleted file mode 100644 index 8e5fcffb..00000000 --- a/xcode/Safari-Extension/Functions.swift +++ /dev/null @@ -1,2037 +0,0 @@ -import Foundation -import SafariServices - -// helpers -func getRequireLocation() -> URL { - // simple helper in case required code save directory needs to change - return getDocumentsDirectory().appendingPathComponent("require") -} - -func dateToMilliseconds(_ date: Date) -> Int { - let since1970 = date.timeIntervalSince1970 - return Int(since1970 * 1000) -} - -func sanitize(_ str: String) -> String { - // removes invalid filename characters from strings - var sanitized = str - if sanitized.first == "." { - sanitized = "%2" + str.dropFirst() - } - sanitized = sanitized.replacingOccurrences(of: "/", with: "%2F") - sanitized = sanitized.replacingOccurrences(of: ":", with: "%3A") - sanitized = sanitized.replacingOccurrences(of: "\\", with: "%5C") - return sanitized -} - -func unsanitize(_ str: String) -> String { - var s = str - if s.hasPrefix("%2") && !s.hasPrefix("%2F") { - s = "." + s.dropFirst(2) - } - if s.removingPercentEncoding != s { - s = s.removingPercentEncoding ?? s - } - return s -} - -func normalizeWeight(_ weight: String) -> String { - if let w = Int(weight) { - if w > 999 { - return "999" - } else if w < 1 { - return "1" - } else { - return weight - } - } else { - return "1" - } -} - -func getSaveLocation() -> URL? { - #if os(iOS) - if - let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName), - let bookmarkUrl = readBookmark(data: sharedBookmarkData, isSecure: true) - { - return bookmarkUrl - } else { - return nil - } - #elseif os(macOS) - let standardDefaults = UserDefaults.standard - let userSaveLocationKey = "userSaveLocation" - var defaultSaveLocation:URL - - // get the default save location, if key doesn't exist write it to user defaults - if let saveLocationValue = standardDefaults.url(forKey: "saveLocation") { - defaultSaveLocation = saveLocationValue - } else { - logText("default save location not set, writing to user defaults") - let url = getDocumentsDirectory().appendingPathComponent("scripts") - UserDefaults.standard.set(url, forKey: "saveLocation") - defaultSaveLocation = url - } - - // check if shared bookmark data exists - // check if can get shared bookmark url - // won't be able to if directory trashed - guard - let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName), - let sharedBookmark = readBookmark(data: sharedBookmarkData, isSecure: false), - directoryExists(path: sharedBookmark.path) - else { - // can't get shared bookmark, use default location and remove shared bookmark key from shared user defaults - UserDefaults(suiteName: SharedDefaults.suiteName)?.removeObject(forKey: SharedDefaults.keyName) - logText("removed sharedbookmark because it was either permanently deleted or in trash") - return defaultSaveLocation - } - - // at this point, it's known sharedbookmark exists - // check local bookmark exists, can read url from bookmark and if bookmark url == shared bookmark url - // if local bookmark exists, no need to check if directory exists for it - // can't think of an instance where shared bookmark directory exists (checked above), yet local bookmark directory does not - if - let userSaveLocationData = standardDefaults.data(forKey: userSaveLocationKey), - let userSaveLocation = readBookmark(data: userSaveLocationData, isSecure: true), - sharedBookmark == userSaveLocation - { - return userSaveLocation - } - - // at this point one of the following conditions met - // - local bookmark data doesn't exist - // - for some reason can't get url from local bookmark data - // - local bookmark url != shared bookmark url (user updated save location) - // when any of those conditions are met, create new local bookmark from shared bookmark - if saveBookmark(url: sharedBookmark, isShared: false, keyName: userSaveLocationKey, isSecure: true) { - // read the newly saved bookmark and return it - guard - let localBookmarkData = standardDefaults.data(forKey: userSaveLocationKey), - let localBookmarkUrl = readBookmark(data: localBookmarkData, isSecure: true) - else { - err("reading local bookmark in getSaveLocation failed") - return nil - } - return localBookmarkUrl - } else { - err("could not save local version of shared bookmark") - return nil - } - #endif -} - -func openSaveLocation() -> Bool { - #if os(macOS) - guard let saveLocation = getSaveLocation() else { - return false - } - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: saveLocation.path) - #endif - return true -} - -func validateUrl(_ urlString: String) -> Bool { - var urlChecked = urlString - // if the url is already encoded, decode it - if isEncoded(urlChecked) { - if let decodedUrl = urlChecked.removingPercentEncoding { - urlChecked = decodedUrl - } else { - err("validateUrl failed at (1), couldn't decode url, \(urlString)") - return false - } - } - // encode all urls strings - if let encodedUrl = urlChecked.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - urlChecked = encodedUrl - } else { - err("validateUrl failed at (2), couldn't percent encode url, \(urlString)") - return false - } - guard - let parts = getUrlProps(urlChecked), - let ptcl = parts["protocol"], - let path = parts["pathname"] - else { - err("validateUrl failed at (3) for \(urlString)") - return false - } - if - (ptcl != "https:" && ptcl != "http:") - || (!path.hasSuffix(".css") && !path.hasSuffix(".js")) - { - return false - } - return true -} - -func isVersionNewer(_ oldVersion: String, _ newVersion: String) -> Bool { - let oldVersions = oldVersion.components(separatedBy: ".") - let newVersions = newVersion.components(separatedBy: ".") - for (index, version) in newVersions.enumerated() { - let a = Int(version) ?? 0 - let oldVersionValue = oldVersions.indices.contains(index) ? oldVersions[index] : "0" - let b = Int(oldVersionValue) ?? 0 - if a > b { - return true - } - if a < b { - return false - } - } - return false -} - -func isEncoded(_ str: String) -> Bool { - return str.removingPercentEncoding != str -} - -// parser -func parse(_ content: String) -> [String: Any]? { - // returns structured data from content of file - // will fail to parse if metablock or required @name key missing - let pattern = #"(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))"# - // force try b/c pattern is known to be valid regex - let regex = try! NSRegularExpression(pattern: pattern, options: []) - let range = NSRange(location: 0, length: content.utf16.count) - // return nil/fail if metablock missing - guard let match = regex.firstMatch(in: content, options: [], range: range) else { - return nil - } - - // at this point the text content has passed initial validation, it contains valid metadata - // the metadata can be in userscript or userstyle format, need to check for this and adjust group numbers - // rather than being too strict, text content can precede the opening userscript tag, however it will be ignored - // adjust start index of file content while assigning group numbers to account for any text content preceding opening tag - let contentStartIndex = content.index(content.startIndex, offsetBy: match.range.lowerBound) - var g1, g2, g3:Int - if (content[contentStartIndex.. Bool { - let content = data - let url = getDocumentsDirectory().appendingPathComponent("manifest.json") - do { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let encoded = try encoder.encode(content) - let fileContent = String(decoding: encoded, as: UTF8.self) - try fileContent.write(to: url, atomically: false, encoding: .utf8) - return true - } catch { - err("Failed to update manifest: \(error.localizedDescription)") - return false - } -} - -func getManifest() -> Manifest { - let url = getDocumentsDirectory().appendingPathComponent("manifest.json") - if - let content = try? String(contentsOf: url, encoding: .utf8), - let data = content.data(using: .utf8), - let decoded = try? JSONDecoder().decode(Manifest.self, from: Data(data)) - { - return decoded - } else { - // manifest missing, improperly formatted or missing key - // create new manifest with default key/vals - let manifest = Manifest( - blacklist: [], - declarativeNetRequest: [], - disabled: [], - exclude: [:], - excludeMatch: [:], - include: [:], - match: [:], - require: [:], - settings: defaultSettings - ) - _ = updateManifest(with: manifest) - return manifest - } -} - -func updateManifestMatches(_ optionalFilesArray: [[String: Any]] = []) -> Bool { - logText("updateManifestMatches started") - // only get all files if files were not provided - var files = [[String: Any]]() - if optionalFilesArray.isEmpty { - guard let getFiles = getAllFiles() else {return false} - files = getFiles - } else { - files = optionalFilesArray - } - var manifest = getManifest() - for file in files { - // can be force unwrapped because getAllFiles didn't return nil - let metadata = file["metadata"] as! [String: [String]] - let filename = file["filename"] as! String - // skip request type userscripts - let runAt = metadata["run-at"]?[0] ?? "document-end" - if runAt == "request" { - continue - } - // populate excludes & matches - var excludeMatched = [String]() - var matched = [String]() - var excluded = [String]() - var included = [String]() - if metadata["exclude-match"] != nil { - excludeMatched.append(contentsOf: metadata["exclude-match"]!) - } - if metadata["match"] != nil { - matched.append(contentsOf: metadata["match"]!) - } - if metadata["include"] != nil { - included.append(contentsOf: metadata["include"]!) - } - if metadata["exclude"] != nil { - excluded.append(contentsOf: metadata["exclude"]!) - } - // if in declarativeNetRequest array, remove it - if manifest.declarativeNetRequest.contains(filename) { - if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { - manifest.declarativeNetRequest.remove(at: index) - } else { - err("failed to remove \(filename) from declarativeNetRequest array") - } - } - - // update manifest values - manifest.excludeMatch = updatePatternDict(filename, excludeMatched, manifest.excludeMatch) - manifest.match = updatePatternDict(filename, matched, manifest.match) - manifest.exclude = updatePatternDict(filename, excluded, manifest.exclude) - manifest.include = updatePatternDict(filename, included, manifest.include) - - if !updateManifest(with: manifest) { - err("failed to update manifest matches") - return false - } - } - logText("updateManifestMatches complete") - return true -} - -func updatePatternDict(_ filename: String, _ filePatterns: [String], _ manifestKeys: [String: [String]]) -> [String: [String]] { - // will hold the exclude/match patterns in manifest that have file name as value - var patternsInManifestForFile = [String]() - // new var from func argument, so it can be manipulated - var returnDictionary = manifestKeys - // patterns from manifest - let keys = returnDictionary.keys - // determine what patterns already have this filename as a value - for key in keys { - // key is an array of filenames - guard let filenames = returnDictionary[key] else { - err("failed to get values for manifest key, \(key)") - continue - } - for name in filenames { - // name is a single filename - // if name is same as filename, file already added for this pattern - // add it to patternsInManifestForFile for later comparison - if name == filename { - patternsInManifestForFile.append(key) - } - } - } - // patterns in file metadata and patterns in manifest that have filename as a value - // filename already present in manifest for these patterns, do nothing with these - // let common = filePatterns.filter{patternsInManifestForFile.contains($0)} - // patterns in file metadata, but don't have the filename as a value within the manifest - // these are the manifest patterns that the filename needs to be added to - let addFilenameTo = filePatterns.filter{!patternsInManifestForFile.contains($0)} - - // the patterns that have the filename as a value, but not present in file metadata - // ie. these are the manifest patterns we need to remove the filename from - let removeFilenameFrom = patternsInManifestForFile.filter{!filePatterns.contains($0)} - - // check if filename needs to be added or new key/val needs to be created - for pattern in addFilenameTo { - if returnDictionary[pattern] != nil { - returnDictionary[pattern]?.append(filename) - } else { - returnDictionary[pattern] = [filename] - } - } - - for pattern in removeFilenameFrom { - // get the index of the filename within the array - let ind = returnDictionary[pattern]?.firstIndex(of: filename) - // remove filename from array by index - returnDictionary[pattern]?.remove(at: ind!) - // if filename was the last item in array, remove the url pattern from dictionary - if returnDictionary[pattern]!.isEmpty { - returnDictionary.removeValue(forKey: pattern) - } - } - - return returnDictionary -} - -func updateManifestRequired(_ optionalFilesArray: [[String: Any]] = []) -> Bool { - logText("updateManifestRequired started") - // only get all files if files were not provided - var files = [[String: Any]]() - if optionalFilesArray.isEmpty { - guard let getFiles = getAllFiles() else { - logText("updateManifestRequired count not get files") - return false - } - files = getFiles - } else { - files = optionalFilesArray - } - logText("updateManifestRequired will loop through \(files.count)") - var manifest = getManifest() - for file in files { - // can be force unwrapped because getAllFiles didn't return nil - let filename = file["filename"] as! String - let metadata = file["metadata"] as! [String: [String]] - let type = file["type"] as! String - let required = metadata["require"] ?? [] - logText("updateManifestRequired start \(filename)") - // get required resources for file, if fail, skip updating manifest - if !getRequiredCode(filename, required, type) { - err("couldn't fetch remote content for \(filename) in updateManifestRequired") - continue - } - - // create filenames from sanitized resource urls - // getRequiredCode does the same thing when saving to disk - // populate array with entries for manifest - var r = [String]() - for resource in required { - let sanitizedResourceName = sanitize(resource) - r.append(sanitizedResourceName) - } - - // if there are values, write them to manifest - // if failed to write to manifest, continue to next file & log error - if !r.isEmpty && r != manifest.require[filename] { - manifest.require[filename] = r - if !updateManifest(with: manifest) { - err("couldn't update manifest when getting required resources") - } - } - logText("updateManifestRequired end \(filename)") - } - logText("updateManifestRequired complete") - return true -} - -func updateManifestDeclarativeNetRequests(_ optionalFilesArray: [[String: Any]] = []) -> Bool { - logText("updateManifestDeclarativeNetRequests started") - var files = [[String: Any]]() - if optionalFilesArray.isEmpty { - guard let getFiles = getAllFiles() else { - err("updateManifestDeclarativeNetRequests failed at (1)") - return false - } - files = getFiles - } else { - files = optionalFilesArray - } - var manifest = getManifest() - for file in files { - // can be force unwrapped because getAllFiles didn't return nil - // and getAllFiles always returns the following - let metadata = file["metadata"] as! [String: [String]] - let filename = file["filename"] as! String - let runAt = metadata["run-at"]?[0] ?? "document-end" - // if not a request type, ignore - if runAt != "request" { - continue - } - var update = false - // if filename already in manifest - if !manifest.declarativeNetRequest.contains(filename) { - manifest.declarativeNetRequest.append(filename) - update = true - } - // if filename in another array remove it - for (pattern, filenames) in manifest.match { - for fn in filenames { - if fn == filename, let index = manifest.match[pattern]?.firstIndex(of: filename) { - manifest.match[pattern]?.remove(at: index) - update = true - } - } - } - for (pattern, filenames) in manifest.excludeMatch { - for fn in filenames { - if fn == filename, let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) { - manifest.excludeMatch[pattern]?.remove(at: index) - update = true - } - } - } - for (pattern, filenames) in manifest.include { - for fn in filenames { - if fn == filename, let index = manifest.include[pattern]?.firstIndex(of: filename) { - manifest.include[pattern]?.remove(at: index) - update = true - } - } - } - for (pattern, filenames) in manifest.exclude { - for fn in filenames { - if fn == filename, let index = manifest.exclude[pattern]?.firstIndex(of: filename) { - manifest.exclude[pattern]?.remove(at: index) - update = true - } - } - } - if update, !updateManifest(with: manifest) { - err("updateManifestDeclarativeNetRequests failed at (2)") - return false - } - } - logText("updateManifestDeclarativeNetRequests complete") - return true -} - -func purgeManifest(_ optionalFilesArray: [[String: Any]] = []) -> Bool { - logText("purgeManifest started") - // purge all manifest keys of any stale entries - var update = false, manifest = getManifest(), allSaveLocationFilenames = [String]() - // only get all files if files were not provided - var allFiles = [[String: Any]]() - if optionalFilesArray.isEmpty { - // if getAllFiles fails to return, ignore and pass an empty array - let getFiles = getAllFiles() ?? [] - allFiles = getFiles - } else { - allFiles = optionalFilesArray - } - // populate array with filenames - for file in allFiles { - if let filename = file["filename"] as? String { - allSaveLocationFilenames.append(filename) - } - } - // loop through manifest keys, if no file exists for value, remove value from manifest - // if there are no more filenames in pattern, remove pattern from manifest - for (pattern, filenames) in manifest.match { - for filename in filenames { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.match[pattern]?.firstIndex(of: filename) { - manifest.match[pattern]?.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from match pattern - \(pattern)") - } - } - } - if let length = manifest.match[pattern]?.count { - if length < 1, let ind = manifest.match.index(forKey: pattern) { - manifest.match.remove(at: ind) - logText("No more files for \(pattern) match pattern, removed from manifest") - } - } - } - for (pattern, filenames) in manifest.excludeMatch { - for filename in filenames { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.excludeMatch[pattern]?.firstIndex(of: filename) { - manifest.excludeMatch[pattern]?.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from exclude-match pattern - \(pattern)") - } - } - } - if let length = manifest.excludeMatch[pattern]?.count { - if length < 1, let ind = manifest.excludeMatch.index(forKey: pattern) { - manifest.excludeMatch.remove(at: ind) - logText("No more files for \(pattern) exclude-match pattern, removed from manifest") - } - } - } - for (pattern, filenames) in manifest.exclude { - for filename in filenames { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.exclude[pattern]?.firstIndex(of: filename) { - manifest.exclude[pattern]?.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from exclude pattern - \(pattern)") - } - } - } - if let length = manifest.exclude[pattern]?.count { - if length < 1, let ind = manifest.exclude.index(forKey: pattern) { - manifest.exclude.remove(at: ind) - logText("No more files for \(pattern) exclude pattern, removed from manifest") - } - } - } - for (pattern, filenames) in manifest.include { - for filename in filenames { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.include[pattern]?.firstIndex(of: filename) { - manifest.include[pattern]?.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from exclude pattern - \(pattern)") - } - } - } - if let length = manifest.include[pattern]?.count { - if length < 1, let ind = manifest.include.index(forKey: pattern) { - manifest.include.remove(at: ind) - logText("No more files for \(pattern) exclude pattern, removed from manifest") - } - } - } - // loop through manifest required - for (filename, _) in manifest.require { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.require.index(forKey: filename) { - manifest.require.remove(at: index) - // remove associated resources - if !getRequiredCode(filename, [], (filename as NSString).pathExtension) { - err("failed to remove required resources when purging \(filename) from manifest required records") - } - update = true - logText("No more required resources for \(filename), removed from manifest along with resource folder") - } - } - } - // loop through manifest disabled - for filename in manifest.disabled { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.disabled.firstIndex(of: filename) { - manifest.disabled.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from disabled") - } - } - } - // loop through manifest declarativeNetRequest - for filename in manifest.declarativeNetRequest { - if !allSaveLocationFilenames.contains(filename) { - if let index = manifest.declarativeNetRequest.firstIndex(of: filename) { - manifest.declarativeNetRequest.remove(at: index) - update = true - logText("Could not find \(filename) in save location, removed from declarativeNetRequest") - } - } - } - // remove obsolete settings - for setting in manifest.settings { - if !defaultSettings.keys.contains(setting.key) { - manifest.settings.removeValue(forKey: setting.key) - update = true - logText("Removed obsolete setting - \(setting.key)") - } - } - if update, !updateManifest(with: manifest) { - err("failed to purge manifest") - return false - } - logText("purgeManifest complete") - return true -} - -// settings -func checkSettings() -> Bool { - // iterate over default settings and individually check if each present - // if missing add setting to manifest about to be returned - // missing keys will occur when new settings introduced - var manifest = getManifest() - var update = false - for (key, value) in defaultSettings { - if manifest.settings[key] == nil { - manifest.settings[key] = value - update = true - } - } - if update, !updateManifest(with: manifest) { - err("failed to update manifest settings") - return false - } - return true -} - -func updateSettings(_ settings: [String: String]) -> Bool { - var manifest = getManifest() - manifest.settings = settings - if updateManifest(with: manifest) != true { - err("failed to update settings") - return false - } - return true -} - -// files -func getAllFiles(includeCode: Bool = false) -> [[String: Any]]? { - // returns all files of proper type with filenames, metadata & more - var files = [[String: Any]]() - let fm = FileManager.default - let manifest = getManifest() - guard let saveLocation = getSaveLocation() else { - err("getAllFiles failed at (1)") - return nil - } - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - // get all file urls within save location - guard let urls = try? fm.contentsOfDirectory(at: saveLocation, includingPropertiesForKeys: []) else { - err("getAllFiles failed at (2)") - return nil - } - for url in urls { - var fileData = [String: Any]() - // only read contents for css & js files - let filename = url.lastPathComponent - if (!filename.hasSuffix(".css") && !filename.hasSuffix(".js")) { - continue - } - // file will be skipped if metablock is missing - guard - let content = try? String(contentsOf: url, encoding: .utf8), - let dateMod = try? fm.attributesOfItem(atPath: url.path)[.modificationDate] as? Date, - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]], - let type = filename.split(separator: ".").last - else { - logText("ignoring \(filename), file missing or metadata missing from file contents") - continue - } - fileData["canUpdate"] = false - fileData["content"] = content - fileData["disabled"] = manifest.disabled.contains(filename) - fileData["filename"] = filename - fileData["lastModified"] = dateToMilliseconds(dateMod) - fileData["metadata"] = metadata - // force unwrap name since parser ensure it exists - fileData["name"] = metadata["name"]![0] - fileData["type"] = "\(type)" - // add extra data if a request userscript - let runAt = metadata["run-at"]?[0] ?? "document-end" - if runAt == "request" { - fileData["request"] = true - } - if metadata["description"] != nil { - fileData["description"] = metadata["description"]![0] - } - if metadata["version"] != nil && metadata["updateURL"] != nil { - fileData["canUpdate"] = true - } - fileData["noframes"] = metadata["noframes"] != nil ? true : false - // if asked, also return the code string - if (includeCode) { - // can force unwrap because always returned from parser - fileData["code"] = parsed["code"] as! String - } - files.append(fileData) - } - logText("getAllFiles completed") - return files -} - -func getRequiredCode(_ filename: String, _ resources: [String], _ fileType: String) -> Bool { - let directory = getRequireLocation().appendingPathComponent(filename) - // if file requires no resource but directory exists, trash it - if resources.isEmpty && FileManager.default.fileExists(atPath: directory.path) { - do { - try FileManager.default.trashItem(at: directory, resultingItemURL: nil) - } catch { - // failing to trash item won't break functionality, so log error and move on - err("failed to trash directory in getRequiredCode \(error.localizedDescription)") - return true - } - } - // loop through resource urls and attempt to fetch it - for resourceUrlString in resources { - // get the path of the url string - guard let resourceUrlPath = URLComponents(string: resourceUrlString)?.path else { - // if path can not be obtained, skip and log - logText("failed to get path on \(filename) for \(resourceUrlString)") - continue - } - // skip urls pointing to files of different types - if resourceUrlPath.hasSuffix(fileType) { - let resourceFilename = sanitize(resourceUrlString) - let fileURL = directory.appendingPathComponent(resourceFilename) - // only attempt to get resource if it does not yet exist - if FileManager.default.fileExists(atPath: fileURL.path) {continue} - // get the remote file contents - guard let contents = getRemoteFileContents(resourceUrlString) else {continue} - // check if file specific folder exists at requires directory - if !FileManager.default.fileExists(atPath: directory.path) { - guard ((try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false)) != nil) else { - logText("failed to create required code directory for \(filename)") - return false - } - } - // finally write file to directory - guard ((try? contents.write(to: fileURL, atomically: false, encoding: .utf8)) != nil) else { - logText("failed to write content to file for \(filename) from \(resourceUrlString)") - return false - } - } - } - return true -} - -func checkForRemoteUpdates(_ optionalFilesArray: [[String: Any]] = []) -> [[String: String]]? { - // only get all files if files were not provided - var files = [[String: Any]]() - if optionalFilesArray.isEmpty { - guard let getFiles = getAllFiles() else { - err("checkForRemoteUpdates failed at (1)") - return nil - } - files = getFiles - } else { - files = optionalFilesArray - } - - var hasUpdates = [[String: String]]() - for file in files { - // can be force unwrapped because getAllFiles didn't return nil - let filename = file["filename"] as! String - let canUpdate = file["canUpdate"] as! Bool - let metadata = file["metadata"] as! [String: [String]] - let type = file["type"] as! String - let name = metadata["name"]![0] - logText("Checking for remote updates for \(filename)") - if canUpdate { - let currentVersion = metadata["version"]![0] - let updateUrl = metadata["updateURL"]![0] - // before fetching remote contents, ensure it points to a file of the same type - if !updateUrl.hasSuffix(type) {continue} - guard - let remoteFileContents = getRemoteFileContents(updateUrl), - let remoteFileContentsParsed = parse(remoteFileContents), - let remoteMetadata = remoteFileContentsParsed["metadata"] as? [String: [String]], - let remoteVersion = remoteMetadata["version"]?[0] - else { - err("failed to parse remote file contents in checkForRemoteUpdates") - return nil - } - let remoteVersionNewer = isVersionNewer(currentVersion, remoteVersion) - if remoteVersionNewer { - hasUpdates.append(["name": name, "filename": filename, "type": type, "url": updateUrl]) - } - } - } - logText("Finished checking for remote updates for \(files.count) files") - return hasUpdates -} - -func getRemoteFileContents(_ url: String) -> String? { - logText("getRemoteFileContents for \(url) start") - // if url is http change to https - var urlChecked = url - if urlChecked.hasPrefix("http:") { - urlChecked = urlChecked.replacingOccurrences(of: "http:", with: "https:") - logText("\(url) is using insecure http, attempt to fetch remote content with https") - } - // if the url is already encoded, decode it - if isEncoded(urlChecked) { - if let decodedUrl = urlChecked.removingPercentEncoding { - urlChecked = decodedUrl - } else { - err("getRemoteFileContents failed at (1), couldn't decode url, \(url)") - return nil - } - } - // encode all urls strings - if let encodedUrl = urlChecked.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - urlChecked = encodedUrl - } else { - err("getRemoteFileContents failed at (2), couldn't percent encode url, \(url)") - return nil - } - // convert url string to url - guard let solidURL = URL(string: urlChecked) else { - err("getRemoteFileContents failed at (3), couldn't convert string to url, \(url)") - return nil - } - var contents = "" - // get remote file contents, synchronously - let semaphore = DispatchSemaphore(value: 0) - var task: URLSessionDataTask? - task = URLSession.shared.dataTask(with: solidURL) { data, response, error in - if let r = response as? HTTPURLResponse, data != nil, error == nil { - if r.statusCode == 200 { - contents = String(data: data!, encoding: .utf8) ?? "" - } - } - semaphore.signal() - } - task?.resume() - // wait 30 seconds before timing out - if semaphore.wait(timeout: .now() + 30) == .timedOut { - task?.cancel() - } - - // if made it to this point and contents still an empty string, something went wrong with the request - if contents.isEmpty { - logText("getRemoteFileContents failed at (4), contents empty, \(url)") - return nil - } - logText("getRemoteFileContents for \(url) end") - return contents -} - -func updateAllFiles(_ optionalFilesArray: [[String: Any]] = []) -> Bool { - // get names of all files with updates available - guard - let filesWithUpdates = checkForRemoteUpdates(optionalFilesArray), - let saveLocation = getSaveLocation() - else { - err("failed to update files (1)") - return false - } - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - for file in filesWithUpdates { - // can be force unwrapped because checkForRemoteUpdates didn't return nil - let filename = file["filename"]! - let fileUrl = saveLocation.appendingPathComponent(filename) - guard - let content = try? String(contentsOf: fileUrl, encoding: .utf8), - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]], - let updateUrl = metadata["updateURL"]?[0] - else { - err("failed to update files (2)") - continue - } - let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl - guard - let remoteFileContents = getRemoteFileContents(downloadUrl), - ((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil) - else { - err("failed to update files (3)") - continue - } - logText("updated \(filename) with contents fetched from \(downloadUrl)") - } - return true -} - -func toggleFile(_ filename: String,_ action: String) -> Bool { - // if file doesn't exist return false - guard let saveLocation = getSaveLocation() else { - err("toggleFile failed at (1)") - return false - } - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - let path = saveLocation.appendingPathComponent(filename).path - if !FileManager.default.fileExists(atPath: path) { - err("toggleFile failed at (2)") - return false - } - var manifest = getManifest() - // if file is already disabled - if action == "disable" && manifest.disabled.contains(filename) || action == "enabled" && !manifest.disabled.contains(filename) { - return true - } - // add filename to disabled array if disabling - if (action == "disable") {manifest.disabled.append(filename)} - // remove filename from disabled array if enabling - if (action == "enable") { - guard let index = manifest.disabled.firstIndex(of: filename) else { - err("toggleFile failed at (3)") - return false - } - manifest.disabled.remove(at: index) - } - if !updateManifest(with: manifest) { - err("toggleFile failed at (4)") - return false - } - return true -} - -func checkDefaultDirectories() -> Bool { - let defaultSaveLocation = getDocumentsDirectory().appendingPathComponent("scripts") - let requireLocation = getRequireLocation() - let urls = [defaultSaveLocation, requireLocation] - for url in urls { - if !FileManager.default.fileExists(atPath: url.path) { - do { - try FileManager.default.createDirectory(at: url, withIntermediateDirectories: false) - } catch { - // could not create the save location directory, show error - err("checkDefaultDirectories failed at (1) - \(url) - \(error.localizedDescription)") - return false - } - } - } - return true -} - -// matching -func getUrlProps(_ url: String) -> [String: String]? { - guard - let parts = URLComponents(string: url), - let ptcl = parts.scheme, - let host = parts.host - else { - err("failed to parse url in getUrlProps") - return nil - } - var search = "" - if let query = parts.query { - search = "?" + query - } - return [ - "protocol": "\(ptcl):", - "host": host, - "pathname": parts.path, - "search": search, - "href": url - ] -} - -func stringToRegex(_ stringPattern: String) -> NSRegularExpression? { - let pattern = #"[\.|\?|\^|\$|\+|\{|\}|\[|\]|\||\\(|\)|\/]"# - var patternReplace = "^\(stringPattern.replacingOccurrences(of: pattern, with: #"\\$0"#, options: .regularExpression))$" - patternReplace = patternReplace.replacingOccurrences(of: "*", with: ".*") - guard let regex = try? NSRegularExpression(pattern: patternReplace, options: .caseInsensitive) else { - return nil - } - return regex -} - -func match(_ url: String, _ matchPattern: String) -> Bool { - guard - let parts = getUrlProps(url), - let ptcl = parts["protocol"], - let host = parts["host"], - var path = parts["pathname"] - else { - err("invalid url \(url)") - return false - } - - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#path - // The value for the path matches against the string which is the URL path plus the URL query string - if let search = parts["search"], search.count > 0 { - path += search - } - - // matchPattern is the value from metatdata key @match or @exclude-match - if (matchPattern == "") { - return true - } - // currently only http/s supported - if (ptcl != "http:" && ptcl != "https:") { - return false - } - let partsPattern = #"^(http:|https:|\*:)\/\/((?:\*\.)?(?:[a-z0-9-]+\.)+(?:[a-z0-9]+)|\*\.[a-z]+|\*|[a-z0-9]+)(\/[^\s]*)$"# - let partsPatternReg = try! NSRegularExpression(pattern: partsPattern, options: .caseInsensitive) - let range = NSMakeRange(0, matchPattern.utf16.count) - guard let parts = partsPatternReg.firstMatch(in: matchPattern, options: [], range: range) else { - err("malformed regex match pattern") - return false - } - // ensure url protocol matches pattern protocol - let protocolPattern = matchPattern[Range(parts.range(at: 1), in: matchPattern)!] - if (protocolPattern != "*:" && ptcl != protocolPattern) { - return false - } - // construct host regex from matchPattern - let matchPatternHost = matchPattern[Range(parts.range(at: 2), in: matchPattern)!] - var hostPattern = "^\(matchPatternHost.replacingOccurrences(of: ".", with: "\\."))$" - hostPattern = hostPattern.replacingOccurrences(of: "^*$", with: ".*") - hostPattern = hostPattern.replacingOccurrences(of: "*\\.", with: "(.*\\.)?") - guard let hostRegEx = try? NSRegularExpression(pattern: hostPattern, options: .caseInsensitive) else { - err("invalid host regex") - return false - } - // construct path regex from matchPattern - let matchPatternPath = matchPattern[Range(parts.range(at: 3), in: matchPattern)!] - guard let pathRegEx = stringToRegex(String(matchPatternPath)) else { - err("invalid path regex") - return false - } - guard - (hostRegEx.firstMatch(in: host, options: [], range: NSMakeRange(0, host.utf16.count)) != nil), - (pathRegEx.firstMatch(in: path, options: [], range: NSMakeRange(0, path.utf16.count)) != nil) - else { - return false - } - - return true -} - -func include(_ url: String,_ pattern: String) -> Bool { - var regex:NSRegularExpression - if pattern.hasPrefix("/") && pattern.hasSuffix("/") { - let p = String(pattern.dropFirst().dropLast()) - guard let exp = try? NSRegularExpression(pattern: p, options: .caseInsensitive) else { - err("invalid regex in include func") - return false - } - regex = exp - } else { - guard let exp = stringToRegex(pattern) else { - err("coudn't convert string to regex in include func") - return false - } - regex = exp - } - if (regex.firstMatch(in: url, options: [], range: NSMakeRange(0, url.utf16.count)) == nil) { - return false - } - return true -} - -func getMatchedFiles(_ url: String, _ optionalManifest: Manifest?, _ checkBlocklist: Bool) -> [String] { - logText("Getting matched files for \(url)") - let manifest = optionalManifest ?? getManifest() - - // filenames that should not load for the passed url - // the manifest values from @exclude and @exclude-match populate this set - var excludedFilenames: Set = [] - // filenames that should load for the passed url - // the manifest values from @include and @match populate this set - var matchedFilenames: Set = [] - // all exclude-match patterns from manifest - let excludeMatchPatterns = manifest.excludeMatch.keys - // all match patterns from manifest - let matchPatterns = manifest.match.keys - // all include expressions from manifest - let includeExpressions = manifest.include.keys - // all exclude expressions from manifest - let excludeExpressions = manifest.exclude.keys - - // if url matches a pattern in blocklist, no injection for this url - if (checkBlocklist) { - for pattern in manifest.blacklist { - if match(url, pattern) { - // return empty array - return Array(matchedFilenames) - } - } - } - - // loop through all the @exclude-match patterns - // if any match passed url, push all filenames to excludedFilenames set - for pattern in excludeMatchPatterns { - if match(url, pattern) { - guard let filenames = manifest.excludeMatch[pattern] else { - err("getMatchedFiles failed at (2) for \(pattern)") - continue - } - excludedFilenames = excludedFilenames.union(filenames) - } - } - for exp in excludeExpressions { - if include(url, exp) { - guard let filenames = manifest.exclude[exp] else { - err("getMatchedFiles failed at (3) for \(exp)") - continue - } - excludedFilenames = excludedFilenames.union(filenames) - } - } - for pattern in matchPatterns { - if match(url, pattern) { - guard let filenames = manifest.match[pattern] else { - err("getMatchedFiles failed at (4) for \(pattern)") - continue - } - matchedFilenames = matchedFilenames.union(filenames) - } - } - for exp in includeExpressions { - if include(url, exp) { - guard let filenames = manifest.include[exp] else { - err("getMatchedFiles failed at (5) for \(exp)") - continue - } - matchedFilenames = matchedFilenames.union(filenames) - } - } - matchedFilenames = matchedFilenames.subtracting(excludedFilenames) - logText("Got \(matchedFilenames.count) matched files for \(url)") - return Array(matchedFilenames) -} - -// injection -func getCode(_ filenames: [String], _ isTop: Bool)-> [String: Any]? { - var cssFiles = [Any]() - var jsFiles = [Any]() - var menuFiles = [Any]() - - guard let saveLocation = getSaveLocation() else { - err("getCode failed at (1)") - return nil - } - - for filename in filenames { - guard - let contents = getFileContentsParsed(saveLocation.appendingPathComponent(filename)), - var code = contents["code"] as? String, - let type = filename.split(separator: ".").last - else { - // if guard fails, log error continue to next file - err("getCode failed at (2) for \(filename)") - continue - } - // can force unwrap b/c getFileContentsParsed ensures metadata exists - let metadata = contents["metadata"] as! [String: [String]] - let name = metadata["name"]![0] - - // if metadata has noframes option and the url is not the top window, don't load - if (metadata["noframes"] != nil && !isTop) { - continue - } - - // get run-at values and set default if missing - // if type request, ignore - var runAt = metadata["run-at"]?[0] ?? "document-end" - if runAt == "request" { - continue - } - - // normalize weight - var weight = metadata["weight"]?[0] ?? "1" - weight = normalizeWeight(weight) - - // get inject-into and set default if missing - var injectInto = metadata["inject-into"]?[0] ?? "auto" - let injectVals: Set = ["auto", "content", "page"] - let runAtVals: Set = ["context-menu", "document-start", "document-end", "document-idle"] - let validGrants: Set = [ - "GM.info", - "GM_info", - "GM.addStyle", - "GM.openInTab", - "GM.closeTab", - "GM.setValue", - "GM.getValue", - "GM.deleteValue", - "GM.listValues", - "GM.setClipboard", - "GM.getTab", - "GM.saveTab", - "GM_xmlhttpRequest", - "GM.xmlHttpRequest" - ] - // if either is invalid use default value - if !injectVals.contains(injectInto) { - injectInto = "auto" - } - if !runAtVals.contains(runAt) { - runAt = "document-end" - } - - // attempt to get all @grant value - var grants = metadata["grant"] ?? [] - // remove duplicates, if any exist - if !grants.isEmpty { - grants = Array(Set(grants)) - } - - // filter out grant values that are not in validGrant set - grants = grants.filter{validGrants.contains($0)} - - // set GM.info data - let description = metadata["description"]?[0] ?? "" - let excludes = metadata["exclude"] ?? [] - let excludeMatches = metadata["exclude-match"] ?? [] - let icon = metadata["icon"]?[0] ?? "" - let includes = metadata["include"] ?? [] - let matches = metadata["match"] ?? [] - let requires = metadata["require"] ?? [] - let version = metadata["version"]?[0] ?? "" - let noframes = metadata["noframes"] != nil ? true : false - var scriptObject:[String: Any] = [ - "description": description, - "excludes": excludes, - "exclude-match": excludeMatches, - "filename": filename, - "grant": grants, - "icon": icon, - "includes": includes, - "inject-into": injectInto, - "matches": matches, - "name": name, - "noframes": noframes, - "namespace": "", - "resources": "", - "require": requires, - "run-at": runAt, - "version": version - ] - // certain metadata keys use a different key name then the actual key name - // for compatibility keeping this when applicable, although the rationale is not clear to me - // for unique keys passed to scriptObject, using the same key name that is present in actual userscript - // this key map is used to check for existence of keys in next loop - let keyMap = [ - "exclude": "excludes", - "include": "includes", - "match": "matches", - "resource": "resources", - ] - for metaline in metadata { - let key = keyMap[metaline.key] ?? metaline.key - if !scriptObject.keys.contains(key) { - let value = metaline.value - // metalines without values aren't included in parsed metadata object - // the only exception is @noframes - scriptObject[key] = !value.isEmpty ? value : value[0] - } - } - let scriptMetaStr = contents["metablock"] as? String ?? "??" - - // attempt to get require resource from disk - // if required resource is inaccessible, log error and continue - if let required = metadata["require"] { - // reverse required metadata - // if required is ["A", "B", "C"], C gets added above B which is above A, etc.. - // the reverse of that is desired - for require in required.reversed() { - let sanitizedName = sanitize(require) - let requiredFileURL = getRequireLocation().appendingPathComponent(filename).appendingPathComponent(sanitizedName) - if let requiredContent = try? String(contentsOf: requiredFileURL, encoding: .utf8) { - code = "\(requiredContent)\n\(code)" - } else { - err("getCode failed at (3) for \(requiredFileURL)") - } - } - } - - if type == "css" { - cssFiles.append([ - "code": code, - "filename": filename, - "name": name, - "type": "css", - "weight": weight - ]) - } else if type == "js" { - if runAt == "context-menu" { - #if os(macOS) - menuFiles.append([ - "code": code, - "scriptMetaStr": scriptMetaStr, - "scriptObject": scriptObject, - "type": "js", - "weight": weight - ]) - #endif - } else { - jsFiles.append([ - "code": code, - "scriptMetaStr": scriptMetaStr, - "scriptObject": scriptObject, - "type": "js", - "weight": weight - ]) - } - } - } - let resp = [ - "files": ["css": cssFiles, "js": jsFiles, "menu": menuFiles], - "scriptHandler": "Userscripts", - "scriptHandlerVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" - ] as [String : Any] - return resp -} - -func getFileContentsParsed(_ url: URL) -> [String: Any]? { - guard let saveLocation = getSaveLocation() else { - err("getFileContentsParsed failed at (1)") - return nil - } - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - // check that url is a valid path to a directory or single file - guard - FileManager.default.fileExists(atPath: url.path), - let content = try? String(contentsOf: url, encoding: .utf8), - let parsed = parse(content) - else { - return nil - } - return parsed -} - -func getInjectionFilenames(_ url: String) -> [String]? { - var filenames = [String]() - let manifest = getManifest() - let matched = getMatchedFiles(url, manifest, true) - guard let active = manifest.settings["active"] else { - err("getInjectionFilenames failed at (1)") - return nil - } - // if injection is disabled return empty array - if active != "true" { - return filenames - } - // filter out all disabled files - filenames = matched.filter{!manifest.disabled.contains($0)} - return filenames -} - -func getRequestScripts() -> [[String: String]]? { - var requestScripts = [[String: String]]() - // check the manifest to see if injection is enabled - let manifest = getManifest() - guard let active = manifest.settings["active"] else { - err("getRequestScripts failed at (1)") - return nil - } - // if not enabled, do not apply any net requests, ie. return empty array - if active != "true" { - return requestScripts - } - guard let files = getAllFiles(includeCode: true) else { - err("getRequestScripts failed at (2)") - return nil - } - for file in files { - let isRequest = file["request"] as? Bool ?? false - // skip any non-request userscripts - if !isRequest { - continue - } - // can be force unwrapped because getAllFiles always returns these - let name = file["name"] as! String - let code = file["code"] as! String - let filename = file["filename"] as! String - - if !manifest.disabled.contains(filename) { - requestScripts.append(["name": name, "code": code]) - } - } - return requestScripts -} - -func getContextMenuScripts() -> [String: Any]? { - var menuFilenames = [String]() - // check the manifest to see if injection is enabled - let manifest = getManifest() - guard let active = manifest.settings["active"] else { - err("getContextMenuScripts failed at (1)") - return nil - } - // if not enabled return empty array - if active != "true" { - return ["files": ["menu": []]] - } - // get all files at save location - guard let files = getAllFiles() else { - err("getContextMenuScripts failed at (2)") - return nil - } - // loop through files and find @run-at context-menu script filenames - for file in files { - if - let fileMetadata = file["metadata"] as? [String: [String]], - let filename = file["filename"] as? String - { - let runAt = fileMetadata["run-at"]?[0] ?? "document-end" - if runAt != "context-menu" { - continue - } - if !manifest.disabled.contains(filename) { - menuFilenames.append(filename) - } - } else { - err("getContextMenuScripts failed at (3), couldn't get metadata for \(file)") - } - } - // get and return script objects for all context-menu scripts - guard let scripts = getCode(menuFilenames, true) else { - err("getContextMenuScripts failed at (4)") - return nil - } - return scripts -} - -// popup -func getPopupMatches(_ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { - var matches = [[String: Any]]() - // if the url doesn't start with http/s return empty array - if !url.starts(with: "http://") && !url.starts(with: "https://") { - return matches - } - // get all the files saved to manifest that match the passed url - let matched = getMatchedFiles(url, nil, false) - // get all the files at the save location - guard - let files = getAllFiles() - else { - err("getPopupMatches failed at (1)") - return nil - } - // filter out the files that are present in both files and matched - // force unwrap filename to string since getAllFiles always returns it - matches = files.filter{matched.contains($0["filename"] as! String)} - - // get the subframe url matches - var frameUrlsMatched = [[String: Any]]() - var frameUrlsMatches = [String]() - // filter out the top page url from the frame urls - let frameUrls = subframeUrls.filter{$0 != url} - // for each url just pushed to frameUrls, get all the files saved to manifest that match their url - for frameUrl in frameUrls { - let frameMatches = getMatchedFiles(frameUrl, nil, false) - for frameMatch in frameMatches { - // for the match against the frameUrl, see if it has @noframes - // if so, it should not be appended to frameUrlsMatches - // filter all files for the first one that matches the frameMatch filename - // can force unwrap filename b/c getAllFiles always returns it - let frameMatchMetadata = files.filter{$0["filename"] as! String == frameMatch}.first - // can force unwrap noframes b/c getAllFiles always returns it - let noFrames = frameMatchMetadata?["noframes"] as? Bool ?? false - if !matched.contains(frameMatch) && !noFrames { - frameUrlsMatches.append(frameMatch) - } - } - } - - // filter out the files that are present in both files and frameUrlsMatches - // force unwrap filename to string since getAllFiles always returns it - frameUrlsMatched = files.filter{frameUrlsMatches.contains($0["filename"] as! String)} - // loop through frameUrlsMatched and add subframe key/val - for (index, var frameUrlsMatch) in frameUrlsMatched.enumerated() { - frameUrlsMatch["subframe"] = true - frameUrlsMatched[index] = frameUrlsMatch - } - // add frameUrlsMatched to matches array - matches.append(contentsOf: frameUrlsMatched) - return matches -} - -func popupUpdateAll() -> Bool { - guard - let files = getAllFiles(), - updateAllFiles(files), - updateManifestMatches(files), - updateManifestRequired(files), - purgeManifest(files) - else { - return false - } - return true -} - -func getPopupBadgeCount(_ url: String, _ subframeUrls: [String]) -> Int? { - if !url.starts(with: "http://") && !url.starts(with: "https://") { - return 0 - } - let manifest = getManifest() - guard - var matches = getPopupMatches(url, subframeUrls) - else { - err("getPopupBadgeCount failed at (1)") - return nil - } - for pattern in manifest.blacklist { - if match(url, pattern) { - return 0 - } - } - matches = matches.filter{!manifest.disabled.contains($0["filename"] as! String)} - return matches.count -} - -func popupUpdateSingle(_ filename: String, _ url: String, _ subframeUrls: [String]) -> [[String: Any]]? { - guard let saveLocation = getSaveLocation() else { - err("updateSingleItem failed at (1)") - return nil - } - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - let fileUrl = saveLocation.appendingPathComponent(filename) - guard - let content = try? String(contentsOf: fileUrl, encoding: .utf8), - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]], - let updateUrl = metadata["updateURL"]?[0] - else { - err("updateSingleItem failed at (2)") - return nil - } - let downloadUrl = metadata["downloadURL"] != nil ? metadata["downloadURL"]![0] : updateUrl - guard - let remoteFileContents = getRemoteFileContents(downloadUrl), - ((try? remoteFileContents.write(to: fileUrl, atomically: false, encoding: .utf8)) != nil) - else { - err("updateSingleItem failed at (3)") - return nil - } - guard - let files = getAllFiles(), - updateManifestMatches(files), - updateManifestRequired(files), - purgeManifest(files), - let matches = getPopupMatches(url, subframeUrls) - else { - err("updateSingleItem failed at (4)") - return nil - } - return matches -} - -// page -func getInitData() -> [String: Any]? { - guard let saveLocation = getSaveLocation() else { - err("getInitData failed at (1)") - return nil - } - return [ - "saveLocation": saveLocation.path, - "version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??", - "build": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" - ] -} - -func getLegacyData() -> [String: Any]? { - let manifest = getManifest() - var data:[String: Any] = manifest.settings - data["blacklist"] = manifest.blacklist - return data -} - -func saveFile(_ item: [String: Any],_ content: String) -> [String: Any] { - var response = [String: Any]() - let newContent = content - guard let saveLocation = getSaveLocation() else { - err("saveFile failed at (1)") - return ["error": "failed to get save location when attempting to save"] - } - guard - let oldFilename = item["filename"] as? String, - let type = item["type"] as? String - else { - return ["error": "invalid argument in save function"] - } - guard - let parsed = parse(newContent), - let metadata = parsed["metadata"] as? [String: [String]] - else { - return ["error": "failed to parse metadata"] - } - guard let n = metadata["name"]?[0] else { - return ["error": "@name not found in metadata"] - } - var name = sanitize(n) - - // construct new file name - let newFilename = "\(name).user.\(type)" - - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - guard - let allFilesUrls = try? FileManager.default.contentsOfDirectory(at: saveLocation, includingPropertiesForKeys: []) - else { - return ["error": "failed to read save urls in save function"] - } - - // validate file before save - var allFilenames:[String] = [] // stores the indv filenames for later comparison - // old and new filenames are equal, overwriting and can skip - if oldFilename.lowercased() != newFilename.lowercased() { - // loop through all the file urls in the save location and save filename to var - for fileUrl in allFilesUrls { - // skip file if it is not of the proper type - let filename = fileUrl.lastPathComponent - if (!filename.hasSuffix(type)) { - continue - } - // if file is of the proper type, add it to the allFilenames array - allFilenames.append(filename.lowercased()) - } - } - - if allFilenames.contains(newFilename.lowercased()) { - return ["error": "filename already taken"] - } - - if newFilename.count > 250 { - return ["error": "filename too long"] - } - - // file passed validation - - // attempt to save to disk - let newFileUrl = saveLocation.appendingPathComponent(newFilename) - do { - try newContent.write(to: newFileUrl, atomically: false, encoding: .utf8) - } catch { - err("saveFile failed at (2)") - return ["error": "failed to write file to disk"] - } - - // saved to disk successfully - - // get the file last modified date - guard - let dateMod = try? FileManager.default.attributesOfItem(atPath: newFileUrl.path)[.modificationDate] as? Date - else { - err("saveFile failed at (3)") - return ["error": "failed to read modified date in save function"] - } - - // remove old file and manifest records for old file if they exist - if oldFilename.lowercased() != newFilename.lowercased() { - // if user changed the filename, remove file with old filename - let oldFileUrl = saveLocation.appendingPathComponent(oldFilename) - // however, when creating a new file, if user changes the temp given name by app... - // oldFilename (the temp name in activeItem) and newFilename (@name in file contents) will differ - // the file with oldFilename will not be on the filesystem and can not be deleted - // for that edge case, using try? rather than try(!) to allow failures - try? FileManager.default.trashItem(at: oldFileUrl, resultingItemURL: nil) - } - - // update manifest for new file and purge anything from old file - guard - let allFiles = getAllFiles(), - updateManifestMatches(allFiles), - updateManifestRequired(allFiles), - updateManifestDeclarativeNetRequests(allFiles), - purgeManifest(allFiles) - else { - err("saveFile failed at (4)") - return ["error": "file save but manifest couldn't be updated"] - } - - // un-santized name - name = unsanitize(name) - - // build response dict - response["canUpdate"] = false - response["content"] = newContent - response["filename"] = newFilename - response["lastModified"] = dateToMilliseconds(dateMod) - response["name"] = name - if metadata["description"] != nil { - response["description"] = metadata["description"]![0] - } - if metadata["version"] != nil && metadata["updateURL"] != nil { - response["canUpdate"] = true - } - // if a request "type" userscript add key/val - let runAt = metadata["run-at"]?[0] ?? "document-end" - if runAt == "request" { - response["request"] = true - } - - return response -} - -func trashFile(_ item: [String: Any]) -> Bool { - guard - let saveLocation = getSaveLocation(), - let filename = item["filename"] as? String - else { - err("trashFile failed at (1)") - return false - } - // security scope - let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() - defer { - if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} - } - let url = saveLocation.appendingPathComponent(filename) - // if file is already removed from path, assume it was removed by user and return true - if (FileManager.default.fileExists(atPath: url.path)) { - do { - try FileManager.default.trashItem(at: url, resultingItemURL: nil) - } catch { - err(error.localizedDescription) - return false - } - } - // update manifest - guard updateManifestMatches(), updateManifestRequired(), purgeManifest() else { - err("trashFile failed at (2)") - return false - } - return true; -} - -func getFileRemoteUpdate(_ content: String) -> [String: String] { - guard - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]] - else { - // can't parse editor contents - return ["error": "Update failed, metadata missing"] - } - // editor contents missing version value - guard let version = metadata["version"]?[0] else { - return ["error": "Update failed, version value required"] - } - // editor contents missing updateURL - guard let updateURL = metadata["updateURL"]?[0] else { - return ["error": "Update failed, update url required"] - } - // set download url - let downloadURL = (metadata["downloadURL"] != nil) ? metadata["downloadURL"]![0] : updateURL - // basic url validation - guard validateUrl(updateURL) else { - return ["error": "Update failed, invalid updateURL"] - } - guard validateUrl(downloadURL) else { - return ["error": "Update failed, invalid downloadURL"] - } - // get the remote file contents for checking version - guard var remoteContent = getRemoteFileContents(updateURL) else { - return ["error": "Update failed, updateURL unreachable"] - } - // parse remote file contents - guard - let remoteParsed = parse(remoteContent), - let remoteMetadata = remoteParsed["metadata"] as? [String: [String]], - let remoteVersion = remoteMetadata["version"]?[0] - else { - // can't parse editor contents - return ["error": "Update failed, couldn't parse remote file contents"] - } - // check if update is needed - if version >= remoteVersion { - return ["info": "No updates found"] - } - // at this point it is known an update is available, get new code from downloadURL - // is there's a specific downloadURL overwrite remoteContents with code from downloadURL - if updateURL != downloadURL { - guard let remoteDownloadContent = getRemoteFileContents(downloadURL) else { - return ["error": "Update failed, downloadURL unreachable"] - } - remoteContent = remoteDownloadContent - } - return ["content": remoteContent] -} - -func popupInit() -> [String: String]? { - // check the default directories - let checkDefaultDirectories = checkDefaultDirectories() - // check the settings - let checkSettings = checkSettings() - // get all files to pass as arguments to function below - guard let allFiles = getAllFiles() else { - err("Failed to getAllFiles in popupInit") - return nil - } - // purge the manifest of old records - let purgeManifest = purgeManifest(allFiles) - // update matches in manifest - let updateManifestMatches = updateManifestMatches(allFiles) - // update the required resources - let updateManifestRequired = updateManifestRequired(allFiles) - // update declarativeNetRequest - let updateDeclarativeNetRequest = updateManifestDeclarativeNetRequests(allFiles) - // verbose error checking - if !checkDefaultDirectories { - err("Failed to checkDefaultDirectories in popupInit") - return nil - } - if !checkSettings { - err("Failed to checkSettings in popupInit") - return nil - } - if !purgeManifest { - err("Failed to purgeManifest in popupInit") - return nil - } - if !updateManifestMatches { - err("Failed to updateManifestMatches in popupInit") - return nil - } - if !updateManifestRequired { - err("Failed to updateManifestRequired in popupInit") - return nil - } - if !updateDeclarativeNetRequest { - err("Failed to updateDeclarativeNetRequest in popupInit") - return nil - } - // pass some info in response - guard let saveLocation = getSaveLocation() else { - err("Failed at getSaveLocation in popupInit") - return nil - } - let documentsDirectory = getDocumentsDirectory() - let requireLocation = getRequireLocation() - - return [ - "saveLocation": saveLocation.absoluteString, - "documentsDirectory": documentsDirectory.absoluteString, - "requireLocation": requireLocation.absoluteString - ] -} - -// userscript install -func installCheck(_ content: String) -> [String: Any] { - // this func checks a userscript's metadata to determine if it's already installed - - guard let files = getAllFiles() else { - err("installCheck failed at (1)") - return ["error": "installCheck failed at (1)"] - } - - guard - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]], - let newName = metadata["name"]?[0] - else { - return ["error": "userscript metadata is invalid"] - } - - // loop through all files nad get their names and filenames - // we will check the new name/filename to see if this is a unique userscript - // or if it will overwrite an existing userscript - var names = [String]() - for file in files { - // can be force unwrapped because getAllFiles didn't return nil - let name = file["name"] as! String - - // populate array - names.append(name) - } - - var directive = "" - #if os(macOS) - directive = "Click" - #elseif os(iOS) - directive = "Tap" - #endif - - if names.contains(newName) { - return [ - "success": "\(directive) to re-install", - "metadata": metadata, - "installed": true - ] - } - - return [ - "success": "\(directive) to install", - "metadata": metadata, - "installed": false - ]; -} - -func installUserscript(_ content: String) -> [String: Any] { - guard - let parsed = parse(content), - let metadata = parsed["metadata"] as? [String: [String]], - let n = metadata["name"]?[0] - else { - err("installUserscript failed at (1)") - return ["error": "installUserscript failed at (1)"] - } - let name = sanitize(n) - let filename = "\(name).user.js" - - let saved = saveFile(["filename": filename, "type": "js"], content) - return saved -} diff --git a/xcode/Safari-Extension/Resources/_locales/en/messages.json b/xcode/Safari-Extension/Resources/_locales/en/messages.json deleted file mode 100644 index a7cee631..00000000 --- a/xcode/Safari-Extension/Resources/_locales/en/messages.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extension_name": { - "message": "Userscripts", - "description": "The display name for the extension." - }, - "extension_description": { - "message": "Save and run javascript for the web pages you visit", - "description": "Description of what the extension does." - } -} diff --git a/xcode/Safari-Extension/Resources/background.js b/xcode/Safari-Extension/Resources/background.js deleted file mode 100644 index 7eae15e0..00000000 --- a/xcode/Safari-Extension/Resources/background.js +++ /dev/null @@ -1,482 +0,0 @@ -// functions from "src/shared/utils.js"; -async function openExtensionPage() { - const extensionPageUrl = browser.runtime.getURL("dist/entry-page.html"); - const tabs = await browser.tabs.query({}); - for (let i = 0; i < tabs.length; i++) { - if (tabs[i].url === extensionPageUrl) { - await browser.windows.update(tabs[i].windowId, {focused: true}); - await browser.tabs.update(tabs[i].id, {active: true}); - return; - } - } - await browser.tabs.create({url: extensionPageUrl}); -} - -// functions from "src/shared/settings.js"; - -// first sorts files by run-at value, then by weight value -function userscriptSort(a, b) { - // map the run-at values to numeric values - const runAtValues = { - "document-start": 1, - "document-end": 2, - "document-idle": 3 - }; - const runAtA = a.scriptObject["run-at"]; - const runAtB = b.scriptObject["run-at"]; - if (runAtA !== runAtB && runAtValues[runAtA] && runAtValues[runAtB]) { - return runAtValues[runAtA] > runAtValues[runAtB]; - } - return Number(a.scriptObject.weight) < Number(b.scriptObject.weight); -} - -async function readAsDataURL(blob) { - return new Promise(resolve => { - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = () => resolve(reader.result); // base64data - }); -} - -async function getPlatform() { - let platform = localStorage.getItem("platform"); - if (!platform) { - const message = {name: "REQ_PLATFORM"}; - const response = await browser.runtime.sendNativeMessage(message); - if (!response.platform) { - console.error("Failed to get platform"); - return ""; - } - platform = response.platform; - localStorage.setItem("platform", platform); - } - return platform; -} - -function setClipboard(data, type = "text/plain") { - // future enhancement? - // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/write - // https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText - const onCopy = e => { - e.stopImmediatePropagation(); - e.preventDefault(); - e.clipboardData.setData(type, data); - document.removeEventListener("copy", onCopy, true); - }; - - const textarea = document.createElement("textarea"); - textarea.textContent = ""; - document.body.appendChild(textarea); - textarea.select(); - document.addEventListener("copy", onCopy, true); - try { - return document.execCommand("copy"); - } catch (error) { - console.warn("setClipboard failed", error); - document.removeEventListener("copy", onCopy, true); - return false; - } finally { - document.body.removeChild(textarea); - } -} - -async function setBadgeCount() { - const clearBadge = () => browser.browserAction.setBadgeText({text: ""}); - // TODO: after the background script is modularized, import and use: - // settingsStorage.get(["global_active","toolbar_badge_count","global_exclude_match"]) - const results = await browser.storage.local.get(["US_GLOBAL_ACTIVE", "US_TOOLBAR_BADGE_COUNT"]); - if (results?.US_GLOBAL_ACTIVE === false) return clearBadge(); - if (results?.US_TOOLBAR_BADGE_COUNT === false) return clearBadge(); - - const currentTab = await browser.tabs.getCurrent(); - // no active tabs exist (user closed all windows) - if (!currentTab) return clearBadge(); - const url = currentTab.url; - // if url doesn't exist, stop - if (!url) return clearBadge(); - // only check for http/s pages - if (!url.startsWith("http://") && !url.startsWith("https://")) return clearBadge(); - // TODO: if url match in global exclude list, clear badge - const frameUrls = new Set(); - const frames = await browser.webNavigation.getAllFrames({tabId: currentTab.id}); - for (let i = 0; i < frames.length; i++) { - const frameUrl = frames[i].url; - if (frameUrl !== url && frameUrl.startsWith("http")) { - frameUrls.add(frameUrl); - } - } - const message = { - name: "POPUP_BADGE_COUNT", - url, - frameUrls: Array.from(frameUrls) - }; - browser.runtime.sendNativeMessage(message, response => { - if (response.error) return console.error(response.error); - const count = response.count; - if (count > 0) { - browser.browserAction.setBadgeText({text: count.toString()}); - } else { - const _url = new URL(url); - if (_url.pathname.endsWith(".user.js")) { - browser.browserAction.setBadgeText({text: "JS"}); - } else { - clearBadge(); - } - } - }); -} - -async function setSessionRules() { - await clearAllSessionRules(); - const message = {name: "REQ_REQUESTS"}; - const response = await browser.runtime.sendNativeMessage(message); - if (response.error) { - console.error(response.error); - return; - } - // there are no rules to apply - if (!response.length) return; - // loop through response, parse the rules, push to array and log - const rules = []; - for (let i = 0; i < response.length; i++) { - const rule = response[i]; - const code = JSON.parse(rule.code); - // check if an array or single rule - if (Array.isArray(code)) { - code.forEach(r => rules.push(r)); - console.info(`Setting session rule: ${rule.name} (${code.length})`); - } else { - rules.push(code); - console.info(`Setting session rule: ${rule.name}`); - } - } - // generate unique ids for all rules to ensure no repeats - const ids = randomNumberSet(1000, rules.length); - rules.map((rule, index) => rule.id = ids[index]); - try { - await browser.declarativeNetRequest.updateSessionRules({addRules: rules}); - } catch (error) { - console.error(`Error setting session rules: ${error}`); - return; - } - console.info(`Finished setting ${rules.length} session rules`); -} - -async function clearAllSessionRules() { - const rules = await browser.declarativeNetRequest.getSessionRules(); - if (!rules.length) return; - console.info(`Clearing ${rules.length} session rules`); - const ruleIds = rules.map(a => a.id); - await browser.declarativeNetRequest.updateSessionRules({ - removeRuleIds: ruleIds - }); -} - -function randomNumberSet(max, count) { - // generates a set of random unique numbers - // returns an array - const numbers = new Set(); - while (numbers.size < count) { - numbers.add((Math.floor(Math.random() * (max - 1 + 1)) + 1)); - } - return [...numbers]; -} - -async function getContextMenuItems() { - // macos exclusive feature - const platform = await getPlatform(); - if (platform !== "macos") return; - // since it's not possible to get a list of currently active menu items - // on update, all context-menu items are cleared, then re-added - // this is done to ensure fresh code changes appear - await browser.menus.removeAll(); - // get the context-menu scripts - const message = {name: "REQ_CONTEXT_MENU_SCRIPTS"}; - const response = await browser.runtime.sendNativeMessage(message); - if (response.error) { - console.error(response.error); - return; - } - // add menus items - const items = response.files?.menu || []; - if (items.length) { - console.info(`Setting ${items.length} context-menu userscripts`); - } - for (let i = 0; i < items.length; i++) { - const item = items[i]; - // context-menu scripts require @match value - // @include values are ignored - if (!item.scriptObject.matches.length) continue; - addContextMenuItem(item); - } -} - -async function addContextMenuItem(userscript) { - // context-menu items persist for a session - // to avoid duplication, when created, save the filename to session storage - const savedItems = sessionStorage.getItem("menu"); - // if the session storage key doesn't exist use empty array - const activeItems = savedItems ? JSON.parse(savedItems) : []; - if (activeItems.indexOf(userscript.scriptObject.filename) !== -1) { - // if already saved, remove it, to get fresh code changes - await browser.menus.remove(userscript.scriptObject.filename); - } - // potential bug? https://developer.apple.com/forums/thread/685273 - // https://stackoverflow.com/q/68431201 - // parse through match values and change pathnames to deal with bug - const patterns = userscript.scriptObject.matches; - patterns.forEach((pattern, index) => { - try { - const url = new URL(pattern); - let pathname = url.pathname; - if (pathname.length > 1 && pathname.endsWith("/")) { - pathname = pathname.slice(0, -1); - } - patterns[index] = `${url.protocol}//${url.hostname}${pathname}`; - } catch (error) { - // prevent breaking when non-url pattern present - } - }); - - browser.menus.create({ - contexts: ["all"], - documentUrlPatterns: patterns, - id: userscript.scriptObject.filename, - title: userscript.scriptObject.name - }, () => { - // add event listener if needed - if (!browser.menus.onClicked.hasListener(contextClick)) { - browser.menus.onClicked.addListener(contextClick); - } - // save the context-menu item reference to sessionStorage - const value = JSON.stringify([userscript.scriptObject.filename]); - sessionStorage.setItem("menu", value); - }); -} - -function contextClick(info, tab) { - // when any created context-menu item is clicked, send message to tab - // the content script for that tag will have the context-menu code - // which will get send back in the response if/when found - const message = {name: "CONTEXT_RUN", menuItemId: info.menuItemId}; - browser.tabs.sendMessage(tab.id, message, response => { - // if code is returned, execute on that tab - if (!response.code) return; - browser.tabs.executeScript(tab.id, { - code: response.code - }); - }); -} - -// handles messages sent with browser.runtime.sendMessage -function handleMessage(request, sender, sendResponse) { - switch (request.name) { - case "REQ_USERSCRIPTS": { - // get the page url from the content script that sent request - const url = sender.url; - // use frameId to determine if request came from top level window - // if @noframes true, and isTop false, swift layer won't return code - const isTop = sender.frameId === 0; - // send request to swift layer to provide code for page url - const message = {name: "REQ_USERSCRIPTS", url, isTop}; - browser.runtime.sendNativeMessage(message, response => { - // if request failed, send error to content script for logging - if (response.error) return sendResponse(response); - // sort files - response.files.js.sort(userscriptSort); - response.files.css.sort((a, b) => { - return Number(a.weight) < Number(b.weight); - }); - // return sorted files for injection - sendResponse(response); - }); - return true; - } - case "API_CLOSE_TAB": { - const tabId = request.tabId || sender.tab.id; - browser.tabs.remove(tabId, () => sendResponse({success: 1})); - return true; - } - case "API_OPEN_TAB": { - const props = { - active: request.active, - index: sender.tab.index + 1, - url: request.url - }; - browser.tabs.create(props, response => sendResponse(response)); - return true; - } - case "API_ADD_STYLE": { - const tabId = sender.tab.id; - const details = {code: request.css, cssOrigin: "user"}; - browser.tabs.insertCSS(tabId, details, () => { - sendResponse(request.css); - }); - return true; - } - case "API_GET_TAB": { - let tab = null; - if (typeof sender.tab !== "undefined") { - const tabData = sessionStorage.getItem(`tab-${sender.tab.id}`); - try { - // if tabData is null, can still parse it and return that - tab = JSON.parse(tabData); - } catch (error) { - console.error("failed to parse tab data for getTab"); - } - } else { - console.error("unable to deliver tab due to empty tab id"); - } - sendResponse(tab == null ? {} : tab); - break; - } - case "API_SAVE_TAB": { - if (sender.tab != null && sender.tab.id) { - const key = `tab-${sender.tab.id}`; - sessionStorage.setItem(key, JSON.stringify(request.tab)); - sendResponse({success: true}); - } else { - console.error("unable to save tab, empty tab id"); - sendResponse({success: false}); - } - break; - } - case "API_SET_CLIPBOARD": { - const result = setClipboard(request.data, request.type); - sendResponse(result); - break; - } - case "API_XHR": { - // parse details and set up for XMLHttpRequest - const details = request.details; - const method = details.method ? details.method : "GET"; - const user = details.user || null; - const password = details.password || null; - let body = details.data || null; - if (body != null && details.binary != null) { - const len = body.length; - const arr = new Uint8Array(len); - for (let i = 0; i < len; i++) { - arr[i] = body.charCodeAt(i); - } - body = new Blob([arr], {type: "text/plain"}); - } - // establish a long-lived port connection to content script - const port = browser.tabs.connect(sender.tab.id, { - name: request.xhrPortName - }); - // set up XMLHttpRequest - const xhr = new XMLHttpRequest(); - xhr.withCredentials = (details.user && details.password); - xhr.timeout = details.timeout || 0; - if (details.overrideMimeType) { - xhr.overrideMimeType(details.overrideMimeType); - } - // add required listeners and send result back to the content script - for (const e of request.events) { - if (!details[e]) continue; - xhr[e] = async event => { - // can not send xhr through postMessage - // construct new object to be sent as "response" - const x = { - readyState: xhr.readyState, - response: xhr.response, - responseHeaders: xhr.getAllResponseHeaders(), - responseType: xhr.responseType, - responseURL: xhr.responseURL, - status: xhr.status, - statusText: xhr.statusText, - timeout: xhr.timeout, - withCredentials: xhr.withCredentials - }; - // only include responseText when needed - if (["", "text"].indexOf(xhr.responseType) !== -1) { - x.responseText = xhr.responseText; - } - // need to convert arraybuffer data to postMessage - if (xhr.responseType === "arraybuffer") { - const arr = Array.from(new Uint8Array(xhr.response)); - x.response = arr; - } - // need to blob arraybuffer data to postMessage - if (xhr.responseType === "blob") { - const base64data = await readAsDataURL(xhr.response); - x.response = { - data: base64data, - type: xhr.responseType - }; - } - port.postMessage({name: e, event, response: x}); - }; - } - xhr.open(method, details.url, true, user, password); - xhr.responseType = details.responseType || ""; - if (details.headers) { - for (const key in details.headers) { - const val = details.headers[key]; - xhr.setRequestHeader(key, val); - } - } - // receive messages from content script and process them - port.onMessage.addListener(msg => { - if (msg.name === "ABORT") xhr.abort(); - if (msg.name === "DISCONNECT") port.disconnect(); - }); - // handle port disconnect and clean tasks - port.onDisconnect.addListener(p => { - if (p.error) { - console.error(`port disconnected due to an error: ${p.error.message}`); - } - }); - xhr.send(body); - // if onloadend not set in xhr details - // onloadend event won't be passed to content script - // if that happens port DISCONNECT message won't be posted - // if details lacks onloadend attach listener - if (!details.onloadend) { - xhr.onloadend = event => { - port.postMessage({name: "onloadend", event}); - }; - } - break; - } - case "REFRESH_SESSION_RULES": { - setSessionRules(); - break; - } - case "REFRESH_CONTEXT_MENU_SCRIPTS": { - getContextMenuItems(); - break; - } - } -} - -browser.runtime.onStartup.addListener(async () => { - // on startup get declarativeNetRequests - // and set the requests for the session - // should also check and refresh when: - // 1. popup opens (done) - // 2. a new save event in the page occurs - // 3. the refresh button is pushed in the popup - await setSessionRules(); - await getContextMenuItems(); -}); -// listens for messages from content script, popup and page -browser.runtime.onMessage.addListener(handleMessage); -// set the badge count -browser.tabs.onActivated.addListener(setBadgeCount); -browser.windows.onFocusChanged.addListener(setBadgeCount); -browser.webNavigation.onCompleted.addListener(setBadgeCount); - -// handle native app messages -const port = browser.runtime.connectNative(); -port.onMessage.addListener(message => { - // console.info(message); // DEBUG - if (message.name === "SAVE_LOCATION_CHANGED") { - openExtensionPage(); - if (message?.userInfo?.returnApp === true) browser.runtime.sendNativeMessage({name: "OPEN_APP"}); - } - // if (message.name === "OPEN_EXTENSION_PAGE") { - // openExtensionPage(); - // } -}); diff --git a/xcode/Safari-Extension/Resources/content.js b/xcode/Safari-Extension/Resources/content.js deleted file mode 100644 index b29798d1..00000000 --- a/xcode/Safari-Extension/Resources/content.js +++ /dev/null @@ -1,470 +0,0 @@ -// code received from background page will be stored in this variable -// code referenced again when strict CSPs block initial injection attempt -let data; -// determines whether strict csp injection has already run (JS only) -let cspFallbackAttempted = false; -// save reference to window's browser object -const browser = window.browser; -// GM APIs -const apis = { - closeTab(tabId) { - return new Promise(resolve => { - const message = { - name: "API_CLOSE_TAB", - tabId - }; - browser.runtime.sendMessage(message, response => resolve(response)); - }); - }, - openInTab(url, openInBackground = false) { - if (!url) return console.error("openInTab missing url arg"); - return new Promise(resolve => { - const message = { - name: "API_OPEN_TAB", - url, - active: !openInBackground - }; - browser.runtime.sendMessage(message, response => resolve(response)); - }); - }, - setValue(key, value) { - if (typeof key !== "string" || !key.length) { - return console.error("setValue invalid key arg"); - } - if (value == null) { - return console.error("setValue invalid value arg"); - } - return new Promise(resolve => { - const item = {}; - item[`${this.US_filename}---${key}`] = value; - browser.storage.local.set(item, () => resolve({success: 1})); - }); - }, - getValue(key, defaultValue) { - if (typeof key !== "string" || !key.length) { - return console.error("getValue invalid key arg"); - } - const prefixedKey = `${this.US_filename}---${key}`; - return new Promise(resolve => { - browser.storage.local.get(prefixedKey, item => { - if (Object.keys(item).length === 0) { - if (defaultValue != null) { - resolve(defaultValue); - } else { - resolve(undefined); - } - } else { - resolve(Object.values(item)[0]); - } - }); - }); - }, - deleteValue(key) { - if (typeof key !== "string" || !key.length) { - return console.error("deleteValue missing key arg"); - } - return new Promise(resolve => { - const prefixedKey = `${this.US_filename}---${key}`; - browser.storage.local.remove(prefixedKey, () => { - resolve({success: 1}); - }); - }); - }, - listValues() { - return new Promise(resolve => { - const prefix = `${this.US_filename}---`; - const keys = []; - browser.storage.local.get().then(items => { - for (const key in items) { - if (key.startsWith(prefix)) { - const k = key.replace(prefix, ""); - keys.push(k); - } - } - resolve(keys); - }); - }); - }, - addStyle(css) { - if (typeof css !== "string") { - return console.error("addStyle invalid css arg"); - } - return new Promise(resolve => { - const message = { - name: "API_ADD_STYLE", - css - }; - browser.runtime.sendMessage(message, response => resolve(response)); - }); - }, - getTab() { - return new Promise(resolve => { - const message = {name: "API_GET_TAB"}; - browser.runtime.sendMessage(message, response => { - resolve(response); - }); - }); - }, - saveTab(tab) { - if (tab == null) return console.error("saveTab invalid arg"); - return new Promise(resolve => { - const message = { - name: "API_SAVE_TAB", - tab - }; - browser.runtime.sendMessage(message, response => { - resolve(response); - }); - }); - }, - setClipboard(clipboardData, type) { - return new Promise(resolve => { - const message = { - name: "API_SET_CLIPBOARD", - clipboardData, - type - }; - browser.runtime.sendMessage(message, response => { - resolve(response); - }); - }); - }, - xhr(details) { - if (details == null) return console.error("xhr invalid details arg"); - if (!details.url) return console.error("xhr details missing url key"); - // generate random port name for single xhr - const xhrPortName = Math.random().toString(36).substring(1, 9); - // strip out functions from details - const detailsParsed = JSON.parse(JSON.stringify(details)); - // get all the "on" events from XMLHttpRequest object - const events = []; - for (const k in XMLHttpRequest.prototype) { - if (k.slice(0, 2) === "on") events.push(k); - } - // check which functions are included in the original details object - // add a bool to indicate if event listeners should be attached - for (const e of events) { - if (typeof details[e] === "function") detailsParsed[e] = true; - } - // define return method, will be populated after port is established - const response = { - abort: () => console.error("xhr has not yet been initialized") - }; - // port listener, most of the messaging logic goes here - const listener = port => { - if (port.name !== xhrPortName) return; - port.onMessage.addListener(async msg => { - if ( - events.includes(msg.name) - && typeof details[msg.name] === "function" - ) { - // process xhr response - const r = msg.response; - if (r.responseType === "arraybuffer") { - // arraybuffer responses had their data converted in background - // convert it back to arraybuffer - try { - const buffer = new Uint8Array(r.response).buffer; - r.response = buffer; - } catch (err) { - console.error("error parsing xhr arraybuffer", err); - } - } else if (r.responseType === "blob" && r.response.data) { - // blob responses had their data converted in background - // convert it back to blob - const resp = await fetch(r.response.data); - const b = await resp.blob(); - r.response = b; - } - // call userscript method - details[msg.name](msg.response); - // all messages received - // tell background it's safe to close port - if (msg.name === "onloadend") { - port.postMessage({name: "DISCONNECT"}); - } - } - }); - - // handle port disconnect and clean tasks - port.onDisconnect.addListener(p => { - if (p.error) { - console.error(`port disconnected due to an error: ${p.error.message}`); - } - browser.runtime.onConnect.removeListener(listener); - }); - // fill the method returned to the user script - response.abort = () => port.postMessage({name: "ABORT"}); - }; - // wait for the background to establish a port connection - browser.runtime.onConnect.addListener(listener); - // pass the basic information to the background through a common message - const message = { - name: "API_XHR", - details: detailsParsed, - xhrPortName, - events - }; - browser.runtime.sendMessage(message); - return response; - }, - // include method names so they don't get skipped when adding to userscript - xmlHttpRequest: true, - GM_xmlhttpRequest: true -}; -// remote window's browser object -delete window.browser; -delete window.chrome; - -// label used to distinguish frames in console -const label = randomLabel(); - -function randomLabel() { - const a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", r = Math.random(); - return a[Math.floor(r * a.length)] + r.toString().slice(5, 6); -} - -function processJS(userscript) { - const runAt = userscript.scriptObject["run-at"]; - if (runAt === "document-start") { - injectJS(userscript); - } else if (runAt === "document-end") { - if (document.readyState !== "loading") { - injectJS(userscript); - } else { - document.addEventListener("DOMContentLoaded", () => { - injectJS(userscript); - }); - } - } else if (runAt === "document-idle") { - if (document.readyState === "complete") { - injectJS(userscript); - } else { - document.addEventListener("readystatechange", () => { - if (document.readyState === "complete") { - injectJS(userscript); - } - }); - } - } -} - -function wrapCode(preCode, code, filename) { - const tag = window.self === window.top ? "" : `(${label})`; - return ` - (function() { - ${preCode} - (function() { - const US_filename = "${filename}"; - const apis = undefined; - const browser = undefined; - // userscript code below - ${code} - //# sourceURL=${filename.replace(/\s/g, "-") + tag} - })(); - })(); - `; -} - -function injectJS(userscript) { - const filename = userscript.scriptObject.filename; - const code = wrapCode(userscript.preCode, userscript.code, filename); - const name = userscript.scriptObject.name; - let injectInto = userscript.scriptObject["inject-into"]; - // change scope to content since strict CSP event detected - if (injectInto === "auto" && (userscript.fallback || cspFallbackAttempted)) { - injectInto = "content"; - console.warn(`Attempting fallback injection for ${name}`); - } else if (window.self === window.top) { - console.info(`Injecting ${name} %c(js)`, "color: #fff600"); - } else { - console.info(`Injecting ${name} %c(js)%c - %cframe(${label})(${window.location})`, "color: #fff600", "color: inherit", "color: #006fff"); - } - if (injectInto !== "content") { - const tag = document.createElement("script"); - tag.textContent = code; - document.head.appendChild(tag); - } else { - try { - // eslint-disable-next-line no-new-func - return Function(code)(); - } catch (error) { - console.error(`${filename} error`, error); - } - } -} - -function injectCSS(name, code) { - if (window.self === window.top) { - console.info(`Injecting ${name} %c(css)`, "color: #60f36c"); - } else { - console.info(`Injecting ${name} %c(css)%c - %cframe(${label})(${window.location})`, "color: #60f36c", "color: inherit", "color: #006fff"); - } - // Safari lacks full support for tabs.insertCSS - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/insertCSS - // specifically frameId and cssOrigin - // if support for those details keys arrives, the method below can be used - // NOTE: manifest V3 does support frameId, but not origin - // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/insertCSS - - // write the css code to head of the document - const tag = document.createElement("style"); - tag.textContent = code; - document.head.appendChild(tag); -} - -function cspFallback(e) { - // if a security policy violation event has occurred - // and the directive is script-src or script-src-elem - // it's fair to assume that there is a strict CSP for javascript - // and that injection was blocked for all userscripts - // when any script-src violation is detected, re-attempt injection - if ( - e.effectiveDirective === "script-src" - || e.effectiveDirective === "script-src-elem" - ) { - // get all "auto" code - // since other code can trigger a security policy violation event - // make sure data var is not undefined before attempting fallback - if (!data || cspFallbackAttempted) return; - // update global that tracks security policy violations - cspFallbackAttempted = 1; - // for all userscripts with @inject-into: auto, attempt re-injection - for (let i = 0; i < data.files.js.length; i++) { - const userscript = data.files.js[i]; - if (userscript.scriptObject["inject-into"] !== "auto") continue; - userscript.fallback = 1; - processJS(userscript); - } - } -} - -function injection() { - browser.runtime.sendMessage({name: "REQ_USERSCRIPTS"}, response => { - // cancel injection if errors detected - if (!response || response.error) { - console.error(response?.error || "REQ_USERSCRIPTS returned undefined"); - return; - } - // save response locally in case CSP events occur - data = response; - // combine regular and context-menu scripts - const scripts = [...data.files.js, ...data.files.menu]; - // loop through each userscript and prepare for processing - for (let i = 0; i < scripts.length; i++) { - const userscript = scripts[i]; - userscript.preCode = ""; - // pass references to the api methods as needed - const gmMethods = []; - const filename = userscript.scriptObject.filename; - const grants = userscript.scriptObject.grant; - const injectInto = userscript.scriptObject["inject-into"]; - // create GM.info object - const scriptData = { - script: userscript.scriptObject, - scriptHandler: data.scriptHandler, - scriptHandlerVersion: data.scriptHandlerVersion, - scriptMetaStr: userscript.scriptMetaStr - }; - // all userscripts get access to GM.info - gmMethods.push("info: GM_info"); - // if @grant explicitly set to none, empty grants array - if (grants.includes("none")) grants.length = 0; - // @grant values exist for page scoped userscript - if (grants.length && injectInto === "page") { - // remove grants - grants.length = 0; - // log warning - console.warn(`${filename} @grant values removed due to @inject-into value: ${injectInto} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); - } - // @grant exist for auto scoped userscript - if (grants.length && injectInto === "auto") { - // change scope - userscript.scriptObject["inject-into"] = "content"; - // log warning - console.warn(`${filename} @inject-into value set to 'content' due to @grant values: ${grants} - https://github.com/quoid/userscripts/issues/265#issuecomment-1213462394`); - } - // loop through each userscript @grant value, add methods as needed - for (let j = 0; j < grants.length; j++) { - const grant = grants[j]; - const method = grant.split(".")[1] || grant.split(".")[0]; - // ensure API method exists in apis object - if (!Object.keys(apis).includes(method)) continue; - // create the method string to be pushed to methods array - let methodStr = `${method}: apis.${method}`; - // add require variables to specific methods - switch (method) { - case "getValue": - case "setValue": - case "deleteValue": - case "listValues": - methodStr += `.bind({"US_filename": "${filename}"})`; - break; - case "info": - case "GM_info": - continue; - case "xmlHttpRequest": - gmMethods.push("xmlHttpRequest: apis.xhr"); - continue; - case "GM_xmlhttpRequest": - userscript.preCode += "const GM_xmlhttpRequest = apis.xhr;"; - continue; - } - gmMethods.push(methodStr); - } - // add GM.info - userscript.preCode += `const GM_info = ${JSON.stringify(scriptData)};`; - // add other included GM API methods - userscript.preCode += `const GM = {${gmMethods.join(",")}};`; - // process file for injection - processJS(userscript); - } - for (let i = 0; i < data.files.css.length; i++) { - const userstyle = data.files.css[i]; - injectCSS(userstyle.name, userstyle.code); - } - }); -} - -function listeners() { - // listens for messages from background, popup, etc... - browser.runtime.onMessage.addListener((request, sender, sendResponse) => { - const name = request.name; - if (name === "CONTEXT_RUN") { - // from bg script when context-menu item is clicked - // double check to ensure context-menu scripts only run in top windows - if (window !== window.top) return; - - // loop through context-menu scripts saved to data object and find match - // if no match found, nothing will execute and error will log - const filename = request.menuItemId; - for (let i = 0; i < data.files.menu.length; i++) { - const item = data.files.menu[i]; - if (item.scriptObject.filename === filename) { - console.info(`Injecting ${filename} %c(js)`, "color: #fff600"); - sendResponse({ - code: wrapCode( - item.preCode, - item.code, - filename - ) - }); - return; - } - } - console.error(`Couldn't find ${filename} code!`); - } - }); - // listen for CSP violations - document.addEventListener("securitypolicyviolation", cspFallback); -} - -async function initialize() { - const results = await browser.storage.local.get("US_GLOBAL_ACTIVE"); - if (results?.US_GLOBAL_ACTIVE === false) return console.info("Userscripts off"); - // start the injection process and add the listeners - injection(); - listeners(); -} - -initialize(); diff --git a/xcode/Safari-Extension/Resources/images/icon-128.png b/xcode/Safari-Extension/Resources/images/icon-128.png deleted file mode 100644 index a775ed07..00000000 Binary files a/xcode/Safari-Extension/Resources/images/icon-128.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/icon-256.png b/xcode/Safari-Extension/Resources/images/icon-256.png deleted file mode 100644 index b3a52bf7..00000000 Binary files a/xcode/Safari-Extension/Resources/images/icon-256.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/icon-48.png b/xcode/Safari-Extension/Resources/images/icon-48.png deleted file mode 100644 index a6fd3c6a..00000000 Binary files a/xcode/Safari-Extension/Resources/images/icon-48.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/icon-512.png b/xcode/Safari-Extension/Resources/images/icon-512.png deleted file mode 100644 index 7458bb07..00000000 Binary files a/xcode/Safari-Extension/Resources/images/icon-512.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/icon-96.png b/xcode/Safari-Extension/Resources/images/icon-96.png deleted file mode 100644 index 7bfd59c1..00000000 Binary files a/xcode/Safari-Extension/Resources/images/icon-96.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/toolbar-icon-16.png b/xcode/Safari-Extension/Resources/images/toolbar-icon-16.png deleted file mode 100644 index 5173e1e2..00000000 Binary files a/xcode/Safari-Extension/Resources/images/toolbar-icon-16.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/images/toolbar-icon-32.png b/xcode/Safari-Extension/Resources/images/toolbar-icon-32.png deleted file mode 100644 index ae1ef8f9..00000000 Binary files a/xcode/Safari-Extension/Resources/images/toolbar-icon-32.png and /dev/null differ diff --git a/xcode/Safari-Extension/Resources/manifest.json b/xcode/Safari-Extension/Resources/manifest.json deleted file mode 100644 index f02a6703..00000000 --- a/xcode/Safari-Extension/Resources/manifest.json +++ /dev/null @@ -1,47 +0,0 @@ - -{ - "manifest_version": 2, - "default_locale": "en", - "name": "__MSG_extension_name__", - "description": "__MSG_extension_description__", - "version": "4.4.1", - "icons": { - "48": "images/icon-48.png", - "96": "images/icon-96.png", - "128": "images/icon-128.png", - "256": "images/icon-256.png", - "512": "images/icon-512.png" - }, - "background": { - "scripts": ["background.js"], - "persistent": false - }, - "browser_action": { - "default_popup": "dist/entry-popup.html", - "default_icon": { - "16": "images/toolbar-icon-16.png", - "32": "images/toolbar-icon-32.png" - } - }, - "content_scripts": [ - { - "js": ["content.js"], - "matches": [""], - "run_at": "document_start", - "all_frames": true - } - ], - "permissions": [ - "", - "clipboardWrite", - "contextMenus", - "declarativeNetRequest", - "declarativeNetRequestWithHostAccess", - "menus", - "nativeMessaging", - "storage", - "tabs", - "unlimitedStorage", - "webNavigation" - ] -} diff --git a/xcode/Safari-Extension/SafariWebExtensionHandler.swift b/xcode/Safari-Extension/SafariWebExtensionHandler.swift deleted file mode 100644 index 8f64175c..00000000 --- a/xcode/Safari-Extension/SafariWebExtensionHandler.swift +++ /dev/null @@ -1,310 +0,0 @@ -import SafariServices - -class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { - func beginRequest(with context: NSExtensionContext) { - let item = context.inputItems[0] as? NSExtensionItem - let message = item?.userInfo?[SFExtensionMessageKey] as? [String: Any] - // if message received without name, ignore - guard let name = message?["name"] as? String else { - err("could not get message name from web extension") - return - } - logText("Got message with name: \(name)") - // got a valid message, construct response based on message received - let response = NSExtensionItem() - // send standard error when there's an issue parsing inbound message - var inBoundError = false - // these if/else if statement are formatted so that they can be neatly collapsed in Xcode - // typically the "else if" would be on the same line as the preceding statements close bracket - // ie. } else if { - if name == "OPEN_APP" { - if let scheme = Bundle.main.infoDictionary?["US_URL_SCHEME"], - let url = URL(string: "\(scheme):") { - #if os(macOS) - NSWorkspace.shared.open(url) - #endif - } - } - else if name == "REQ_PLATFORM" { - let platform = getPlatform() - response.userInfo = [SFExtensionMessageKey: ["platform": platform]] - } - else if name == "REQ_USERSCRIPTS" { - if let url = message?["url"] as? String, let isTop = message?["isTop"] as? Bool { - if - let matches = getInjectionFilenames(url), - let code = getCode(matches, isTop) - { - response.userInfo = [SFExtensionMessageKey: code] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "REQ_USERSCRIPTS failed"]] - } - } else { - inBoundError = true - } - } - else if name == "REQ_REQUESTS" { - if let requestScripts = getRequestScripts() { - response.userInfo = [SFExtensionMessageKey: requestScripts] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get requestScripts"]] - } - } - else if name == "REQ_CONTEXT_MENU_SCRIPTS" { - if let contextMenuScripts = getContextMenuScripts() { - response.userInfo = [SFExtensionMessageKey: contextMenuScripts] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get contextMenuScripts"]] - } - } - else if name == "POPUP_BADGE_COUNT" { - if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { - if let matches = getPopupBadgeCount(url, frameUrls) { - response.userInfo = [SFExtensionMessageKey: ["count": matches]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to update badge count"]] - } - } else { - inBoundError = true - } - } - else if name == "POPUP_INIT" { - if let initData = popupInit() { - response.userInfo = [SFExtensionMessageKey: ["initData": initData]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get init data"]] - } - } - else if name == "POPUP_MATCHES"{ - if let url = message?["url"] as? String, let frameUrls = message?["frameUrls"] as? [String] { - if let matches = getPopupMatches(url, frameUrls) { - response.userInfo = [SFExtensionMessageKey: ["matches": matches]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get matches"]] - } - } else { - inBoundError = true - } - } - else if name == "POPUP_UPDATES" { - if let updates = checkForRemoteUpdates() { - response.userInfo = [SFExtensionMessageKey: ["updates": updates]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get updates"]] - } - } - else if name == "POPUP_UPDATE_ALL" { - if popupUpdateAll(), let updates = checkForRemoteUpdates() { - response.userInfo = [SFExtensionMessageKey: ["updates": updates]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to run update sequence"]] - } - } - else if name == "POPUP_UPDATE_SINGLE" { - if - let filename = message?["filename"] as? String, - let url = message?["url"] as? String, - let frameUrls = message?["frameUrls"] as? [String] - { - if let matches = popupUpdateSingle(filename, url, frameUrls) { - response.userInfo = [SFExtensionMessageKey: ["items": matches]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to update file"]] - } - } else { - inBoundError = true - } - } - else if name == "POPUP_CHECK_UPDATES" { - if let updates = checkForRemoteUpdates() { - response.userInfo = [SFExtensionMessageKey: ["updates": updates]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to check for updates"]] - } - } - else if name == "TOGGLE_EXTENSION" { - if let active = message?["active"] as? String, ["true", "false"].contains(active) { - var manifest = getManifest() - manifest.settings["active"] = active - if updateManifest(with: manifest) { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to update injection state"]] - } - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "missing or wrong message content"]] - } - } - else if name == "TOGGLE_ITEM" { - // the current status of the item to toggle comes in as 0 || 1 - if - let item = message?["item"] as? [String: Any], - let filename = item["filename"] as? String, - let current = item["disabled"] as? Int - { - let action = current == 0 ? "disable" : "enable" - if toggleFile(filename, action) { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to toggle file"]] - } - } else { - inBoundError = true - } - } - else if name == "OPEN_SAVE_LOCATION" { - #if os(macOS) - if openSaveLocation() { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } - #elseif os(iOS) - if let files = getAllFiles() { - response.userInfo = [SFExtensionMessageKey: ["items": files]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get all files"]] - } - #endif - } - else if name == "CHANGE_SAVE_LOCATION" { - #if os(macOS) - if let scheme = Bundle.main.infoDictionary?["US_URL_SCHEME"], - let url = URL(string: "\(scheme)://changesavelocation") { - NSWorkspace.shared.open(url) - } - #endif - } - else if name == "POPUP_INSTALL_CHECK" { - if let content = message?["content"] as? String { - response.userInfo = [SFExtensionMessageKey: installCheck(content)] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content"]] - } - } - else if name == "POPUP_INSTALL_SCRIPT" { - if let content = message?["content"] as? String { - response.userInfo = [SFExtensionMessageKey: installUserscript(content)] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get script content (2)"]] - } - } - else if name == "PAGE_INIT_DATA" { - #if os(macOS) - if let initData = getInitData(), checkDefaultDirectories() { - response.userInfo = [SFExtensionMessageKey: initData] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get init data"]] - } - #endif - } - else if name == "PAGE_LEGACY_IMPORT" { - #if os(macOS) - if let settings = getLegacyData() { - response.userInfo = [SFExtensionMessageKey: settings] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get legacy data"]] - } - #endif - } - else if name == "PAGE_ALL_FILES" { - #if os(macOS) - if let files = getAllFiles() { - response.userInfo = [SFExtensionMessageKey: files] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to get all files"]] - } - #endif - } - else if name == "PAGE_SAVE" { - #if os(macOS) - if - let item = message?["item"] as? [String: Any], - let content = message?["content"] as? String - { - let saveResponse = saveFile(item, content) - response.userInfo = [SFExtensionMessageKey: saveResponse] - } else { - inBoundError = true - } - #endif - } - else if name == "PAGE_TRASH" { - #if os(macOS) - if let item = message?["item"] as? [String: Any] { - if trashFile(item) { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "failed to trash file"]] - } - } else { - inBoundError = true - } - #endif - } - else if name == "PAGE_UPDATE" { - #if os(macOS) - if let content = message?["content"] as? String { - let updateResponse = getFileRemoteUpdate(content) - response.userInfo = [SFExtensionMessageKey: updateResponse] - } else { - inBoundError = true - } - #endif - } - else if name == "CANCEL_REQUESTS" { - URLSession.shared.getAllTasks { tasks in - for task in tasks { - task.cancel() - } - } - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } - else if name == "PAGE_NEW_REMOTE" { - #if os(macOS) - if let url = message?["url"] as? String { - if !validateUrl(url) { - response.userInfo = [SFExtensionMessageKey: ["error": "Failed to get remote content, invalid url"]] - } else if let content = getRemoteFileContents(url) { - response.userInfo = [SFExtensionMessageKey: content] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "Failed to get remote content"]] - } - } else { - inBoundError = true - } - #endif - } - else if name == "PAGE_UPDATE_SETTINGS" { - #if os(macOS) - if let settings = message?["settings"] as? [String: String] { - if updateSettings(settings) { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } else { - response.userInfo = [SFExtensionMessageKey: ["error": "Failed to save settings to disk"]] - } - } else { - inBoundError = true - } - #endif - } - else if name == "PAGE_UPDATE_BLACKLIST" { - #if os(macOS) - if let blacklist = message?["blacklist"] as? [String] { - var manifest = getManifest() - manifest.blacklist = blacklist - if !updateManifest(with: manifest) { - response.userInfo = [SFExtensionMessageKey: ["error": "Failed to save blacklist to disk"]] - } else { - response.userInfo = [SFExtensionMessageKey: ["success": true]] - } - } else { - inBoundError = true - } - #endif - } - // send inBoundError if found - if inBoundError { - response.userInfo = [SFExtensionMessageKey: ["error": "Failed to parse inbound message"]] - } - context.completeRequest(returningItems: [response], completionHandler: nil) - } -} diff --git a/xcode/Shared/Logger.swift b/xcode/Shared/Logger.swift new file mode 100644 index 00000000..f2137a70 --- /dev/null +++ b/xcode/Shared/Logger.swift @@ -0,0 +1,232 @@ +import OSLog +import SwiftUI + +private let logger = USLogger(#fileID) +private let logDir = "Library/Caches/\(projectName)/Debug" +private let logFile = "\(bundleIdentifier).log" + +func USLogger(_ category: String) -> Logger? { +#if DEBUG // Always enable logger in DEBUG builds + print("DEBUG: logger enabled") +#else + guard Preferences.enableLogger else { return nil } +#endif + let subsystem = Bundle.main.bundleIdentifier! + return Logger(subsystem: subsystem, category: category) +} + +func USLoggerSwitch(_ enable: Bool) { + Preferences.enableLogger = enable + if enable { + USLogStoreToFile() // clean up + } else { + USLogStoreToFile(prioritize: true) + } + // update app gui + if bundleIdentifier != extIdentifier { +#if os(macOS) && APP + if let appDelegate = NSApp.delegate as? AppDelegate { + // update app menu item state + appDelegate.enbaleNativeLogger?.state = enable ? .on : .off + // update app webview state + if let viewController = appDelegate.window?.contentViewController as? ViewController { + viewController.updateEnableLoggerState() + } + } +#elseif os(iOS) && APP + if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate, + let viewController = sceneDelegate.window?.rootViewController as? ViewController { + // update app webview state + viewController.updateEnableLoggerState() + } +#endif + } +} + +// https://forums.swift.org/t/best-way-to-append-text-to-a-text-file/35764 +// Swift has no easy way to append to files, use POSIX APIs +private func appendToFile(_ file: URL, content: String) -> Bool { + guard file.isFileURL else { + print("Not a file scheme URL") + return false + } + if file.hasDirectoryPath { + print("Should be a file but is indicated as a directory URL") + return false + } + do { + let dir = file.deletingLastPathComponent() + try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true, attributes: nil) + } catch { + print("Failed mkdir - \(error)") + return false + } + let fileDescriptor = open(file.path, O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR) + if fileDescriptor == -1 { + perror("Failed open") + return false + } + defer { close(fileDescriptor) } + guard let textData = content.cString(using: .utf8) else { + print("Failed cString") + return false + } + let result = write(fileDescriptor, textData, strlen(textData)) + if result == -1 { + perror("Failed write") + return false + } + return true +} + +private func stringFromLogLevel(_ level: OSLogEntryLog.Level) -> String { + switch level { + case .undefined: + return "Undefined" + case .debug: + return "Debug" + case .info: + return "Info" + case .notice: + return "Notice" + case .error: + return "Error" + case .fault: + return "Fault" + @unknown default: + return "Unknown" + } +} + +// The last time written to the log file +private var lastDate: Date = Date().addingTimeInterval(-3600) + +private func writeLogStoreToFile() { + logger?.debug("\(#function, privacy: .public) - USLogStoreToFile start") + guard let logDirURL: URL = getProjectSharedDirectoryURL(path: logDir) else { + logger?.error("\(#function, privacy: .public) - Failed get logDirURL") + return + } + let logFileURL: URL = logDirURL.appendingPathComponent(logFile, isDirectory: false) + // log file size limit + if let fileSize = try? FileManager.default.attributesOfItem(atPath: logFileURL.path)[.size] as? Int { +#if DEBUG + print("DEBUG: logFileSize/maxLogFileSize", fileSize, Preferences.maxLogFileSize) +#endif + if fileSize > Preferences.maxLogFileSize { + logger?.info("\(#function, privacy: .public) - Logger disabling due to max file size exceeded") + USLoggerSwitch(false) + } + } +#if DEBUG + print("DEBUG: enableLogger", Preferences.enableLogger) +#endif + guard Preferences.enableLogger else { + // clean log files + logger?.info("\(#function, privacy: .public) - Cleaning log files") + do { + try FileManager.default.removeItem(at: logDirURL) + } catch { + logger?.error("\(#function, privacy: .public) - \(error)") + } + // reset logger prompt + Preferences.promptLogger = true + return + } + // log array written to the file in one go + var logs: [String] = [] + do { + let logStore = try OSLogStore(scope: .currentProcessIdentifier) + // `position` doesn't work in `getEntries`, always returns all logs + // instead use `predicate` to filter +// let position = logStore.position(date: lastDate) + let nowDate = Date() + let predicate = NSPredicate( + format: "date between {%@, %@}", + lastDate as CVarArg, + nowDate as CVarArg + ) + lastDate = nowDate + let entries = try logStore.getEntries(matching: predicate) + for entry in entries { + if let osLogEntry = entry as? OSLogEntryLog { + lastDate = osLogEntry.date + // format a single log line + let log = [ + osLogEntry.date.description, + stringFromLogLevel(osLogEntry.level), + osLogEntry.process, + osLogEntry.subsystem, + osLogEntry.category, + osLogEntry.composedMessage + + ].joined(separator: "\t") + logs.append(log) + } + } + } catch { + logger?.error("\(#function, privacy: .public) - Failed get entries: \(error)") + return + } + var content = logs.joined(separator: "\n") + // separate single write, there may be several duplicates before and after + content += "\n-------------------------\n" + guard appendToFile(logFileURL, content: content) else { + logger?.error("\(#function, privacy: .public) - Failed appendToFile") + return + } +} + +func USLogStoreToFile(prioritize: Bool = false) { + // Note qos `.background` doesn't seem to work in extension + DispatchQueue.global(qos: prioritize ? .userInteractive : .utility).async { + writeLogStoreToFile() + } +} + +func USLogFilesExportTo(_ directory: URL) throws -> URL { + + struct USLogExportError: Error, CustomStringConvertible { + enum errType: String { + case notDirectory = "Not a directory URL" + case logDirURLNil = "Failed get logDirURL" + case noLogDir = "The log is not ready, please try again later (d)" + case noLogFiles = "The log is not ready, please try again later (f)" + case exportFailed = "Export failed" + } + let type: errType + var error: Error? = nil + var description: String { + if let error = self.error { + return "\(error) - \(self.type.rawValue)" + } + return self.type.rawValue + } + } + + logger?.debug("\(#function, privacy: .public) - USLogFilesExportTo start") + guard directory.hasDirectoryPath else { + throw USLogExportError(type: .notDirectory) + } + guard let logDirURL: URL = getProjectSharedDirectoryURL(path: logDir) else { + throw USLogExportError(type: .logDirURLNil) + } + guard directoryExists(path: logDirURL.path) else { + throw USLogExportError(type: .noLogDir) + } + guard directoryEmpty(at: logDirURL) == false else { + throw USLogExportError(type: .noLogFiles) + } + let timeInterval = Date().timeIntervalSince1970 + let timestampSrt = String(Int(timeInterval)) + // Use timestamps to ensure no destination folders already exist + let outdir = "\(projectName)-logs-\(timestampSrt)" + let dstURL = directory.appendingPathComponent(outdir, isDirectory: true) + do { + try FileManager.default.copyItem(at: logDirURL, to: dstURL) + return dstURL + } catch { + throw USLogExportError(type: .logDirURLNil, error: error) + } + +} diff --git a/xcode/Shared/Preferences.swift b/xcode/Shared/Preferences.swift new file mode 100644 index 00000000..20f0b60e --- /dev/null +++ b/xcode/Shared/Preferences.swift @@ -0,0 +1,301 @@ +import Foundation + +private let logger = USLogger(#fileID) +private let SDefaults = UserDefaults(suiteName: groupIdentifier) // Shared UserDefaults + +// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties#Property-Wrappers +// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/ +@propertyWrapper +private struct SharedUserDefaults { + private let key: String + private let defaultValue: T + + init(wrappedValue: T, _ key: String) { + self.key = key + self.defaultValue = wrappedValue + SDefaults?.register(defaults: [key: wrappedValue]) + } + + var wrappedValue: T { + get { SDefaults?.object(forKey: key) as? T ?? defaultValue } + set { SDefaults?.set(newValue, forKey: key) } + } +} + +#if os(macOS) +/* https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox + # Design ideas and processes + Since the security-scoped URL bookmark is only available in the current app, + pass to extensions can only use URL bookmark with an implicit security scope. + app set URL write to: + - `_SetterId` (setter app bundleIdentifier) + - `_Transfer` (with an implicit security scope) + - `_SecurityScoped_` (with an explicit security scope) + app get URL read from: `_SecurityScoped_` + ext get URL read flow: + - if `_Transfer` exist, remove to `_SecurityScoped_` + - if `_Transfer` empty, read from `_SecurityScoped_` + */ +@propertyWrapper +private struct SecurityScopedBookmark { + private let key: String + private let keySetter: String + private let keyTransfer: String + private let keySecurityScoped: String + private let defaultValue: URL + + init(wrappedValue: URL, _ key: String) { + self.key = key + self.keySetter = key + "/SetterId" + self.keyTransfer = key + "/Transfer" + self.keySecurityScoped = key + "/SecurityScoped/" + bundleIdentifier + self.defaultValue = wrappedValue + } + + private func createBookmark(_ url: URL, _ withSecurityScope: Bool) -> Data? { + do { + return try url.bookmarkData( + options: withSecurityScope ? .withSecurityScope : [], + includingResourceValuesForKeys: nil, + relativeTo: nil + ) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } + return nil + } + + private func resolvBookmark(_ data: Data, _ withSecurityScope: Bool, _ isStale: inout Bool) -> URL? { + do { + return try URL( + resolvingBookmarkData: data, + options: withSecurityScope ? .withSecurityScope : [], + relativeTo: nil, + bookmarkDataIsStale: &isStale + ) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } + return nil + } + + private func updateBookmark() { + guard let setterIdentifier = SDefaults?.string(forKey: keySetter) else { + logger?.debug("\(#function, privacy: .public) - setterId not exist: \(key, privacy: .public)") + return + } + guard bundleIdentifier != setterIdentifier else { return } // update only in non-setter environment + guard let data = SDefaults?.data(forKey: keyTransfer) else { // no need to update due empty data + logger?.debug("\(#function, privacy: .public) - no update: \(key, privacy: .public)") + return + } + var isStale = false // no need to renew since will remove anyway + guard let url = resolvBookmark(data, false, &isStale) else { // get URL bookmark with an implicit security scope + removeBookmark() + return + } + defer { url.stopAccessingSecurityScopedResource() } // revoke implicitly starts security-scoped access + if let data = createBookmark(url, true) { // set URL bookmark with an explicit security scope + logger?.info("\(#function, privacy: .public) - update bookmark: \(key, privacy: .public) \(url, privacy: .public)") + SDefaults?.removeObject(forKey: keyTransfer) + SDefaults?.set(data, forKey: keySecurityScoped) + } + } + + private func removeBookmark() { + logger?.info("\(#function, privacy: .public) - remove invalid bookmark: \(key, privacy: .public)") + SDefaults?.removeObject(forKey: keyTransfer) + SDefaults?.removeObject(forKey: keySecurityScoped) + if let setterIdentifier = SDefaults?.string(forKey: keySetter) { + SDefaults?.removeObject(forKey: key + "/SecurityScoped/" + setterIdentifier) + } + } + + // NOTE: This function will be deleted after several public versions of v4.5.0 + private func legacyBookmarkImporter() { + guard key == "ScriptsDirectoryUrlBookmarkData" else { return } + guard bundleIdentifier == extIdentifier else { return } + func legacyCleanup() { + logger?.debug("\(#function, privacy: .public) - cleanup legacy bookmark data") + SDefaults?.removeObject(forKey: "hostSelectedSaveLocation") // shared + UserDefaults.standard.removeObject(forKey: "saveLocation") // ext + UserDefaults.standard.removeObject(forKey: "userSaveLocation") // ext + } + guard let data = UserDefaults.standard.data(forKey: "userSaveLocation") else { // get from old key + logger?.debug("\(#function, privacy: .public) - legacy bookmark not exist") + return + } + guard SDefaults?.string(forKey: keySetter) == nil else { // already a new key + return legacyCleanup() + } + logger?.debug("\(#function, privacy: .public) - Import legacy bookmark data") + var isStale = false // no need to renew since will remove anyway + guard let url = resolvBookmark(data, true, &isStale) else { // get URL bookmark with an explicit security scope + return legacyCleanup() + } + guard url.startAccessingSecurityScopedResource() else { + return legacyCleanup() + } + defer { url.stopAccessingSecurityScopedResource() } + guard let transferData = createBookmark(url, false) else { + return legacyCleanup() + } + SDefaults?.set(bundleIdentifier, forKey: keySetter) + SDefaults?.set(transferData, forKey: keyTransfer) // set URL bookmark with an implicit security scope + SDefaults?.set(data, forKey: keySecurityScoped) // set URL bookmark with an explicit security scope + legacyCleanup() + } + + var wrappedValue: URL { + get { + legacyBookmarkImporter() + updateBookmark() + logger?.info("\(#function, privacy: .public) - try get bookmark: \(key, privacy: .public)") + guard let data = SDefaults?.data(forKey: keySecurityScoped) else { + logger?.debug("\(#function, privacy: .public) - bookmark not exist: \(key, privacy: .public)") + return defaultValue + } + var isStale = false + guard let url = resolvBookmark(data, true, &isStale) else { // get URL bookmark with an explicit security scope + removeBookmark() // remove data that cannot be resolved + return defaultValue + } + if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark + defer { url.stopAccessingSecurityScopedResource() } + if let data = createBookmark(url, true) { // set URL bookmark with an explicit security scope + logger?.info("\(#function, privacy: .public) - renew bookmark: \(key, privacy: .public) \(url, privacy: .public)") + SDefaults?.set(data, forKey: keySecurityScoped) + } + } + return url + } + set(url) { + let k = key // key cannot be log directly with error: Escaping autoclosure captures mutating 'self' parameter + logger?.info("\(#function, privacy: .public) - try set bookmark: \(k, privacy: .public) \(url, privacy: .public)") + guard let tdata = createBookmark(url, false), let sdata = createBookmark(url, true) else { + logger?.info("\(#function, privacy: .public) - failed create bookmark: \(k, privacy: .public) \(url, privacy: .public)") + return + } + SDefaults?.set(bundleIdentifier, forKey: keySetter) + SDefaults?.set(tdata, forKey: keyTransfer) // set URL bookmark with an implicit security scope + SDefaults?.set(sdata, forKey: keySecurityScoped) // set URL bookmark with an explicit security scope + } + } +} +#elseif os(iOS) +/* https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories#3331285 + iOS and macOS have completely different processes, iOS has no implicit security scope + The security-scoped URL is not bind to the app and can be shared directly between the app and ext + Separating the wrappers for the two platforms is intentional for ease of viewing code logic + */ +@propertyWrapper +private struct SecurityScopedBookmark { + private let key: String + private let defaultValue: URL + + init(wrappedValue: URL, _ key: String) { + self.key = key + self.defaultValue = wrappedValue + } + + private func createBookmark(_ url: URL) -> Data? { + do { + return try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } + return nil + } + + private func resolvBookmark(_ data: Data, _ isStale: inout Bool) -> URL? { + do { + return try URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) + } catch { + logger?.error("\(#function, privacy: .public) - \(error, privacy: .public)") + } + return nil + } + + // NOTE: This function will be deleted after several public versions of v4.5.0 + private func legacyBookmarkImporter() { + guard let data = SDefaults?.data(forKey: "iosReadLocation") else { // get from old key + logger?.debug("\(#function, privacy: .public) - legacy bookmark not exist") + return + } + logger?.debug("\(#function, privacy: .public) - Import legacy bookmark data") + SDefaults?.set(data, forKey: key) + SDefaults?.removeObject(forKey: "iosReadLocation") + } + + var wrappedValue: URL { + get { + legacyBookmarkImporter() + logger?.info("\(#function, privacy: .public) - try get bookmark: \(key, privacy: .public)") + guard let data = SDefaults?.data(forKey: key) else { + logger?.debug("\(#function, privacy: .public) - bookmark not exist: \(key, privacy: .public)") + return defaultValue + } + var isStale = false + guard let url = resolvBookmark(data, &isStale) else { // get security-scoped URL + // Note: temporary mitigation measures for occasional failures on some iOS devices +// SDefaults?.removeObject(forKey: key) // remove data that cannot be resolved + logger?.warning("\(#function, privacy: .public) - faild resolv bookmark, return default") + return defaultValue + } + if isStale, url.startAccessingSecurityScopedResource() { // renew URL bookmark + defer { url.stopAccessingSecurityScopedResource() } + if let data = createBookmark(url) { // set security-scoped URL + logger?.info("\(#function, privacy: .public) - renew bookmark: \(key, privacy: .public) \(url, privacy: .public)") + SDefaults?.set(data, forKey: key) + } + } + return url + } + set(url) { + let k = key // key cannot be log directly with error: Escaping autoclosure captures mutating 'self' parameter + logger?.info("\(#function, privacy: .public) - try set bookmark: \(k, privacy: .public) \(url, privacy: .public)") + // true - when url from UIDocumentPicker + // false - when set default app document + let didStartAccessing = url.startAccessingSecurityScopedResource() + defer { + if didStartAccessing { url.stopAccessingSecurityScopedResource() } + } + guard let data = createBookmark(url) else { + logger?.info("\(#function, privacy: .public) - failed create bookmark: \(k, privacy: .public) \(url, privacy: .public)") + return + } + SDefaults?.set(data, forKey: key) // set security-scoped URL + } + } +} +#endif + +// Define shared preferences, default values determine data type +struct Preferences { + // Example preference, can be get or set with: Preferences.propertyName + // @SharedUserDefaults("SharedUserDefaultsKeyName") + // static var propertyName = "DefaultValue" // Type -> String + + // This is a separate wrapper, type is URL + @SecurityScopedBookmark("ScriptsDirectoryUrlBookmarkData") + static var scriptsDirectoryUrl = getDefaultScriptsDirectoryUrl() + + @SharedUserDefaults("EnableLogger") + static var enableLogger = false + + @SharedUserDefaults("PromptLogger") + static var promptLogger = true + + @SharedUserDefaults("MaxLogFileSize") + static var maxLogFileSize = 500_000_000 // 500MB + + @SharedUserDefaults("FirstRunTime") + static var firstRunTime = 0 { + willSet { + print("willSet \(newValue)") + } + didSet { + print("didSet \(oldValue)") + } + } +} diff --git a/xcode/Shared/Shared.swift b/xcode/Shared/Shared.swift deleted file mode 100644 index 7dc4cfe4..00000000 --- a/xcode/Shared/Shared.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation -import SafariServices -import os - -struct SharedDefaults { - // https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups - static let suiteName = Bundle.main.infoDictionary?["US_SHARED_GID"] as? String - #if os(iOS) - static let keyName = "iosReadLocation" - #elseif os(macOS) - static let keyName = "hostSelectedSaveLocation" - #endif -} - -func err(_ message: String) { - let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "general") - os_log("%{public}@", log: log, type: .error, "Error: \(message)") -} - -func logText(_ message: String) { - // create helper log func to easily disable logging - // NSLog(message) - let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "general") - os_log("%{public}@", log: log, type: .default, message) -} - -func getDocumentsDirectory() -> URL { - let fm = FileManager.default - let paths = fm.urls(for: .documentDirectory, in: .userDomainMask) - let documentsDirectory = paths[0] - return documentsDirectory -} - -func directoryExists(path: String) -> Bool { - var isDirectory = ObjCBool(true) - let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) - let inTrash = path.contains(".Trash") ? false : true - return exists && inTrash && isDirectory.boolValue -} - -func getPlatform() -> String { - var platform:String - #if os(iOS) - if UIDevice.current.userInterfaceIdiom == .pad { - platform = "ipados" - } - else { - platform = "ios" - } - #elseif os(macOS) - platform = "macos" - #endif - return platform -} - -func saveBookmark(url: URL, isShared: Bool, keyName: String, isSecure: Bool) -> Bool { - #if os(iOS) - let options:URL.BookmarkCreationOptions = [] - #elseif os(macOS) - let options:URL.BookmarkCreationOptions = isSecure ? [.withSecurityScope] : [] - #endif - do { - let bookmark = try url.bookmarkData( - options: options, - includingResourceValuesForKeys: nil, - relativeTo: nil - ) - #if os(iOS) - UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName) - #elseif os(macOS) - if isShared { - UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName) - } else { - UserDefaults.standard.set(bookmark, forKey: keyName) - } - #endif - return true - } catch let error { - err("\(error)") - return false - } -} - -func readBookmark(data: Data, isSecure: Bool) -> URL? { - #if os(iOS) - let options:URL.BookmarkResolutionOptions = [] - #elseif os(macOS) - let options:URL.BookmarkResolutionOptions = isSecure ? [.withSecurityScope] : [] - #endif - do { - var bookmarkIsStale = false - let url = try URL( - resolvingBookmarkData: data, - options: options, - relativeTo: nil, - bookmarkDataIsStale: &bookmarkIsStale - ) - if bookmarkIsStale { - NSLog("Stale bookmark, renewing it \(url)") - if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) { - NSLog("Successfully renewed stale bookmark - \(url)") - } else { - NSLog("Could not renew stale bookmark - \(url)") - } - } - return url - } catch let error { - err("Error: \(error)") - return nil - } -} diff --git a/xcode/Shared/UrlPolyfill.swift b/xcode/Shared/UrlPolyfill.swift new file mode 100644 index 00000000..4f8d4180 --- /dev/null +++ b/xcode/Shared/UrlPolyfill.swift @@ -0,0 +1,105 @@ +import Foundation + +extension CharacterSet { + // https://developer.apple.com/documentation/foundation/characterset#2902136 + public static let urlAllowed_ = CharacterSet(charactersIn: "#") + .union(.urlFragmentAllowed) + .union(.urlHostAllowed) + .union(.urlPasswordAllowed) + .union(.urlPathAllowed) + .union(.urlQueryAllowed) + .union(.urlUserAllowed) +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI +func encodeURI(_ uri: String) -> String { + // https://developer.apple.com/documentation/foundation/characterset#2902136 +// var urlAllowed = CharacterSet(charactersIn: "#") +// urlAllowed.formUnion(.urlFragmentAllowed) +// urlAllowed.formUnion(.urlHostAllowed) +// urlAllowed.formUnion(.urlPasswordAllowed) +// urlAllowed.formUnion(.urlPathAllowed) +// urlAllowed.formUnion(.urlQueryAllowed) +// urlAllowed.formUnion(.urlUserAllowed) + return uri.addingPercentEncoding(withAllowedCharacters: .urlAllowed_) ?? uri +} + +func fixedURL(string urlString: String) -> URL? { + let rawUrlString = urlString.removingPercentEncoding ?? urlString + var url: URL? + if #available(macOS 14.0, iOS 17.0, *) { + url = URL(string: rawUrlString, encodingInvalidCharacters: true) + } else { + url = URL(string: encodeURI(rawUrlString)) + } + return url +} + +// https://developer.mozilla.org/en-US/docs/Web/API/URL +func jsLikeURL(_ urlString: String, baseString: String? = nil) -> [String: String]? { + var _url: URL? + if let baseString = baseString { + guard let baseUrl = fixedURL(string: baseString) else { + return nil + } + _url = URL(string: urlString, relativeTo: baseUrl) + } else { + _url = fixedURL(string: urlString) + } + guard let url = _url else { + return nil + } + guard let scheme = url.scheme else { + return nil + } + var port = "" + if let portInt = url.port { port = String(portInt) } + if (scheme == "http" && port == "80") { port = "" } + if (scheme == "https" && port == "443") { port = "" } + /* + The issue still exists as of macOS 14.4, iOS 17.0 + https://stackoverflow.com/questions/74445926/url-host-deprecated-but-replacement-crashes + https://forums.swift.org/t/does-url-query-percentencoded-calls-url-host-percentencoded-under-the-hood/70452 + https://forums.developer.apple.com/forums/thread/722451 + */ + if #available(macOS 15.0, iOS 18.0, *) { + guard let hostname = url.host(percentEncoded: true) else { return nil } + let host = (port == "") ? hostname : "\(hostname):\(port)" + let query = url.query(percentEncoded: true) ?? "" + let fragment = url.fragment(percentEncoded: true) ?? "" + return [ + "hash": fragment == "" ? "" : "#\(fragment)", + "host": host, + "hostname": hostname, + // "href": url.absoluteString, + "origin": "\(scheme)://\(host)", + "password": url.password(percentEncoded: true) ?? "", + "pathname": url.path(percentEncoded: true), + "port": port, + "protocol": "\(scheme):", + "search": query == "" ? "" : "?\(query)", + "username": url.user(percentEncoded: true) ?? "" + ] + } else { + guard let hostname = url.host else { return nil } + let host = (port == "") ? hostname : "\(hostname):\(port)" + let query = url.query ?? "" + let fragment = url.fragment ?? "" + /// If the path has a trailing slash, it is stripped. https://developer.apple.com/documentation/foundation/nsurl/1408809-path + let strippedPath = url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? url.path + let pathname = (strippedPath != "/" && url.hasDirectoryPath) ? "\(strippedPath)/" : strippedPath + return [ + "hash": fragment == "" ? "" : "#\(fragment)", + "host": host, + "hostname": hostname, + // "href": url.absoluteString, + "origin": "\(scheme)://\(host)", + "password": url.password ?? "", + "pathname": pathname, + "port": port, + "protocol": "\(scheme):", + "search": query == "" ? "" : "?\(query)", + "username": url.user?.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed) ?? "" + ] + } +} diff --git a/xcode/Shared/Utilities.swift b/xcode/Shared/Utilities.swift new file mode 100644 index 00000000..a60de5d2 --- /dev/null +++ b/xcode/Shared/Utilities.swift @@ -0,0 +1,168 @@ +import SwiftUI + +let bundleIdentifier = Bundle.main.bundleIdentifier! +let groupIdentifier = Bundle.main.infoDictionary!["US_SHARED_GID"] as! String +let extIdentifier = Bundle.main.infoDictionary?["US_EXT_IDENTIFIER"] as! String +let projectName = "Userscripts" +//let projectName = Bundle.main.infoDictionary!["PROJECT_NAME"] as! String +private let logger = USLogger(#fileID) + +func getPlatform() -> String { +#if os(macOS) + return "macos" +#elseif os(iOS) + // https://developer.apple.com/documentation/uikit/uiuserinterfaceidiom + // There is no reliable way to detect visionOS for now, will match `.phone` in Apple Vision (Designed for iPad) mode + switch UIDevice.current.userInterfaceIdiom { + case .phone: + return "ios" + case .pad, .mac: + return "ipados" + case .vision: + return "visionos" + case .tv: + return "tvos" + case .carPlay: + return "carplay" + case .unspecified: + return "unspecified" + @unknown default: + return "unknown" + } +#endif +} + +func directoryExists(path: String) -> Bool { + var isDirectory = ObjCBool(true) + let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) + let inTrash = path.contains(".Trash") ? false : true + return exists && inTrash && isDirectory.boolValue +} + +func directoryEmpty(at url: URL) -> Bool? { + // get a list of non-hidden files + guard let contents = try? FileManager.default.contentsOfDirectory( + at: url, + includingPropertiesForKeys: [], + options: .skipsHiddenFiles + ) else { + logger?.error("\(#function, privacy: .public) - failed get contentsOfDirectory") + return nil + } + return contents.isEmpty +} + +func getDocumentsDirectory() -> URL { + let fm = FileManager.default + let paths = fm.urls(for: .documentDirectory, in: .userDomainMask) + let documentsDirectory = paths[0] + return documentsDirectory +} + +func getProjectSharedDirectoryURL(path: String? = nil) -> URL? { + // https://developer.apple.com/documentation/foundation/filemanager/1412643-containerurl/ + guard let sharedContainerURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: groupIdentifier + ) else { + logger?.error("\(#function, privacy: .public) - Failed get sharedContainerURL") + return nil + } + guard let path = path else { + return sharedContainerURL + } + if #available(macOS 13.0, iOS 16.0, *) { + return sharedContainerURL.appending(path: path, directoryHint: .isDirectory) + } else { + return sharedContainerURL.appendingPathComponent(path, isDirectory: true) + } +} + +func getDefaultScriptsDirectoryUrl() -> URL { +#if os(iOS) + return URL(string: "default://uninitialized")! +#elseif os(macOS) + var url: URL + if #available(macOS 13.0, iOS 16.0, *) { + url = getDocumentsDirectory().appending(path: "scripts") + } else { + url = getDocumentsDirectory().appendingPathComponent("scripts") + } + // if not in safari extension environment, replace with extension path + if bundleIdentifier != extIdentifier { + var path: String + if #available(macOS 13.0, iOS 16.0, *) { + path = url.path(percentEncoded: false) + } else { + path = url.path.removingPercentEncoding ?? url.path + } + let truePath = path.replacingOccurrences(of: bundleIdentifier, with: extIdentifier) + url = URL(fileURLWithPath: truePath, isDirectory: true) + } + return url +#endif +} + +#if os(iOS) +// Initialization directory is only required in iOS +func isCurrentDefaultScriptsDirectory() -> Bool { + return Preferences.scriptsDirectoryUrl == getDefaultScriptsDirectoryUrl() +} +#endif + +// For mac, the initial directory is Ext-Documents (default directory) +// For ios, the initial directory is App-Documents +// In ios only App-Documents can be displayed in Files App +func isCurrentInitialScriptsDirectory() -> Bool { + let url = Preferences.scriptsDirectoryUrl.standardizedFileURL +#if os(iOS) + return url == getDocumentsDirectory().standardizedFileURL +#elseif os(macOS) + return url == getDefaultScriptsDirectoryUrl().standardizedFileURL +#endif +} + +func getCurrentScriptsDirectoryString() -> String { + if isCurrentInitialScriptsDirectory() { + return "Userscripts App Documents" + } + let url = Preferences.scriptsDirectoryUrl.standardizedFileURL + if #available(macOS 13.0, iOS 16.0, *) { + return url.path(percentEncoded: false) + } else { + return url.path.removingPercentEncoding ?? url.path + } +} + +func getSaveLocation() -> URL? { +#if os(iOS) + if isCurrentDefaultScriptsDirectory() { + logger?.info("\(#function, privacy: .public) - Uninitialized save location") + return nil + } +#endif + let url = Preferences.scriptsDirectoryUrl + logger?.debug("\(#function, privacy: .public) - \(url, privacy: .public)") + return url +} + +func openSaveLocation() -> Bool { +#if os(macOS) + guard let saveLocation = getSaveLocation() else { + return false + } + let didStartAccessing = saveLocation.startAccessingSecurityScopedResource() + defer { + if didStartAccessing {saveLocation.stopAccessingSecurityScopedResource()} + } + NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: saveLocation.path) +#endif + return true +} + +func resetSaveLocation() { + logger?.info("\(#function, privacy: .public) - Reset save location") +#if os(iOS) + Preferences.scriptsDirectoryUrl = getDocumentsDirectory() +#elseif os(macOS) +#endif +} diff --git a/xcode/Tests-Mac/UrlCodecTests.swift b/xcode/Tests-Mac/UrlCodecTests.swift new file mode 100644 index 00000000..ca54bcfd --- /dev/null +++ b/xcode/Tests-Mac/UrlCodecTests.swift @@ -0,0 +1,213 @@ +import XCTest + +final class UrlCodecTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + + func testEncodeURI() throws { + + func check(_ urlString: String, _ res: String) -> Bool { + let url = encodeURI(urlString) + if (url != res) { + print(urlString, url) + return false + } + return true + } + + XCTAssert(check("http://user:password@host:80/path?q=1#id", + "http://user:password@host:80/path?q=1#id")) + + XCTAssert(check("https://用户名:密码@中.文:80/path/中文/?a=中文#中文", + "https://%E7%94%A8%E6%88%B7%E5%90%8D:%E5%AF%86%E7%A0%81@%E4%B8%AD.%E6%96%87:80/path/%E4%B8%AD%E6%96%87/?a=%E4%B8%AD%E6%96%87#%E4%B8%AD%E6%96%87")) + + XCTAssert(check("https://用户名:密码@中.文:80/path/中%E6%96%87/?a=中&b=%E6%96%87&c#中文", + "https://%E7%94%A8%E6%88%B7%E5%90%8D:%E5%AF%86%E7%A0%81@%E4%B8%AD.%E6%96%87:80/path/%E4%B8%AD%25E6%2596%2587/?a=%E4%B8%AD&b=%25E6%2596%2587&c#%E4%B8%AD%E6%96%87")) + + } + + func testJsLikeURL() throws { + + func diffPrint(_ dA: Dictionary?, _ dB: Dictionary) { + guard let dA = dA else { + return print("dA is nil") + } + for k in dA.keys { + if dA[k] != dB[k] { + print(k, dA[k] ?? "nil", dB[k] ?? "nil") + } + } + } + + func check(_ urlString: String, _ res: Dictionary) -> Bool { + let url = jsLikeURL(urlString) + if (url != res) { + print(urlString) + diffPrint(url, res) + return false + } + return true + } + + /** javascript get res + + url = new URL("http://user:password@host:80/path?q=1#id") + res = {} + for(let k of ["hash","host","hostname","href","origin","password","pathname","port","protocol","search","username"]){ + res[k] = url[k]; + } + JSON.stringify(res) + */ + + XCTAssert(check("http://user:password@host:80/path?q=1#id", [ + "hash": "#id", + "host": "host", + "hostname": "host", + // "href": "http://user:password@host/path?q=1#id", + "origin": "http://host", + "password": "password", + "pathname": "/path", + "port": "", + "protocol": "http:", + "search": "?q=1", + "username": "user" + ])) + + XCTAssert(check("http://host.test:8080/path?#id", [ + "hash": "#id", + "host": "host.test:8080", + "hostname": "host.test", + // "href": "http://host.test:8080/path?#id", + "origin": "http://host.test:8080", + "password": "", + "pathname": "/path", + "port": "8080", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("http://host.test:8080/path?", [ + "hash": "", + "host": "host.test:8080", + "hostname": "host.test", + // "href": "http://host.test:8080/path?", + "origin": "http://host.test:8080", + "password": "", + "pathname": "/path", + "port": "8080", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("http://host.test/path#id", [ + "hash": "#id", + "host": "host.test", + "hostname": "host.test", + // "href": "http://host.test/path#id", + "origin": "http://host.test", + "password": "", + "pathname": "/path", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("http://host.test/path", [ + "hash": "", + "host": "host.test", + "hostname": "host.test", + // "href": "http://host.test/path", + "origin": "http://host.test", + "password": "", + "pathname": "/path", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("http://host.test/path//", [ + "hash": "", + "host": "host.test", + "hostname": "host.test", + // "href": "http://host.test/path//", + "origin": "http://host.test", + "password": "", + "pathname": "/path//", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("http://host.test/", [ + "hash": "", + "host": "host.test", + "hostname": "host.test", + // "href": "http://host.test/", + "origin": "http://host.test", + "password": "", + "pathname": "/", + "port": "", + "protocol": "http:", + "search": "", + "username": "" + ])) + + XCTAssert(check("https://用户名:密码@host.test:80/path/中%E6%96%87/?a=中&b=%E6%96%87&c#中文", [ + "hash": "#%E4%B8%AD%E6%96%87", + "host": "host.test:80", + "hostname": "host.test", + // "href": "https://%E7%94%A8%E6%88%B7%E5%90%8D:%E5%AF%86%E7%A0%81@host.test:80/path/%E4%B8%AD%E6%96%87/?a=%E4%B8%AD&b=%E6%96%87&c#%E4%B8%AD%E6%96%87", + "origin": "https://host.test:80", + "password": "%E5%AF%86%E7%A0%81", + "pathname": "/path/%E4%B8%AD%E6%96%87/", + "port": "80", + "protocol": "https:", + "search": "?a=%E4%B8%AD&b=%E6%96%87&c", + "username": "%E7%94%A8%E6%88%B7%E5%90%8D" + ])) + + if #available(macOS 14.0, iOS 17.0, *) { + XCTAssert(check("https://用户名:密码@中.文:80/path/中%E6%96%87/?a=中&b=%E6%96%87&c#中文", [ + "hash": "#%E4%B8%AD%E6%96%87", + "host": "xn--fiq.xn--7dv:80", + "hostname": "xn--fiq.xn--7dv", + // "href": "https://%E7%94%A8%E6%88%B7%E5%90%8D:%E5%AF%86%E7%A0%81@xn--fiq.xn--7dv:80/path/%E4%B8%AD%E6%96%87/?a=%E4%B8%AD&b=%E6%96%87&c#%E4%B8%AD%E6%96%87", + "origin": "https://xn--fiq.xn--7dv:80", + "password": "%E5%AF%86%E7%A0%81", + "pathname": "/path/%E4%B8%AD%E6%96%87/", + "port": "80", + "protocol": "https:", + "search": "?a=%E4%B8%AD&b=%E6%96%87&c", + "username": "%E7%94%A8%E6%88%B7%E5%90%8D" + ])) + } + + } // testJsLikeURL() -> END + +} diff --git a/xcode/Tests-Mac/UserscriptsTests.swift b/xcode/Tests-Mac/UserscriptsTests.swift new file mode 100644 index 00000000..b099c0b8 --- /dev/null +++ b/xcode/Tests-Mac/UserscriptsTests.swift @@ -0,0 +1,307 @@ +// +// UserscriptsTests.swift +// UserscriptsTests +// +// Created by Justin Wasack on 1/23/22. +// Copyright © 2022 Justin Wasack. All rights reserved. +// + +import XCTest +@testable import Userscripts_Mac_Safari_Extension + +class UserscriptsTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testStringSanitization() throws { + // given + let strs = [ + "String", + "https://something.com/?foo=12", + "I have backslashes \\\\", + ".....ok", + ":Akneh.,><>dkie:lm", + "..解锁B站大会员番剧、", + "解锁B站大会员番剧、B站视频解析下载;全网VIP视频免费破解去广告;全网音乐直接下载;油管、Facebook等国外视频解析下载;网盘搜索引擎破解无限下载等", + "5CLksm3AAbb2F2F2f----___--+87363&^#%o%3O3", + "Example Userscript Name" + ] + + // when + var result = [String]() + for str in strs { + let sanitizedString = sanitize(str) + let unsanitizedString = unsanitize(sanitizedString) + result.append(unsanitizedString) + } + + // then + XCTAssert(result.elementsEqual(strs)) + } + + func testEncodedCheck() throws { + let urls = [ + "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", + "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", + "https://cdn.frankerfacez.com/static/ffz_injector.user.js", + "http://www.k21p.com/example.user.js", // add http protocol + "https://example.test/%%%test.user.js", // removingPercentEncoding -> nil + "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js" + ] + var result = [String]() + for url in urls { + if isEncoded(url) { + result.append(url) + } + } + // 2 urls already percent encoded + XCTAssert(result.count == 2) + } + + func testGetRemoteFileContents() throws { + var urls:[String] = [ + "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", + "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", + "https://update.greasyfork.org/scripts/460897/1277476/gbCookies.js#sha256-Sv+EuBerch8z/6LvAU0m/ufvjmqB1Q/kbQrX7zAvOPk=", + "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", + "https://cdn.frankerfacez.com/static/ffz_injector.user.js", + "http://www.k21p.com/example.user.js" // add http protocol + ] + if #available(macOS 14.0, iOS 17.0, *) { + urls += [ + "https://☁️.com/", // IDN / Non-Punycode-encoded domain name + ] + } + for url in urls { + if getRemoteFileContents(url) == nil { + print(#function, url) + XCTAssert(false) + } + } + } + + func testFileRemoteUpdate() throws { + let urls = [ + "https://www.k21p.com/example.user.js", + "https://www.k21p.com/example.user.js?foo=bar", // query string + "http://www.k21p.com/example.user.js", // http protocol + "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", // non latin chars + "https://www.k21p.com/example.user.jsx" // should fail + + ] + var result = [Int]() + for url in urls { + let content = """ + // ==UserScript== + // @name test + // @match *://*/* + // @version 0.1 + // @updateURL http://www.k21p.com/example.user.js + // @downloadURL \(url) + // ==/UserScript== + """; + let response = getFileRemoteUpdate(content) + if !response.keys.contains("error") { + result.append(1) + } + } + XCTAssert(result.count == (urls.count - 1)) + } + + func testMatching() throws { + /// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#examples + let patternDict = [ + "": [ + "http://example.org/", + "https://a.org/some/path/" + ], + "*://*/*": [ + "http://example.org/", + "https://a.org/some/path/", + "https://www.example.com/", + "https://example.org/foo/bar.html", + "https://☁️.com/", + "https://🙂.☁️/", + ], + "*://*.mozilla.org/*": [ + "http://mozilla.org/", + "https://mozilla.org/", + "http://a.mozilla.org/", + "http://a.b.mozilla.org/", + "https://b.mozilla.org/path/", + ], + "*://mozilla.org/": [ + "http://mozilla.org/", + "https://mozilla.org/", + ], + "https://*/path": [ + "https://mozilla.org/path", + "https://a.mozilla.org/path", + "https://something.com/path", + ], + "https://*/path/": [ + "https://mozilla.org/path/", + "https://a.mozilla.org/path/", + "https://something.com/path/", + ], + "https://mozilla.org/*": [ + "https://mozilla.org/", + "https://mozilla.org/path", + "https://mozilla.org/another", + "https://mozilla.org/path/to/doc", + "https://mozilla.org/path/to/doc?foo=1", + ], + "https://mozilla.org/a/b/c/": [ + "https://mozilla.org/a/b/c/", + "https://mozilla.org/a/b/c/#section1", + ], + "https://mozilla.org/*/b/*/": [ + "https://mozilla.org/a/b/c/", + "https://mozilla.org/d/b/f/", + "https://mozilla.org/a/b/c/d/", + "https://mozilla.org/a/b/c/d/#section1", + "https://mozilla.org/a/b/c/d/?foo=/", + "https://mozilla.org/a?foo=21314&bar=/b/&extra=c/", + ], + "*://www.google.com/*": [ + "https://www.google.com/://aa", + "https://www.google.com/preferences?prev=https://www.google.com/", + "https://www.google.com/preferences?prev=", + "https://www.google.com/", + ], + "*://localhost/*": [ + "http://localhost:8000/", + "https://localhost:3000/foo.html", + ], + "http://127.0.0.1/*": [ + "http://127.0.0.1/", + "http://127.0.0.1/foo/bar.html", + "http://127.0.0.1/?bar=1", + "http://127.0.0.1/foo?bar=1", + ], + "*://*.example.com/*?a=1*": [ + "http://example.com/?a=1", + "https://www.example.com/index?a=1&b=2", + ], + "*://*.example.com/path/": [ + "http://example.com/path/", + "http://a.example.com/path/", + "https://example.com/path/", + "https://a.b.example.com/path/", + "https://☁️.example.com/path/", + ], + "*://*.example.com/path/*": [ + "https://example.com/path/", + "https://example.com/path/foo", + "https://example.com/path/foo?bar", + "https://example.com/path/?foo=bar", + "https://example.com/path/☁️?☁️=🙂", + ], + ] + let patternDictFails = [ + "*://*/*": [ + "ftp://ftp.example.org/", // unmatched scheme) + "file:///a/", // unmatched scheme + ], + "*://*.mozilla.org/*": [ + "ftp://mozilla.org/", // unmatched scheme + "http://mozilla.com/", // unmatched host + "http://firefox.org/", // unmatched host + ], + "*://mozilla.org/": [ + "ftp://mozilla.org/", // unmatched scheme + "http://a.mozilla.org/", // unmatched host + "http://mozilla.org/a", // unmatched path + ], + "https://*/path": [ + "http://mozilla.org/path", // unmatched scheme + "https://mozilla.org/path/", // unmatched path + "https://mozilla.org/a", // unmatched path + "https://mozilla.org/", // unmatched path + "https://mozilla.org/path?foo=1", // unmatched path due to URL query string + ], + "https://*/path/": [ + "http://mozilla.org/path/", // unmatched scheme + "https://mozilla.org/path", // unmatched path + "https://mozilla.org/a", // unmatched path + "https://mozilla.org/", // unmatched path + "https://mozilla.org/path/?foo=1", // unmatched path due to URL query string + ], + "https://mozilla.org/*": [ + "http://mozilla.org/path", // unmatched scheme + "https://mozilla.com/path", // unmatched host + ], + "https://mozilla.org/*/b/*/": [ + "https://mozilla.org/b/*/", // unmatched path + "https://mozilla.org/a/b/", // unmatched path + "https://mozilla.org/a/b/c/d/?foo=bar", // unmatched path due to URL query string + ], + "https://www.example.com/*": [ + "file://www.example.com/", + "ftp://www.example.com/", + "ws://www.example.com/", + "http://www.example.com/", + ], + "http://www.example.com/index.html": [ + "http://www.example.com/", + "https://www.example.com/index.html", + ], + "*://localhost/*": [ + "https://localhost.com/", + "ftp://localhost:8080/", + ], + "https://www.example*/*": [ + "https://www.example.com/", + ], + "*://*.example.com/*?a=1*": [ + "http://example.com/", + "https://www.example.com/?a=2", + ], + "*://*.example.com/foo/": [ + "https://example.com/foo", + "https://example.com/foo?bar", + "https://example.com/foo/bar", + "https://example.com/foo/?bar", + ], + ] + for (pattern, urls) in patternDict { + for url in urls { + if !match(url, pattern) { + print(#function, "patternDict", url, pattern) + XCTAssert(false) + } + } + } + for (pattern, urls) in patternDictFails { + for url in urls { + if match(url, pattern) { + print(#function, "patternDictFails", url, pattern) + XCTAssert(false) + } + } + } + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/xcode/Userscripts.xcodeproj/project.pbxproj b/xcode/Userscripts.xcodeproj/project.pbxproj index 8286d541..8708c6b7 100644 --- a/xcode/Userscripts.xcodeproj/project.pbxproj +++ b/xcode/Userscripts.xcodeproj/project.pbxproj @@ -3,13 +3,36 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ + 007596BF2E10B67F008F8C04 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 007596BE2E10B67F008F8C04 /* AppIcon.icon */; }; + 007596C02E10B67F008F8C04 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 007596BE2E10B67F008F8C04 /* AppIcon.icon */; }; + 030C64F62AC62CC900548FBD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 030C64F52AC62CC900548FBD /* Settings.bundle */; }; + 030C64FC2AD0B24C00548FBD /* Initialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030C64FB2AD0B24C00548FBD /* Initialization.swift */; }; + 030FCAD42B182008004D13CD /* UrlCodecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FCAD32B182008004D13CD /* UrlCodecTests.swift */; }; + 030FCADC2B189AAB004D13CD /* UrlPolyfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FCAD52B1851F8004D13CD /* UrlPolyfill.swift */; }; + 030FCADD2B18BF70004D13CD /* UrlPolyfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FCAD52B1851F8004D13CD /* UrlPolyfill.swift */; }; + 030FCADF2B18BF72004D13CD /* UrlPolyfill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030FCAD52B1851F8004D13CD /* UrlPolyfill.swift */; }; + 032CBD3C2CBCF19A00B31D5E /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 039F59522AD9591C002E9977 /* dist */; }; + 032CBD3D2CBCF1AB00B31D5E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4CF6E9270A38BD00111584 /* ViewController.swift */; }; + 032CBD3E2CBCF1AB00B31D5E /* USchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039F59552ADA2FE2002E9977 /* USchemeHandler.swift */; }; 0336619F294DF7C900CFE179 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0336619E294DF7C900CFE179 /* Functions.swift */; }; - 033661A529510B7900CFE179 /* View.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 033661A329510B7900CFE179 /* View.storyboard */; }; - 0376905B29808EEA00F3474D /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 0376905A29808EEA00F3474D /* background.js */; }; + 039F59532AD9591C002E9977 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 039F59522AD9591C002E9977 /* dist */; }; + 039F59562ADA2FE2002E9977 /* USchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039F59552ADA2FE2002E9977 /* USchemeHandler.swift */; }; + 03B81A152CB8466F00B3D17C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B81A142CB8466B00B3D17C /* Logger.swift */; }; + 03B81A162CB8466F00B3D17C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B81A142CB8466B00B3D17C /* Logger.swift */; }; + 03B81A172CB8466F00B3D17C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B81A142CB8466B00B3D17C /* Logger.swift */; }; + 03B81A182CB8466F00B3D17C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B81A142CB8466B00B3D17C /* Logger.swift */; }; + 03B81A192CB8466F00B3D17C /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B81A142CB8466B00B3D17C /* Logger.swift */; }; + 03C24F972ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; }; + 03C24F982ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; }; + 03C24F992ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; }; + 03C24F9A2ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; }; + 03C24F9B2ABD2CBB00F130F9 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C24F962ABD2CBB00F130F9 /* Preferences.swift */; }; + 03D4054B2CC66DC90091FE6F /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03D405482CC66DC90091FE6F /* Shared.xcassets */; }; + 03D4054D2CC66DC90091FE6F /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 03D405482CC66DC90091FE6F /* Shared.xcassets */; }; 03E20D06291E170000B4F692 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 03E20D05291E170000B4F692 /* dist */; }; 03E20D07291E170000B4F692 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 03E20D05291E170000B4F692 /* dist */; }; 4A143AAC279DE6FF0029BFD0 /* UserscriptsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A143AAB279DE6FF0029BFD0 /* UserscriptsTests.swift */; }; @@ -17,14 +40,10 @@ 4A301B2A270A474400C7E9E1 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */; }; 4A301B2B270A474F00C7E9E1 /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 4A36A617268266B30018536B /* _locales */; }; 4A301B2C270A475200C7E9E1 /* images in Resources */ = {isa = PBXBuildFile; fileRef = 4A36A616268266B30018536B /* images */; }; - 4A301B2E270A475D00C7E9E1 /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 4AB22357268B9B9200D551C6 /* content.js */; }; - 4A301B2F270A476000C7E9E1 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 4AD66D2026826D8100B004E8 /* manifest.json */; }; 4A301B32270A49F200C7E9E1 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AED6491268CDB58001794BF /* Functions.swift */; }; 4A36A61A268266B30018536B /* images in Resources */ = {isa = PBXBuildFile; fileRef = 4A36A616268266B30018536B /* images */; }; 4A36A61B268266B30018536B /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 4A36A617268266B30018536B /* _locales */; }; 4A4CF6DE270A38BD00111584 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4CF6DD270A38BD00111584 /* AppDelegate.swift */; }; - 4A4CF6E2270A38BD00111584 /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = 4A4CF6E0270A38BD00111584 /* Main.html */; }; - 4A4CF6E4270A38BD00111584 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 4A4CF6E3270A38BD00111584 /* Icon.png */; }; 4A4CF6E8270A38BD00111584 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4CF6E7270A38BD00111584 /* SceneDelegate.swift */; }; 4A4CF6EA270A38BD00111584 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A4CF6E9270A38BD00111584 /* ViewController.swift */; }; 4A4CF6ED270A38BD00111584 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A4CF6EB270A38BD00111584 /* LaunchScreen.storyboard */; }; @@ -33,20 +52,16 @@ 4A4CF710270A38BF00111584 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A4CF6F1270A38BF00111584 /* Assets.xcassets */; }; 4A57B9F2227235CD008A9763 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57B9F1227235CD008A9763 /* AppDelegate.swift */; }; 4A57B9F5227235CD008A9763 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4A57B9F3227235CD008A9763 /* Main.storyboard */; }; - 4A57B9F7227235CD008A9763 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57B9F6227235CD008A9763 /* ViewController.swift */; }; 4A57B9F9227235CE008A9763 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4A57B9F8227235CE008A9763 /* Assets.xcassets */; }; 4A57BA00227235CE008A9763 /* Userscripts-Mac-Safari-Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4A57B9FF227235CE008A9763 /* Userscripts-Mac-Safari-Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4A57BA08227235CE008A9763 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */; }; - 4AB22358268B9B9200D551C6 /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 4AB22357268B9B9200D551C6 /* content.js */; }; - 4AD66D2326826D8100B004E8 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 4AD66D2026826D8100B004E8 /* manifest.json */; }; 4AED6492268CDB58001794BF /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AED6491268CDB58001794BF /* Functions.swift */; }; 7878ED24299BA27B00E36A24 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */; }; - 7878ED2B299BA36800E36A24 /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = 0376905A29808EEA00F3474D /* background.js */; }; - 78B25583299D4C27000B2E9B /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Shared.swift */; }; - 78B25584299D4C27000B2E9B /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Shared.swift */; }; - 78B25585299D4C27000B2E9B /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Shared.swift */; }; - 78B25586299D4C27000B2E9B /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Shared.swift */; }; - 78B25587299D4C27000B2E9B /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Shared.swift */; }; + 78B25583299D4C27000B2E9B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Utilities.swift */; }; + 78B25584299D4C27000B2E9B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Utilities.swift */; }; + 78B25585299D4C27000B2E9B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Utilities.swift */; }; + 78B25586299D4C27000B2E9B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Utilities.swift */; }; + 78B25587299D4C27000B2E9B /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B25582299D4C27000B2E9B /* Utilities.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -99,9 +114,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 007596BE2E10B67F008F8C04 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; + 030C64F52AC62CC900548FBD /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; + 030C64FB2AD0B24C00548FBD /* Initialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Initialization.swift; sourceTree = ""; }; + 030FCAD32B182008004D13CD /* UrlCodecTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlCodecTests.swift; sourceTree = ""; }; + 030FCAD52B1851F8004D13CD /* UrlPolyfill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlPolyfill.swift; sourceTree = ""; }; 0336619E294DF7C900CFE179 /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; - 033661A429510B7900CFE179 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/View.storyboard; sourceTree = ""; }; - 0376905A29808EEA00F3474D /* background.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = background.js; sourceTree = ""; }; + 039F59522AD9591C002E9977 /* dist */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dist; sourceTree = ""; }; + 039F59552ADA2FE2002E9977 /* USchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = USchemeHandler.swift; sourceTree = ""; }; + 039F59662B03AE5A002E9977 /* Userscripts-Vite.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Userscripts-Vite.xcconfig"; sourceTree = ""; }; + 03B81A142CB8466B00B3D17C /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 03C24F962ABD2CBB00F130F9 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; + 03D405482CC66DC90091FE6F /* Shared.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Shared.xcassets; sourceTree = ""; }; 03E20D05291E170000B4F692 /* dist */ = {isa = PBXFileReference; lastKnownFileType = folder; path = dist; sourceTree = ""; }; 4A143AA9279DE6FF0029BFD0 /* Mac-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Mac-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 4A143AAB279DE6FF0029BFD0 /* UserscriptsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserscriptsTests.swift; sourceTree = ""; }; @@ -109,8 +133,6 @@ 4A36A617268266B30018536B /* _locales */ = {isa = PBXFileReference; lastKnownFileType = folder; path = _locales; sourceTree = ""; }; 4A4CF6DB270A38BD00111584 /* Userscripts-iOS-App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Userscripts-iOS-App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 4A4CF6DD270A38BD00111584 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 4A4CF6E1270A38BD00111584 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = ../Base.lproj/Main.html; sourceTree = ""; }; - 4A4CF6E3270A38BD00111584 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = ""; }; 4A4CF6E7270A38BD00111584 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 4A4CF6E9270A38BD00111584 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 4A4CF6EC270A38BD00111584 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; @@ -123,14 +145,12 @@ 4A57B9EF227235CD008A9763 /* App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = App.entitlements; sourceTree = ""; }; 4A57B9F1227235CD008A9763 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 4A57B9F4227235CD008A9763 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 4A57B9F6227235CD008A9763 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 4A57B9F8227235CE008A9763 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 4A57B9FA227235CE008A9763 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4A57B9FF227235CE008A9763 /* Userscripts-Mac-Safari-Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Userscripts-Mac-Safari-Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = ""; }; 4A57BA0E227235CE008A9763 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4A57BA1D227235E6008A9763 /* Mac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mac.entitlements; sourceTree = ""; }; - 4AB22357268B9B9200D551C6 /* content.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = content.js; sourceTree = ""; }; 4AD66D2026826D8100B004E8 /* manifest.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = manifest.json; sourceTree = ""; }; 4AED6491268CDB58001794BF /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; 78391D4629A4CB3C00979E34 /* Userscripts-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Userscripts-Debug.xcconfig"; sourceTree = ""; }; @@ -141,7 +161,7 @@ 7878ED32299BBEBF00E36A24 /* Mac-Safari-Extension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-Safari-Extension.xcconfig"; sourceTree = ""; }; 7878ED34299BBEBF00E36A24 /* iOS-App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "iOS-App.xcconfig"; sourceTree = ""; }; 7878ED38299BBEBF00E36A24 /* Mac-App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Mac-App.xcconfig"; sourceTree = ""; }; - 78B25582299D4C27000B2E9B /* Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; + 78B25582299D4C27000B2E9B /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -183,37 +203,49 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 4A143AAA279DE6FF0029BFD0 /* Mac-Tests */ = { + 0316A9992AD7709600AAEC8A /* App-Shared */ = { + isa = PBXGroup; + children = ( + 039F59512AD95485002E9977 /* Resources */, + 007596BE2E10B67F008F8C04 /* AppIcon.icon */, + 03D405482CC66DC90091FE6F /* Shared.xcassets */, + 4A4CF6E9270A38BD00111584 /* ViewController.swift */, + 039F59552ADA2FE2002E9977 /* USchemeHandler.swift */, + ); + path = "App-Shared"; + sourceTree = ""; + }; + 039F59512AD95485002E9977 /* Resources */ = { + isa = PBXGroup; + children = ( + 039F59522AD9591C002E9977 /* dist */, + ); + path = Resources; + sourceTree = ""; + }; + 4A143AAA279DE6FF0029BFD0 /* Tests-Mac */ = { isa = PBXGroup; children = ( 4A143AAB279DE6FF0029BFD0 /* UserscriptsTests.swift */, + 030FCAD32B182008004D13CD /* UrlCodecTests.swift */, ); - path = "Mac-Tests"; + path = "Tests-Mac"; sourceTree = ""; }; - 4A4CF6DC270A38BD00111584 /* iOS-App */ = { + 4A4CF6DC270A38BD00111584 /* App-iOS */ = { isa = PBXGroup; children = ( 4A4CF6F2270A38BF00111584 /* Info.plist */, 4A4CF718270A38C700111584 /* App.entitlements */, - 4A4CF6DF270A38BD00111584 /* Resources */, + 030C64F52AC62CC900548FBD /* Settings.bundle */, 4A4CF6F1270A38BF00111584 /* Assets.xcassets */, 4A4CF6DD270A38BD00111584 /* AppDelegate.swift */, 4A4CF6E7270A38BD00111584 /* SceneDelegate.swift */, - 4A4CF6E9270A38BD00111584 /* ViewController.swift */, + 030C64FB2AD0B24C00548FBD /* Initialization.swift */, 4A4CF6EB270A38BD00111584 /* LaunchScreen.storyboard */, 4A4CF6EE270A38BD00111584 /* Main.storyboard */, ); - path = "iOS-App"; - sourceTree = ""; - }; - 4A4CF6DF270A38BD00111584 /* Resources */ = { - isa = PBXGroup; - children = ( - 4A4CF6E3270A38BD00111584 /* Icon.png */, - 4A4CF6E0270A38BD00111584 /* Main.html */, - ); - path = Resources; + path = "App-iOS"; sourceTree = ""; }; 4A57B9E3227235CD008A9763 = { @@ -221,13 +253,15 @@ children = ( 7878ED2E299BBEBF00E36A24 /* xcconfig */, 78B25581299D4C27000B2E9B /* Shared */, - 4A57BA06227235CE008A9763 /* Safari-Extension */, - 4A57B9EE227235CD008A9763 /* Mac-App */, - 4A4CF6DC270A38BD00111584 /* iOS-App */, - 4A143AAA279DE6FF0029BFD0 /* Mac-Tests */, + 4A57BA06227235CE008A9763 /* Ext-Safari */, + 0316A9992AD7709600AAEC8A /* App-Shared */, + 4A57B9EE227235CD008A9763 /* App-Mac */, + 4A4CF6DC270A38BD00111584 /* App-iOS */, + 4A143AAA279DE6FF0029BFD0 /* Tests-Mac */, 4A57B9ED227235CD008A9763 /* Products */, ); sourceTree = ""; + usesTabs = 1; }; 4A57B9ED227235CD008A9763 /* Products */ = { isa = PBXGroup; @@ -241,32 +275,30 @@ name = Products; sourceTree = ""; }; - 4A57B9EE227235CD008A9763 /* Mac-App */ = { + 4A57B9EE227235CD008A9763 /* App-Mac */ = { isa = PBXGroup; children = ( 4A57B9FA227235CE008A9763 /* Info.plist */, 4A57B9EF227235CD008A9763 /* App.entitlements */, 4A57B9F8227235CE008A9763 /* Assets.xcassets */, 4A57B9F1227235CD008A9763 /* AppDelegate.swift */, - 4A57B9F6227235CD008A9763 /* ViewController.swift */, 0336619E294DF7C900CFE179 /* Functions.swift */, 4A57B9F3227235CD008A9763 /* Main.storyboard */, - 033661A329510B7900CFE179 /* View.storyboard */, ); - path = "Mac-App"; + path = "App-Mac"; sourceTree = ""; }; - 4A57BA06227235CE008A9763 /* Safari-Extension */ = { + 4A57BA06227235CE008A9763 /* Ext-Safari */ = { isa = PBXGroup; children = ( - 4AF1DD0D268265DB00086E88 /* Resources */, - 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */, - 4AED6491268CDB58001794BF /* Functions.swift */, 4A57BA0E227235CE008A9763 /* Info.plist */, 4A57BA1D227235E6008A9763 /* Mac.entitlements */, 7878ED2D299BA40800E36A24 /* iOS.entitlements */, + 4AF1DD0D268265DB00086E88 /* Resources */, + 4A57BA07227235CE008A9763 /* SafariWebExtensionHandler.swift */, + 4AED6491268CDB58001794BF /* Functions.swift */, ); - path = "Safari-Extension"; + path = "Ext-Safari"; sourceTree = ""; }; 4AF1DD0D268265DB00086E88 /* Resources */ = { @@ -276,8 +308,6 @@ 4A36A616268266B30018536B /* images */, 03E20D05291E170000B4F692 /* dist */, 4AD66D2026826D8100B004E8 /* manifest.json */, - 0376905A29808EEA00F3474D /* background.js */, - 4AB22357268B9B9200D551C6 /* content.js */, ); path = Resources; sourceTree = ""; @@ -285,6 +315,7 @@ 7878ED2E299BBEBF00E36A24 /* xcconfig */ = { isa = PBXGroup; children = ( + 039F59662B03AE5A002E9977 /* Userscripts-Vite.xcconfig */, 78391D4629A4CB3C00979E34 /* Userscripts-Debug.xcconfig */, 78391D4729A4CB3C00979E34 /* Userscripts-Release.xcconfig */, 7878ED38299BBEBF00E36A24 /* Mac-App.xcconfig */, @@ -299,7 +330,10 @@ 78B25581299D4C27000B2E9B /* Shared */ = { isa = PBXGroup; children = ( - 78B25582299D4C27000B2E9B /* Shared.swift */, + 03B81A142CB8466B00B3D17C /* Logger.swift */, + 78B25582299D4C27000B2E9B /* Utilities.swift */, + 03C24F962ABD2CBB00F130F9 /* Preferences.swift */, + 030FCAD52B1851F8004D13CD /* UrlPolyfill.swift */, ); path = Shared; sourceTree = ""; @@ -333,6 +367,7 @@ 4A4CF6D8270A38BD00111584 /* Frameworks */, 4A4CF6D9270A38BD00111584 /* Resources */, 4A4CF713270A38BF00111584 /* Embed Foundation Extensions */, + 030C64F72AC713A600548FBD /* UScript: Introduce values to app Settings.bundle */, ); buildRules = ( ); @@ -351,6 +386,7 @@ 4A4CF6F3270A38BF00111584 /* Sources */, 4A4CF6F4270A38BF00111584 /* Frameworks */, 4A4CF6F5270A38BF00111584 /* Resources */, + 039F59592ADFA415002E9977 /* UScript: Produce ext manifest.json */, ); buildRules = ( ); @@ -387,6 +423,7 @@ 4A57B9FB227235CE008A9763 /* Sources */, 4A57B9FC227235CE008A9763 /* Frameworks */, 4A57B9FD227235CE008A9763 /* Resources */, + 039F59572ADF9558002E9977 /* UScript: Produce ext manifest.json */, ); buildRules = ( ); @@ -403,6 +440,7 @@ 4A57B9E4227235CD008A9763 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1320; LastUpgradeCheck = 1420; ORGANIZATIONNAME = "Justin Wasack"; @@ -464,11 +502,13 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A4CF6E4270A38BD00111584 /* Icon.png in Resources */, 4A4CF6F0270A38BD00111584 /* Main.storyboard in Resources */, 4A4CF6ED270A38BD00111584 /* LaunchScreen.storyboard in Resources */, - 4A4CF6E2270A38BD00111584 /* Main.html in Resources */, + 03D4054B2CC66DC90091FE6F /* Shared.xcassets in Resources */, + 007596BF2E10B67F008F8C04 /* AppIcon.icon in Resources */, + 030C64F62AC62CC900548FBD /* Settings.bundle in Resources */, 4A4CF710270A38BF00111584 /* Assets.xcassets in Resources */, + 039F59532AD9591C002E9977 /* dist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -476,11 +516,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A301B2E270A475D00C7E9E1 /* content.js in Resources */, 4A301B2C270A475200C7E9E1 /* images in Resources */, - 7878ED2B299BA36800E36A24 /* background.js in Resources */, 03E20D07291E170000B4F692 /* dist in Resources */, - 4A301B2F270A476000C7E9E1 /* manifest.json in Resources */, 4A301B2B270A474F00C7E9E1 /* _locales in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -490,7 +527,9 @@ buildActionMask = 2147483647; files = ( 4A57B9F9227235CE008A9763 /* Assets.xcassets in Resources */, - 033661A529510B7900CFE179 /* View.storyboard in Resources */, + 032CBD3C2CBCF19A00B31D5E /* dist in Resources */, + 007596C02E10B67F008F8C04 /* AppIcon.icon in Resources */, + 03D4054D2CC66DC90091FE6F /* Shared.xcassets in Resources */, 4A57B9F5227235CD008A9763 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -499,26 +538,95 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4AD66D2326826D8100B004E8 /* manifest.json in Resources */, - 4AB22358268B9B9200D551C6 /* content.js in Resources */, 03E20D06291E170000B4F692 /* dist in Resources */, 4A36A61A268266B30018536B /* images in Resources */, - 0376905B29808EEA00F3474D /* background.js in Resources */, 4A36A61B268266B30018536B /* _locales in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 030C64F72AC713A600548FBD /* UScript: Introduce values to app Settings.bundle */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/$(SRC_DIR)/Settings.bundle/Root.plist", + "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)", + ); + name = "UScript: Introduce values to app Settings.bundle"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(TARGET_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)/Settings.bundle/Root.plist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Dynamically introduce US_SHARED_GID in xcconfig for Settings.bundle\nUS_SHARED_GID=$(/usr/libexec/PlistBuddy -c \"Print US_SHARED_GID\" \"$TARGET_BUILD_DIR/$INFOPLIST_PATH\")\nSETTINGS_BUNDLE=$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/Settings.bundle/Root.plist\n/usr/libexec/PlistBuddy -c \"Set ApplicationGroupContainerIdentifier $US_SHARED_GID\" \"$SETTINGS_BUNDLE\"\n"; + }; + 039F59572ADF9558002E9977 /* UScript: Produce ext manifest.json */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/$(MANIFEST_FILE)", + "$(SRCROOT)/$(SRC_DIR)/Resources/_locales/en/messages.json", + "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)", + ); + name = "UScript: Produce ext manifest.json"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/manifest.json", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "sed \"s/_VERSION_/$MARKETING_VERSION/\" $SRCROOT/$MANIFEST_FILE > $TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/manifest.json\nsed -i \"\" \"s/_NAME_/$MANIFEST_NAME/\" $TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/_locales/en/messages.json\n"; + }; + 039F59592ADFA415002E9977 /* UScript: Produce ext manifest.json */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "$(SRCROOT)/$(MANIFEST_FILE)", + "$(SRCROOT)/$(SRC_DIR)/Resources/_locales/en/messages.json", + "$(TARGET_BUILD_DIR)/$(INFOPLIST_PATH)", + ); + name = "UScript: Produce ext manifest.json"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(TARGET_BUILD_DIR)/$(CONTENTS_FOLDER_PATH)/manifest.json", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "sed \"s/_VERSION_/$MARKETING_VERSION/\" $SRCROOT/$MANIFEST_FILE > $TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/manifest.json\nsed -i \"\" \"s/_NAME_/$MANIFEST_NAME/\" $TARGET_BUILD_DIR/$CONTENTS_FOLDER_PATH/_locales/en/messages.json\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 4A143AA5279DE6FF0029BFD0 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7878ED24299BA27B00E36A24 /* SafariWebExtensionHandler.swift in Sources */, + 030FCADC2B189AAB004D13CD /* UrlPolyfill.swift in Sources */, + 030FCAD42B182008004D13CD /* UrlCodecTests.swift in Sources */, 4A143AB2279DEA170029BFD0 /* Functions.swift in Sources */, - 78B25587299D4C27000B2E9B /* Shared.swift in Sources */, + 03C24F9B2ABD2CBB00F130F9 /* Preferences.swift in Sources */, + 78B25587299D4C27000B2E9B /* Utilities.swift in Sources */, 4A143AAC279DE6FF0029BFD0 /* UserscriptsTests.swift in Sources */, + 03B81A152CB8466F00B3D17C /* Logger.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -528,8 +636,12 @@ files = ( 4A4CF6EA270A38BD00111584 /* ViewController.swift in Sources */, 4A4CF6DE270A38BD00111584 /* AppDelegate.swift in Sources */, - 78B25585299D4C27000B2E9B /* Shared.swift in Sources */, + 03C24F992ABD2CBB00F130F9 /* Preferences.swift in Sources */, + 030C64FC2AD0B24C00548FBD /* Initialization.swift in Sources */, + 78B25585299D4C27000B2E9B /* Utilities.swift in Sources */, 4A4CF6E8270A38BD00111584 /* SceneDelegate.swift in Sources */, + 039F59562ADA2FE2002E9977 /* USchemeHandler.swift in Sources */, + 03B81A162CB8466F00B3D17C /* Logger.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -537,8 +649,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78B25586299D4C27000B2E9B /* Shared.swift in Sources */, + 78B25586299D4C27000B2E9B /* Utilities.swift in Sources */, + 03B81A182CB8466F00B3D17C /* Logger.swift in Sources */, 4A301B2A270A474400C7E9E1 /* SafariWebExtensionHandler.swift in Sources */, + 030FCADD2B18BF70004D13CD /* UrlPolyfill.swift in Sources */, + 03C24F9A2ABD2CBB00F130F9 /* Preferences.swift in Sources */, 4A301B32270A49F200C7E9E1 /* Functions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -547,10 +662,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4A57B9F7227235CD008A9763 /* ViewController.swift in Sources */, + 03B81A172CB8466F00B3D17C /* Logger.swift in Sources */, 4A57B9F2227235CD008A9763 /* AppDelegate.swift in Sources */, - 78B25583299D4C27000B2E9B /* Shared.swift in Sources */, + 03C24F972ABD2CBB00F130F9 /* Preferences.swift in Sources */, + 78B25583299D4C27000B2E9B /* Utilities.swift in Sources */, 0336619F294DF7C900CFE179 /* Functions.swift in Sources */, + 032CBD3D2CBCF1AB00B31D5E /* ViewController.swift in Sources */, + 032CBD3E2CBCF1AB00B31D5E /* USchemeHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -558,8 +676,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 78B25584299D4C27000B2E9B /* Shared.swift in Sources */, + 78B25584299D4C27000B2E9B /* Utilities.swift in Sources */, + 03B81A192CB8466F00B3D17C /* Logger.swift in Sources */, 4A57BA08227235CE008A9763 /* SafariWebExtensionHandler.swift in Sources */, + 030FCADF2B18BF72004D13CD /* UrlPolyfill.swift in Sources */, + 03C24F982ABD2CBB00F130F9 /* Preferences.swift in Sources */, 4AED6492268CDB58001794BF /* Functions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -585,22 +706,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 033661A329510B7900CFE179 /* View.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 033661A429510B7900CFE179 /* Base */, - ); - name = View.storyboard; - sourceTree = ""; - }; - 4A4CF6E0270A38BD00111584 /* Main.html */ = { - isa = PBXVariantGroup; - children = ( - 4A4CF6E1270A38BD00111584 /* Base */, - ); - name = Main.html; - sourceTree = ""; - }; 4A4CF6EB270A38BD00111584 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -628,6 +733,48 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 039F59602B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 039F59662B03AE5A002E9977 /* Userscripts-Vite.xcconfig */; + buildSettings = { + }; + name = Vite; + }; + 039F59612B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7878ED38299BBEBF00E36A24 /* Mac-App.xcconfig */; + buildSettings = { + }; + name = Vite; + }; + 039F59622B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7878ED32299BBEBF00E36A24 /* Mac-Safari-Extension.xcconfig */; + buildSettings = { + }; + name = Vite; + }; + 039F59632B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7878ED34299BBEBF00E36A24 /* iOS-App.xcconfig */; + buildSettings = { + }; + name = Vite; + }; + 039F59642B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7878ED2F299BBEBF00E36A24 /* iOS-Safari-Extension.xcconfig */; + buildSettings = { + }; + name = Vite; + }; + 039F59652B03A48A002E9977 /* Vite */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7878ED31299BBEBF00E36A24 /* Mac-Tests.xcconfig */; + buildSettings = { + }; + name = Vite; + }; 4A143AAF279DE6FF0029BFD0 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7878ED31299BBEBF00E36A24 /* Mac-Tests.xcconfig */; @@ -718,56 +865,62 @@ 4A143AB1279DE6FF0029BFD0 /* Build configuration list for PBXNativeTarget "Mac-Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59652B03A48A002E9977 /* Vite */, 4A143AAF279DE6FF0029BFD0 /* Debug */, 4A143AB0279DE6FF0029BFD0 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 4A4CF716270A38BF00111584 /* Build configuration list for PBXNativeTarget "iOS-Safari-Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59642B03A48A002E9977 /* Vite */, 4A4CF714270A38BF00111584 /* Debug */, 4A4CF715270A38BF00111584 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 4A4CF717270A38BF00111584 /* Build configuration list for PBXNativeTarget "iOS-App" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59632B03A48A002E9977 /* Vite */, 4A4CF711270A38BF00111584 /* Debug */, 4A4CF712270A38BF00111584 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 4A57B9E7227235CD008A9763 /* Build configuration list for PBXProject "Userscripts" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59602B03A48A002E9977 /* Vite */, 4A57BA14227235CE008A9763 /* Debug */, 4A57BA15227235CE008A9763 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 4A57BA16227235CE008A9763 /* Build configuration list for PBXNativeTarget "Mac-Safari-Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59622B03A48A002E9977 /* Vite */, 4A57BA17227235CE008A9763 /* Debug */, 4A57BA18227235CE008A9763 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 4A57BA1A227235CE008A9763 /* Build configuration list for PBXNativeTarget "Mac-App" */ = { isa = XCConfigurationList; buildConfigurations = ( + 039F59612B03A48A002E9977 /* Vite */, 4A57BA1B227235CE008A9763 /* Debug */, 4A57BA1C227235CE008A9763 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; diff --git a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts Extension.xcscheme b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac-Vite-Dev.xcscheme similarity index 53% rename from xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts Extension.xcscheme rename to xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac-Vite-Dev.xcscheme index bf2b16e1..0a3c056b 100644 --- a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts Extension.xcscheme +++ b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac-Vite-Dev.xcscheme @@ -1,26 +1,11 @@ + LastUpgradeVersion = "1500" + version = "1.7"> - - - - @@ -38,19 +23,11 @@ - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> @@ -65,63 +42,48 @@ - - - + allowLocationSimulation = "YES"> + - - - - - + + debugDocumentVersioning = "YES"> + buildConfiguration = "Vite"> diff --git a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts.xcscheme b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac.xcscheme similarity index 83% rename from xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts.xcscheme rename to xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac.xcscheme index e3d1c38e..d78f492e 100644 --- a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts.xcscheme +++ b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/Mac.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1500" + version = "1.7"> @@ -15,7 +15,7 @@ @@ -26,16 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> @@ -64,7 +56,7 @@ @@ -81,7 +73,7 @@ diff --git a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS-Vite-Dev.xcscheme b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS-Vite-Dev.xcscheme new file mode 100644 index 00000000..c87e739c --- /dev/null +++ b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS-Vite-Dev.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme new file mode 100644 index 00000000..9c0ceed5 --- /dev/null +++ b/xcode/Userscripts.xcodeproj/xcshareddata/xcschemes/iOS.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xcode/iOS-App/AppDelegate.swift b/xcode/iOS-App/AppDelegate.swift deleted file mode 100644 index e1fe4a8a..00000000 --- a/xcode/iOS-App/AppDelegate.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AppDelegate.swift -// Userscripts-iOS -// -// Created by Justin Wasack on 10/3/21. -// Copyright © 2021 Justin Wasack. All rights reserved. -// - -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - -} diff --git a/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/Contents.json b/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/Contents.json deleted file mode 100644 index 8e400851..00000000 --- a/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "LaunchScreen.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/LaunchScreen.pdf b/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/LaunchScreen.pdf deleted file mode 100644 index 51a0b7dc..00000000 Binary files a/xcode/iOS-App/Assets.xcassets/LaunchScreen.imageset/LaunchScreen.pdf and /dev/null differ diff --git a/xcode/iOS-App/Base.lproj/Main.html b/xcode/iOS-App/Base.lproj/Main.html deleted file mode 100644 index e718ab5a..00000000 --- a/xcode/iOS-App/Base.lproj/Main.html +++ /dev/null @@ -1 +0,0 @@ -
    Userscripts App Icon

    You can turn on the Userscripts iOS Safari extension in Settings. Read the docs.

    CURRENT DIRECTORY:
    init
    diff --git a/xcode/iOS-App/Resources/Icon.png b/xcode/iOS-App/Resources/Icon.png deleted file mode 100644 index 9fe96182..00000000 Binary files a/xcode/iOS-App/Resources/Icon.png and /dev/null differ diff --git a/xcode/iOS-App/SceneDelegate.swift b/xcode/iOS-App/SceneDelegate.swift deleted file mode 100644 index 1d5426bd..00000000 --- a/xcode/iOS-App/SceneDelegate.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// SceneDelegate.swift -// Userscripts-iOS -// -// Created by Justin Wasack on 10/3/21. -// Copyright © 2021 Justin Wasack. All rights reserved. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let _ = (scene as? UIWindowScene) else { return } - } - -} diff --git a/xcode/iOS-App/ViewController.swift b/xcode/iOS-App/ViewController.swift deleted file mode 100644 index 423fa91b..00000000 --- a/xcode/iOS-App/ViewController.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// ViewController.swift -// Userscripts-iOS -// -// Created by Justin Wasack on 10/3/21. -// Copyright © 2021 Justin Wasack. All rights reserved. -// - -import UIKit -import WebKit -import UniformTypeIdentifiers - -class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, UIDocumentPickerDelegate { - - @IBOutlet var webView: WKWebView! - - override func viewDidLoad() { - super.viewDidLoad() - let backgroundColor = UIColor.init(red: (47/255.0), green: (51/255.0), blue: (55/255.0), alpha: 1.0) - view.setValue(backgroundColor, forKey: "backgroundColor") - self.webView.isOpaque = false - self.webView.backgroundColor = backgroundColor - self.webView.navigationDelegate = self - self.webView.scrollView.isScrollEnabled = false - self.webView.configuration.userContentController.add(self, name: "controller") - - self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" - let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" - var readLocation:String - if let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) { - if let bookmarkUrl = readBookmark(data: sharedBookmarkData, isSecure: true) { - readLocation = bookmarkUrl.absoluteString - } else { - readLocation = "Failed to get read directory" - } - } else { - readLocation = "Select a directory to load userscripts" - } - webView.evaluateJavaScript("printDirectory('\(readLocation)')") - webView.evaluateJavaScript("printVersion('v\(appVersion)', '(\(buildNumber))')") - } - - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if navigationAction.navigationType == .linkActivated { - guard - let url = navigationAction.request.url, - UIApplication.shared.canOpenURL(url) - else { - return - } - UIApplication.shared.open(url) - decisionHandler(.cancel) - } else { - decisionHandler(.allow) - } - } - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - guard let name = message.body as? String else { - err("Userscripts iOS received a message without a name") - return - } - if name == "SET_READ_LOCATION" { - logText("Userscripts iOS has requested to set the readLocation") - let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) - documentPicker.delegate = self - present(documentPicker, animated: true, completion: nil) - } - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { - // https://developer.apple.com/videos/play/wwdc2018/216 - do { - let shouldStopAccessing = url.startAccessingSecurityScopedResource() - defer { - if shouldStopAccessing {url.stopAccessingSecurityScopedResource()} - } - if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) { - webView.evaluateJavaScript("printDirectory('\(url.absoluteString)')") - } else { - throw NSError(domain: "Failed to saved bookmark", code: 0, userInfo: [:]) - } - } catch let error { - err("\(error)") - return - } - } -} diff --git a/xcode/xcconfig/Mac-App.xcconfig b/xcode/xcconfig/Mac-App.xcconfig index 76526d70..81ab00d6 100644 --- a/xcode/xcconfig/Mac-App.xcconfig +++ b/xcode/xcconfig/Mac-App.xcconfig @@ -1,18 +1,23 @@ +SRC_DIR = App-Mac ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon -CODE_SIGN_ENTITLEMENTS = Mac-App/App.entitlements +CODE_SIGN_ENTITLEMENTS = $(SRC_DIR)/App.entitlements COMBINE_HIDPI_IMAGES = YES -CURRENT_PROJECT_VERSION = $(MAC_APP_BUILD) ENABLE_HARDENED_RUNTIME = YES GENERATE_INFOPLIST_FILE = YES -INFOPLIST_FILE = Mac-App/Info.plist +INFOPLIST_FILE = $(SRC_DIR)/Info.plist INFOPLIST_KEY_CFBundleDisplayName = $(PROJECT_NAME)-$(TARGET_NAME) INFOPLIST_KEY_CFBundleDisplayName[config=Debug] = $(PROJECT_NAME)-$(TARGET_NAME)-Debug +INFOPLIST_KEY_CFBundleDisplayName[config=Vite] = $(PROJECT_NAME)-$(TARGET_NAME)-Vite LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks -MARKETING_VERSION = $(MAC_APP_VERSION) PRODUCT_BUNDLE_IDENTIFIER = $(APP_IDENTIFIER) PRODUCT_NAME = $(PROJECT_NAME) PRODUCT_NAME[config=Debug] = $(PROJECT_NAME)-Debug +PRODUCT_NAME[config=Vite] = $(PROJECT_NAME)-Vite +SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP $(inherited) + +// Provisioning Profile +PROVISIONING_PROFILE_SPECIFIER[config=Release] = match AppStore $(APP_IDENTIFIER) macos // Override this file -#include? "Mac-App.dev.xcconfig" \ No newline at end of file +#include? "Mac-App.dev.xcconfig" diff --git a/xcode/xcconfig/Mac-Safari-Extension.xcconfig b/xcode/xcconfig/Mac-Safari-Extension.xcconfig index 13f53620..07b33fb0 100644 --- a/xcode/xcconfig/Mac-Safari-Extension.xcconfig +++ b/xcode/xcconfig/Mac-Safari-Extension.xcconfig @@ -1,15 +1,20 @@ -CODE_SIGN_ENTITLEMENTS = Safari-Extension/Mac.entitlements -CURRENT_PROJECT_VERSION = $(MAC_APP_BUILD) +// Shared config file +#include? "Safari-Extension.xcconfig" + +CODE_SIGN_ENTITLEMENTS = $(SRC_DIR)/Mac.entitlements ENABLE_HARDENED_RUNTIME = YES GENERATE_INFOPLIST_FILE = YES -INFOPLIST_FILE = Safari-Extension/Info.plist +INFOPLIST_FILE = $(SRC_DIR)/Info.plist INFOPLIST_KEY_CFBundleDisplayName = $(PRODUCT_NAME) INFOPLIST_KEY_CFBundleDisplayName[config=Debug] = $(PRODUCT_NAME)-Debug LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks -MARKETING_VERSION = $(MAC_APP_VERSION) PRODUCT_BUNDLE_IDENTIFIER = $(EXT_IDENTIFIER) PRODUCT_NAME = $(PROJECT_NAME)-$(TARGET_NAME) SKIP_INSTALL = YES +SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXT $(inherited) + +// Provisioning Profile +PROVISIONING_PROFILE_SPECIFIER[config=Release] = match AppStore $(EXT_IDENTIFIER) macos // Override this file -#include? "Mac-Safari-Extension.dev.xcconfig" \ No newline at end of file +#include? "Mac-Safari-Extension.dev.xcconfig" diff --git a/xcode/xcconfig/Mac-Tests.xcconfig b/xcode/xcconfig/Mac-Tests.xcconfig index 998554bc..01a14d09 100644 --- a/xcode/xcconfig/Mac-Tests.xcconfig +++ b/xcode/xcconfig/Mac-Tests.xcconfig @@ -8,4 +8,4 @@ SWIFT_EMIT_LOC_STRINGS = NO TEST_HOST = $(BUILT_PRODUCTS_DIR)/Userscripts-Debug.app/Contents/MacOS/Userscripts-Debug // Override this file -#include? "Mac-Tests.dev.xcconfig" \ No newline at end of file +#include? "Mac-Tests.dev.xcconfig" diff --git a/xcode/xcconfig/Safari-Extension.xcconfig b/xcode/xcconfig/Safari-Extension.xcconfig new file mode 100644 index 00000000..d2724321 --- /dev/null +++ b/xcode/xcconfig/Safari-Extension.xcconfig @@ -0,0 +1,5 @@ +SRC_DIR = Ext-Safari +MANIFEST_FILE = $(SRC_DIR)/Resources/manifest.json +MANIFEST_NAME = $(PROJECT_NAME) +MANIFEST_NAME[config=Debug] = $(PROJECT_NAME)-Debug +MANIFEST_NAME[config=Vite] = $(PROJECT_NAME)-Vite \ No newline at end of file diff --git a/xcode/xcconfig/Userscripts-Base.xcconfig b/xcode/xcconfig/Userscripts-Base.xcconfig index 7f35556a..505bf3a9 100644 --- a/xcode/xcconfig/Userscripts-Base.xcconfig +++ b/xcode/xcconfig/Userscripts-Base.xcconfig @@ -54,11 +54,12 @@ MTL_ENABLE_DEBUG_INFO[config=Release] = NO MTL_FAST_MATH = YES ONLY_ACTIVE_ARCH[config=Debug] = YES SDKROOT = macosx -SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG +SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG $(inherited) +SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Vite] = DEBUG VITE $(inherited) SWIFT_COMPILATION_MODE[config=Release] = wholemodule SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone SWIFT_OPTIMIZATION_LEVEL[config=Release] = -O SWIFT_VERSION = 5.0 // Override this file -#include? "Userscripts-Base.dev.xcconfig" \ No newline at end of file +#include? "Userscripts-Base.dev.xcconfig" diff --git a/xcode/xcconfig/Userscripts-Debug.xcconfig b/xcode/xcconfig/Userscripts-Debug.xcconfig index f7b4cc7f..8d2ac4cd 100644 --- a/xcode/xcconfig/Userscripts-Debug.xcconfig +++ b/xcode/xcconfig/Userscripts-Debug.xcconfig @@ -1,24 +1,24 @@ #include "Userscripts-Base.xcconfig" INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © Userscripts -IOS_APP_BUILD = 1 -IOS_APP_VERSION = 1.0.0 -MAC_APP_BUILD = 1 -MAC_APP_VERSION = 1.0.0 +CURRENT_PROJECT_VERSION = 1 +MARKETING_VERSION = 1.0.0 -// Sign to Run Locally -CODE_SIGN_IDENTITY = - +// Development +CODE_SIGN_IDENTITY = Apple Development +// Developer Team ID +DEVELOPMENT_TEAM = XXXXXXXXXX // Organization Identifier -ORG_IDENTIFIER = debug +ORG_IDENTIFIER = dev.debug // Application Identifier APP_IDENTIFIER = $(ORG_IDENTIFIER).userscripts // Safari Extension Identifier EXT_IDENTIFIER = $(APP_IDENTIFIER).ext.safari // App Groups and Group Containers IOS_SHARED_GID = group.$(ORG_IDENTIFIER).userscripts -MAC_SHARED_GID = 00000.$(ORG_IDENTIFIER).userscripts +MAC_SHARED_GID = $(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).userscripts // App URL Scheme APP_URL_SCHEME = userscriptsdebug // Override this file -#include? "Userscripts-Debug.dev.xcconfig" \ No newline at end of file +#include? "Userscripts-Debug.dev.xcconfig" diff --git a/xcode/xcconfig/Userscripts-Release.xcconfig b/xcode/xcconfig/Userscripts-Release.xcconfig index 9a90bb36..8c52d66b 100644 --- a/xcode/xcconfig/Userscripts-Release.xcconfig +++ b/xcode/xcconfig/Userscripts-Release.xcconfig @@ -1,22 +1,24 @@ #include "Userscripts-Base.xcconfig" -INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2018–2023 Justin Wasack. All rights reserved. -IOS_APP_BUILD = 43 -IOS_APP_VERSION = 1.4.1 -MAC_APP_BUILD = 70 -MAC_APP_VERSION = 4.4.1 +INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2018–2025 Justin Wasack. All rights reserved. +CURRENT_PROJECT_VERSION = 1 +MARKETING_VERSION = 1.0.0 +// Distribution +CODE_SIGN_STYLE = Manual +CODE_SIGN_IDENTITY = Apple Distribution // Developer Team ID DEVELOPMENT_TEAM = J74Q8V8V8N // Organization Identifier ORG_IDENTIFIER = com.userscripts // Application Identifier -APP_IDENTIFIER = com.userscripts.macos +APP_IDENTIFIER = $(ORG_IDENTIFIER).macos // Safari Extension Identifier EXT_IDENTIFIER = $(APP_IDENTIFIER).Userscripts-Extension // App Groups and Group Containers -IOS_SHARED_GID = group.com.userscripts.ios -MAC_SHARED_GID = $(DEVELOPMENT_TEAM).com.userscripts.macos +// https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups +IOS_SHARED_GID = group.$(ORG_IDENTIFIER).ios +MAC_SHARED_GID = $(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).macos // App URL Scheme APP_URL_SCHEME = userscriptsurlscheme diff --git a/xcode/xcconfig/Userscripts-Vite.xcconfig b/xcode/xcconfig/Userscripts-Vite.xcconfig new file mode 100644 index 00000000..6224a75b --- /dev/null +++ b/xcode/xcconfig/Userscripts-Vite.xcconfig @@ -0,0 +1,24 @@ +#include "Userscripts-Base.xcconfig" + +INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © Userscripts +CURRENT_PROJECT_VERSION = 2 +MARKETING_VERSION = 2.0.0 + +// Development +CODE_SIGN_IDENTITY = Apple Development +// Developer Team ID +DEVELOPMENT_TEAM = XXXXXXXXXX +// Organization Identifier +ORG_IDENTIFIER = dev.vite +// Application Identifier +APP_IDENTIFIER = $(ORG_IDENTIFIER).userscripts +// Safari Extension Identifier +EXT_IDENTIFIER = $(APP_IDENTIFIER).ext.safari +// App Groups and Group Containers +IOS_SHARED_GID = group.$(ORG_IDENTIFIER).userscripts +MAC_SHARED_GID = $(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).userscripts +// App URL Scheme +APP_URL_SCHEME = userscripts-vite + +// Override this file +#include? "Userscripts-Vite.dev.xcconfig" diff --git a/xcode/xcconfig/iOS-App.xcconfig b/xcode/xcconfig/iOS-App.xcconfig index f52d56d6..5dbd1119 100644 --- a/xcode/xcconfig/iOS-App.xcconfig +++ b/xcode/xcconfig/iOS-App.xcconfig @@ -1,12 +1,13 @@ +SRC_DIR = App-iOS ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor -CODE_SIGN_ENTITLEMENTS = iOS-App/App.entitlements -CURRENT_PROJECT_VERSION = $(IOS_APP_BUILD) +CODE_SIGN_ENTITLEMENTS = $(SRC_DIR)/App.entitlements GENERATE_INFOPLIST_FILE = YES -INFOPLIST_FILE = iOS-App/Info.plist +INFOPLIST_FILE = $(SRC_DIR)/Info.plist INFOPLIST_KEY_CFBundleDisplayName = $(PROJECT_NAME) INFOPLIST_KEY_CFBundleDisplayName[config=Debug] = $(PROJECT_NAME)-D +INFOPLIST_KEY_CFBundleDisplayName[config=Vite] = $(PROJECT_NAME)-V INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen INFOPLIST_KEY_UIMainStoryboardFile = Main @@ -14,7 +15,6 @@ INFOPLIST_KEY_UIStatusBarHidden = NO INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks -MARKETING_VERSION = $(IOS_APP_VERSION) OTHER_LDFLAGS = -framework SafariServices -framework WebKit PRODUCT_BUNDLE_IDENTIFIER = $(APP_IDENTIFIER) PRODUCT_NAME = $(PROJECT_NAME)-$(TARGET_NAME) @@ -25,6 +25,10 @@ SUPPORTS_MACCATALYST = NO SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO TARGETED_DEVICE_FAMILY = 1,2 VALIDATE_PRODUCT[config=Release] = YES +SWIFT_ACTIVE_COMPILATION_CONDITIONS = APP $(inherited) + +// Provisioning Profile +PROVISIONING_PROFILE_SPECIFIER[config=Release] = match AppStore $(APP_IDENTIFIER) // Override this file -#include? "iOS-App.dev.xcconfig" \ No newline at end of file +#include? "iOS-App.dev.xcconfig" diff --git a/xcode/xcconfig/iOS-Safari-Extension.xcconfig b/xcode/xcconfig/iOS-Safari-Extension.xcconfig index 0bdd357f..0d65c2ca 100644 --- a/xcode/xcconfig/iOS-Safari-Extension.xcconfig +++ b/xcode/xcconfig/iOS-Safari-Extension.xcconfig @@ -1,11 +1,12 @@ -CODE_SIGN_ENTITLEMENTS = Safari-Extension/iOS.entitlements -CURRENT_PROJECT_VERSION = $(IOS_APP_BUILD) +// Shared config file +#include? "Safari-Extension.xcconfig" + +CODE_SIGN_ENTITLEMENTS = $(SRC_DIR)/iOS.entitlements GENERATE_INFOPLIST_FILE = YES -INFOPLIST_FILE = Safari-Extension/Info.plist +INFOPLIST_FILE = $(SRC_DIR)/Info.plist INFOPLIST_KEY_CFBundleDisplayName = $(PRODUCT_NAME) INFOPLIST_KEY_CFBundleDisplayName[config=Debug] = $(PROJECT_NAME)-D LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks -MARKETING_VERSION = $(IOS_APP_VERSION) OTHER_LDFLAGS = -framework SafariServices PRODUCT_BUNDLE_IDENTIFIER = $(EXT_IDENTIFIER) PRODUCT_NAME = $(PROJECT_NAME)-$(TARGET_NAME) @@ -14,6 +15,10 @@ SKIP_INSTALL = YES SWIFT_EMIT_LOC_STRINGS = YES TARGETED_DEVICE_FAMILY = 1,2 VALIDATE_PRODUCT[config=Release] = YES +SWIFT_ACTIVE_COMPILATION_CONDITIONS = EXT $(inherited) + +// Provisioning Profile +PROVISIONING_PROFILE_SPECIFIER[config=Release] = match AppStore $(EXT_IDENTIFIER) // Override this file -#include? "iOS-Safari-Extension.dev.xcconfig" \ No newline at end of file +#include? "iOS-Safari-Extension.dev.xcconfig"