mirror of
https://github.com/ld3z/new-manga.git
synced 2026-11-09 21:03:45 -07:00
Compare commits
4 commits
e66faac5b0
...
90f0e2417e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90f0e2417e | ||
|
|
547880a5f5 | ||
|
|
f465484b1c | ||
|
|
530866ae9d |
6 changed files with 304 additions and 332 deletions
270
package-lock.json
generated
270
package-lock.json
generated
|
|
@ -10,12 +10,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/rss": "^4.0.11",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/tailwind": "^6.0.0",
|
"@astrojs/tailwind": "^6.0.0",
|
||||||
"@astrojs/vercel": "^8.1.1",
|
"@astrojs/vercel": "^8.1.2",
|
||||||
"@floating-ui/dom": "^1.6.13",
|
"@floating-ui/dom": "^1.6.13",
|
||||||
"@types/node": "^22.13.9",
|
"@types/node": "^22.13.10",
|
||||||
"@vercel/analytics": "^1.5.0",
|
"@vercel/analytics": "^1.5.0",
|
||||||
"@vercel/kv": "^3.0.0",
|
"astro": "^5.4.3",
|
||||||
"astro": "^5.4.2",
|
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"ioredis": "^5.6.0",
|
"ioredis": "^5.6.0",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
|
|
@ -38,16 +37,16 @@
|
||||||
"integrity": "sha512-86B3QGagP99MvSNwuJGiYSBHnh8nLvm2Q1IFI15wIUJJsPeQTO3eb2uwBmrqRsXykeR/mBzH8XCgz5AAt1BJrQ=="
|
"integrity": "sha512-86B3QGagP99MvSNwuJGiYSBHnh8nLvm2Q1IFI15wIUJJsPeQTO3eb2uwBmrqRsXykeR/mBzH8XCgz5AAt1BJrQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/internal-helpers": {
|
"node_modules/@astrojs/internal-helpers": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.6.1.tgz",
|
||||||
"integrity": "sha512-XgHIJDQaGlFnTr0sDp1PiJrtqsWzbHP2qkTU+JpQ8SnBewKP2IKOe/wqCkl0CyfyRXRu3TSWu4t/cpYMVfuBNA=="
|
"integrity": "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/markdown-remark": {
|
"node_modules/@astrojs/markdown-remark": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.2.1.tgz",
|
||||||
"integrity": "sha512-LUDjgd9p1yG0qTFSocaj3GOLmZs8Hsw/pNtvqzvNY58Acebxvb/46vDO/e/wxYgsKgIfWS+p+ZI5SfOjoVrbCg==",
|
"integrity": "sha512-qtQXfZXeG84XSH9bMgG2e/kZfA4J7U19PKjhmFDNsKX47nautSHC0DitvxaWgQFSED66k6hWKDHLq3VKHCy/rg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/internal-helpers": "0.6.0",
|
"@astrojs/internal-helpers": "0.6.1",
|
||||||
"@astrojs/prism": "3.2.0",
|
"@astrojs/prism": "3.2.0",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"hast-util-from-html": "^2.0.3",
|
"hast-util-from-html": "^2.0.3",
|
||||||
|
|
@ -57,7 +56,7 @@
|
||||||
"mdast-util-definitions": "^6.0.0",
|
"mdast-util-definitions": "^6.0.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-stringify": "^10.0.1",
|
"rehype-stringify": "^10.0.1",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.1",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.1",
|
"remark-rehype": "^11.1.1",
|
||||||
"remark-smartypants": "^3.0.2",
|
"remark-smartypants": "^3.0.2",
|
||||||
|
|
@ -122,11 +121,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/vercel": {
|
"node_modules/@astrojs/vercel": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-8.1.2.tgz",
|
||||||
"integrity": "sha512-4tTib9LkAtg7tqac4L+pnWEveUmYyWRXpHivaVV3lHKeQjB28KUZ+YXtR/VUG6/xpMduo+OCOOg0l1zOPq/e4w==",
|
"integrity": "sha512-Qps7oYfQVvXov/ucP5sjTqRAgqbY84ayf8zyz5lNqTy88As09KGaZJTW4qrxU2xO5GdoE3+AoMGDUkU2rVY+cg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/internal-helpers": "0.6.0",
|
"@astrojs/internal-helpers": "0.6.1",
|
||||||
"@vercel/analytics": "^1.5.0",
|
"@vercel/analytics": "^1.5.0",
|
||||||
"@vercel/edge": "^1.2.1",
|
"@vercel/edge": "^1.2.1",
|
||||||
"@vercel/nft": "^0.29.2",
|
"@vercel/nft": "^0.29.2",
|
||||||
|
|
@ -1465,9 +1464,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.13.9",
|
"version": "22.13.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
|
||||||
"integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
|
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.20.0"
|
"undici-types": "~6.20.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1486,6 +1485,8 @@
|
||||||
"version": "1.34.3",
|
"version": "1.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.34.3.tgz",
|
"resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.34.3.tgz",
|
||||||
"integrity": "sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==",
|
"integrity": "sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-js": "^4.2.0"
|
"crypto-js": "^4.2.0"
|
||||||
}
|
}
|
||||||
|
|
@ -1532,17 +1533,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.1.tgz",
|
||||||
"integrity": "sha512-1++yncEyIAi68D3UEOlytYb1IUcIulMWdoSzX2h9LuSeeyR7JtaIgR8DcTQ6+DmYOQn+5MCh6LY+UmK6QBByNA=="
|
"integrity": "sha512-1++yncEyIAi68D3UEOlytYb1IUcIulMWdoSzX2h9LuSeeyR7JtaIgR8DcTQ6+DmYOQn+5MCh6LY+UmK6QBByNA=="
|
||||||
},
|
},
|
||||||
"node_modules/@vercel/kv": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/kv/-/kv-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==",
|
|
||||||
"dependencies": {
|
|
||||||
"@upstash/redis": "^1.34.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vercel/nft": {
|
"node_modules/@vercel/nft": {
|
||||||
"version": "0.29.2",
|
"version": "0.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.2.tgz",
|
||||||
|
|
@ -1754,13 +1744,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/astro": {
|
"node_modules/astro": {
|
||||||
"version": "5.4.2",
|
"version": "5.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/astro/-/astro-5.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/astro/-/astro-5.4.3.tgz",
|
||||||
"integrity": "sha512-9Z3fAniIRJaK/o43OroZA1wHUIU+qHiOR9ovlVT/2XQaN25QRXScIsKWlFp0G/zrx5OuuoJ+QnaoHHW061u26A==",
|
"integrity": "sha512-GKkOJQCHLx6CrPoghGhj7824WDSvIuuc+HTVjfjMPdB9axp238iJLByREJNDaSdzMeR/lC13xvBiUnKvcYyEIA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^2.10.4",
|
"@astrojs/compiler": "^2.10.4",
|
||||||
"@astrojs/internal-helpers": "0.6.0",
|
"@astrojs/internal-helpers": "0.6.1",
|
||||||
"@astrojs/markdown-remark": "6.2.0",
|
"@astrojs/markdown-remark": "6.2.1",
|
||||||
"@astrojs/telemetry": "3.2.0",
|
"@astrojs/telemetry": "3.2.0",
|
||||||
"@oslojs/encoding": "^1.1.0",
|
"@oslojs/encoding": "^1.1.0",
|
||||||
"@rollup/pluginutils": "^5.1.4",
|
"@rollup/pluginutils": "^5.1.4",
|
||||||
|
|
@ -1795,8 +1785,8 @@
|
||||||
"neotraverse": "^0.6.18",
|
"neotraverse": "^0.6.18",
|
||||||
"p-limit": "^6.2.0",
|
"p-limit": "^6.2.0",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
|
"package-manager-detector": "^1.0.0",
|
||||||
"picomatch": "^4.0.2",
|
"picomatch": "^4.0.2",
|
||||||
"preferred-pm": "^4.1.1",
|
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
"rehype": "^13.0.2",
|
"rehype": "^13.0.2",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
|
|
@ -1810,7 +1800,6 @@
|
||||||
"vfile": "^6.0.3",
|
"vfile": "^6.0.3",
|
||||||
"vite": "^6.2.0",
|
"vite": "^6.2.0",
|
||||||
"vitefu": "^1.0.6",
|
"vitefu": "^1.0.6",
|
||||||
"which-pm": "^3.0.1",
|
|
||||||
"xxhash-wasm": "^1.1.0",
|
"xxhash-wasm": "^1.1.0",
|
||||||
"yargs-parser": "^21.1.1",
|
"yargs-parser": "^21.1.1",
|
||||||
"yocto-spinner": "^0.2.1",
|
"yocto-spinner": "^0.2.1",
|
||||||
|
|
@ -2403,7 +2392,9 @@
|
||||||
"node_modules/crypto-js": {
|
"node_modules/crypto-js": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
@ -2433,9 +2424,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/decode-named-character-reference": {
|
"node_modules/decode-named-character-reference": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
|
||||||
"integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
|
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"character-entities": "^2.0.0"
|
"character-entities": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -2626,18 +2617,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esprima": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"bin": {
|
|
||||||
"esparse": "bin/esparse.js",
|
|
||||||
"esvalidate": "bin/esvalidate.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/estree-walker": {
|
"node_modules/estree-walker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
|
@ -2749,38 +2728,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/find-up": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
|
||||||
"dependencies": {
|
|
||||||
"locate-path": "^5.0.0",
|
|
||||||
"path-exists": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/find-up-simple": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/find-yarn-workspace-root2": {
|
|
||||||
"version": "1.2.16",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz",
|
|
||||||
"integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==",
|
|
||||||
"dependencies": {
|
|
||||||
"micromatch": "^4.0.2",
|
|
||||||
"pkg-dir": "^4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/flattie": {
|
"node_modules/flattie": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz",
|
||||||
|
|
@ -3343,59 +3290,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
},
|
},
|
||||||
"node_modules/load-yaml-file": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz",
|
|
||||||
"integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.1.5",
|
|
||||||
"js-yaml": "^3.13.0",
|
|
||||||
"pify": "^4.0.1",
|
|
||||||
"strip-bom": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/load-yaml-file/node_modules/argparse": {
|
|
||||||
"version": "1.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"dependencies": {
|
|
||||||
"sprintf-js": "~1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/load-yaml-file/node_modules/js-yaml": {
|
|
||||||
"version": "3.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"dependencies": {
|
|
||||||
"argparse": "^1.0.7",
|
|
||||||
"esprima": "^4.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"js-yaml": "bin/js-yaml.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/load-yaml-file/node_modules/pify": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/locate-path": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
|
||||||
"dependencies": {
|
|
||||||
"p-locate": "^4.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lodash.defaults": {
|
"node_modules/lodash.defaults": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
|
@ -4457,31 +4351,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-locate": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
|
||||||
"dependencies": {
|
|
||||||
"p-limit": "^2.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-locate/node_modules/p-limit": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
|
||||||
"dependencies": {
|
|
||||||
"p-try": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-queue": {
|
"node_modules/p-queue": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz",
|
||||||
|
|
@ -4508,19 +4377,16 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-try": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/package-json-from-dist": {
|
"node_modules/package-json-from-dist": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/package-manager-detector": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-7elnH+9zMsRo7aS72w6MeRugTpdRvInmEB4Kmm9BVvPw/SLG8gXUGQ+4wF0Mys0RSWPz0B9nuBbDe8vFeA2sfg=="
|
||||||
|
},
|
||||||
"node_modules/parse-latin": {
|
"node_modules/parse-latin": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz",
|
||||||
|
|
@ -4549,14 +4415,6 @@
|
||||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-exists": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
|
@ -4628,17 +4486,6 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pkg-dir": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"find-up": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
|
|
@ -4775,23 +4622,10 @@
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
"node_modules/preferred-pm": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-rU+ZAv1Ur9jAUZtGPebQVQPzdGhNzaEiQ7VL9+cjsAWPHFYOccNXPNiev1CCDSOg/2j7UujM7ojNhpkuILEVNQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"find-up-simple": "^1.0.0",
|
|
||||||
"find-yarn-workspace-root2": "1.2.16",
|
|
||||||
"which-pm": "^3.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prismjs": {
|
"node_modules/prismjs": {
|
||||||
"version": "1.29.0",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
|
@ -5370,11 +5204,6 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sprintf-js": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
|
||||||
},
|
|
||||||
"node_modules/standard-as-callback": {
|
"node_modules/standard-as-callback": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
|
@ -5481,14 +5310,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/strip-bom": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/strnum": {
|
"node_modules/strnum": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
|
||||||
|
|
@ -6074,17 +5895,6 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/which-pm": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/which-pm/-/which-pm-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-v2JrMq0waAI4ju1xU5x3blsxBBMgdgZve580iYMN5frDaLGjbA24fok7wKCsya8KLVO19Ju4XDc5+zTZCJkQfg==",
|
|
||||||
"dependencies": {
|
|
||||||
"load-yaml-file": "^0.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/which-pm-runs": {
|
"node_modules/which-pm-runs": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,11 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/rss": "^4.0.11",
|
"@astrojs/rss": "^4.0.11",
|
||||||
"@astrojs/tailwind": "^6.0.0",
|
"@astrojs/tailwind": "^6.0.0",
|
||||||
"@astrojs/vercel": "^8.1.1",
|
"@astrojs/vercel": "^8.1.2",
|
||||||
"@floating-ui/dom": "^1.6.13",
|
"@floating-ui/dom": "^1.6.13",
|
||||||
"@types/node": "^22.13.9",
|
"@types/node": "^22.13.10",
|
||||||
"@vercel/analytics": "^1.5.0",
|
"@vercel/analytics": "^1.5.0",
|
||||||
"@vercel/kv": "^3.0.0",
|
"astro": "^5.4.3",
|
||||||
"astro": "^5.4.2",
|
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"ioredis": "^5.6.0",
|
"ioredis": "^5.6.0",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,28 @@ interface Props {
|
||||||
|
|
||||||
const { announcement } = Astro.props;
|
const { announcement } = Astro.props;
|
||||||
|
|
||||||
const bgColorClass = {
|
// Define colors based on announcement type
|
||||||
info: 'bg-[rgb(var(--accent))] text-[rgb(var(--accent-text))]',
|
const typeStyles = {
|
||||||
warning: 'bg-yellow-500 text-white',
|
info: {
|
||||||
success: 'bg-emerald-500 text-white'
|
bgColor: 'bg-[rgb(var(--accent))]',
|
||||||
}[announcement.type];
|
textColor: 'text-[rgb(var(--accent-text))]',
|
||||||
|
icon: 'info-icon'
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
bgColor: 'bg-yellow-500',
|
||||||
|
textColor: 'text-white',
|
||||||
|
icon: 'warning-icon'
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
bgColor: 'bg-emerald-500',
|
||||||
|
textColor: 'text-white',
|
||||||
|
icon: 'success-icon'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create a simple hash of the announcement content
|
const { bgColor, textColor, icon } = typeStyles[announcement.type];
|
||||||
|
|
||||||
|
// Create a simple hash of the announcement content for localStorage key
|
||||||
const announcementHash = btoa(JSON.stringify({
|
const announcementHash = btoa(JSON.stringify({
|
||||||
message: announcement.message,
|
message: announcement.message,
|
||||||
type: announcement.type,
|
type: announcement.type,
|
||||||
|
|
@ -21,44 +36,79 @@ const announcementHash = btoa(JSON.stringify({
|
||||||
}));
|
}));
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class={`announcement-banner hidden w-full ${bgColorClass} px-6 py-4 shadow-lg`}>
|
<div
|
||||||
|
class={`announcement-banner hidden w-full ${bgColor} ${textColor} px-6 py-4 shadow-lg`}
|
||||||
|
role="alert"
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
<div class="container mx-auto flex items-center justify-between">
|
<div class="container mx-auto flex items-center justify-between">
|
||||||
<div class="flex items-center gap-3 text-lg">
|
<div class="flex items-center gap-3 text-lg">
|
||||||
|
{/* Info Icon */}
|
||||||
{announcement.type === 'info' && (
|
{announcement.type === 'info' && (
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path
|
||||||
</svg>
|
stroke-linecap="round"
|
||||||
)}
|
stroke-linejoin="round"
|
||||||
{announcement.type === 'warning' && (
|
stroke-width="2"
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
/>
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{announcement.type === 'success' && (
|
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Warning Icon */}
|
||||||
|
{announcement.type === 'warning' && (
|
||||||
|
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Success Icon */}
|
||||||
|
{announcement.type === 'success' && (
|
||||||
|
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Message */}
|
||||||
<span class="font-medium">{announcement.message}</span>
|
<span class="font-medium">{announcement.message}</span>
|
||||||
|
|
||||||
|
{/* Link if provided */}
|
||||||
{announcement.link && (
|
{announcement.link && (
|
||||||
<a
|
<a
|
||||||
href={announcement.link.url}
|
href={announcement.link.url}
|
||||||
class="underline hover:opacity-80 transition-opacity font-semibold ml-2"
|
class="underline hover:opacity-80 transition-opacity font-semibold ml-2"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{announcement.link.text} →
|
{announcement.link.text} →
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Dismiss button if announcement is dismissible */}
|
||||||
{announcement.dismissible && (
|
{announcement.dismissible && (
|
||||||
<button
|
<button
|
||||||
class="dismiss-announcement p-2 hover:bg-black/10 rounded-full transition-colors"
|
class="dismiss-announcement p-2 hover:bg-black/10 rounded-full transition-colors"
|
||||||
aria-label="Dismiss announcement"
|
aria-label="Dismiss announcement"
|
||||||
|
title="Dismiss"
|
||||||
>
|
>
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -66,11 +116,14 @@ const announcementHash = btoa(JSON.stringify({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script define:vars={{ dismissible: announcement.dismissible, announcementHash }}>
|
<script define:vars={{ dismissible: announcement.dismissible, announcementHash }}>
|
||||||
|
// DOM elements
|
||||||
const banner = document.querySelector('.announcement-banner');
|
const banner = document.querySelector('.announcement-banner');
|
||||||
const dismissBtn = document.querySelector('.dismiss-announcement');
|
const dismissBtn = document.querySelector('.dismiss-announcement');
|
||||||
const storageKey = `announcement-dismissed-${announcementHash}`;
|
const storageKey = `announcement-dismissed-${announcementHash}`;
|
||||||
|
|
||||||
// Check if announcement should be shown
|
/**
|
||||||
|
* Check if the announcement should be shown based on localStorage
|
||||||
|
*/
|
||||||
function checkVisibility() {
|
function checkVisibility() {
|
||||||
// Only check localStorage if the banner is dismissible
|
// Only check localStorage if the banner is dismissible
|
||||||
const isDismissed = dismissible ?
|
const isDismissed = dismissible ?
|
||||||
|
|
@ -82,20 +135,22 @@ const announcementHash = btoa(JSON.stringify({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run after DOM is loaded
|
// Run after DOM is loaded to prevent flash of hidden content
|
||||||
document.addEventListener('DOMContentLoaded', checkVisibility);
|
document.addEventListener('DOMContentLoaded', checkVisibility);
|
||||||
|
|
||||||
dismissBtn?.addEventListener('click', () => {
|
// Add click handler for dismiss button if present
|
||||||
if (dismissible) {
|
if (dismissBtn && dismissible) {
|
||||||
|
dismissBtn.addEventListener('click', () => {
|
||||||
banner?.classList.add('hidden');
|
banner?.classList.add('hidden');
|
||||||
localStorage.setItem(storageKey, 'true');
|
localStorage.setItem(storageKey, 'true');
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.announcement-banner {
|
.announcement-banner {
|
||||||
animation: slideDown 0.5s ease-out;
|
animation: slideDown 0.5s ease-out;
|
||||||
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Comic, Genre, ComicType } from './types';
|
import type { Comic, Genre, ComicType, ChapterDetail } from './types';
|
||||||
import { getRedisClient } from './db';
|
import { getRedisClient, CHAPTER_CACHE_TTL } from './db';
|
||||||
|
|
||||||
const languageMap = {
|
const languageMap = {
|
||||||
"en": "English",
|
"en": "English",
|
||||||
|
|
@ -139,20 +139,6 @@ interface ComicDetail {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChapterDetail {
|
|
||||||
id: string;
|
|
||||||
chap: string;
|
|
||||||
title: string;
|
|
||||||
updated_at: string;
|
|
||||||
md_comics: {
|
|
||||||
title: string;
|
|
||||||
slug: string;
|
|
||||||
md_covers?: {
|
|
||||||
b2key: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ChapterParams {
|
interface ChapterParams {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
|
|
@ -270,7 +256,6 @@ export async function getChaptersForSlugs(
|
||||||
): Promise<ChapterDetail[]> {
|
): Promise<ChapterDetail[]> {
|
||||||
console.log(`Fetching chapters for ${slugs.length} comics`);
|
console.log(`Fetching chapters for ${slugs.length} comics`);
|
||||||
const redis = await getRedisClient();
|
const redis = await getRedisClient();
|
||||||
const CACHE_TTL = 60 * 60;
|
|
||||||
|
|
||||||
const uniqueSlugs = [...new Set(slugs)];
|
const uniqueSlugs = [...new Set(slugs)];
|
||||||
const pipeline = redis.pipeline();
|
const pipeline = redis.pipeline();
|
||||||
|
|
@ -293,7 +278,7 @@ export async function getChaptersForSlugs(
|
||||||
cacheResults.forEach(([err, result], index) => {
|
cacheResults.forEach(([err, result], index) => {
|
||||||
if (!err && result) {
|
if (!err && result) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(result);
|
const parsed = JSON.parse(result as string);
|
||||||
if (Array.isArray(parsed)) {
|
if (Array.isArray(parsed)) {
|
||||||
cachedChapters.push(...parsed);
|
cachedChapters.push(...parsed);
|
||||||
}
|
}
|
||||||
|
|
@ -351,7 +336,7 @@ export async function getChaptersForSlugs(
|
||||||
cacheKey,
|
cacheKey,
|
||||||
JSON.stringify(chaps),
|
JSON.stringify(chaps),
|
||||||
'EX',
|
'EX',
|
||||||
CACHE_TTL
|
CHAPTER_CACHE_TTL
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -360,10 +345,18 @@ export async function getChaptersForSlugs(
|
||||||
// Combine and sort all chapters
|
// Combine and sort all chapters
|
||||||
const allChapters = [...cachedChapters, ...liveChapters];
|
const allChapters = [...cachedChapters, ...liveChapters];
|
||||||
const deduplicatedChapters = deduplicateChapters(allChapters);
|
const deduplicatedChapters = deduplicateChapters(allChapters);
|
||||||
return deduplicatedChapters.sort((a, b) =>
|
return deduplicatedChapters.sort((a, b) => {
|
||||||
parseFloat(b.chap) - parseFloat(a.chap) ||
|
// Ensure the created_at fields are valid dates
|
||||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
const dateA = new Date(a.created_at).getTime();
|
||||||
);
|
const dateB = new Date(b.created_at).getTime();
|
||||||
|
|
||||||
|
// First try to sort by chapter number
|
||||||
|
const chapDiff = parseFloat(b.chap) - parseFloat(a.chap);
|
||||||
|
if (!isNaN(chapDiff)) return chapDiff;
|
||||||
|
|
||||||
|
// Fall back to date if chapter numbers are equal or invalid
|
||||||
|
return dateB - dateA;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add export to the validation function
|
// Add export to the validation function
|
||||||
|
|
|
||||||
201
src/lib/db.ts
201
src/lib/db.ts
|
|
@ -1,12 +1,19 @@
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
import type { FeedMapping } from "./types";
|
import type { FeedMapping } from "./types";
|
||||||
import { getChaptersForSlugs } from "./api";
|
|
||||||
|
|
||||||
// Redis URLs
|
// Configuration constants
|
||||||
|
const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days
|
||||||
|
export const CHAPTER_CACHE_TTL = 60 * 60; // 1 hour
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
const BASE_DELAY_MS = 50;
|
||||||
|
const IS_VERCEL = process.env.VERCEL || false;
|
||||||
|
|
||||||
|
// Redis URLs from environment variables
|
||||||
const PRIMARY_REDIS_URL =
|
const PRIMARY_REDIS_URL =
|
||||||
import.meta.env.PRIMARY_REDIS_URL ||
|
import.meta.env.PRIMARY_REDIS_URL ||
|
||||||
process.env.PRIMARY_REDIS_URL ||
|
process.env.PRIMARY_REDIS_URL ||
|
||||||
process.env.REDIS_URL;
|
process.env.REDIS_URL;
|
||||||
|
|
||||||
const REPLICA_URLS = [
|
const REPLICA_URLS = [
|
||||||
import.meta.env.REPLICA_REDIS_URL_1 ||
|
import.meta.env.REPLICA_REDIS_URL_1 ||
|
||||||
process.env.REPLICA_REDIS_URL_1 ||
|
process.env.REPLICA_REDIS_URL_1 ||
|
||||||
|
|
@ -15,31 +22,43 @@ const REPLICA_URLS = [
|
||||||
import.meta.env.REPLICA_REDIS_URL_3 || process.env.REPLICA_REDIS_URL_3,
|
import.meta.env.REPLICA_REDIS_URL_3 || process.env.REPLICA_REDIS_URL_3,
|
||||||
].filter(Boolean) as string[];
|
].filter(Boolean) as string[];
|
||||||
|
|
||||||
const IS_VERCEL = process.env.VERCEL || false;
|
|
||||||
const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days
|
|
||||||
const MAX_RETRIES = 3; // Reduced retries to avoid Vercel timeout
|
|
||||||
const BASE_DELAY_MS = 50; // Faster retries for serverless
|
|
||||||
|
|
||||||
// Singleton clients
|
// Singleton clients
|
||||||
let primaryClient: Redis | null = null;
|
let primaryClient: Redis | null = null;
|
||||||
let replicaClients: Map<string, Redis> = new Map();
|
let replicaClients: Map<string, Redis> = new Map();
|
||||||
|
|
||||||
// Redis instance naming for logging
|
// Redis instance naming for logging
|
||||||
const redisInstanceNames = new Map<string, string>();
|
const redisInstanceNames = new Map<string, string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Redis instance names for better logging
|
||||||
|
*/
|
||||||
function initializeRedisNames() {
|
function initializeRedisNames() {
|
||||||
redisInstanceNames.set(PRIMARY_REDIS_URL!, "Primary");
|
if (PRIMARY_REDIS_URL) {
|
||||||
|
redisInstanceNames.set(PRIMARY_REDIS_URL, "Primary");
|
||||||
|
}
|
||||||
|
|
||||||
REPLICA_URLS.forEach((url, index) =>
|
REPLICA_URLS.forEach((url, index) =>
|
||||||
redisInstanceNames.set(url, `Replica ${index + 1}`)
|
redisInstanceNames.set(url, `Replica ${index + 1}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeRedisNames();
|
initializeRedisNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the friendly name of a Redis instance
|
||||||
|
*/
|
||||||
const getRedisName = (url: string) => redisInstanceNames.get(url) || "Unknown";
|
const getRedisName = (url: string) => redisInstanceNames.get(url) || "Unknown";
|
||||||
|
|
||||||
// Create Redis client with Vercel-optimized settings
|
/**
|
||||||
|
* Create a new Redis client with optimized settings
|
||||||
|
*/
|
||||||
async function createRedisClient(url: string): Promise<Redis> {
|
async function createRedisClient(url: string): Promise<Redis> {
|
||||||
if (!url) throw new Error("Redis URL not set");
|
if (!url) {
|
||||||
|
throw new Error("Redis URL not provided");
|
||||||
|
}
|
||||||
|
|
||||||
const redisName = getRedisName(url);
|
const redisName = getRedisName(url);
|
||||||
|
|
||||||
const client = new Redis(url, {
|
const client = new Redis(url, {
|
||||||
retryStrategy: (times) => {
|
retryStrategy: (times) => {
|
||||||
if (times >= MAX_RETRIES) return null;
|
if (times >= MAX_RETRIES) return null;
|
||||||
|
|
@ -54,13 +73,14 @@ async function createRedisClient(url: string): Promise<Redis> {
|
||||||
enableReadyCheck: true,
|
enableReadyCheck: true,
|
||||||
connectTimeout: IS_VERCEL ? 5000 : 10000, // 5s for Vercel, 10s local
|
connectTimeout: IS_VERCEL ? 5000 : 10000, // 5s for Vercel, 10s local
|
||||||
keepAlive: IS_VERCEL ? 2000 : 10000, // 2s ping for Vercel
|
keepAlive: IS_VERCEL ? 2000 : 10000, // 2s ping for Vercel
|
||||||
commandTimeout: 5000, // Align with Vercel’s Hobby tier timeout
|
commandTimeout: 5000, // Align with Vercel's timeout
|
||||||
reconnectOnError: (err) =>
|
reconnectOnError: (err) =>
|
||||||
["READONLY", "ETIMEDOUT", "ECONNRESET"].some((e) =>
|
["READONLY", "ETIMEDOUT", "ECONNRESET"].some((e) =>
|
||||||
err.message.includes(e)
|
err.message.includes(e)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup event listeners
|
||||||
client
|
client
|
||||||
.on("ready", () => console.log(`${redisName} ready`))
|
.on("ready", () => console.log(`${redisName} ready`))
|
||||||
.on("error", (err) => console.error(`${redisName} error:`, err))
|
.on("error", (err) => console.error(`${redisName} error:`, err))
|
||||||
|
|
@ -70,28 +90,36 @@ async function createRedisClient(url: string): Promise<Redis> {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get or create a Redis client
|
/**
|
||||||
async function getRedisClient(): Promise<Redis> {
|
* Check if a Redis client is connected and responsive
|
||||||
const checkConnection = async (client: Redis, name: string) => {
|
*/
|
||||||
try {
|
async function checkConnection(client: Redis, name: string): Promise<boolean> {
|
||||||
await Promise.race([
|
try {
|
||||||
client.ping(),
|
await Promise.race([
|
||||||
new Promise((_, reject) =>
|
client.ping(),
|
||||||
setTimeout(() => reject(new Error("Ping timeout")), 2000)
|
new Promise((_, reject) =>
|
||||||
),
|
setTimeout(() => reject(new Error("Ping timeout")), 2000)
|
||||||
]);
|
),
|
||||||
return true;
|
]);
|
||||||
} catch (err) {
|
return true;
|
||||||
console.warn(`${name} ping failed:`, err);
|
} catch (err) {
|
||||||
return false;
|
console.warn(`${name} ping failed:`, err);
|
||||||
}
|
return false;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an active Redis client from the pool
|
||||||
|
*/
|
||||||
|
export async function getRedisClient(): Promise<Redis> {
|
||||||
// Try primary client
|
// Try primary client
|
||||||
if (!primaryClient || primaryClient.status !== "ready") {
|
if (!primaryClient || primaryClient.status !== "ready") {
|
||||||
primaryClient = await createRedisClient(PRIMARY_REDIS_URL!);
|
primaryClient = await createRedisClient(PRIMARY_REDIS_URL!);
|
||||||
}
|
}
|
||||||
if (await checkConnection(primaryClient, "Primary")) return primaryClient;
|
|
||||||
|
if (await checkConnection(primaryClient, "Primary")) {
|
||||||
|
return primaryClient;
|
||||||
|
}
|
||||||
|
|
||||||
// Try replicas
|
// Try replicas
|
||||||
for (const url of REPLICA_URLS) {
|
for (const url of REPLICA_URLS) {
|
||||||
|
|
@ -100,7 +128,10 @@ async function getRedisClient(): Promise<Redis> {
|
||||||
client = await createRedisClient(url);
|
client = await createRedisClient(url);
|
||||||
replicaClients.set(url, client);
|
replicaClients.set(url, client);
|
||||||
}
|
}
|
||||||
if (await checkConnection(client, getRedisName(url))) return client;
|
|
||||||
|
if (await checkConnection(client, getRedisName(url))) {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: Force new primary connection
|
// Fallback: Force new primary connection
|
||||||
|
|
@ -108,7 +139,9 @@ async function getRedisClient(): Promise<Redis> {
|
||||||
return primaryClient;
|
return primaryClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to all Redis instances
|
/**
|
||||||
|
* Write data to all available Redis instances
|
||||||
|
*/
|
||||||
async function writeToAllRedis(
|
async function writeToAllRedis(
|
||||||
operation: (redis: Redis) => Promise<any>
|
operation: (redis: Redis) => Promise<any>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
@ -116,6 +149,7 @@ async function writeToAllRedis(
|
||||||
primaryClient,
|
primaryClient,
|
||||||
...Array.from(replicaClients.values()),
|
...Array.from(replicaClients.values()),
|
||||||
].filter(Boolean) as Redis[];
|
].filter(Boolean) as Redis[];
|
||||||
|
|
||||||
const errors: Error[] = [];
|
const errors: Error[] = [];
|
||||||
|
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
|
|
@ -132,33 +166,120 @@ async function writeToAllRedis(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length === clients.length) {
|
if (errors.length === clients.length && clients.length > 0) {
|
||||||
throw new Error("All Redis writes failed");
|
throw new Error("All Redis writes failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your existing functions (unchanged except for using getRedisClient/writeToAllRedis)
|
/**
|
||||||
|
* Store a feed mapping in Redis
|
||||||
|
*/
|
||||||
export async function storeFeedMapping(
|
export async function storeFeedMapping(
|
||||||
feedId: string,
|
feedId: string,
|
||||||
slugs: string[],
|
slugs: string[],
|
||||||
lang: string
|
lang: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const mapping = { slugs, lang, created_at: new Date().toISOString() };
|
const mapping = {
|
||||||
|
slugs,
|
||||||
|
lang,
|
||||||
|
created_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
await writeToAllRedis(async (redis) =>
|
await writeToAllRedis(async (redis) =>
|
||||||
redis.set(feedId, JSON.stringify(mapping), "EX", DEFAULT_CACHE_TTL)
|
redis.set(feedId, JSON.stringify(mapping), "EX", DEFAULT_CACHE_TTL)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a feed mapping from Redis
|
||||||
|
*/
|
||||||
export async function getFeedMapping(
|
export async function getFeedMapping(
|
||||||
feedId: string
|
feedId: string
|
||||||
): Promise<FeedMapping | null> {
|
): Promise<FeedMapping | null> {
|
||||||
const redis = await getRedisClient();
|
try {
|
||||||
const mapping = await redis.get(feedId);
|
const redis = await getRedisClient();
|
||||||
if (!mapping) return null;
|
const mapping = await redis.get(feedId);
|
||||||
await redis.expire(feedId, DEFAULT_CACHE_TTL);
|
|
||||||
const data = JSON.parse(mapping);
|
if (!mapping) return null;
|
||||||
return { slugs: data.slugs, lang: data.lang };
|
|
||||||
|
// Reset expiration on access
|
||||||
|
await redis.expire(feedId, DEFAULT_CACHE_TTL);
|
||||||
|
|
||||||
|
const data = JSON.parse(mapping);
|
||||||
|
return {
|
||||||
|
slugs: data.slugs,
|
||||||
|
lang: data.lang
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error retrieving feed mapping for ${feedId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize connections lazily (no blocking startup)
|
/**
|
||||||
|
* Pre-warm the chapter cache for better performance
|
||||||
|
* This function checks cache status and uses the getChaptersForSlugs function from api.ts
|
||||||
|
* to fetch and cache data in the background
|
||||||
|
*/
|
||||||
|
export async function warmChapterCache(
|
||||||
|
slugs: string[],
|
||||||
|
lang: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log(`Warming chapter cache for ${slugs.length} comics in ${lang}`);
|
||||||
|
const redis = await getRedisClient();
|
||||||
|
|
||||||
|
// Validate slugs
|
||||||
|
const validSlugs = slugs.filter(slug =>
|
||||||
|
slug && typeof slug === 'string' && slug.match(/^[a-z0-9-]+$/));
|
||||||
|
|
||||||
|
if (validSlugs.length !== slugs.length) {
|
||||||
|
console.warn(`Filtered out ${slugs.length - validSlugs.length} invalid slugs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check which slugs are already in cache
|
||||||
|
const pipeline = redis.pipeline();
|
||||||
|
const cacheKeys = validSlugs.map(slug => `chapters:${slug}:${lang}`);
|
||||||
|
|
||||||
|
cacheKeys.forEach(key => {
|
||||||
|
pipeline.exists(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await pipeline.exec();
|
||||||
|
const slugsToFetch: string[] = [];
|
||||||
|
|
||||||
|
results?.forEach(([err, exists], index) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`Error checking cache for ${cacheKeys[index]}:`, err);
|
||||||
|
slugsToFetch.push(validSlugs[index]);
|
||||||
|
} else if (!exists) {
|
||||||
|
slugsToFetch.push(validSlugs[index]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (slugsToFetch.length === 0) {
|
||||||
|
console.log('All chapters already in cache, no warming needed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Cache miss for ${slugsToFetch.length} comics - starting background fetch`);
|
||||||
|
|
||||||
|
// To avoid circular dependencies, we'll import getChaptersForSlugs dynamically
|
||||||
|
const { getChaptersForSlugs } = await import('./api');
|
||||||
|
|
||||||
|
// Start fetching in the background without waiting for the result
|
||||||
|
getChaptersForSlugs(slugsToFetch, lang)
|
||||||
|
.then(chapters => {
|
||||||
|
console.log(`Successfully warmed cache with ${chapters.length} chapters`);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error warming chapter cache:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in warmChapterCache:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize connections lazily
|
||||||
console.log("Redis will connect on first use");
|
console.log("Redis will connect on first use");
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@ import type { APIRoute } from "astro";
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { storeFeedMapping } from '../../lib/db';
|
import { storeFeedMapping } from '../../lib/db';
|
||||||
|
|
||||||
// In-memory storage (replace with database in production)
|
|
||||||
const feedMappings = new Map<string, { slugs: string[], lang: string }>();
|
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
const { slugs, lang } = await request.json();
|
const { slugs, lang } = await request.json();
|
||||||
|
|
@ -17,7 +14,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
const dataString = `${slugs.sort().join(',')}:${lang}`;
|
const dataString = `${slugs.sort().join(',')}:${lang}`;
|
||||||
const hash = createHash('md5').update(dataString).digest('hex').slice(0, 8);
|
const hash = createHash('md5').update(dataString).digest('hex').slice(0, 8);
|
||||||
|
|
||||||
// Store the mapping in Vercel KV
|
// Store the mapping in Redis
|
||||||
await storeFeedMapping(hash, slugs, lang);
|
await storeFeedMapping(hash, slugs, lang);
|
||||||
|
|
||||||
return new Response(JSON.stringify({ feedId: hash }), {
|
return new Response(JSON.stringify({ feedId: hash }), {
|
||||||
|
|
@ -31,6 +28,3 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
return new Response("Internal server error", { status: 500 });
|
return new Response("Internal server error", { status: 500 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Export the mappings for use in other files
|
|
||||||
export { feedMappings };
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue