commit 5ae824b63ac73636d3bdaaac24a5950f99c29489 Author: Niki Wix Skaarup Date: Sat Mar 29 05:01:26 2025 +0100 init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..afc63ba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d7ad77c --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DEVELOPMENT=true diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3897265 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..c67f88e --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,16 @@ +/** @type { import("eslint").Linter.Config } */ +module.exports = { + root: true, + extends: ["eslint:recommended", "greasemonkey", "prettier"], + parserOptions: { + sourceType: "module", + ecmaVersion: 2020, + extraFileExtensions: [".svelte"], + }, + env: { + browser: true, + es2020: true, + node: true, + greasemonkey: true, + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..342e92b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "greasemonkey": true + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1c7090 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +build diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..82eeeed --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +engine-strict=true +resolution-mode=highest +@jsr:registry=https://npm.jsr.io diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5aa7ac4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["manganato", "truyen"] +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..76aee88 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:latest AS base +WORKDIR /app + +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lock /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lock /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +# [optional] tests & build +ENV NODE_ENV=production +# RUN bun test +# RUN bun run build +RUN bun run build:userscripts + +# copy production dependencies and source code into final image +FROM base AS release +WORKDIR /app + +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /app/ . + +# run the app + +ENTRYPOINT [ "bun", "run", "start" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..63695c0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Userscripts + +Userscripts along with a userscripts server diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..30f7ef4 --- /dev/null +++ b/bun.lock @@ -0,0 +1,478 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "userscripts", + "dependencies": { + "@tailwindcss/cli": "4.0.14", + "bun-plugin-svelte": "^0.0.6", + "bun-plugin-tailwind": "^0.0.15", + "tailwindcss": "^4.0.17", + }, + "devDependencies": { + "@types/bun": "^latest", + "eslint": "^9.23.0", + "eslint-config-greasemonkey": "^1.0.1", + "eslint-config-prettier": "^10.1.1", + "prettier": "^4.0.0-alpha.12", + "prettier-plugin-tailwindcss": "^0.6.11", + }, + "peerDependencies": { + "typescript": "^5.8.2", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.5.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.2.0", "", {}, "sha512-yJLLmLexii32mGrhW29qvU3QBVTu0GUmEf/J4XsBtVhp4JkIUFN/BjWqTF63yRvGApIDpZm5fa97LtYtINmfeQ=="], + + "@eslint/core": ["@eslint/core@0.12.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "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" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.23.0", "", {}, "sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.7", "", { "dependencies": { "@eslint/core": "^0.12.0", "levn": "^0.4.1" } }, "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.2", "", {}, "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], + + "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], + + "@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw=="], + + "@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg=="], + + "@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ=="], + + "@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA=="], + + "@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q=="], + + "@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w=="], + + "@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg=="], + + "@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A=="], + + "@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg=="], + + "@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw=="], + + "@parcel/watcher-win32-ia32": ["@parcel/watcher-win32-ia32@2.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ=="], + + "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], + + "@prettier/cli": ["@prettier/cli@0.7.1", "", { "dependencies": { "atomically": "^2.0.3", "fast-ignore": "^1.1.3", "find-up-json": "^2.0.4", "function-once": "^3.0.0", "import-meta-resolve": "^4.1.0", "is-binary-path": "^2.1.0", "js-yaml": "^4.1.0", "json-sorted-stringify": "^1.0.0", "json5": "^2.2.3", "kasi": "^1.1.0", "lomemo": "^1.0.0", "pioppo": "^1.2.0", "promise-resolve-timeout": "^2.0.0", "smol-toml": "^1.3.1", "specialist": "^1.4.5", "tiny-editorconfig": "^1.0.0", "tiny-jsonc": "^1.0.1", "tiny-readdir": "^2.7.4", "tiny-readdir-glob": "^1.23.1", "tiny-spinner": "^2.0.4", "worktank": "^2.7.3", "zeptomatch": "^2.0.0", "zeptomatch-escape": "^1.0.0", "zeptomatch-is-static": "^1.0.0" }, "peerDependencies": { "prettier": "^3.1.0 || ^4.0.0-alpha" }, "bin": { "prettier-next": "dist/bin.js" } }, "sha512-YoXPLOLmEEHP4MKgzcEilzaUtlo80Qm5Pb+59QbgDeOsIExGBkRqZmC2+iwzSwEhlhTEpGqDwqb3+nP/dPay9A=="], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.5", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="], + + "@tailwindcss/cli": ["@tailwindcss/cli@4.0.14", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.0.14", "@tailwindcss/oxide": "4.0.14", "enhanced-resolve": "^5.18.1", "lightningcss": "1.29.2", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.0.14" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-W/pwxYwC+GZwrB4m4ASHFfvi1eND7yIoqZ6uqkQihWP/OYfuH3v00YjEhiTQVSiRWic/MlfnKRNseO5JEEzwew=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.0.14", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.14" } }, "sha512-Ux9NbFkKWYE4rfUFz6M5JFLs/GEYP6ysxT8uSyPn6aTbh2K3xDE1zz++eVK4Vwx799fzMF8CID9sdHn4j/Ab8w=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.14", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.14", "@tailwindcss/oxide-darwin-arm64": "4.0.14", "@tailwindcss/oxide-darwin-x64": "4.0.14", "@tailwindcss/oxide-freebsd-x64": "4.0.14", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.14", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.14", "@tailwindcss/oxide-linux-arm64-musl": "4.0.14", "@tailwindcss/oxide-linux-x64-gnu": "4.0.14", "@tailwindcss/oxide-linux-x64-musl": "4.0.14", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.14", "@tailwindcss/oxide-win32-x64-msvc": "4.0.14" } }, "sha512-M8VCNyO/NBi5vJ2cRcI9u8w7Si+i76a7o1vveoGtbbjpEYJZYiyc7f2VGps/DqawO56l3tImIbq2OT/533jcrA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.14", "", { "os": "android", "cpu": "arm64" }, "sha512-VBFKC2rFyfJ5J8lRwjy6ub3rgpY186kAcYgiUr8ArR8BAZzMruyeKJ6mlsD22Zp5ZLcPW/FXMasJiJBx0WsdQg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U3XOwLrefGr2YQZ9DXasDSNWGPZBCh8F62+AExBEDMLDfvLLgI/HDzY8Oq8p/JtqkAY38sWPOaNnRwEGKU5Zmg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-V5AjFuc3ndWGnOi1d379UsODb0TzAS2DYIP/lwEbfvafUaD2aNZIcbwJtYu2DQqO2+s/XBvDVA+w4yUyaewRwg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.14", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tXvtxbaZfcPfqBwW3f53lTcyH6EDT+1eT7yabwcfcxTs+8yTPqxsDUhrqe9MrnEzpNkd+R/QAjJapfd4tjWdLg=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.14", "", { "os": "linux", "cpu": "arm" }, "sha512-cSeLNWWqIWeSTmBntQvyY2/2gcLX8rkPFfDDTQVF8qbRcRMVPLxBvFVJyfSAYRNch6ZyVH2GI6dtgALOBDpdNA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-bwDWLBalXFMDItcSXzFk6y7QKvj6oFlaY9vM+agTlwFL1n1OhDHYLZkSjaYsh6KCeG0VB0r7H8PUJVOM1LRZyg=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-gVkJdnR/L6iIcGYXx64HGJRmlme2FGr/aZH0W6u4A3RgPMAb+6ELRLi+UBiH83RXBm9vwCfkIC/q8T51h8vUJQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.14", "", { "os": "linux", "cpu": "x64" }, "sha512-EE+EQ+c6tTpzsg+LGO1uuusjXxYx0Q00JE5ubcIGfsogSKth8n8i2BcS2wYTQe4jXGs+BQs35l78BIPzgwLddw=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.14", "", { "os": "linux", "cpu": "x64" }, "sha512-KCCOzo+L6XPT0oUp2Jwh233ETRQ/F6cwUnMnR0FvMUCbkDAzHbcyOgpfuAtRa5HD0WbTbH4pVD+S0pn1EhNfbw=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-AHObFiFL9lNYcm3tZSPqa/cHGpM5wOrNmM2uOMoKppp+0Hom5uuyRh0QkOp7jftsHZdrZUpmoz0Mp6vhh2XtUg=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.14", "", { "os": "win32", "cpu": "x64" }, "sha512-rNXXMDJfCJLw/ZaFTOLOHoGULxyXfh2iXTGiChFiYTSgKBKQHIGEpV0yn5N25WGzJJ+VBnRjHzlmDqRV+d//oQ=="], + + "@types/bun": ["@types/bun@1.2.8", "", { "dependencies": { "bun-types": "1.2.7" } }, "sha512-t8L1RvJVUghW5V+M/fL3Thbxcs0HwNsXsnTEBEfEVqGteiJToOlZ/fyOEaR1kZsNqnu+3XA4RI/qmnX4w6+S+w=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@22.13.14", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w=="], + + "@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-purge": ["ansi-purge@1.0.1", "", {}, "sha512-5NNMT7rljQ24DKHnIYG1qFXs8eUv5mZcT6kOPf5NopQUzpURBh/T4tbQw3TX//q3Zpw3JwVvsVHHsRKJesQHZQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ansi-truncate": ["ansi-truncate@1.2.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-/SLVrxNIP8o8iRHjdK3K9s2hDqdvb86NEjZOAB6ecWFsOo+9obaby97prnvAPn6j7ExXCpbvtlJFYPkkspg4BQ=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "atomically": ["atomically@2.0.3", "", { "dependencies": { "stubborn-fs": "^1.2.5", "when-exit": "^2.1.1" } }, "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-plugin-svelte": ["bun-plugin-svelte@0.0.6", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-HuEDvOieVwXvhpcHLcASeQIOVgje2GRO3Tu0ypJh3MkjGLkEBOQ1+6FWsMgb54FxfbPw9JP3q0WlBd8SjgrnGQ=="], + + "bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="], + + "bun-types": ["bun-types@1.2.7", "", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }, "sha512-P4hHhk7kjF99acXqKvltyuMQ2kf/rzIw3ylEDpCxDS9Xa0X0Yp/gJu/vDCucmWpiur5qJ0lwB2bWzOXa2GlHqA=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + + "dettle": ["dettle@1.0.5", "", {}, "sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@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" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw=="], + + "eslint-config-greasemonkey": ["eslint-config-greasemonkey@1.0.1", "", {}, "sha512-WBkAOdKNzofYQZtQU9K/ML5VxloKkmKo26+rv8hfTF2OkIzFriq87eBlVDkVdcepVEWbvDlEo6Tn+gQe+eKjGw=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.1", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw=="], + + "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrap": ["esrap@1.4.5", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-ignore": ["fast-ignore@1.1.3", "", { "dependencies": { "grammex": "^3.1.2", "string-escape-regex": "^1.0.0" } }, "sha512-xTo4UbrOKfEQgOFlPaqFScodTV/Wf3KATEqCZZSMh6OP4bcez0lTsqww3n3/Fve1q9u0jmfDP0q0nOhH4POZEg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="], + + "fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "find-up-json": ["find-up-json@2.0.5", "", { "dependencies": { "find-up-path": "^1.0.1" } }, "sha512-1zZZUfD1GOOEEd1AqwbRmCkCCv1O9t0vOpCYgmzfJqKty8WKaKlDyxWej8Aew+vI5lvDiTviaQuaVuu6GzlHzQ=="], + + "find-up-path": ["find-up-path@1.0.1", "", {}, "sha512-cl4Sfxufq9WK848L887b4r+NVZoBjMeB4QydPZ+pXbp6Jt2nUVspTo2svNOm48stIIeSxtuCsULa9+e+LMTzwA=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "function-once": ["function-once@3.0.1", "", {}, "sha512-bE3E8REk4jANDot3l0sLFkXgywBwzFKsmbwdnVHLJUnt/3kV6dNG0oJJqoRBuS1Z9Lr4ZoQgwV0ZNLDgWDbv7Q=="], + + "get-current-package": ["get-current-package@1.0.1", "", { "dependencies": { "find-up-json": "^2.0.5" } }, "sha512-c/Rw5ByDQ+zg+Lh/emBWv0bDpugEFdmXPR6/srIemVtIvol0XbT0JAr8Db0cX+Jj/xY9wj1wdjeq2qNB35Tayg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "grammex": ["grammex@3.1.10", "", {}, "sha512-UCfMsV/sfqk4TN1+m5ehSOXuADyLUgSuwMI2vCVlbN/REoSmTl4eagswC9DzzVxtsKv7Yp2CmIJNn4fMk8PaQA=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-meta-resolve": ["import-meta-resolve@4.1.0", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "ini-simple-parser": ["ini-simple-parser@1.0.1", "", {}, "sha512-myU5nhF2miBQP3tO/giUi+8BI9QhfM/XRZd0RD7G0p+40K6KPAwxMDtH3UEtJ2XJZbd+ZiQOoGh432DTYfzNVQ=="], + + "ionstore": ["ionstore@1.0.1", "", {}, "sha512-g+99vyka3EiNFJCnbq3NxegjV211RzGtkDUMbZGB01Con8ZqUmMx/FpWMeqgDXOqgM7QoVeDhe+CfYCWznaDVA=="], + + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-sorted-stringify": ["json-sorted-stringify@1.0.1", "", {}, "sha512-pWv9hqWho37EpwpBgqDYVPKPCgT/ytuvqtlBvb6M44BrnvooTk/5D/aSeohsGDLp+g8waP5dUUGODR+Ley+Idg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "kasi": ["kasi@1.1.1", "", {}, "sha512-pzBwGWFIjf84T/8aD0XzMli1T3Ckr/jVLh6v0Jskwiv5ehmcgDM+vpYFSk8WzGn4ed4HqgaifTgQUHzzZHa+Qw=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lomemo": ["lomemo@1.0.1", "", {}, "sha512-g8CnVp7UYypeQKpXpMzyrJoDzhOoqVQYSJApoq/cFI3vGxXoHQ+6lH5cApW9XwzVy5SL9/Owil7/JxbKckw0Lg=="], + + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "optionator": ["optionator@0.9.4", "", { "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" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pioppo": ["pioppo@1.2.1", "", { "dependencies": { "dettle": "^1.0.5", "when-exit": "^2.1.4" } }, "sha512-1oErGVWD6wFDPmrJWEY1Cj2p829UGT6Fw9OItYFxLkWtBjCvQSMC8wA5IcAR5ms/6gqiY8pnJvIV/+/Imyobew=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@4.0.0-alpha.12", "", { "dependencies": { "@prettier/cli": "^0.7.1" }, "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-wQ8RK48Io6nRr39OQFXZu+EALwTygXnstPgN9UplY+mqkg6P52ceGifo5gylIwX1X9lOuXxreUFrLxXsCbA+sg=="], + + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="], + + "promise-make-counter": ["promise-make-counter@1.0.2", "", { "dependencies": { "promise-make-naked": "^3.0.2" } }, "sha512-FJAxTBWQuQoAs4ZOYuKX1FHXxEgKLEzBxUvwr4RoOglkTpOjWuM+RXsK3M9q5lMa8kjqctUrhwYeZFT4ygsnag=="], + + "promise-make-naked": ["promise-make-naked@2.1.2", "", {}, "sha512-y7s8ZuHIG56JYspB24be9GFkXA1zXL85Ur9u1DKrW/tvyUoPxWgBjnalK6Nc6l7wHBcAW0c3PO07+XOsWTRuhg=="], + + "promise-resolve-timeout": ["promise-resolve-timeout@2.0.1", "", {}, "sha512-90Qzzu5SmR+ksmTPsc79121NZGtEiPvKACQLCl6yofknRx5xJI9kNj3oDVSX6dVTneF8Ju6+xpVFdDSzb7cNcg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "smol-toml": ["smol-toml@1.3.1", "", {}, "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ=="], + + "specialist": ["specialist@1.4.5", "", { "dependencies": { "tiny-bin": "^1.10.3", "tiny-colors": "^2.2.2", "tiny-parse-argv": "^2.8.1", "tiny-updater": "^3.5.3" } }, "sha512-4mPQEREzBUW2hzlXX/dWFbQdUWzpkqvMFVpUAdRlo1lUlhKMObDHiAo09oZ94x4cS3uWMJebPOTn+GaQYLfv3Q=="], + + "stdin-blocker": ["stdin-blocker@2.0.1", "", {}, "sha512-NEcAEpag+gE/Iivx1prq1AFPwnmgmcyHNvGZLUqGBoOE/7DZtmhtP9iYqJt8ymueFL+kknhfEebAMWbrWp3FJw=="], + + "string-escape-regex": ["string-escape-regex@1.0.1", "", {}, "sha512-cdSXOHSJ32K/T2dbj9t7rJwonujaOkaINpa1zsXT+PNFIv1zuPjtr0tXanCvUhN2bIu2IB0z/C7ksl+Qsy44nA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "stubborn-fs": ["stubborn-fs@1.2.5", "", {}, "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "svelte": ["svelte@5.25.3", "", { "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.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg=="], + + "tailwindcss": ["tailwindcss@4.0.17", "", {}, "sha512-OErSiGzRa6rLiOvaipsDZvLMSpsBZ4ysB4f0VKGXUrjw2jfkJRd6kjRKV2+ZmTCNvwtvgdDam5D7w6WXsdLJZw=="], + + "tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="], + + "tiny-bin": ["tiny-bin@1.11.1", "", { "dependencies": { "ansi-purge": "^1.0.1", "fast-string-width": "^1.1.0", "get-current-package": "^1.0.1", "tiny-colors": "^2.2.2", "tiny-levenshtein": "^1.0.1", "tiny-parse-argv": "^2.8.2", "tiny-updater": "^3.5.3" } }, "sha512-UFC5EwtmCkFshKOBgXZzNFJsHpZrtbWZ/jQj+pwoIGUUbmenlQGGVDOwVqVOuG1nTxICSd+GLp3b+j7dUKZr2Q=="], + + "tiny-colors": ["tiny-colors@2.2.2", "", {}, "sha512-Elmv7JL+dX0c78caKEelH1nHHBskHzJkaqBRgVvQuxsvVA/Z9Fa2R3ZZtfmkkajcd18e96RLMwJvtFqC8jsZWA=="], + + "tiny-cursor": ["tiny-cursor@2.0.1", "", { "dependencies": { "when-exit": "^2.1.4" } }, "sha512-28ytGEfb7m/8Gdflv+wSo5qRM01fROo2CjJVYon6yYbzPsc3ap3Ps5CZXuS19pIROwswSvZMGbEQ7kWnokdUGA=="], + + "tiny-editorconfig": ["tiny-editorconfig@1.0.0", "", { "dependencies": { "ini-simple-parser": "^1.0.0", "zeptomatch": "^1.1.3" } }, "sha512-rxpWaSurnvPUkL2/qydRH10llK7MD1XfE38zoWTsp/ZWWYnnwPBzGUePBOcXFaNA3cJQm8ItqrofGeRJ6AVaew=="], + + "tiny-jsonc": ["tiny-jsonc@1.0.2", "", {}, "sha512-f5QDAfLq6zIVSyCZQZhhyl0QS6MvAyTxgz4X4x3+EoCktNWEYJ6PeoEA97fyb98njpBNNi88ybpD7m+BDFXaCw=="], + + "tiny-levenshtein": ["tiny-levenshtein@1.0.1", "", {}, "sha512-Q4rRa0pxGIbYoXQDejEDnonHt+QUTFrejaAxdv7h352/PWQBJ2eKsbzw1khvbIXKrpG1n2ZABX0A34oBGZXB2w=="], + + "tiny-parse-argv": ["tiny-parse-argv@2.8.2", "", {}, "sha512-RnIDHQ+r9zMuslQWVoRxfKVOumteeheQqbwNYJyQxzM2vzx/vdN5xAeL64F3rQOpfbVdxFkhM4zPDyfq7SxsBQ=="], + + "tiny-readdir": ["tiny-readdir@2.7.4", "", { "dependencies": { "promise-make-counter": "^1.0.2" } }, "sha512-721U+zsYwDirjr8IM6jqpesD/McpZooeFi3Zc6mcjy1pse2C+v19eHPFRqz4chGXZFw7C3KITDjAtHETc2wj7Q=="], + + "tiny-readdir-glob": ["tiny-readdir-glob@1.23.1", "", { "dependencies": { "tiny-readdir": "^2.7.0", "zeptomatch": "^1.2.2", "zeptomatch-explode": "^1.0.0", "zeptomatch-is-static": "^1.0.0", "zeptomatch-unescape": "^1.0.0" } }, "sha512-UJB7alIB+lSmIfVGKLxNyErlo5mto1luldvXW+s+NSO5gyleU/Ut9P4QKghPEenCHqVZhW0j6OggDkmattVhKw=="], + + "tiny-spinner": ["tiny-spinner@2.0.5", "", { "dependencies": { "stdin-blocker": "^2.0.1", "tiny-colors": "^2.2.2", "tiny-cursor": "^2.0.1", "tiny-truncate": "^1.0.3" } }, "sha512-OIGogtfEbA2IQdCBgF0zI3EjpFyiUEd6Uj5j0q5jhIPPq8pgNR83D0t9WIckbD2FzPann8lH/uLf1vX0YIu04w=="], + + "tiny-truncate": ["tiny-truncate@1.0.3", "", { "dependencies": { "ansi-truncate": "^1.2.0" } }, "sha512-ZdCMtUg6N5VgYAInid90lnA4R720w5iU7raqPspAoYxOSMyzp132b8DeKZGrO2yC3tvoJMUDaymY3XFN3Zr5sQ=="], + + "tiny-updater": ["tiny-updater@3.5.3", "", { "dependencies": { "ionstore": "^1.0.1", "tiny-colors": "^2.2.2", "when-exit": "^2.1.4" } }, "sha512-wEUssfOOkVLg2raSaRbyZDHpVCDj6fnp7UjynpNE4XGuF+Gkj8GRRMoHdfk73VzLQs/AHKsbY8fCxXNz8Hx4Qg=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "webworker-shim": ["webworker-shim@1.1.1", "", {}, "sha512-XCWuBjJH3Xn/7SbyUF1WrrCbe6ZEsgaD7kxlFhxIwdkljGYX3BqP/dhG6ge0NBT+V7ZPjR4/BXq5BvbdaxrpKg=="], + + "when-exit": ["when-exit@2.1.4", "", {}, "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "worktank": ["worktank@2.7.3", "", { "dependencies": { "promise-make-naked": "^2.0.0", "webworker-shim": "^1.1.0" } }, "sha512-M0fesnpttBPdvNYBdzRvLDsacN0na9RYWFxwmM/x1+/6mufjduv9/9vBObK8EXDqxRMX/SOYJabpo0UCYYBUdQ=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zeptomatch": ["zeptomatch@2.0.1", "", { "dependencies": { "grammex": "^3.1.10" } }, "sha512-nbnIYF2n3o3EqV36HkIhEMLIDFbG3M6RUjhkdKIn6qqIJkdkL7bgVSfTTCEXBJpk1T45tLfEYfStndJc2lUEnA=="], + + "zeptomatch-escape": ["zeptomatch-escape@1.0.1", "", {}, "sha512-kAc5HzvnF66djCYDqpsS46Y/FKi+4pe/KJRmTmm/hwmoaNYzmm6bBY07cdkxmJCdY018S5UeQn4yP+9X2x1MbQ=="], + + "zeptomatch-explode": ["zeptomatch-explode@1.0.1", "", {}, "sha512-7cUQASLLRGZ20+zEQcEgQ9z/gH1+jSfrNg4KfRJSxF1QU2fpymAwWvnAxl69GD5pr3IV0V9vo3ke2np//Nh4tQ=="], + + "zeptomatch-is-static": ["zeptomatch-is-static@1.0.1", "", {}, "sha512-bN9q7H/UdXhkub01WE7b7Grg07jLldNnIWG2T1IpBq5NtvcQ4DwFbNiGGapnbKHUdWiCNjg/bIvixV88nj9gog=="], + + "zeptomatch-unescape": ["zeptomatch-unescape@1.0.1", "", {}, "sha512-xhSFkKV0aQ03e/eiN4VhOTwJhcqfH7SMiGHrWKw9gXi+0EVJAxJ8Gt4ehozYsYLhUXL1JFbP1g3EE6ZmkStB0g=="], + + "zimmerframe": ["zimmerframe@1.1.2", "", {}, "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@tailwindcss/cli/tailwindcss": ["tailwindcss@4.0.14", "", {}, "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw=="], + + "@tailwindcss/node/tailwindcss": ["tailwindcss@4.0.14", "", {}, "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw=="], + + "lightningcss/detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="], + + "promise-make-counter/promise-make-naked": ["promise-make-naked@3.0.2", "", {}, "sha512-B+b+kQ1YrYS7zO7P7bQcoqqMUizP06BOyNSBEnB5VJKDSWo8fsVuDkfSmwdjF0JsRtaNh83so5MMFJ95soH5jg=="], + + "tiny-editorconfig/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="], + + "tiny-readdir-glob/zeptomatch": ["zeptomatch@1.2.2", "", { "dependencies": { "grammex": "^3.1.1" } }, "sha512-0ETdzEO0hdYmT8aXHHf5aMjpX+FHFE61sG4qKFAoJD2Umt3TWdCmH7ADxn2oUiWTlqBGC+SGr8sYMfr+37J8pQ=="], + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..331faab --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,5 @@ +[install.scopes] +"@jsr" = "https://npm.jsr.io" + +[serve.static] +plugins = ["bun-plugin-tailwind"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..03a7342 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "userscripts", + "module": "src/server.ts", + "type": "module", + "private": true, + "scripts": { + "start": "bun run src/server.ts", + "dev": "bun --watch run src/server.ts", + "dev:userscripts": "bun --watch run src/build.ts", + "hot": "bun --hot run src/server.ts", + "build": "bun build src/server.ts --compile --minify --sourcemap --target=bun-linux-x64-modern --outfile server", + "build:userscripts": "bun run src/build.ts" + }, + "devDependencies": { + "@types/bun": "^latest", + "eslint": "^9.23.0", + "eslint-config-greasemonkey": "^1.0.1", + "eslint-config-prettier": "^10.1.1", + "prettier": "^4.0.0-alpha.12", + "prettier-plugin-tailwindcss": "^0.6.11" + }, + "dependencies": { + "@tailwindcss/cli": "4.0.14", + "bun-plugin-svelte": "^0.0.6", + "bun-plugin-tailwind": "^0.0.15", + "tailwindcss": "^4.0.17" + }, + "peerDependencies": { + "typescript": "^5.8.2" + } +} diff --git a/src/build.ts b/src/build.ts new file mode 100644 index 0000000..a8c269c --- /dev/null +++ b/src/build.ts @@ -0,0 +1,128 @@ +import { env } from "bun"; +import { SveltePlugin } from "bun-plugin-svelte"; +import Tail from "bun-plugin-tailwind"; +import path from "path"; + +const development = env.NODE_ENV !== "production"; + +const publicPath = development + ? "http://localhost:3000/html/" + : "https://userscripts.skaarup.dev/html/"; + +function replaceUrl(text: string): string { + if (!development) return text; + + return text.replaceAll( + "https://userscripts.skaarup.dev", + "http://localhost:3000", + ); +} + +const tagStart = `// ==UserScript==\n`; +const tagEnd = `// ==/UserScript==\n`; +const tagEndLen = tagEnd.length; + +const globScanOptions: Bun.GlobScanOptions = { + absolute: true, +}; + +const userscripts = new Bun.Glob("./src/userscripts/*/*.user.ts"); +for await (const absPath of userscripts.scan(globScanOptions)) { + const parsed = path.parse(absPath); + console.log(`Building ${parsed.base}...`); + + const file = Bun.file(absPath); + const fileText = await file.text(); + + const bannerStart = fileText.indexOf(tagStart); + const bannerEnd = fileText.indexOf(tagEnd) + tagEndLen; + + let banner = fileText.slice(bannerStart, bannerEnd); + + banner = replaceUrl(banner); + + await Bun.build({ + entrypoints: [absPath], + banner, + outdir: `./build`, + splitting: false, + minify: false, + env: "PUBLIC_*", + sourcemap: "linked", + packages: "bundle", + target: "browser", // Also supports "bun" or "node" for server-side components + plugins: [ + Tail, + SveltePlugin({ + development: false, // Enable dev features, set to false for production + }), + ], + publicPath, + }); + + await swapHtmlImports(fileText, `./build/${parsed.name}.js`); +} + +const styles = new Bun.Glob("./src/userscripts/*/*.user.css"); +for await (const absPath of styles.scan(globScanOptions)) { + const name = path.parse(absPath).base; + + console.log(`Building ${name}...`); + + const outFile = `./build/${name}`; + + await Bun.$`bunx @tailwindcss/cli --optimize -i ${absPath} -o ${outFile}`.quiet(); + + const file = Bun.file(outFile); + + let text = await file.text(); + + text = replaceUrl(text); + + file.writer().write(text); +} + +async function swapHtmlImports(fileText: string, filePath: string) { + const imports: string[] = []; + + const lines = fileText.split("\n"); + for (const line of lines) { + if ( + !line.startsWith("import ") || + (!line.endsWith('.html";') && !line.endsWith(".html';")) + ) + continue; + + // Extract the import path between the quotes + const match = line.match(/['"]([^'"]+)['"]/); + if (!match) continue; + + const importPath = match[1]; + // Check if the import path is a .html file + if (!importPath) continue; + imports.push(path.parse(importPath).name); + } + + const outFile = Bun.file(filePath); + const text = await outFile.text(); + + const startIdx = text.indexOf(tagStart); + const endIdx = text.indexOf(tagEnd); + const banner = text.slice(startIdx, endIdx); + + let js = text.slice(endIdx); + + const htmlFiles = new Bun.Glob("./build/*.html"); + for await (const filePath of htmlFiles.scan(globScanOptions)) { + const parsed = path.parse(filePath); + const fileName = parsed.base; + if (!imports.some((x) => fileName.startsWith(x))) continue; + + const file = Bun.file(filePath); + const html = await file.text(); + + js = js.replace(`${publicPath}${fileName}`, btoa(html)); + } + + outFile.writer().write(`${banner}${js}`); +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..836c314 --- /dev/null +++ b/src/index.css @@ -0,0 +1,28 @@ +@import 'tailwindcss'; + +@theme { + --breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2); + --ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr); + --breakout-val: minmax(0, var(--breakout-size)); + --content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg)); +} + +@layer base { + * { + min-width: 0; + } + + .content { + grid-template-columns: + [ultrawide-start] var(--ultrawide-val) + [breakout-start] var(--breakout-val) + [content-start] var(--content-val) + [content-end] var(--breakout-val) + [breakout-end] var(--ultrawide-val) + [ultrawide-end]; + } + + .content > * { + grid-column: content; + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..3a94aa7 --- /dev/null +++ b/src/index.html @@ -0,0 +1,86 @@ + + + + Niki Wix Skaarup + + + + + + + + +
+ + Skip to main content + + +
+ +
+ +
+ + diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..a624571 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,113 @@ +import { env } from "bun"; +import homepage from "./index.html"; +import robotsTxt from "./static/robots.txt"; +import sitemapTxt from "./static/sitemap.txt"; +// @ts-ignore ts2307 +import icon from "./static/favicon.png" with { type: "file" }; +import path from "path"; + +const favicon = await Bun.file(icon).bytes(); + +let htmlFiles: Set; +let scriptFiles: Set; +let cssFiles: Set; + +const development = env.NODE_ENV !== "production"; + +const globScanOptions: Bun.GlobScanOptions = { + absolute: true, +}; + +async function load() { + await Bun.$`bun run src/build.ts`; + const tempHtml: Set = new Set(); + const userHtml = new Bun.Glob("./build/*.html"); + for await (const filePath of userHtml.scan(globScanOptions)) { + tempHtml.add(path.parse(filePath).base); + } + htmlFiles = tempHtml; + + const tempScripts: Set = new Set(); + const userJs = new Bun.Glob("./build/*.user.js"); + for await (const filePath of userJs.scan(globScanOptions)) { + tempScripts.add(path.parse(filePath).base); + } + scriptFiles = tempScripts; + + const tempStyles: Set = new Set(); + const css = new Bun.Glob("./build/*.user.css"); + for await (const filePath of css.scan(globScanOptions)) { + tempStyles.add(path.parse(filePath).base); + } + cssFiles = tempStyles; +} +await load(); + +Bun.serve({ + routes: { + "/": homepage, + "/robots.txt": new Response(robotsTxt, { + headers: { "Content-Type": "text/plain" }, + }), + "/sitemap.txt": new Response(sitemapTxt, { + headers: { "Content-Type": "text/plain" }, + }), + "/favicon.ico": new Response(favicon, { + headers: { "Content-Type": "image/png" }, + }), + "/health": new Response("OK"), + "/html/:html": async (req) => { + const html = req.params.html.toLocaleLowerCase(); + + if (!htmlFiles.has(html)) { + return new Response("Not found", { status: 404 }); + } + + const file = Bun.file(`./build/${html}`); + + return new Response(file, { + headers: { "Content-Type": "text/css" }, + }); + }, + "/scripts/:script": async (req) => { + const script = req.params.script.toLocaleLowerCase(); + + if (!scriptFiles.has(script)) { + return new Response("Not found", { status: 404 }); + } + + const file = Bun.file(`./build/${script}`); + + return new Response(file, { + headers: { "Content-Type": "application/javascript" }, + }); + }, + "/styles/:style": async (req) => { + const style = req.params.style.toLocaleLowerCase(); + + if (!cssFiles.has(style)) { + return new Response("Not found", { status: 404 }); + } + + const file = Bun.file(`./build/${style}`); + + return new Response(file, { + headers: { "Content-Type": "text/css" }, + }); + }, + }, + development, + // async fetch(req, server) { + // return new Response("Not found", { status: 404 }); + // }, +}); + +if (development) { + setInterval(async () => { + try { + await load(); + } catch (e) { + console.error(e); + } + }, 2500); +} diff --git a/src/static/favicon.base64.txt b/src/static/favicon.base64.txt new file mode 100644 index 0000000..e227f8a --- /dev/null +++ b/src/static/favicon.base64.txt @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/src/static/favicon.png b/src/static/favicon.png new file mode 100644 index 0000000..dfa8b45 Binary files /dev/null and b/src/static/favicon.png differ diff --git a/src/static/manifest.json b/src/static/manifest.json new file mode 100644 index 0000000..8d93880 --- /dev/null +++ b/src/static/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Userscripts by skaarup.dev", + "short_name": "Userscripts", + "display": "browser", + "background_color": "#141141", + "theme_color": "#371D85", + "description": "Userscripts", + "icons": [], + "related_applications": [] +} diff --git a/src/static/robots.txt b/src/static/robots.txt new file mode 100644 index 0000000..8a979a5 --- /dev/null +++ b/src/static/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://userscripts.skaarup.dev/sitemap.txt diff --git a/src/static/sitemap.txt b/src/static/sitemap.txt new file mode 100644 index 0000000..8d409e0 --- /dev/null +++ b/src/static/sitemap.txt @@ -0,0 +1 @@ +https://userscripts.skaarup.dev/ diff --git a/src/types/configuration-ui.d.ts b/src/types/configuration-ui.d.ts new file mode 100644 index 0000000..7847e52 --- /dev/null +++ b/src/types/configuration-ui.d.ts @@ -0,0 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +namespace configuration_ui { + type globalsType = { + uiInitialized: boolean; + configurationWindowOpen: boolean; + }; +} diff --git a/src/types/greasemonkey.d.ts b/src/types/greasemonkey.d.ts new file mode 100644 index 0000000..a86ca3c --- /dev/null +++ b/src/types/greasemonkey.d.ts @@ -0,0 +1,76 @@ +// Greasemonkey api +// https://wiki.greasespot.net/Greasemonkey_Manual:API + +type gm_responseObjectType = { + readyState: number; + responseHeaders: string; + responseText: string; + status: number; + statusText: string; + context?: unknown; + lengthComputable?: boolean; + loaded?: number; + total?: number; +}; + +type gm_detailType = { + binary?: boolean; + context?: unknown; + data?: string; + headers?: { [key: string]: string }; + method?: string; + overrideMimeType?: string; + password?: string; + responseType?: string; + synchronous?: boolean; + timeout?: number; + upload?: { + onabort?: (event: gm_responseObjectType) => void; + onerror?: (event: gm_responseObjectType) => void; + onload?: (event: gm_responseObjectType) => void; + onprogress?: (event: gm_responseObjectType) => void; + }; + url: string; + user?: string; + onabort?: (event: gm_responseObjectType) => void; + onerror?: (event: gm_responseObjectType) => void; + onload?: (event: gm_responseObjectType) => void; + onprogress?: (event: gm_responseObjectType) => void; + onreadystatechange?: (event: gm_responseObjectType) => void; + ontimeout?: (event: gm_responseObjectType) => void; +}; + +type gm_optionType = { + text: string; + title?: string; + image?: string; + onclick?: () => void; + ondone?: () => void; +}; + +type gm_resourceType = { + [key: string]: { + name: string; + mimetype: string; + url: string; + }; +}; + +type gm_scriptType = { + description: string; + excludes: string[]; + includes: string[]; + matches: string[]; + name: string; + namespace: string; + resources: gm_resourceType; + "run-at": string; + version: string; +}; + +type gm_infoType = { + script: gm_scriptType; + scriptMetaStr: string; + scriptHandler: string; + version: string; +}; diff --git a/src/types/manga-reading.d.ts b/src/types/manga-reading.d.ts new file mode 100644 index 0000000..cd56c92 --- /dev/null +++ b/src/types/manga-reading.d.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +namespace manga_reading { + type At = "neither" | "chapter" | "manga"; + + type globalsType = { + nextUrl: string; + prevUrl: string; + currentTitle: string; + uiInitialized: boolean; + at: At; + titleList: string[]; + ptApi: { + url: string; + bearerToken: string; + }; + }; +} + +type shortcutType = "Global" | "ConfigOpen" | "ConfigClosed"; + +type shortcutCallback = (event: KeyboardEvent) => Promise; + +type shortcut = { + name: string; + callback: shortcutCallback; +}; + +type registeredShortcut = { shortcutType: shortcutType; shortcut: shortcut }; diff --git a/src/types/violentmonkey.d.ts b/src/types/violentmonkey.d.ts new file mode 100644 index 0000000..d160929 --- /dev/null +++ b/src/types/violentmonkey.d.ts @@ -0,0 +1,291 @@ +type vm_responseObjectTypeUnknown = vm_responseObjectType; +type vm_responseObjectType = { + status: number; + statusText: string; + readyState: number; + responseHeaders: string; + response: string | Blob | ArrayBuffer | Document | object | null; + responseText: string | undefined; + responseXML: Document | null; + lengthComputable: boolean; + loaded: number; + total: number; + finalUrl: string; + context: T; +}; + +type vm_detailType = { + url: string; + method?: string; + user?: string; + password?: string; + overrideMimeType?: string; + headers?: { [key: string]: string }; + responseType?: string; + timeout?: number; + data?: + | string + | ArrayBuffer + | Blob + | DataView + | FormData + | ReadableStream + | TypedArray + | URLSearchParams; + binary?: boolean; + context?: T; + anonymous?: boolean; + onabort?: (resp: vm_responseObjectType) => void; + onerror?: (resp: vm_responseObjectType) => void; + onload?: (resp: vm_responseObjectType) => void; + onloadend?: (resp: vm_responseObjectType) => void; + onloadstart?: (resp: vm_responseObjectType) => void; + onprogress?: (resp: vm_responseObjectType) => void; + onreadystatechange?: (resp: vm_responseObjectType) => void; + ontimeout?: (resp: vm_responseObjectType) => void; +}; + +type vm_optionType = { + text: string; + title?: string; + image?: string; + onclick?: () => void; + ondone?: () => void; +}; + +type vm_resourceType = { + name: string; + url: string; +}; + +type vm_scriptType = { + description: string; + excludes: Array; + includes: Array; + matches: Array; + name: string; + namespace: string; + resources: Array; + "run-at": string; + version: string; +}; + +type vm_platformType = { + arch: string; + browserName: string; + browserVersion: string; + fullVersionList: Array<{ + brand: string; + version: string; + }>; + mobile: boolean; + os: string; +}; + +type vm_infoType = { + uuid: string; + scriptMetaStr: string; + scriptWillUpdate: boolean; + scriptHandler: string; + version: string; + isIncognito: boolean; + platform: vm_platformType; + userAgent: string; + userAgentData: { + brands: Array<{ brand: string; version: string }>; + mobile: boolean; + platform: string; + getHighEntropyValues: ( + hints: string[], + ) => Promise<{ [key: string]: unknown }>; + }; + script: vm_scriptType; + injectInto: string; +}; + +type openInNewTabOptionsType = { + active?: boolean; + container?: number; + insert?: boolean; + pinned?: boolean; +}; + +type openInNewTabReturnType = { + onclose: () => void; + closed: boolean; + close: () => void; +}; + +declare namespace GM { + function addStyle(css: string): void; + function addElement( + tagName: string, + attributes?: { [key: string]: unknown }, + ): HTMLElement; + function addElement( + parent: Node | Element | ShadowRoot, + tagName: string, + attributes?: { [key: string]: unknown }, + ): HTMLElement; + function registerMenuCommand( + caption: string, + func: (event: MouseEvent | KeyboardEvent) => void, + options?: { id?: string; title?: string; autoClose?: boolean }, + ): string; + function deleteValue(key: string): Promise; + function deleteValues(keys: string[]): Promise; + function download(options: { + url: string; + name: string; + headers?: object; + timeout?: number; + context?: unknown; + user?: string; + password?: string; + anonymous?: boolean; + onabort?: () => void; + onerror?: () => void; + onload?: () => void; + onloadend?: () => void; + onloadstart?: () => void; + onprogress?: () => void; + onreadystatechange?: () => void; + ontimeout?: () => void; + }): Promise; + function download(url: string, name: string): Promise; + function getResourceUrl(name: string, isBlobUrl?: boolean): Promise; + function getValue( + key: string, + defaultValue?: string | number | boolean, + ): Promise; + function getValue( + key: string, + defaultValue?: string | number | boolean, + ): Promise; + function getValues(keys: string[]): Promise<{ [key: string]: T }>; + function getValues(obj: { + [key: string]: T; + }): Promise<{ [key: string]: T }>; + const info: vm_infoType; + function listValues(): Promise; + function notification(options: { + text: string; + title?: string; + image?: string; + silent?: boolean; // false + tag?: string; + zombieTimeout?: number; + zombieUrl?: string; + onclick?: () => void; + ondone?: () => void; + }): Promise<() => void>; + function notification( + text: string, + title?: string, + image?: string, + onclick?: () => void, + ): promise<() => void>; + function openInTab( + url: string, + options?: { + active?: boolean; // true + container?: number; // 0 = default (main) container 1, 2, etc. = internal container index + insert?: boolean; // true + pinned?: boolean; // false + }, + ): { + onclose?: () => void; + closed: boolean; + close: () => void; + }; + function openInTab( + url: string, + openInBackground: boolean, + ): { + onclose?: () => void; + closed: boolean; + close: () => void; + }; + function setClipboard(data: string, type: string): void; + function setValue(key: string, value: unknown): Promise; + function setValues(obj: { [key: string]: unknown }): Promise; + + // function xmlHttpRequest(details: vm_detailType): { abort: () => void }; + function xmlHttpRequest( + details: vm_detailType, + ): Promise>; +} + +function GM_getValue(key: string, defaultValue?: t): t; +function GM_getValue( + key: string, + defaultValue?: string | number | boolean, +): t; +function GM_getValue( + key: string, + defaultValue?: string | number | boolean, +): string; + +function GM_setValue(key: string, value: t): void; +function GM_setValue(key: string, value: string | number | boolean): void; + +function GM_deleteValue(key: string): void; + +function GM_listValues(): string[]; + +function GM_addValueChangeListener( + name, + callback: (name, oldValue, newValue, remote) => void, +): string; + +function GM_removeValueChangeListener(listenerId: string): void; + +function GM_getResourceText(name: string): string; + +function GM_getResourceURL(name: string): string; +function GM_getResourceURL(name: string, isBlobUrl: boolean): string; + +function GM_addElement( + tagName: string, + attributes?: { [key: string]: string }, +): HTMLElement; +function GM_addElement( + parentNode: Node | Element | ShadowRoot, + tagName: string, + attributes?: { [key: string]: string }, +): HTMLElement; + +function GM_addStyle(css: string): HTMLStyleElement; + +function GM_openInTab( + url: string, + options?: openInNewTabOptionsType, +): openInNewTabReturnType; +function GM_openInTab( + url: string, + openInBackground?: boolean, +): openInNewTabReturnType; + +function GM_registerMenuCommand( + caption: string, + onClick: (e: MouseEvent | KeyboardEvent) => void, +): void; + +function GM_unregisterMenuCommand(caption: string): void; + +function GM_notification(option: vm_optionType): vm_notificationReturnType; +function GM_notification( + text: string, + title?: string, + image?: string, + onclick?: () => void, +): vm_notificationReturnType; + +function GM_setClipboard(data: string, type: string): void; + +function GM_xmlhttpRequest( + request: vm_xmlhttpRequestType, +): Promise; + +function GM_download(url: string, name?: string): void; +function GM_download(options: vm_downloadOptionsType): void; diff --git a/src/userscripts/manga-reading/manga-reading-overrides.user.css b/src/userscripts/manga-reading/manga-reading-overrides.user.css new file mode 100644 index 0000000..9e8ddc3 --- /dev/null +++ b/src/userscripts/manga-reading/manga-reading-overrides.user.css @@ -0,0 +1,168 @@ +/* overrides */ +body { + border-color: currentcolor; + background: rgba(0, 0, 0, 0) url("/images/icons/black_thread.webp") !important; + border-top: 0px !important; +} + +body > div, +body > header, +body > footer { + isolation: isolate; +} + +body::before { + inset: 0; + content: ""; + position: fixed; + z-index: 0; /* Ensure this is below the main content */ + pointer-events: none; + backdrop-filter: contrast(1.2) brightness(0.75); +} + +body .top-logo img, +body .logo_chapter img { + filter: invert() hue-rotate(180deg) brightness(0.75); +} + +body > div::last-of-type { + padding-bottom: 120px; +} + +/* overrides chapter */ +.container-chapter-reader { + margin-bottom: 60px; +} + +.container-chapter-reader > img { + min-width: 630px; +} + +.overrides-header-container, +.overrides-footer-container { + background-color: rgba(0, 0, 0, 0.5); + padding-bottom: 60px; +} + +.logo_chapter { + background: none; +} + +.overrides-header-container > div, +.overrides-footer-container > div { + background: none; +} + +/* overrides manga */ +body > div.body-site { + padding-bottom: 120px; +} + +div#panel-story-info-description { + max-height: none !important; +} + +div#panel-description-linear, +span#panel-story-info-description-show-more, +span#panel-story-info-description-show-less { + display: none !important; +} + +/* overrides neither chapter nor manga */ +div.truyen-list > div.list-truyen-item-wrap > .genres-item-info { + display: flex; + flex-flow: column nowrap; + gap: 4px; + box-sizing: border-box; + padding: 4px; + /*background-color: rgb(24, 26, 27, 0.4);*/ + float: none; + flex-grow: 1; +} + +div.truyen-list > div.breadcrumb { + grid-column: span 2; +} + +div.truyen-list { + display: grid; + grid-gap: 12px; + grid-template-columns: calc(50% - 6px) calc(50% - 6px); +} + +div.truyen-list > div.list-truyen-item-wrap * { + float: unset !important; +} + +div.truyen-list > div.list-truyen-item-wrap { + backdrop-filter: blur(3px); + margin: 0; + width: auto; + background-color: rgb(24, 26, 27, 0.2); + background: none; + transition: background-color linear 0.15s; + padding: 4px; + display: flex; + gap: 4px; +} + +div.truyen-list > div.list-truyen-item-wrap.mr-genre-all-active-item { + background-color: rgb(4, 126, 123, 0.3); + transition: background-color linear 0s; +} + +div.truyen-list > div.list-truyen-item-wrap > .genres-item-img { + margin: 0; +} + +div.truyen-list > div.list-truyen-item-wrap { + display: grid; + grid-template: + "img title" auto + "img link" auto + "img description" 1fr / 123px 1fr; +} + +div.truyen-list > div.list-truyen-item-wrap > .list-story-item { + grid-area: img; +} +div.truyen-list > div.list-truyen-item-wrap > h3 { + grid-area: title; +} +div.truyen-list > div.list-truyen-item-wrap > a.list-story-item-wrap-chapter { + grid-area: link; +} +div.truyen-list > div.list-truyen-item-wrap > p { + grid-area: description; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + height: fit-content; +} + +div.truyen-list + > div.list-truyen-item-wrap + > .genres-item-info + > .genres-item-chap, +div.truyen-list + > div.list-truyen-item-wrap + > .genres-item-info + > .genres-item-view-time, +div.truyen-list > div.list-truyen-item-wrap > .genres-item-info > h3 { + min-height: 22px; + height: 22px; +} + +div.truyen-list + > div.list-truyen-item-wrap + > .genres-item-info + > .genres-item-description { + height: auto; + flex-grow: 1; +} + +div.truyen-list > div.list-truyen-item-wrap > div, +div.truyen-list > div.list-truyen-item-wrap > br, +div.truyen-list > div.list-truyen-item-wrap > a.read-more { + display: none; +} diff --git a/src/userscripts/manga-reading/manga-reading.user.css b/src/userscripts/manga-reading/manga-reading.user.css new file mode 100644 index 0000000..deeb1c5 --- /dev/null +++ b/src/userscripts/manga-reading/manga-reading.user.css @@ -0,0 +1,6 @@ +@import "tailwindcss"; +@source "templates/manga-reading-config.html"; +@source "templates/manga-reading-notification-button.html"; +@source "templates/manga-reading-notification-container.html"; +@source "templates/manga-reading-notification.html"; +@source "templates/manga-reading-lib-frame.html"; diff --git a/src/userscripts/manga-reading/manga-reading.user.ts b/src/userscripts/manga-reading/manga-reading.user.ts new file mode 100644 index 0000000..26fe6b5 --- /dev/null +++ b/src/userscripts/manga-reading/manga-reading.user.ts @@ -0,0 +1,1535 @@ +// ==UserScript== +// @name manga reading +// @namespace https://userscripts.skaarup.dev +// @homepageURL https://userscripts.skaarup.dev +// @version 3.0 +// @author nws +// @description Adds nearly complete keyboard navigation and cleans up the user interface of manganato +// @updateURL https://userscripts.skaarup.dev/scripts/manga-reading.user.js +// @downloadURL https://userscripts.skaarup.dev/scripts/manga-reading.user.js +// @resource stylesheet https://userscripts.skaarup.dev/styles/manga-reading.user.css +// @resource overrides https://userscripts.skaarup.dev/styles/manga-reading-overrides.user.css +// @connect https://pt-api.web.skaarup.dev +// @icon data:image/png;base64,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 +// @match https://readmanganato.com/* +// @match https://www.readmanganato.com/* +// @match https://manganato.com/* +// @match https://www.manganato.com/* +// @match https://manganelo.com/* +// @match https://www.manganelo.com/* +// @match https://readmanganato.com/* +// @match https://www.readmanganato.com/* +// @match https://chapmanganato.com/* +// @match https://www.chapmanganato.com/* +// @match https://natomanga.com/* +// @match https://www.natomanga.com/* +// @match https://mangakakalove.com/* +// @match https://www.mangakakalove.com/* +// @match https://nelomanga.com/* +// @match https://www.nelomanga.com/* +// @match https://www.manganato.gg/* +// @match https://www.mangakakalot.gg/* +// @match https://mangakakalot.gg/* +// @match https://manganato.gg/* +// @match https://chapmanganato.to/* +// @match https://www.chapmanganato.to/* +// @grant none +// @grant GM.info +// @grant GM.setValue +// @grant GM.getValue +// @grant GM_getResourceText +// @grant GM.registerMenuCommand +// @grant GM.notification +// @grant GM.openInTab +// @grant GM.xmlHttpRequest +// @inject-into content +// @run-at document-start +// @top-level-await +// @noframes +// ==/UserScript== + +import mangaReadingLibFrame from "./templates/manga-reading-lib-frame.html"; +import mangaReadingConfig from "./templates/manga-reading-config.html"; +import mangaReadingNotificationButton from "./templates/manga-reading-notification-button.html"; +import mangaReadingNotificationContainer from "./templates/manga-reading-notification-container.html"; +import mangaReadingNotification from "./templates/manga-reading-notification.html"; + +async function initMangaReading() { + function disableScrolling() { + document.documentElement.style.top = `${-document.documentElement.scrollTop}px`; + document.documentElement.style.position = "fixed"; + document.documentElement.style.overflowY = "scroll"; + document.body.style.overflowY = "scroll"; + document.documentElement.style.width = "100vw"; + } + + function enableScrolling() { + const top = Number.parseInt(document.documentElement.style.top, 10); + const scrollTop = Math.abs(top); + document.documentElement.style.position = ""; + document.documentElement.style.overflowY = ""; + document.body.style.overflowY = ""; + document.documentElement.scrollTop = scrollTop; + document.body.scrollTop = scrollTop; + } + function loadStyle( + name: string, + inline: boolean, + shadowRoot: ShadowRoot | null, + ) { + const data = GM_getResourceText(name); + if (!data) { + console.warn(`Resource ${name} not found`); + return; + } + + const style = document.createElement("style"); + style.setAttribute("type", "text/css"); + style.setAttribute("data-mr-style", ""); + style.setAttribute("data-name", name); + style.innerHTML = data; + + if (inline) document.head.append(style); + else shadowRoot?.appendChild(style); + } + + function getMr() { + const focusableSelector = + "a[href].nws, area[href].nws, input:not([disabled]).nws, select:not([disabled]).nws, textarea:not([disabled]).nws, button:not([disabled]).nws, [tabindex].nws, [contenteditable].nws"; + + const configGlobals: configuration_ui.globalsType = { + uiInitialized: false, + configurationWindowOpen: false, + }; + + function createHTMLElement( + tag: string, + className: string | undefined = undefined, + innerHTML: string | undefined = undefined, + ) { + const element = document.createElement(tag); + if (className) element.className = className; + if (innerHTML) element.innerHTML = innerHTML; + element.classList.add("nws"); + return element; + } + + const frameTemplate = createHTMLElement("div", "mr-lib-outer-frame"); + + frameTemplate.innerHTML = atob(mangaReadingLibFrame); + + const customElementRegistry = window.customElements; + customElementRegistry.define( + "mr-config-container", + class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }).appendChild( + frameTemplate.cloneNode(true), + ); + } + }, + ); + + const outerFrame = document.createElement("mr-config-container"); + const shadowRoot = outerFrame.shadowRoot; + if (!shadowRoot) return; + + loadStyle("stylesheet", false, shadowRoot); + const frame = shadowRoot.children[0]; + if (!frame) return; + + const backdrop = frame.querySelector("[data-mr-backdrop]"); + if (!backdrop) return; + + backdrop.onclick = closeConfig; + + const subConfigTarget = frame.querySelector( + "[data-mr-sub-config-target]", + ); + const closeConfigButtons = + frame.querySelectorAll("[data-mr-close]"); + + for (let i = 0; i < closeConfigButtons.length; i++) { + const button = closeConfigButtons[i]; + if (!button) continue; + button.onclick = closeConfig; + } + + // UI elements + const ui = { + backdrop, + outerFrame, + shadowRoot, + frame, + subConfigTarget, + }; + + const registeredConfigurations: { + [key: string]: { + name: string; + container: HTMLElement; + callback: () => void; + }; + } = {}; + + function registerConfig( + info: vm_infoType, + container: HTMLElement, + callback: () => void, + ) { + registeredConfigurations[info.script.name] = { + name: info.script.name, + container, + callback, + }; + + ui.subConfigTarget?.appendChild(container); + } + + function unregisterConfig(info: vm_infoType) { + registeredConfigurations[info.script.name]?.container.remove(); + delete registeredConfigurations[info.script.name]; + } + + function setConfigOpen(value: boolean) { + configGlobals.configurationWindowOpen = value; + } + + function openConfig() { + setConfigOpen(true); + + if (!configGlobals.uiInitialized) { + configGlobals.uiInitialized = true; + } + + for (const registered of Object.values(registeredConfigurations)) { + registered.callback(); + } + + document.body.appendChild(outerFrame); + + disableScrolling(); + + shadowRoot?.querySelector("input")?.focus(); + } + + function closeConfig() { + outerFrame.remove(); + setConfigOpen(false); + enableScrolling(); + } + + function tabSwitch(event: KeyboardEvent) { + const target = event.target; + const elements = + ui.frame.querySelectorAll(focusableSelector); + + if (elements.length === 0) return; + + const firstElement = elements[0]; + const lastElement = elements[elements.length - 1]; + + if (event.code === "Tab" && event.shiftKey && target === firstElement) { + event.preventDefault(); + setTimeout(() => lastElement?.focus(), 0); + } else if ( + event.code === "Tab" && + !event.shiftKey && + target === lastElement + ) { + event.preventDefault(); + setTimeout(() => firstElement?.focus(), 0); + } + } + + function keydownEventListener(event: KeyboardEvent) { + if (event.code === "Tab") { + tabSwitch(event); + return true; + } + + if (event.code === "Tab" && event.shiftKey) { + tabSwitch(event); + return true; + } + + return false; + } + + function attachFocusEvent() { + console.log("MR - Attaching focus event..."); + ui.frame?.addEventListener("keydown", keydownEventListener as any); + console.log("MR - Attached focus events."); + } + + function noModifier(e: KeyboardEvent) { + return !(e.ctrlKey || e.altKey || e.shiftKey); + } + + function shiftModifier(e: KeyboardEvent) { + return e.shiftKey && !(e.ctrlKey || e.altKey); + } + + function ctrlModifier(e: KeyboardEvent) { + return e.ctrlKey && !(e.altKey || e.shiftKey); + } + + function altModifier(e: KeyboardEvent) { + return e.altKey && !(e.ctrlKey || e.shiftKey); + } + + async function globalShortcuts(event: KeyboardEvent) { + switch (true) { + case event.code === "Backslash" && ctrlModifier(event): + // setDebugging(!configGlobals.debugging); + // ui.debuggingCheckbox.checked = configGlobals.debugging; + return true; + case event.code === "Semicolon" && ctrlModifier(event): + // reloadResources(); + return true; + } + return false; + } + + async function configOpenShortcuts(event: KeyboardEvent) { + if (event.code === "Escape" && noModifier(event)) { + closeConfig(); + return true; + } + return false; + } + + async function configClosedShortcuts(event: KeyboardEvent) { + if (event.code === "Slash" && ctrlModifier(event)) { + openConfig(); + return true; + } + return false; + } + + const registeredKeyUpShortCuts: registeredShortcut[] = []; + + function filterRegisteredShortcut(shortcutType: shortcutType) { + return registeredKeyUpShortCuts.filter( + (registered) => registered.shortcutType === shortcutType, + ); + } + + async function keyupEventListener(event: KeyboardEvent) { + const shortcuts = [ + ...filterRegisteredShortcut("Global"), + ...(configGlobals.configurationWindowOpen + ? filterRegisteredShortcut("ConfigOpen") + : filterRegisteredShortcut("ConfigClosed")), + ]; + + for (const registered of shortcuts) { + if (await registered.shortcut.callback(event)) { + return; + } + } + } + + function attachShortcutEvents() { + console.log("MR - Attaching shortcut events..."); + document.addEventListener("keyup", keyupEventListener); + console.log("MR - Attached shortcut events."); + } + + function registerKeyUp( + shortcutType: shortcutType, + ...shortcuts: shortcut[] + ) { + for (const shortcut of shortcuts) { + registeredKeyUpShortCuts.unshift({ shortcutType, shortcut }); + } + } + + function unregisterKeyUp(name: string) { + const index = registeredKeyUpShortCuts.findIndex( + (registered) => registered.shortcut.name === name, + ); + if (index > -1) { + registeredKeyUpShortCuts.splice(index, 1); + } + } + + function registerKeyUps() { + const namePrefix = "nwsLib"; + registerKeyUp("Global", { + name: `${namePrefix} - global`, + callback: globalShortcuts, + }); + registerKeyUp("ConfigOpen", { + name: `${namePrefix} - config open`, + callback: configOpenShortcuts, + }); + registerKeyUp("ConfigClosed", { + name: `${namePrefix} - config closed`, + callback: configClosedShortcuts, + }); + } + + async function onInit( + callback: () => Promise, + postCallback: () => Promise, + ) { + try { + console.log(`MR - ${GM.info.script.name} - Loading...`); + const id = GM.registerMenuCommand( + `Configure ${GM.info.script.name}`, + () => { + openConfig(); + }, + ); + registerKeyUps(); + attachFocusEvent(); + attachShortcutEvents(); + console.log(`MR - ${GM.info.script.name} - Loaded.`); + await callback(); + if (postCallback !== undefined) { + await postCallback(); + } + } catch (e) { + console.error("MR - Error:", e); + } + } + + async function init( + callback: () => Promise, + postCallback: () => Promise, + ) { + console.log("MR - Initializing..."); + switch (document.readyState) { + case "complete": + await onInit(callback, postCallback); + break; + case "interactive": + await onInit(callback, postCallback); + break; + case "loading": + setTimeout(init, 10, callback, postCallback); + break; + } + } + + return { + config: { + register: registerConfig, + unregister: unregisterConfig, + isOpen: () => configGlobals.configurationWindowOpen, + }, + shortcut: { + keyUp: { + register: registerKeyUp, + unregister: unregisterKeyUp, + }, + helpers: { + noModifier, + shiftModifier, + ctrlModifier, + altModifier, + }, + }, + createHTMLElement, + outerFrame, + shadowRoot, + frame, + init, + }; + } + const mr = getMr(); + if (!mr) return; + + const globals: manga_reading.globalsType = { + nextUrl: "", + prevUrl: "", + currentTitle: "", + uiInitialized: false, + at: "neither", + titleList: [], + ptApi: { + url: "", + bearerToken: "", + }, + }; + + const key = { + firstRun: "firstRun", + titleList: "titleList", + ptAPi: "ptApi", + }; + + const containerTemplate = document.createElement("section"); + + containerTemplate.innerHTML = atob(mangaReadingNotificationContainer); + const customElementRegistry = window.customElements; + customElementRegistry.define( + "mr-notification-container", + class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }).appendChild( + containerTemplate.cloneNode(true), + ); + } + }, + ); + const outerFrame = document.createElement("mr-notification-container"); + if (!outerFrame.shadowRoot) return; + const shadowRoot = outerFrame.shadowRoot; + + const container = shadowRoot.children[0] as HTMLDivElement; + const notificationList = container.querySelector("ol"); + if (!notificationList) return; + + notificationList.style.setProperty("--offset", "32px"); + notificationList.style.setProperty("--width", "356px"); + notificationList.style.setProperty("--gap", "14px"); + notificationList.style.setProperty("--border-radius", "8px"); + notificationList.style.setProperty("--z-index", "999999999"); + + function initToastContainer() { + console.log("MR - Initializing Toast Container."); + if (!mr) return; + + for (let i = 0; i < mr.shadowRoot.children.length; i++) { + const element = mr.shadowRoot.children[i]; + if (element?.tagName.toLowerCase() === "style") { + shadowRoot.appendChild(element.cloneNode(true)); + } + } + console.log("MR - Initialized Toast Container."); + } + + function insertToastContainer() { + console.log("MR - Inserting Toast Container."); + document.body.appendChild(outerFrame); + console.log("MR - Inserted Toast Container."); + } + + const notificationTemp = document.createElement("div"); + + notificationTemp.innerHTML = atob(mangaReadingNotification); + const notificationElement = notificationTemp.children[0] as HTMLDivElement; + const notificationButtonTemp = document.createElement("div"); + + notificationButtonTemp.innerHTML = atob(mangaReadingNotificationButton); + const notificationButton = notificationButtonTemp + .children[0] as HTMLDivElement; + + const temp = document.createElement("div"); + + temp.innerHTML = atob(mangaReadingConfig); + const configElement = temp.children[0] as HTMLDivElement; + + const ui = { + ptApiUrl: configElement.querySelector( + "[data-mr-pt-api-url]", + ), + ptApiBearerToken: configElement.querySelector( + "[data-mr-pt-api-bearer-token]", + ), + titleList: configElement.querySelector( + "[data-mr-title-list]", + ), + subTitle: configElement.querySelector( + "[data-mr-title-current]", + ), + btnAdd: configElement.querySelector( + "[data-mr-title-current-add]", + ), + btnRemove: configElement.querySelector( + "[data-mr-title-current-remove]", + ), + btnSave: configElement.querySelector( + "[data-mr-title-list-save]", + ), + btnReset: configElement.querySelector( + "[data-mr-title-list-reset]", + ), + }; + + function escapeRegExp(input: string) { + return input.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); + } + function setLocation(url: string) { + window.location = url as string & Location; + } + function setTitleList() { + if (!ui.titleList) return; + ui.titleList.value = globals.titleList.join("\r\n"); + } + + function setPTUi() { + if (ui.ptApiUrl) ui.ptApiUrl.value = globals.ptApi.url; + if (ui.ptApiBearerToken) + ui.ptApiBearerToken.value = globals.ptApi.bearerToken; + } + + function atNeither() { + return globals.at === "neither"; + } + function atChapter() { + return globals.at === "chapter"; + } + function atManga() { + return globals.at === "manga"; + } + function atChapterOrManga() { + return atChapter() || atManga(); + } + function getChapterList() { + return document.querySelector("div.chapter-list"); + } + + function setStyle( + arr: NodeListOf, + style: string, + value: string, + ) { + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; + if (!item) continue; + item.style[style as any] = value; + } + } + + function resize() { + if (!atChapter()) return; + + const title = "Image Width / %"; + const height = "'auto h' to fit images to page height,"; + const width = "'auto w' to fit images to page width."; + const inp = prompt(`${title}\n${height}\n${width}`); + const input = inp?.valueOf().toLowerCase(); + + if (input === null || input === "0") return; + + const pageWidth = document.body.clientWidth; + + const images = document.querySelectorAll( + ".container-chapter-reader img", + ); + + if (input === "auto w" || input === "w") { + for (let i = 0; i < images.length; i++) { + const image = images[i]; + if (!image) continue; + image.style.width = `${(pageWidth / image.width) * 100}%`; + } + + return; + } + + if (input === "auto h" || input === "h") { + for (let i = 0; i < images.length; i++) { + const image = images[i]; + if (!image) continue; + image.style.width = `${(pageWidth / image.width) * 100}%`; + image.style.width = `${(window?.visualViewport?.height ?? 1 / image.height) * 100}%`; + } + + return; + } + + for (let i = 0; i < images.length; i++) { + const image = images[i]; + if (!image) continue; + image.style.width = image.style.width = `${input}%`; + } + } + + function setSubTitle() { + if (!ui.subTitle) return; + ui.subTitle.innerText = atChapterOrManga() + ? globals.currentTitle + : "No title"; + } + + function registerConfig() { + setSubTitle(); + if (!mr) return; + if (!ui.btnRemove) return; + if (!ui.btnAdd) return; + if (!ui.btnReset) return; + if (!ui.btnSave) return; + + ui.btnRemove.onclick = removeTitle; + ui.btnRemove.disabled = atNeither(); + + ui.btnAdd.onclick = addTitle; + ui.btnAdd.disabled = atNeither(); + + ui.btnReset.onclick = () => { + if (!ui.titleList) return; + ui.titleList.value = globals.titleList.join("\r\n"); + }; + + ui.btnSave.onclick = () => { + saveTitles(); + savePtApi(); + }; + + mr.config.register(GM.info, configElement, () => { + setTitleList(); + setPTUi(); + + if (!ui.btnRemove) return; + if (!ui.btnAdd) return; + + ui.btnRemove.disabled = atNeither(); + ui.btnAdd.disabled = atNeither(); + }); + } + + function addTitle() { + const trimmedValue = ui.titleList?.value.trim() ?? ""; + const taTitleListValue = trimmedValue.split(/\r?\n/); + + if (taTitleListValue.includes(globals.currentTitle)) return; + + const curTAVal = trimmedValue.length > 0 ? `${trimmedValue}\r\n` : ""; + + if (!ui.titleList) return; + ui.titleList.value = curTAVal + globals.currentTitle; + } + + async function saveTitles() { + globals.titleList = [ + ...new Set(ui.titleList?.value.trim().split(/\r?\n/).sort()), + ]; + await GM.setValue(key.titleList, JSON.stringify(globals.titleList)); + + if (atChapter()) { + removeMargins(); + } + } + + async function savePtApi() { + globals.ptApi.url = ui.ptApiUrl?.value.trim() ?? ""; + globals.ptApi.bearerToken = ui.ptApiBearerToken?.value.trim() ?? ""; + await GM.setValue(key.ptAPi, JSON.stringify(globals.ptApi)); + } + + function removeTitle() { + if (!ui.titleList) return; + + const curTAVal = ui.titleList.value.trim() ?? ""; + const regex = new RegExp( + `${escapeRegExp(globals.currentTitle)}\\r?\\n?`, + "gi", + ); + ui.titleList.value = curTAVal.replace(regex, ""); + } + + function goToFirstChapter() { + const firstChapter = getChapterList() + ?.lastElementChild as HTMLDivElement | null; + const firstChapterLink = + firstChapter?.querySelector("& > span > a"); + + if (!firstChapterLink) return; + + setLocation(firstChapterLink.href); + } + + function goToLatestChapter() { + const latestChapter = getChapterList() + ?.firstElementChild as HTMLDivElement | null; + const latestChapterLink = + latestChapter?.querySelector("& > span > a"); + + if (!latestChapterLink) return; + setLocation(latestChapterLink.href); + } + + function removeMargins() { + if (!atChapter()) return; + + let margin = "5px auto 0"; + + if (globals.titleList.includes(globals.currentTitle)) { + margin = "0 auto"; + } + + setStyle( + document.querySelectorAll(".container-chapter-reader > img"), + "margin", + margin, + ); + } + + function findUrls() { + console.log("MR - Finding URLs..."); + + const links = document.querySelectorAll( + "div.breadcrumb > p > span > a", + ); + const titleLink = links[1]; + + globals.currentTitle = titleLink?.innerText.trim().toLowerCase() ?? "None"; + + setSubTitle(); + + if (!atChapter() || titleLink === undefined) { + console.log("MR - Found URLs."); + return; + } + + const nextChapterLink = document.querySelector( + ".btn-navigation-chap a.back", + ); + + if (nextChapterLink) { + globals.nextUrl = nextChapterLink.href; + } else { + globals.nextUrl = titleLink.href; + } + + const prevChapterLink = document.querySelector( + ".btn-navigation-chap a.next", + ); + + if (prevChapterLink) { + globals.prevUrl = prevChapterLink.href; + } else { + globals.prevUrl = titleLink.href; + } + + console.log("MR - Found URLs."); + } + + async function loadTitleList() { + console.log("MR - Loading title list..."); + + const value = await GM.getValue( + key.titleList, + JSON.stringify({ titleList: [] }), + ); + + if (typeof value !== "string") { + console.error("Invalid titleList value:", value); + globals.titleList = []; + return; + } + + globals.titleList = JSON.parse(value); + + console.log("MR - Loaded title list."); + } + + async function loadPtApi() { + console.log("MR - Loading progress tracker api..."); + + const value = await GM.getValue( + key.ptAPi, + JSON.stringify({ url: "", bearerToken: "" }), + ); + + if (typeof value !== "string") { + console.error("Invalid ptApi value:", value); + globals.ptApi = { url: "", bearerToken: "" }; + return; + } + + globals.ptApi = JSON.parse(value); + + console.log("MR - Loaded progress tracker api."); + } + + function manganatoSiteOverrides() { + document.querySelector("body .comments")?.remove(); + document.querySelector("#fb-root")?.remove(); + const div = document.createElement("div"); + div.id = "current-time"; + div.style.display = "none"; + document.body.appendChild(div); + document.querySelector("body > footer")?.remove(); + + const containers = document.querySelectorAll(".ads-contain"); + containers.forEach((container) => container.remove()); + + if (atChapter()) { + const chapterContainer = document.querySelector( + ".container-chapter-reader", + ); + chapterContainer?.nextSibling?.remove(); + chapterContainer?.nextSibling?.remove(); + + const removables = document.querySelectorAll( + "body > div.info-top-chapter > p.info-top-chapter-text", + ); + removables.forEach((removable) => removable.remove()); + } + + if (!atChapterOrManga()) { + genreAllSetFirstActive(); + } + } + + function siteOverrides() { + console.log("MR - Applying site overrides..."); + manganatoSiteOverrides(); + console.log("MR - Applied site overrides."); + } + + function setAt() { + const path = window.location.pathname; + + const atChapter = new RegExp(/\/manga\/[\w.\-~%]+\/chapter-[\d.-]+/).test( + path, + ); + const atManga = new RegExp(/\/manga\/[\w.\-~%]+$/).test(path); + + console.log("MR - Setting at...", { atChapter, atManga }); + + if (atChapter) globals.at = "chapter"; + else if (atManga) globals.at = "manga"; + else globals.at = "neither"; + } + + let genreAllItems: NodeListOf; + let genreAllItemsIndex = 0; + const genreAllActiveItemClass = "mr-genre-all-active-item"; + + function genreAllGoToPage(direction: string) { + const pageSelected = document.querySelector( + ".panel_page_number > .group_page > a.page_select", + ); + + console.log("pageSelected", pageSelected); + if (!pageSelected) return; + + const previous = + pageSelected.previousElementSibling as HTMLAnchorElement | null; + + const next = pageSelected.nextElementSibling as HTMLAnchorElement | null; + + switch (direction) { + case "ArrowLeft": + if (!previous || previous.classList.contains("page_blue")) return; + setLocation(previous.href); + break; + case "ArrowRight": + if (!next || next.classList.contains("page_blue")) return; + setLocation(next.href); + break; + } + } + + function genreAllSetFirstActive() { + genreAllItems = document.querySelectorAll( + "div.truyen-list > div.list-truyen-item-wrap", + ); + + if (genreAllItems.length > 0) { + genreAllItemsIndex = 0; + genreAllItems[genreAllItemsIndex]?.classList.add(genreAllActiveItemClass); + } + } + + function genreAllOpenMangaInNewTab() { + const anchor = + genreAllItems[genreAllItemsIndex]?.querySelector( + ".list-story-item", + ); + + if (!anchor) return; + + GM.openInTab(anchor.href, { active: false, insert: true }); + } + + function genreAllBrowse(direction: string) { + switch (direction) { + case "ArrowUp": + if (genreAllItemsIndex < 2) return; + genreAllItems[genreAllItemsIndex]?.classList.remove( + genreAllActiveItemClass, + ); + genreAllItemsIndex = genreAllItemsIndex - 2; + genreAllItems[genreAllItemsIndex]?.classList.add( + genreAllActiveItemClass, + ); + break; + case "ArrowDown": + if (genreAllItemsIndex + 2 > genreAllItems.length - 1) return; + genreAllItems[genreAllItemsIndex]?.classList.remove( + genreAllActiveItemClass, + ); + genreAllItemsIndex = genreAllItemsIndex + 2; + genreAllItems[genreAllItemsIndex]?.classList.add( + genreAllActiveItemClass, + ); + break; + case "ArrowLeft": + if (genreAllItemsIndex === 0) return; + genreAllItems[genreAllItemsIndex]?.classList.remove( + genreAllActiveItemClass, + ); + genreAllItemsIndex = genreAllItemsIndex - 1; + genreAllItems[genreAllItemsIndex]?.classList.add( + genreAllActiveItemClass, + ); + break; + case "ArrowRight": + if (genreAllItemsIndex === genreAllItems.length - 1) return; + genreAllItems[genreAllItemsIndex]?.classList.remove( + genreAllActiveItemClass, + ); + genreAllItemsIndex = genreAllItemsIndex + 1; + genreAllItems[genreAllItemsIndex]?.classList.add( + genreAllActiveItemClass, + ); + break; + } + + genreAllItems[genreAllItemsIndex]?.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + + window.getSelection()?.removeAllRanges(); + } + + function parseHeaders(headersString: string) { + const headers = new Headers(); + const arr = headersString.trim().split("\n"); + for (const header of arr) { + const [key, value] = header.split(":"); + if (!key || !value) continue; + headers.append(key, value); + } + return headers; + } + + async function nwsFetch( + input: string | URL | globalThis.Request, + init: RequestInit | undefined = undefined, + ) { + const method = init?.method ?? "GET"; + + const headers: { [key: string]: string } = + method !== "GET" ? { "content-type": "application/json" } : {}; + + if (init?.headers !== undefined) { + if (init.headers instanceof Headers) { + init.headers.forEach((value, key) => { + headers[key.toLowerCase()] = value; + }); + } else if (typeof init.headers === "object") { + for (const [key, value] of Object.entries(init.headers)) { + headers[key.toLowerCase()] = value; + } + } + } + + const resp = await GM.xmlHttpRequest({ + url: input.toString(), + method, + headers, + timeout: 5000, + responseType: "json", + anonymous: true, + data: init?.body ?? undefined, + }); + + const respHeaders = parseHeaders(resp.responseHeaders); + + return new Response(resp.responseText, { + status: resp.status, + statusText: resp.statusText, + headers: respHeaders, + }); + } + + const shortcutHelpers = mr.shortcut.helpers; + + async function query( + options: { input: string; headers: HeadersInit }, + q: string | undefined = undefined, + ): Promise { + try { + const input = q !== undefined ? `${options.input}?q=${q}` : options.input; + const response = await nwsFetch(input, { + method: "GET", + headers: options.headers, + }); + + if (!response.ok) { + console.error("Failed to fetch bookmarks"); + return []; + } + + return response.json(); + } catch (e) { + console.error("Failed to fetch bookmarks"); + console.error(e); + return []; + } + } + + async function remove( + options: { input: string; headers: HeadersInit }, + id: number, + ) { + try { + const response = await nwsFetch(`${options.input}/${id}`, { + method: "DELETE", + headers: options.headers, + }); + + if (!response.ok) { + console.error("Failed to delete bookmark"); + return false; + } + return true; + } catch (e) { + console.error("Failed to delete bookmarks"); + console.error(e); + return false; + } + } + + async function createOrUpdate( + options: { input: string; headers: HeadersInit }, + body: { name: string; href: string }, + ) { + try { + console.log("MR - Creating or updating bookmark", options.input, body); + const response = await nwsFetch(options.input, { + method: "PUT", + body: JSON.stringify(body), + headers: options.headers, + }); + + if (!response.ok) { + console.error("MR - Failed to create or update bookmark"); + return { success: false, created: false }; + } + + try { + const json: { success: boolean; created: boolean } = + await response.json(); + json.success = true; + return json; + } catch (e) { + console.error("MR - Failed to parse response"); + console.error(e); + return { success: false, created: false }; + } + } catch (e) { + console.error("MR - Failed to create or update bookmarks"); + console.error(e); + return { success: false, created: false }; + } + } + + async function check( + options: { input: string; headers: HeadersInit }, + id: number, + finished: boolean, + ) { + try { + const response = await nwsFetch( + `${options.input}/${id}/check/${finished}`, + { + method: "PUT", + headers: options.headers, + }, + ); + + if (!response.ok) { + console.error("MR - Failed to check bookmark"); + return false; + } + return true; + } catch (e) { + console.error("MR - Failed to check bookmark"); + console.error(e); + return false; + } + } + + function getProgressTrackerApi() { + const baseUrl = globals.ptApi.url; + const input = `${baseUrl}/bookmarks`; + const bearerToken = globals.ptApi.bearerToken; + const noBodyHeaders = new Headers({ + "Content-Type": "application/json", + Authorization: `Bearer ${bearerToken}`, + }); + const bodyHeaders = new Headers({ + "Content-Type": "application/json", + Authorization: `Bearer ${bearerToken}`, + }); + + return { baseUrl, input, bearerToken, noBodyHeaders, bodyHeaders }; + } + + function getNotificationElement() { + return notificationElement.cloneNode(true) as HTMLLIElement; + } + + function getNotificationButtonElement(options: { + text: string; + onclick?: () => Promise | void; + dismiss?: boolean; + }) { + const button = notificationButton.cloneNode(true) as HTMLButtonElement; + + button.innerText = options.text; + if (options.onclick) { + button.onclick = options.onclick; + } + + return button; + } + + function htmlNotification(options: { + title: string; + description?: string; + autoDismiss?: boolean; + buttons?: Array<{ + text: string; + onclick?: () => Promise | void; + dismiss?: boolean; + }>; + }) { + const notification = getNotificationElement(); + + const title = notification.querySelector("[data-title]"); + const description = + notification.querySelector("[data-description]"); + const content = + notification.querySelector("[data-content]"); + + if (title === null || description === null || content === null) { + console.error("MR - Notification elements not found."); + return; + } + + title.innerText = options.title; + description.innerText = options.description ?? ""; + + if (Array.isArray(options.buttons)) { + for (const button of options.buttons) { + const btn = getNotificationButtonElement(button); + if (button.dismiss) { + btn.onclick = () => { + notification.remove(); + if (!button.onclick) return; + button.onclick(); + }; + } + content.appendChild(btn); + } + } + + notificationList?.appendChild(notification); + + if (options.autoDismiss === undefined || options.autoDismiss) { + setTimeout(() => notification.remove(), 2500); + } + } + + function triggerNotification(options: { + title: string; + description?: string; + autoDismiss?: boolean; + buttons?: Array<{ + text: string; + onclick?: () => Promise | void; + dismiss?: boolean; + }>; + }) { + setTimeout(htmlNotification, 0, options); + } + + async function removeMangaFromProgressTracker() { + const chapter = atChapter(); + const manga = atManga(); + const chapterOrManga = chapter || manga; + + if (!chapterOrManga) return; + + if (globals.ptApi.url === "" || globals.ptApi.bearerToken === "") { + triggerNotification({ + title: "Configuration Error", + description: "Progress Tracker API URL and Bearer Token must be set.", + }); + return; + } + + const { input, noBodyHeaders, bodyHeaders } = getProgressTrackerApi(); + + const title = globals.currentTitle; + + const bookmarks = await query({ input, headers: noBodyHeaders }, title); + + if (bookmarks.length === 0) return; + + if (bookmarks.length === 1) { + if (!chapter) return; + + const id = bookmarks[0].id; + await remove({ input, headers: bodyHeaders }, id); + triggerNotification({ + title: "Bookmark Removed", + description: title, + }); + } else { + const bookmark = bookmarks.find((b) => b.name === title); + + if (bookmark === undefined) { + triggerNotification({ + title: "No bookmark found for title.", + description: title, + }); + return; + } + + if (!chapter) return; + + const id = bookmark.id; + await remove({ input, headers: bodyHeaders }, id); + triggerNotification({ + title: "Bookmark Removed", + description: title, + }); + } + } + + async function updateOrAddMangaToProgressTracker() { + const chapter = atChapter(); + const manga = atManga(); + const chapterOrManga = chapter || manga; + + if (!chapterOrManga) return; + + if (globals.ptApi.url === "" || globals.ptApi.bearerToken === "") { + triggerNotification({ + title: "Configuration Error", + description: "Progress Tracker API URL and Bearer Token must be set.", + }); + return; + } + + const { input, bodyHeaders } = getProgressTrackerApi(); + const title = globals.currentTitle; + + const body: { name: string; href: string } = { + name: title.trim().toLocaleLowerCase(), + href: window.location.href, + }; + + const result = await createOrUpdate({ input, headers: bodyHeaders }, body); + + if (!result.success) return; + + triggerNotification({ + title: `Bookmark ${result.created ? "created" : "updated"}`, + description: title, + }); + } + + async function configClosedShortcuts(e: KeyboardEvent) { + const chapter = atChapter(); + const manga = atManga(); + const chapterOrManga = chapter || manga; + + if (e.code === "ArrowLeft" && shortcutHelpers.noModifier(e) && chapter) { + setLocation(globals.prevUrl); + return true; + } + + if (e.code === "ArrowLeft" && shortcutHelpers.noModifier(e) && manga) { + goToLatestChapter(); + return true; + } + + if ( + e.code === "ArrowLeft" && + shortcutHelpers.noModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllGoToPage(e.code); + return true; + } + + if (e.code === "ArrowRight" && shortcutHelpers.noModifier(e) && chapter) { + setLocation(globals.nextUrl); + return true; + } + + if (e.code === "ArrowRight" && shortcutHelpers.noModifier(e) && manga) { + goToFirstChapter(); + return true; + } + + if ( + e.code === "ArrowRight" && + shortcutHelpers.noModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllGoToPage(e.code); + return true; + } + + if ( + e.code === "ArrowUp" && + shortcutHelpers.shiftModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllBrowse(e.code); + return true; + } + + if ( + e.code === "ArrowDown" && + shortcutHelpers.shiftModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllBrowse(e.code); + return true; + } + + if ( + e.code === "ArrowLeft" && + shortcutHelpers.shiftModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllBrowse(e.code); + return true; + } + + if ( + e.code === "ArrowRight" && + shortcutHelpers.shiftModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllBrowse(e.code); + return true; + } + + if ( + e.code === "Enter" && + shortcutHelpers.shiftModifier(e) && + !chapterOrManga && + (window.location.pathname.startsWith("/genre/all") || + window.location.pathname.startsWith("/manga-list/latest-manga")) + ) { + genreAllOpenMangaInNewTab(); + return true; + } + + if (e.code === "Quote" && shortcutHelpers.shiftModifier(e) && chapter) { + resize(); + return true; + } + + if ( + e.code === "BracketLeft" && + shortcutHelpers.shiftModifier(e) && + chapterOrManga + ) { + setTitleList(); + removeTitle(); + await saveTitles(); + return true; + } + + if ( + e.code === "BracketRight" && + shortcutHelpers.shiftModifier(e) && + chapterOrManga + ) { + setTitleList(); + addTitle(); + await saveTitles(); + return true; + } + + if ( + e.code === "BracketLeft" && + shortcutHelpers.altModifier(e) && + chapterOrManga + ) { + setTimeout(removeMangaFromProgressTracker, 0); + return true; + } + + if ( + e.code === "BracketRight" && + shortcutHelpers.altModifier(e) && + chapterOrManga + ) { + setTimeout(updateOrAddMangaToProgressTracker, 0); + return true; + } + + return false; + } + + function registerKeyUps() { + mr?.shortcut.keyUp.register("ConfigClosed", { + name: `${GM.info.script.name} - config closed`, + callback: configClosedShortcuts, + }); + } + + async function checkFirstRun() { + console.log("MR - First run check..."); + const value = await GM.getValue(key.firstRun, true); + if (value) { + console.log("MR - First run detected."); + GM.setValue(key.firstRun, false); + GM.notification( + "First run setup complete", + `MR - ${GM.info.script.name}`, + ); + } + console.log("MR - First run checked."); + } + + async function onInit() { + console.log(`MR - ${GM.info.script.name} - Loading...`); + await checkFirstRun(); + registerKeyUps(); + setAt(); + await loadTitleList(); + await loadPtApi(); + findUrls(); + if (atChapter()) { + removeMargins(); + } + siteOverrides(); + console.log(`MR - ${GM.info.script.name} - Loaded.`); + } + async function postInit() { + console.log(`MR - ${GM.info.script.name} - Post Loading...`); + initToastContainer(); + insertToastContainer(); + loadStyle("stylesheet", false, shadowRoot); + loadStyle("overrides", true, shadowRoot); + console.log(`MR - ${GM.info.script.name} - Post Loaded.`); + } + + registerConfig(); + mr.init(onInit, postInit); +} + +initMangaReading(); diff --git a/src/userscripts/manga-reading/templates/manga-reading-config.html b/src/userscripts/manga-reading/templates/manga-reading-config.html new file mode 100644 index 0000000..baf7aa3 --- /dev/null +++ b/src/userscripts/manga-reading/templates/manga-reading-config.html @@ -0,0 +1,73 @@ +
+

+ Current Title: + +

+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
diff --git a/src/userscripts/manga-reading/templates/manga-reading-lib-frame.html b/src/userscripts/manga-reading/templates/manga-reading-lib-frame.html new file mode 100644 index 0000000..bf56740 --- /dev/null +++ b/src/userscripts/manga-reading/templates/manga-reading-lib-frame.html @@ -0,0 +1,38 @@ +
+
+
+
+

Manga Reading - Configuration UI

+
+
+
+
+ +
+
diff --git a/src/userscripts/manga-reading/templates/manga-reading-notification-button.html b/src/userscripts/manga-reading/templates/manga-reading-notification-button.html new file mode 100644 index 0000000..34c1398 --- /dev/null +++ b/src/userscripts/manga-reading/templates/manga-reading-notification-button.html @@ -0,0 +1,4 @@ + diff --git a/src/userscripts/manga-reading/templates/manga-reading-notification-container.html b/src/userscripts/manga-reading/templates/manga-reading-notification-container.html new file mode 100644 index 0000000..b674678 --- /dev/null +++ b/src/userscripts/manga-reading/templates/manga-reading-notification-container.html @@ -0,0 +1,5 @@ +
    diff --git a/src/userscripts/manga-reading/templates/manga-reading-notification.html b/src/userscripts/manga-reading/templates/manga-reading-notification.html new file mode 100644 index 0000000..6973f00 --- /dev/null +++ b/src/userscripts/manga-reading/templates/manga-reading-notification.html @@ -0,0 +1,12 @@ +
  1. +
    +
    +
    +
    +
  2. diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9c62f74 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}