From 5ae824b63ac73636d3bdaaac24a5950f99c29489 Mon Sep 17 00:00:00 2001 From: Niki Wix Skaarup Date: Sat, 29 Mar 2025 05:01:26 +0100 Subject: [PATCH] init --- .editorconfig | 15 + .env.example | 1 + .eslintignore | 13 + .eslintrc.cjs | 16 + .eslintrc.json | 5 + .gitignore | 36 + .npmrc | 3 + .vscode/settings.json | 3 + Dockerfile | 39 + README.md | 3 + bun.lock | 478 +++++ bunfig.toml | 5 + package.json | 31 + src/build.ts | 128 ++ src/index.css | 28 + src/index.html | 86 + src/index.ts | 0 src/server.ts | 113 ++ src/static/favicon.base64.txt | 1 + src/static/favicon.png | Bin 0 -> 5348 bytes src/static/manifest.json | 10 + src/static/robots.txt | 4 + src/static/sitemap.txt | 1 + src/types/configuration-ui.d.ts | 7 + src/types/greasemonkey.d.ts | 76 + src/types/manga-reading.d.ts | 28 + src/types/violentmonkey.d.ts | 291 ++++ .../manga-reading-overrides.user.css | 168 ++ .../manga-reading/manga-reading.user.css | 6 + .../manga-reading/manga-reading.user.ts | 1535 +++++++++++++++++ .../templates/manga-reading-config.html | 73 + .../templates/manga-reading-lib-frame.html | 38 + .../manga-reading-notification-button.html | 4 + .../manga-reading-notification-container.html | 5 + .../templates/manga-reading-notification.html | 12 + tsconfig.json | 28 + 36 files changed, 3290 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .eslintignore create mode 100644 .eslintrc.cjs create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 bun.lock create mode 100644 bunfig.toml create mode 100644 package.json create mode 100644 src/build.ts create mode 100644 src/index.css create mode 100644 src/index.html create mode 100644 src/index.ts create mode 100644 src/server.ts create mode 100644 src/static/favicon.base64.txt create mode 100644 src/static/favicon.png create mode 100644 src/static/manifest.json create mode 100644 src/static/robots.txt create mode 100644 src/static/sitemap.txt create mode 100644 src/types/configuration-ui.d.ts create mode 100644 src/types/greasemonkey.d.ts create mode 100644 src/types/manga-reading.d.ts create mode 100644 src/types/violentmonkey.d.ts create mode 100644 src/userscripts/manga-reading/manga-reading-overrides.user.css create mode 100644 src/userscripts/manga-reading/manga-reading.user.css create mode 100644 src/userscripts/manga-reading/manga-reading.user.ts create mode 100644 src/userscripts/manga-reading/templates/manga-reading-config.html create mode 100644 src/userscripts/manga-reading/templates/manga-reading-lib-frame.html create mode 100644 src/userscripts/manga-reading/templates/manga-reading-notification-button.html create mode 100644 src/userscripts/manga-reading/templates/manga-reading-notification-container.html create mode 100644 src/userscripts/manga-reading/templates/manga-reading-notification.html create mode 100644 tsconfig.json 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 0000000000000000000000000000000000000000..dfa8b458c7bebb013ccbb22e75ca9648bf2fe207 GIT binary patch literal 5348 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 C$>o~> literal 0 HcmV?d00001 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 + } +}