diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e290a2f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2006 @@ +{ + "name": "memos-extension", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "memos-extension", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@tailwindcss/postcss": "^4.2.1", + "@tailwindcss/typography": "^0.5.19", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", + "vite": "^8.0.0", + "vite-plugin-static-copy": "^3.3.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", + "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", + "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz", + "integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz", + "integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz", + "integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz", + "integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz", + "integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz", + "integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz", + "integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz", + "integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.1.tgz", + "integrity": "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "postcss": "^8.5.6", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", + "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", + "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz", + "integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.9" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.9", + "@rolldown/binding-darwin-x64": "1.0.0-rc.9", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.9", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.9", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.9", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz", + "integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.9", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-static-copy": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.3.0.tgz", + "integrity": "sha512-XiAtZcev7nppxNFgKoD55rfL+ukVp/RtrnTJONRwRuzv/B2FK2h2ZRCYjvxhwBV/Oarse83SiyXBSxMTfeEM0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "p-map": "^7.0.4", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/sapphi-red" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..aa76870 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "memos-extension", + "version": "1.0.0", + "description": "Clip any web page or selection as Markdown directly to your [usememos](https://usememos.com) instance. Images are uploaded as attachments. Preview and edit before sending.", + "main": "background.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@tailwindcss/postcss": "^4.2.1", + "@tailwindcss/typography": "^0.5.19", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", + "vite": "^8.0.0", + "vite-plugin-static-copy": "^3.3.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..668a5b9 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} diff --git a/src/background.js b/src/background.js new file mode 100644 index 0000000..8e5e3f8 --- /dev/null +++ b/src/background.js @@ -0,0 +1,5 @@ +// background.js - service worker + +chrome.runtime.onInstalled.addListener(() => { + console.log("Memos Clipper installed."); +}); diff --git a/src/content.js b/src/content.js new file mode 100644 index 0000000..38a82e5 --- /dev/null +++ b/src/content.js @@ -0,0 +1,272 @@ +// content.js - Extracts page content and converts to Markdown +// Guard against double-injection (MV3 scripting.executeScript can fire multiple times) +if (window.__memosClipperLoaded) { /* skip */ } else { +window.__memosClipperLoaded = true; + +(function () { + // ── Turndown-lite: a minimal but solid HTML→Markdown converter ────────────── + function htmlToMarkdown(element, isSelection = false) { + const clone = element.cloneNode(true); + + // Remove unwanted elements — comprehensive list covering real-world sites + // Skip this if we're in selection mode, because the user explicitly picked this content + if (!isSelection) { + const removeSelectors = [ + // Semantic structural chrome + 'script', 'style', 'noscript', 'template', + 'nav', 'header', 'footer', 'aside', + // ARIA roles for chrome + '[role="navigation"]', '[role="banner"]', '[role="complementary"]', + '[role="contentinfo"]', '[role="search"]', '[role="toolbar"]', + '[role="menubar"]', '[role="menu"]', '[role="dialog"]', + // Common class/id patterns for site chrome + '[class*="navbar"]', '[class*="nav-bar"]', '[class*="site-nav"]', + '[class*="site-header"]', '[class*="site-footer"]', + '[class*="page-header"]', '[class*="page-footer"]', + '[id*="navbar"]', '[id*="site-nav"]', '[id*="site-header"]', '[id*="site-footer"]', + // Ads and tracking + '[class*="advertisement"]', '[class*="advert"]', '[class*=" ad-"]', + '[class*="google-ad"]', '[class*="sponsored"]', + '[id*="advertisement"]', '[id*="google_ad"]', + // Cookie banners, popups, overlays + '[class*="cookie"]', '[id*="cookie"]', + '[class*="consent"]', '[id*="consent"]', + '[class*="gdpr"]', '[id*="gdpr"]', + '[class*="popup"]', '[class*="modal"]', '[class*="overlay"]', + '[class*="banner"]', '[id*="banner"]', + // Social / share widgets + '[class*="share-bar"]', '[class*="social-bar"]', '[class*="share-buttons"]', + '[class*="sharing"]', + // Subscription / newsletter prompts + '[class*="newsletter"]', '[class*="subscribe"]', + // Comments sections + '[id="comments"]', '[class*="comments-section"]', '[id*="disqus"]', + // Related / recommended articles + '[class*="related-posts"]', '[class*="recommended"]', '[class*="more-articles"]', + // Sidebar + '[class*="sidebar"]', '[id*="sidebar"]', + // Print/hidden + '[hidden]', '[aria-hidden="true"]', + ].join(', '); + + clone.querySelectorAll(removeSelectors).forEach((el) => el.remove()); + + // Also remove elements that are visually hidden via inline style + clone.querySelectorAll('[style*="display:none"],[style*="display: none"],[style*="visibility:hidden"]') + .forEach((el) => el.remove()); + } else { + // In selection mode, we still want to remove script/style tags if any + clone.querySelectorAll('script, style, noscript, template').forEach((el) => el.remove()); + } + + return nodeToMd(clone).replace(/\n{3,}/g, "\n\n").trim(); + } + + function nodeToMd(node, ctx = { listDepth: 0, ordered: false, index: 0 }) { + if (node.nodeType === Node.TEXT_NODE) { + return node.textContent.replace(/\s+/g, " "); + } + if (node.nodeType !== Node.ELEMENT_NODE) return ""; + + const tag = node.tagName.toLowerCase(); + const children = () => + Array.from(node.childNodes) + .map((c) => nodeToMd(c, ctx)) + .join(""); + + switch (tag) { + case "h1": return `\n\n# ${children().trim()}\n\n`; + case "h2": return `\n\n## ${children().trim()}\n\n`; + case "h3": return `\n\n### ${children().trim()}\n\n`; + case "h4": return `\n\n#### ${children().trim()}\n\n`; + case "h5": return `\n\n##### ${children().trim()}\n\n`; + case "h6": return `\n\n###### ${children().trim()}\n\n`; + case "p": return `\n\n${children().trim()}\n\n`; + case "br": return " \n"; + case "hr": return "\n\n---\n\n"; + + case "strong": + case "b": return `**${children()}**`; + case "em": + case "i": return `_${children()}_`; + case "s": + case "del": return `~~${children()}~~`; + case "code": { + const text = node.textContent; + return text.includes("`") ? `\`\`${text}\`\`` : `\`${text}\``; + } + case "pre": { + const codeEl = node.querySelector("code"); + const lang = codeEl + ? (codeEl.className.match(/language-(\S+)/) || [])[1] || "" + : ""; + const text = (codeEl || node).textContent; + return `\n\n\`\`\`${lang}\n${text}\n\`\`\`\n\n`; + } + case "blockquote": return `\n\n${children() + .trim() + .split("\n") + .map((l) => `> ${l}`) + .join("\n")}\n\n`; + + case "a": { + const href = node.getAttribute("href") || ""; + const text = children().trim(); + if (!text) return href; + try { + const abs = new URL(href, location.href).href; + return `[${text}](${abs})`; + } catch { + return `[${text}](${href})`; + } + } + + case "img": { + const src = node.getAttribute("src") || ""; + const alt = node.getAttribute("alt") || ""; + try { + const abs = new URL(src, location.href).href; + return `![${alt}](${abs})`; + } catch { + return src ? `![${alt}](${src})` : ""; + } + } + + case "ul": { + const lines = Array.from(node.children) + .map((li) => `${" ".repeat(ctx.listDepth)}- ${nodeToMd(li, { ...ctx, listDepth: ctx.listDepth + 1 }).trim()}`) + .join("\n"); + return `\n\n${lines}\n\n`; + } + case "ol": { + const lines = Array.from(node.children) + .map((li, i) => `${" ".repeat(ctx.listDepth)}${i + 1}. ${nodeToMd(li, { ...ctx, listDepth: ctx.listDepth + 1 }).trim()}`) + .join("\n"); + return `\n\n${lines}\n\n`; + } + case "li": return children(); + + case "table": return convertTable(node); + + case "figure": { + const img = node.querySelector("img"); + const caption = node.querySelector("figcaption"); + let md = img ? nodeToMd(img, ctx) : children(); + if (caption) md += `\n*${caption.textContent.trim()}*`; + return `\n\n${md}\n\n`; + } + + // skip presentational / hidden + case "svg": + case "canvas": + case "video": + case "audio": + case "iframe": + case "button": + case "input": + case "select": + case "textarea": + case "form": + case "nav": + case "header": + case "footer": + case "aside": + return ""; + + default: + return children(); + } + } + + function convertTable(table) { + const rows = Array.from(table.querySelectorAll("tr")); + if (!rows.length) return ""; + const toRow = (tr) => + "| " + + Array.from(tr.querySelectorAll("th,td")) + .map((c) => c.textContent.trim().replace(/\|/g, "\\|")) + .join(" | ") + + " |"; + const header = toRow(rows[0]); + const sep = + "| " + + Array.from(rows[0].querySelectorAll("th,td")) + .map(() => "---") + .join(" | ") + + " |"; + const body = rows.slice(1).map(toRow).join("\n"); + return `\n\n${header}\n${sep}\n${body}\n\n`; + } + + // ── Extract images from HTML ──────────────────────────────────────────────── + function extractImages(element) { + const seen = new Set(); + const imgs = Array.from(element.querySelectorAll("img")); + return imgs + .filter((img) => { + const src = img.getAttribute("src") || ""; + if (!src) return false; + // skip tiny icons / tracking pixels by rendered size + const w = img.naturalWidth || img.width || 0; + const h = img.naturalHeight || img.height || 0; + if (w > 0 && w < 32 && h > 0 && h < 32) return false; + // skip 1x1 gif trackers + if (src.startsWith("data:image/gif")) return false; + return true; + }) + .map((img) => { + const src = img.getAttribute("src") || ""; + let abs = src; + try { abs = new URL(src, location.href).href; } catch {} + return { src: abs, alt: img.getAttribute("alt") || "" }; + }) + .filter((img) => { + if (seen.has(img.src)) return false; + seen.add(img.src); + return true; + }); + } + + // ── Message handler ───────────────────────────────────────────────────────── + chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { + if (msg.action === "getContent") { + try { + let markdown = ""; + let images = []; + let title = document.title || location.href; + + if (msg.mode === "selection") { + const sel = window.getSelection(); + if (sel && sel.rangeCount > 0) { + const frag = sel.getRangeAt(0).cloneContents(); + const div = document.createElement("div"); + div.appendChild(frag); + markdown = htmlToMarkdown(div, true); + images = extractImages(div); + } else { + markdown = ""; + } + } else { + // full page — prefer article/main, fall back to body + const root = + document.querySelector("article") || + document.querySelector("main") || + document.querySelector('[role="main"]') || + document.body; + markdown = htmlToMarkdown(root); + images = extractImages(root); + } + + // Prepend source line + const sourceNote = `> Source: [${title}](${location.href})\n\n`; + markdown = sourceNote + markdown; + + sendResponse({ ok: true, markdown, images, title, url: location.href }); + } catch (e) { + sendResponse({ ok: false, error: e.message }); + } + return true; // async + } + }); +})(); +} // end guard diff --git a/src/icons/icon128.png b/src/icons/icon128.png new file mode 100644 index 0000000..e434d52 Binary files /dev/null and b/src/icons/icon128.png differ diff --git a/src/icons/icon16.png b/src/icons/icon16.png new file mode 100644 index 0000000..ee76939 Binary files /dev/null and b/src/icons/icon16.png differ diff --git a/src/icons/icon48.png b/src/icons/icon48.png new file mode 100644 index 0000000..b551fdf Binary files /dev/null and b/src/icons/icon48.png differ diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 0000000..c5af39a --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,38 @@ +{ + "manifest_version": 3, + "name": "Memos Clipper", + "version": "1.0.0", + "description": "Clip web pages or selections as Markdown to your usememos instance", + "permissions": [ + "activeTab", + "scripting", + "storage" + ], + "host_permissions": [ + "" + ], + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + }, + "background": { + "service_worker": "background.js" + }, + "options_page": "settings.html", + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"], + "run_at": "document_idle" + } + ] +} diff --git a/src/marked.min.js b/src/marked.min.js new file mode 100644 index 0000000..0826999 --- /dev/null +++ b/src/marked.min.js @@ -0,0 +1,146 @@ +/*! + * marked-mini - minimal Markdown renderer for Memos Clipper + * Supports: headings, bold, italic, strike, code, pre, blockquote, + * links, images, ul, ol, hr, tables, line breaks + */ +(function(global){ +"use strict"; +function escape(s){return s.replace(/&/g,"&").replace(//g,">").replace(/"/g,""");} + +function parseInline(s){ + // code spans + s = s.replace(/`([^`]+)`/g,(_,c)=>`${escape(c)}`); + // bold+italic + s = s.replace(/\*\*\*(.+?)\*\*\*/g,"$1"); + // bold + s = s.replace(/\*\*(.+?)\*\*/g,"$1"); + s = s.replace(/__(.+?)__/g,"$1"); + // italic + s = s.replace(/\*(.+?)\*/g,"$1"); + s = s.replace(/_([^_]+)_/g,"$1"); + // strikethrough + s = s.replace(/~~(.+?)~~/g,"$1"); + // images + s = s.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,(_,alt,src)=>`${escape(alt)}`); + // links + s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(_,txt,href)=>`${txt}`); + // auto-link + s = s.replace(/(?)]+)/g,url=>`${escape(url)}`); + // hard break + s = s.replace(/ \n/g,"
"); + return s; +} + +function parse(src){ + const lines = src.split("\n"); + const out = []; + let i = 0; + + while(i < lines.length){ + const line = lines[i]; + + // Fenced code block + const fenceM = line.match(/^```(\w*)/); + if(fenceM){ + const lang = fenceM[1]||""; + const code = []; + i++; + while(i${escape(code.join("\n"))}`); + continue; + } + + // Headings + const hM = line.match(/^(#{1,6})\s+(.*)/); + if(hM){ + const lvl = hM[1].length; + out.push(`${parseInline(hM[2])}`); + i++; continue; + } + + // HR + if(/^[-*_]{3,}\s*$/.test(line)){ + out.push("
"); + i++; continue; + } + + // Blockquote + if(line.startsWith("> ")){ + const bq = []; + while(i ")){ + bq.push(lines[i].slice(2)); + i++; + } + out.push(`
${parse(bq.join("\n"))}
`); + continue; + } + + // Unordered list + if(/^[-*+]\s/.test(line)){ + const items = []; + while(i${parseInline(lines[i].replace(/^[-*+]\s/,""))}`); + i++; + } + out.push(`
    ${items.join("")}
`); + continue; + } + + // Ordered list + if(/^\d+\.\s/.test(line)){ + const items = []; + while(i${parseInline(lines[i].replace(/^\d+\.\s/,""))}`); + i++; + } + out.push(`
    ${items.join("")}
`); + continue; + } + + // Table (| col | col |) + if(line.startsWith("|") && i+1idx>0&&idx`${parseInline(h.trim())}`); + i+=2; // skip header + separator + const rows = []; + while(iidx>0&&idx`${parseInline(c.trim())}`); + rows.push(`${cells.join("")}`); + i++; + } + out.push(`${headers.join("")}${rows.join("")}
`); + continue; + } + + // Empty line + if(line.trim()===""){ + i++; continue; + } + + // Paragraph — collect until blank line + const para = []; + while(i|[-*+]\s|\d+\.\s|[-*_]{3,}\s*$)/.test(lines[i])) break; + para.push(lines[i]); + i++; + } + if(para.length) out.push(`

${parseInline(para.join(" "))}

`); + } + return out.join("\n"); +} + +global.marked = { + parse: function(src, opts) { + const breaks = opts && opts.breaks; + // With breaks:true, single newlines in paragraphs become
+ if (breaks) { + // Pre-process: single \n inside paragraph text → two spaces + \n (markdown hard break) + src = src.replace(/([^\n])\n([^\n])/g, "$1 \n$2"); + } + return parse(src); + } +};})(window); diff --git a/src/popup.css b/src/popup.css new file mode 100644 index 0000000..fcf92fe --- /dev/null +++ b/src/popup.css @@ -0,0 +1,404 @@ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap'); + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --bg: #0f0f11; + --surface: #1a1a1f; + --surface2: #22222a; + --border: #2a2a35; + --accent: #7c6af7; + --accent-dim: #7c6af720; + --accent-hover: #9585fa; + --text: #e8e8f0; + --text-dim: #777788; + --text-muted: #444455; + --success: #4ade80; + --error: #f87171; + --radius: 6px; + --font: 'IBM Plex Sans', sans-serif; + --mono: 'IBM Plex Mono', monospace; +} + +html, body { + width: 440px; + min-height: 200px; + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 13px; + line-height: 1.5; + overflow-x: hidden; +} + +/* ── Views ── */ +.view { display: flex; flex-direction: column; } +.view.hidden { display: none !important; } + +/* ── Loading ── */ +#view-loading { + align-items: center; + justify-content: center; + gap: 12px; + padding: 48px; + color: var(--text-dim); + font-size: 13px; +} +.spinner { + width: 22px; height: 22px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin .7s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + +/* ── Setup / error / success ── */ +.setup-box, .success-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 40px 32px; + text-align: center; + color: var(--text-dim); + font-size: 13px; +} +.setup-icon { + width: 44px; height: 44px; + border-radius: 50%; + display: flex; align-items: center; justify-content: center; + background: var(--surface); + border: 1px solid var(--border); +} +.setup-icon svg { stroke: var(--text-dim); } +.setup-icon.err { color: var(--error); font-size: 20px; font-weight: 700; } + +.success-icon { + width: 44px; height: 44px; + border-radius: 50%; + background: #4ade8015; + border: 1px solid #4ade8040; + color: var(--success); + font-size: 22px; + display: flex; align-items: center; justify-content: center; +} +.success-box p { font-size: 14px; font-weight: 600; color: var(--text); } +.success-box a { color: var(--accent); text-decoration: none; font-size: 12px; } +.success-box a:hover { text-decoration: underline; } + +/* ── Header ── */ +header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 14px 10px; + border-bottom: 1px solid var(--border); + background: var(--surface); +} +.logo { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + font-size: 13px; + color: var(--text); +} +.logo svg { stroke: var(--accent); flex-shrink: 0; } +.page-title { + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 400; + font-size: 12px; + color: var(--text-dim); +} +.header-actions { display: flex; gap: 4px; } +.icon-btn { + background: transparent; + border: 1px solid transparent; + border-radius: var(--radius); + padding: 5px 6px; + cursor: pointer; + color: var(--text-dim); + display: flex; align-items: center; + transition: all .12s; +} +.icon-btn:hover { border-color: var(--border); color: var(--text); background: var(--surface2); } +.icon-btn svg { display: block; } + +/* ── Tabs ── */ +.tabs { + display: flex; + position: relative; + border-bottom: 1px solid var(--border); + background: var(--surface); + padding: 0 14px; + gap: 2px; +} +.tab { + background: none; + border: none; + color: var(--text-dim); + font-family: var(--font); + font-size: 12px; + font-weight: 500; + padding: 8px 12px; + cursor: pointer; + position: relative; + transition: color .12s; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} +.tab:hover { color: var(--text); } +.tab.active { color: var(--accent); border-bottom-color: var(--accent); } + +/* ── Tab panels ── */ +.tab-panel { flex: 1; } +.tab-panel.hidden { display: none; } + +#md-editor { + display: block; + width: 100%; + height: 260px; + background: var(--bg); + border: none; + border-bottom: 1px solid var(--border); + color: var(--text); + font-family: var(--mono); + font-size: 12px; + line-height: 1.7; + padding: 14px; + resize: none; + outline: none; +} +#md-editor::placeholder { color: var(--text-muted); } + +.preview-body { + height: 260px; + overflow-y: auto; + padding: 14px 16px; + border-bottom: 1px solid var(--border); + background: var(--bg); + font-size: 13px; + line-height: 1.7; +} + +/* preview markdown styles */ +.preview-body h1 { font-size: 18px; margin: 0 0 12px; border-bottom: 1px solid var(--border); padding-bottom: 6px; } +.preview-body h2 { font-size: 15px; margin: 16px 0 8px; } +.preview-body h3 { font-size: 13px; font-weight: 600; margin: 14px 0 6px; } +.preview-body p { margin: 0 0 10px; } +.preview-body blockquote { + border-left: 3px solid var(--accent); + margin: 10px 0; + padding: 4px 12px; + color: var(--text-dim); + background: var(--accent-dim); + border-radius: 0 4px 4px 0; +} +.preview-body code { + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 3px; + padding: 1px 5px; + font-family: var(--mono); + font-size: 11px; +} +.preview-body pre { + background: var(--surface2); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 10px 12px; + overflow-x: auto; + margin: 10px 0; +} +.preview-body pre code { background: none; border: none; padding: 0; } +.preview-body a { color: var(--accent); } +.preview-body img { max-width: 100%; border-radius: 4px; margin: 8px 0; } +.preview-body table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 12px; } +.preview-body th, .preview-body td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; } +.preview-body th { background: var(--surface2); } +.preview-body hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; } +.preview-body ul, .preview-body ol { padding-left: 20px; margin: 0 0 10px; } + +/* ── Images section ── */ +#images-section { + border-bottom: 1px solid var(--border); + background: var(--surface); +} +.images-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 14px; + font-size: 12px; + color: var(--text-dim); +} +.badge { + display: inline-block; + background: var(--accent-dim); + color: var(--accent); + border-radius: 10px; + padding: 1px 6px; + font-size: 10px; + font-weight: 600; + margin-left: 4px; +} +.toggle-label { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + font-size: 11px; +} +.toggle-label input { accent-color: var(--accent); cursor: pointer; } + +#images-list { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 0 14px 10px; + max-height: 90px; + overflow-y: auto; +} +.img-chip { + display: flex; + align-items: center; + gap: 5px; + background: var(--surface2); + border: 1px solid var(--border); + border-radius: 4px; + padding: 3px 8px 3px 5px; + font-size: 11px; + color: var(--text-dim); + max-width: 160px; +} +.img-chip img { + width: 20px; height: 20px; + object-fit: cover; + border-radius: 2px; + flex-shrink: 0; +} +.img-chip span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} +.img-chip .remove-img { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 13px; + line-height: 1; + padding: 0; + flex-shrink: 0; +} +.img-chip .remove-img:hover { color: var(--error); } +.img-chip.skipped { opacity: .4; } + +/* ── Tags row ── */ +#tags-row { + padding: 6px 14px; + border-bottom: 1px solid var(--border); + background: var(--surface); +} +#tags-input { + width: 100%; + background: transparent; + border: none; + outline: none; + color: var(--accent); + font-family: var(--mono); + font-size: 12px; + padding: 2px 0; +} +#tags-input::placeholder { color: var(--text-muted); font-family: var(--font); } + +/* ── Char counter ── */ +.char-counter { + padding: 3px 14px; + font-size: 10px; + color: var(--text-muted); + font-family: var(--mono); + text-align: right; + background: var(--bg); + border-bottom: 1px solid var(--border); +} + +/* ── Footer ── */ +footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 14px; + background: var(--surface); + border-top: 1px solid var(--border); + gap: 8px; +} +.footer-left, .footer-right { + display: flex; + align-items: center; + gap: 8px; +} + +select { + background: var(--surface2); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-dim); + font-family: var(--font); + font-size: 12px; + padding: 5px 8px; + outline: none; + cursor: pointer; + transition: border-color .12s; +} +select:focus { border-color: var(--accent); } +select option { background: var(--surface); } + +.secondary-btn { + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-dim); + font-family: var(--font); + font-size: 12px; + padding: 6px 10px; + cursor: pointer; + transition: all .12s; +} +.secondary-btn:hover { border-color: var(--accent); color: var(--accent); } + +.send-btn { + display: flex; + align-items: center; + gap: 6px; + background: var(--accent); + border: none; + border-radius: var(--radius); + color: #fff; + font-family: var(--font); + font-size: 12px; + font-weight: 600; + padding: 7px 14px; + cursor: pointer; + transition: background .12s, transform .08s; +} +.send-btn:hover { background: var(--accent-hover); } +.send-btn:active { transform: scale(.97); } +.send-btn:disabled { opacity: .5; cursor: default; } +.send-btn svg { flex-shrink: 0; } + +/* scrollbar */ +::-webkit-scrollbar { width: 4px; height: 4px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } +::-webkit-scrollbar-thumb:hover { background: var(--text-muted); } + +/* mode badge */ +#mode-toggle.mode-selection { color: var(--accent); background: var(--accent-dim); border-color: var(--accent); } diff --git a/src/popup.html b/src/popup.html new file mode 100644 index 0000000..85640eb --- /dev/null +++ b/src/popup.html @@ -0,0 +1,135 @@ + + + + + Memos Clipper + + + + + +
+
+ Extracting content… +
+ + + + + + + + + + + + + + + + + diff --git a/src/popup.js b/src/popup.js new file mode 100644 index 0000000..5e3b93c --- /dev/null +++ b/src/popup.js @@ -0,0 +1,398 @@ +// popup.js +"use strict"; + +// ── State ─────────────────────────────────────────────────────────────────── +const state = { + markdown: "", + images: [], // {src, alt, keep: bool} + mode: "page", // "page" | "selection" + settings: null, +}; + +// ── DOM refs ───────────────────────────────────────────────────────────────── +const views = { + loading: document.getElementById("view-loading"), + setup: document.getElementById("view-setup"), + main: document.getElementById("view-main"), + success: document.getElementById("view-success"), + error: document.getElementById("view-error"), +}; + +const mdEditor = document.getElementById("md-editor"); +const mdPreview = document.getElementById("md-preview"); +const imagesList = document.getElementById("images-list"); +const imgCount = document.getElementById("img-count"); +const pageTitle = document.getElementById("page-title"); +const visibilityEl = document.getElementById("visibility-select"); +const attachCheck = document.getElementById("attach-images"); +const modeToggle = document.getElementById("mode-toggle"); +const sendBtn = document.getElementById("send-btn"); +const reloadBtn = document.getElementById("reload-btn"); +const errorMsg = document.getElementById("error-msg"); + +// ── Helpers ─────────────────────────────────────────────────────────────────── +function showView(name) { + Object.values(views).forEach((v) => v.classList.add("hidden")); + views[name].classList.remove("hidden"); +} + +async function getSettings() { + return chrome.storage.sync.get([ + "memosUrl", "apiToken", "visibility", "clipMode", "includeImages", "includeTags" + ]); +} + +async function getActiveTab() { + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + return tab; +} + +// ── Boot ────────────────────────────────────────────────────────────────────── +(async () => { + showView("loading"); + state.settings = await getSettings(); + + if (!state.settings.memosUrl || !state.settings.apiToken) { + showView("setup"); + return; + } + + // Apply saved defaults + state.mode = state.settings.clipMode || "page"; + if (state.settings.visibility) visibilityEl.value = state.settings.visibility; + if (state.settings.includeImages === false) attachCheck.checked = false; + + updateModeButton(); + await clip(); +})(); + +// ── Clip page ───────────────────────────────────────────────────────────────── +async function clip() { + showView("loading"); + try { + const tab = await getActiveTab(); + pageTitle.textContent = tab.title || tab.url; + + // Ensure content script is available (MV3 — inject on demand) + try { + await chrome.scripting.executeScript({ + target: { tabId: tab.id }, + files: ["content.js"], + }); + } catch (_) { /* already injected or restricted page */ } + + const response = await new Promise((resolve, reject) => { + chrome.tabs.sendMessage(tab.id, { action: "getContent", mode: state.mode }, (res) => { + if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message)); + else resolve(res); + }); + }); + + if (!response || !response.ok) { + throw new Error(response?.error || "Content script did not respond. Try reloading the page."); + } + + state.markdown = response.markdown; + // Warn if selection mode returned nothing + if (state.mode === "selection" && !response.markdown.replace(/^>.*\n\n/, "").trim()) { + state.markdown = response.markdown + "\n\n> ⚠️ No text was selected. Select some text on the page first, or switch to full-page mode."; + } + state.images = response.images.map((img) => ({ ...img, keep: true })); + + mdEditor.value = state.markdown; + updateCharCounter(); + renderImages(); + showView("main"); + switchTab("edit"); + } catch (e) { + errorMsg.textContent = e.message; + showView("error"); + } +} + +// ── Tabs ────────────────────────────────────────────────────────────────────── +function switchTab(name) { + document.querySelectorAll(".tab").forEach((t) => t.classList.remove("active")); + document.querySelectorAll(".tab-panel").forEach((p) => p.classList.add("hidden")); + document.querySelector(`.tab[data-tab="${name}"]`).classList.add("active"); + document.getElementById(`tab-${name}`).classList.remove("hidden"); + if (name === "preview") renderPreview(); +} + +document.querySelectorAll(".tab").forEach((btn) => { + btn.addEventListener("click", () => switchTab(btn.dataset.tab)); +}); + +// ── Preview ─────────────────────────────────────────────────────────────────── +function renderPreview() { + state.markdown = mdEditor.value; + if (typeof marked !== "undefined") { + mdPreview.innerHTML = marked.parse(state.markdown, { breaks: true }); + } else { + // Fallback: basic escaping + mdPreview.innerHTML = `
${escHtml(state.markdown)}
`; + } +} + +function escHtml(s) { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +// ── Images ───────────────────────────────────────────────────────────────────── +function renderImages() { + // Deduplicate by src, exclude oversized data: URIs + const seen = new Set(); + const visible = state.images.filter((img) => { + if (seen.has(img.src)) return false; + seen.add(img.src); + if (img.src.startsWith("data:") && img.src.length >= 500_000) return false; + return true; + }); + + // Sync keep flags back (dedup may have removed some) + state.images = visible; + + const uploadable = visible.filter((img) => !img.src.startsWith("data:")).length; + imgCount.textContent = uploadable; + imagesList.innerHTML = ""; + + if (!visible.length) { + document.getElementById("images-section").style.display = "none"; + return; + } + document.getElementById("images-section").style.display = ""; + + visible.forEach((img, i) => { + const isDataUri = img.src.startsWith("data:"); + const chip = document.createElement("div"); + chip.className = "img-chip" + (img.keep ? "" : " skipped"); + + const thumb = document.createElement("img"); + thumb.src = img.src; + thumb.onerror = () => { thumb.style.display = "none"; }; + + const label = document.createElement("span"); + const name = isDataUri + ? `inline-image-${i + 1}` + : (img.src.split("/").pop().split("?")[0].slice(0, 28) || `image-${i + 1}`); + label.textContent = name; + if (isDataUri) label.title = "Embedded data URI — cannot be uploaded"; + + const remove = document.createElement("button"); + remove.className = "remove-img"; + remove.textContent = "×"; + remove.title = img.keep ? "Exclude this image" : "Include this image"; + if (isDataUri) { + remove.style.display = "none"; // data URIs can't be uploaded anyway + } + remove.addEventListener("click", () => { + img.keep = !img.keep; + chip.classList.toggle("skipped", !img.keep); + remove.title = img.keep ? "Exclude this image" : "Include this image"; + const kept = state.images.filter((im) => im.keep && !im.src.startsWith("data:")).length; + imgCount.textContent = kept; + }); + + chip.append(thumb, label, remove); + imagesList.appendChild(chip); + }); +} + +// ── Char counter ───────────────────────────────────────────────────────────── +const charCounter = document.getElementById("char-counter"); +function updateCharCounter() { + const n = mdEditor.value.length; + charCounter.textContent = `${n.toLocaleString()} char${n !== 1 ? "s" : ""}`; +} +mdEditor.addEventListener("input", updateCharCounter); + +// ── Tags input ──────────────────────────────────────────────────────────────── +const tagsInput = document.getElementById("tags-input"); + +// Load saved tags default +getSettings().then((s) => { + if (s.includeTags) tagsInput.value = "#clipped"; +}); + +// ── Mode toggle ─────────────────────────────────────────────────────────────── +function updateModeButton() { + modeToggle.title = state.mode === "page" + ? "Currently: full page — click for selection mode" + : "Currently: selection — click for full page mode"; + modeToggle.classList.toggle("mode-selection", state.mode === "selection"); +} + +modeToggle.addEventListener("click", async () => { + state.mode = state.mode === "page" ? "selection" : "page"; + updateModeButton(); + await clip(); +}); + +// ── Reload ──────────────────────────────────────────────────────────────────── +reloadBtn.addEventListener("click", () => clip()); + +// ── Settings buttons ─────────────────────────────────────────────────────────── +document.getElementById("settings-btn").addEventListener("click", () => { + chrome.runtime.openOptionsPage(); +}); +document.getElementById("open-settings-btn").addEventListener("click", () => { + chrome.runtime.openOptionsPage(); +}); +document.getElementById("retry-btn").addEventListener("click", () => clip()); +document.getElementById("new-clip-btn").addEventListener("click", () => clip()); + +// ── Send to Memos ───────────────────────────────────────────────────────────── +sendBtn.addEventListener("click", async () => { + state.markdown = mdEditor.value; + // Append tags from tag bar if any + const tagStr = tagsInput.value.trim(); + const finalContent = tagStr ? `${state.markdown}\n\n${tagStr}` : state.markdown; + const settings = await getSettings(); + const baseUrl = settings.memosUrl.replace(/\/$/, ""); + const token = settings.apiToken; + const visibility = visibilityEl.value; + + sendBtn.disabled = true; + + try { + let resourceNames = []; + + // 1. Upload images if requested + const imageMap = new Map(); // originalUrl -> resourceName + if (attachCheck.checked) { + const toUpload = state.images.filter((img) => img.keep && !img.src.startsWith("data:")); + let uploaded = 0; + for (const img of toUpload) { + sendBtn.textContent = `Uploading image ${uploaded + 1}/${toUpload.length}…`; + try { + const resource = await uploadImage(baseUrl, token, img); + if (resource) { + // v0.22+ uses resource.name; older APIs return resource.id + const name = resource.name || `resources/${resource.id}`; + resourceNames.push(name); + imageMap.set(img.src, name); + } + uploaded++; + } catch (e) { + console.warn("Failed to upload image:", img.src, e.message); + } + } + } + + sendBtn.textContent = "Creating memo…"; + + // 2. Create memo + // Try to replace original image URLs with local attachment URLs in the markdown + let contentWithImages = finalContent; + for (const [origUrl, resName] of imageMap.entries()) { + const urlPart = resName.startsWith("resources/") ? resName : `resources/${resName}`; + const attachmentUrl = `${baseUrl}/file/${urlPart}`; + // Escape for regex + const escapedUrl = origUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const re = new RegExp(`!\\[(.*?)\\]\\(${escapedUrl}\\)`, 'g'); + contentWithImages = contentWithImages.replace(re, `![$1](${attachmentUrl})`); + } + + // Append any uploaded images that weren't already in the markdown (e.g. from gallery) + for (const resName of imageMap.values()) { + const urlPart = resName.startsWith("resources/") ? resName : `resources/${resName}`; + if (!contentWithImages.includes(`/file/${urlPart}`)) { + contentWithImages += `\n\n![attachment](${baseUrl}/file/${urlPart})`; + } + } + + const body = { + content: contentWithImages, + visibility, + }; + // Also set resources array for native attachment display (v0.22+) + if (resourceNames.length) { + body.resources = resourceNames.map((name) => { + const norm = name.startsWith("resources/") ? name : `resources/${name}`; + return { name: norm }; + }); + // Backward compatibility: some versions use resourceIdList (array of ints) + const resourceIds = resourceNames + .map(n => parseInt(n.replace("resources/", ""))) + .filter(id => !isNaN(id)); + if (resourceIds.length === resourceNames.length) { + body.resourceIdList = resourceIds; + } + } + + const res = await fetch(`${baseUrl}/api/v1/memos`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(body), + }); + + if (!res.ok) { + const txt = await res.text(); + throw new Error(`API error ${res.status}: ${txt.slice(0, 200)}`); + } + + const memo = await res.json(); + // Handle both v0.22+ (memo.name = "memos/123") and older (memo.id = "123" or memo.uid) + let memoId = ""; + if (memo.name) { + memoId = memo.name.replace(/^memos\//, ""); + } else if (memo.uid) { + memoId = memo.uid; + } else if (memo.id) { + memoId = memo.id; + } + + document.getElementById("memo-link").href = memoId ? `${baseUrl}/memos/${memoId}` : baseUrl; + showView("success"); + } catch (e) { + errorMsg.textContent = e.message; + showView("error"); + } finally { + sendBtn.disabled = false; + sendBtn.innerHTML = ` Send to Memos`; + } +}); + +// ── Upload a single image resource ──────────────────────────────────────────── +async function uploadImage(baseUrl, token, img) { + const response = await fetch(img.src); + if (!response.ok) throw new Error(`Fetch failed: ${response.status}`); + const blob = await response.blob(); + + // Derive a filename with a valid extension + let filename = img.src.split("/").pop().split("?")[0].split("#")[0]; + // Strip non-filename characters + filename = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80); + // If no extension, infer from MIME type + if (!/\.\w{2,5}$/.test(filename)) { + const mimeToExt = { + "image/jpeg": "jpg", "image/jpg": "jpg", "image/png": "png", + "image/gif": "gif", "image/webp": "webp", "image/svg+xml": "svg", + "image/avif": "avif","image/tiff": "tiff", + }; + const ext = mimeToExt[blob.type] || "jpg"; + filename = (filename || "image") + "." + ext; + } + if (!filename || filename === ".") filename = "image.jpg"; + + const formData = new FormData(); + formData.append("file", blob, filename); + + const res = await fetch(`${baseUrl}/api/v1/resources`, { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + body: formData, + }); + if (!res.ok) { + const txt = await res.text(); + throw new Error(`Resource upload failed ${res.status}: ${txt.slice(0, 120)}`); + } + const resource = await res.json(); + // Normalise the returned name — could be "resources/abc", "abc", or an int id + if (resource.name) return resource; + if (resource.id) return { ...resource, name: `resources/${resource.id}` }; + throw new Error("Resource upload returned unexpected shape: " + JSON.stringify(resource).slice(0, 80)); +} diff --git a/src/settings.css b/src/settings.css new file mode 100644 index 0000000..e7b6c9b --- /dev/null +++ b/src/settings.css @@ -0,0 +1,157 @@ +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap'); + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --bg: #0f0f11; + --surface: #1a1a1f; + --border: #2a2a35; + --accent: #7c6af7; + --accent-dim: #7c6af730; + --text: #e8e8f0; + --text-dim: #888899; + --success: #4ade80; + --error: #f87171; + --radius: 8px; + --font: 'IBM Plex Sans', sans-serif; + --mono: 'IBM Plex Mono', monospace; +} + +body { + font-family: var(--font); + background: var(--bg); + color: var(--text); + min-height: 100vh; + font-size: 14px; + line-height: 1.6; +} + +.page { max-width: 560px; margin: 0 auto; padding: 32px 24px; } + +header { + margin-bottom: 32px; +} + +.logo { + display: flex; + align-items: center; + gap: 10px; + font-size: 16px; + font-weight: 600; + color: var(--text); +} +.logo svg { width: 22px; height: 22px; stroke: var(--accent); } + +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 12px; + padding: 24px; + margin-bottom: 20px; +} + +.card h2 { + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: .08em; + color: var(--text-dim); + margin-bottom: 20px; +} + +label { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 16px; +} + +label > span { + font-size: 13px; + font-weight: 500; + color: var(--text); +} + +input[type="url"], +input[type="password"], +input[type="text"], +select { + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text); + font-family: var(--mono); + font-size: 13px; + padding: 9px 12px; + outline: none; + transition: border-color .15s; + width: 100%; +} + +input:focus, select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-dim); +} + +select option { background: var(--surface); } + +small { + font-size: 11px; + color: var(--text-dim); + font-family: var(--font); +} + +.checkbox-label { + flex-direction: row; + align-items: center; + gap: 10px; + cursor: pointer; +} +.checkbox-label input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + cursor: pointer; +} +.checkbox-label span { font-size: 13px; font-weight: 400; } + +.actions { + display: flex; + gap: 10px; + margin-top: 20px; +} + +button { + font-family: var(--font); + font-size: 13px; + font-weight: 500; + padding: 9px 18px; + border-radius: var(--radius); + border: none; + cursor: pointer; + transition: all .15s; +} + +button:not(.secondary) { + background: var(--accent); + color: #fff; +} +button:not(.secondary):hover { filter: brightness(1.15); } + +button.secondary { + background: transparent; + border: 1px solid var(--border); + color: var(--text-dim); +} +button.secondary:hover { border-color: var(--accent); color: var(--accent); } + +.status { + margin-top: 12px; + padding: 9px 12px; + border-radius: var(--radius); + font-size: 12px; + font-family: var(--mono); +} +.status.hidden { display: none; } +.status.ok { background: #4ade8015; border: 1px solid #4ade8040; color: var(--success); } +.status.err { background: #f8717115; border: 1px solid #f8717140; color: var(--error); } diff --git a/src/settings.html b/src/settings.html new file mode 100644 index 0000000..ace1579 --- /dev/null +++ b/src/settings.html @@ -0,0 +1,80 @@ + + + + + Memos Clipper — Settings + + + + +
+
+ +
+ +
+
+

Connection

+ + +
+ + +
+ +
+ +
+

Defaults

+ + +
+ + +
+
+ +
+ +
+
+
+ + + diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..900e10c --- /dev/null +++ b/src/settings.js @@ -0,0 +1,63 @@ +// settings.js +const $ = (id) => document.getElementById(id); + +async function load() { + const s = await chrome.storage.sync.get([ + "memosUrl", "apiToken", "visibility", "clipMode", "includeImages", "includeTags" + ]); + if (s.memosUrl) $("memos-url").value = s.memosUrl; + if (s.apiToken) $("api-token").value = s.apiToken; + if (s.visibility) $("visibility").value = s.visibility; + if (s.clipMode) $("clip-mode").value = s.clipMode; + if (s.includeImages !== undefined) $("include-images").checked = s.includeImages; + if (s.includeTags !== undefined) $("include-tags").checked = s.includeTags; +} + +function showStatus(el, ok, msg) { + el.textContent = msg; + el.className = `status ${ok ? "ok" : "err"}`; +} + +$("save-btn").addEventListener("click", async () => { + const url = $("memos-url").value.trim().replace(/\/$/, ""); + const token = $("api-token").value.trim(); + if (!url || !token) { + showStatus($("status"), false, "URL and token are required."); + return; + } + await chrome.storage.sync.set({ memosUrl: url, apiToken: token }); + showStatus($("status"), true, "✓ Saved."); +}); + +$("save-defaults-btn").addEventListener("click", async () => { + await chrome.storage.sync.set({ + visibility: $("visibility").value, + clipMode: $("clip-mode").value, + includeImages: $("include-images").checked, + includeTags: $("include-tags").checked, + }); + showStatus($("defaults-status"), true, "✓ Defaults saved."); +}); + +$("test-btn").addEventListener("click", async () => { + const url = $("memos-url").value.trim().replace(/\/$/, ""); + const token = $("api-token").value.trim(); + $("test-btn").textContent = "Testing…"; + try { + const res = await fetch(`${url}/api/v1/memos?pageSize=1`, { + headers: { Authorization: `Bearer ${token}` } + }); + if (res.ok) { + showStatus($("status"), true, `✓ Connected! (HTTP ${res.status})`); + } else { + const txt = await res.text(); + showStatus($("status"), false, `✗ HTTP ${res.status}: ${txt.slice(0, 120)}`); + } + } catch (e) { + showStatus($("status"), false, `✗ ${e.message}`); + } finally { + $("test-btn").textContent = "Test Connection"; + } +}); + +load(); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..5c00348 --- /dev/null +++ b/src/style.css @@ -0,0 +1,9 @@ +@import "tailwindcss"; +@plugin "@tailwindcss/typography"; + +@source "./**/*.{html,js}"; + +@theme { + --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif; + --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..11343b5 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,32 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; + +export default defineConfig({ + root: 'src', + build: { + outDir: '../dist', + emptyOutDir: true, + rollupOptions: { + input: { + popup: resolve(__dirname, 'src/popup.html'), + settings: resolve(__dirname, 'src/settings.html'), + background: resolve(__dirname, 'src/background.js'), + content: resolve(__dirname, 'src/content.js'), + }, + output: { + entryFileNames: '[name].js', + assetFileNames: 'assets/[name].[ext]', + }, + }, + }, + plugins: [ + viteStaticCopy({ + targets: [ + { src: 'manifest.json', dest: '.' }, + { src: 'icons/*', dest: 'icons' }, + { src: 'marked.min.js', dest: '.' } + ] + }) + ] +});