fix classes and make fic loader more responsive in dev

This commit is contained in:
haetae 2025-08-15 01:23:18 -04:00
parent 496ba125d0
commit e7ed23bb39
19 changed files with 167 additions and 165 deletions

View File

@ -4,15 +4,17 @@
"": { "": {
"name": "astro", "name": "astro",
"dependencies": { "dependencies": {
"@astrojs/db": "^0.16.1", "@astrojs/db": "0.17.1",
"@astrojs/mdx": "^4.3.3", "@astrojs/mdx": "^4.3.3",
"@astrojs/node": "^9.4.0", "@astrojs/node": "9.4.1",
"@astrojs/rss": "4.0.12", "@astrojs/rss": "4.0.12",
"@fujocoded/astro-dev-only": "0.0.3", "@fujocoded/astro-dev-only": "0.0.4",
"astro": "5.12.3", "astro": "5.13.0",
"astro-breadcrumbs": "^3.3.1", "astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dompurify": "^3.2.6",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"marked": "^16.1.2",
"node-html-parser": "^7.0.1", "node-html-parser": "^7.0.1",
"sanitize-html": "^2.17.0", "sanitize-html": "^2.17.0",
}, },
@ -26,7 +28,7 @@
"packages": { "packages": {
"@astrojs/compiler": ["@astrojs/compiler@2.12.2", "", {}, "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw=="], "@astrojs/compiler": ["@astrojs/compiler@2.12.2", "", {}, "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw=="],
"@astrojs/db": ["@astrojs/db@0.16.1", "", { "dependencies": { "@libsql/client": "^0.15.2", "deep-diff": "^1.0.2", "drizzle-orm": "^0.42.0", "kleur": "^4.1.5", "nanoid": "^5.1.5", "prompts": "^2.4.2", "yargs-parser": "^21.1.1", "zod": "^3.24.4" } }, "sha512-pBX19Rh1Ds7riQReDH6gZ5gCf1Txk03WWqMhHIkZIW7lNPnyEx5cuYtl/V9fUnXnqMrBxL9bOHkW4e937DFtWg=="], "@astrojs/db": ["@astrojs/db@0.17.1", "", { "dependencies": { "@libsql/client": "^0.15.2", "deep-diff": "^1.0.2", "drizzle-orm": "^0.42.0", "kleur": "^4.1.5", "nanoid": "^5.1.5", "prompts": "^2.4.2", "yargs-parser": "^21.1.1", "zod": "^3.24.4" } }, "sha512-QL09xZf5Om8AshIlt+YhLDYf6M1QSzv+kfuljsPrhEXJ8U/tuKnbWs2M3wimFaLG3/fU0prFix8lWt7zU8ytfA=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.1", "", {}, "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ=="], "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.1", "", {}, "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ=="],
@ -34,7 +36,7 @@
"@astrojs/mdx": ["@astrojs/mdx@4.3.3", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.5", "@mdx-js/mdx": "^3.1.0", "acorn": "^8.14.1", "es-module-lexer": "^1.6.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-+9+xGP2TBXxcm84cpiq4S9JbuHOHM1fcvREfqW7VHxlUyfUQPByoJ9YYliqHkLS6BMzG+O/+o7n8nguVhuEv4w=="], "@astrojs/mdx": ["@astrojs/mdx@4.3.3", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.5", "@mdx-js/mdx": "^3.1.0", "acorn": "^8.14.1", "es-module-lexer": "^1.6.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "kleur": "^4.1.5", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.4", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-+9+xGP2TBXxcm84cpiq4S9JbuHOHM1fcvREfqW7VHxlUyfUQPByoJ9YYliqHkLS6BMzG+O/+o7n8nguVhuEv4w=="],
"@astrojs/node": ["@astrojs/node@9.4.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "send": "^1.2.0", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^5.3.0" } }, "sha512-Gxs0iVUvOmQmK+H1DBoabcgvdSDg277SwbujRv2cUBlnpcOTJQDFRhRvyJ7G+Zkd06/jhRphsTTmmrBY0PqI4g=="], "@astrojs/node": ["@astrojs/node@9.4.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.1", "send": "^1.2.0", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^5.3.0" } }, "sha512-lpSAQFS4IiCFGQHL/q/1CcX+AmFbma4NOoV5j7Z7Ml2wevyDe/2kAjScKIKk2DA7k/MrXSZ5ZN+IxJgpPbnAOQ=="],
"@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
@ -106,7 +108,7 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
"@fujocoded/astro-dev-only": ["@fujocoded/astro-dev-only@0.0.3", "", {}, "sha512-BOLYZcivrJVUA60d4R2+yEGwDJZ+3Z/zndLACxk6YpClu5ETl0adghwCCt9yIHsq79KDx/Or8LH2aqS6fVlQbg=="], "@fujocoded/astro-dev-only": ["@fujocoded/astro-dev-only@0.0.4", "", {}, "sha512-1I/h/M5QguTjl+MxXUqwDiuLs1xUDhzQ3bzWwwhyrrZL8bhqXsHbEcuxw+AUT0RS3WTFclF7L/5wq7vw1YEXdg=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
@ -268,6 +270,8 @@
"@types/sanitize-html": ["@types/sanitize-html@2.16.0", "", { "dependencies": { "htmlparser2": "^8.0.0" } }, "sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw=="], "@types/sanitize-html": ["@types/sanitize-html@2.16.0", "", { "dependencies": { "htmlparser2": "^8.0.0" } }, "sha512-l6rX1MUXje5ztPT0cAFtUayXF06DqPhRyfVXareEN5gGCFaP/iwsxIyKODr9XDhfxPpN6vXUFNfo5kZMXCxBtw=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
@ -294,7 +298,7 @@
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"astro": ["astro@5.12.3", "", { "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.6.1", "@astrojs/markdown-remark": "6.3.3", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "smol-toml": "^1.3.4", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-fU1hNPMkccm+FuonGsY5DFkC2QyuLCju++8L2ubzBtYBDBf6bmfgmVM7A2dK+Hl+ZJCUNgepsClhBpczj+2LRw=="], "astro": ["astro@5.13.0", "", { "dependencies": { "@astrojs/compiler": "^2.12.2", "@astrojs/internal-helpers": "0.7.1", "@astrojs/markdown-remark": "6.3.5", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "acorn": "^8.14.1", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.2.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.0.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "package-manager-detector": "^1.1.0", "picomatch": "^4.0.2", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^3.2.1", "smol-toml": "^1.3.4", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", "tsconfck": "^3.1.5", "ultrahtml": "^1.6.0", "unifont": "~0.5.0", "unist-util-visit": "^5.0.0", "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.3.4", "vitefu": "^1.0.6", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.1", "zod": "^3.24.4", "zod-to-json-schema": "^3.24.5", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-kWahyvrrxUtgxFRRWSH5X0DESvgg4ZZ0fk6tU7blVZRgmj4PY79nzfaXFT+N+Ac2vWXX7eYUPYs1kLLk5IjBfQ=="],
"astro-breadcrumbs": ["astro-breadcrumbs@3.3.1", "", { "peerDependencies": { "astro": "^2.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, "sha512-O3wWzlc4HOU//ePefcwk2F4yQn830buVUVn6h7+sGmVq7+xvv9JmHTcmihU18ISte72ELnJlG2U8s58nIuPXfg=="], "astro-breadcrumbs": ["astro-breadcrumbs@3.3.1", "", { "peerDependencies": { "astro": "^2.0.0-beta.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" } }, "sha512-O3wWzlc4HOU//ePefcwk2F4yQn830buVUVn6h7+sGmVq7+xvv9JmHTcmihU18ISte72ELnJlG2U8s58nIuPXfg=="],
@ -408,6 +412,8 @@
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"dompurify": ["dompurify@3.2.6", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"drizzle-orm": ["drizzle-orm@0.42.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg=="], "drizzle-orm": ["drizzle-orm@0.42.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg=="],
@ -570,6 +576,8 @@
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["marked@16.1.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-rNQt5EvRinalby7zJZu/mB+BvaAY2oz3wCuCjt1RDrWNpS1Pdf9xqMOeC9Hm5adBdcV/3XZPJpG58eT+WBc0XQ=="],
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
@ -960,10 +968,6 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
"astro/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.2.1", "smol-toml": "^1.3.4", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-DDRtD1sPvAuA7ms2btc9A7/7DApKqgLMNrE6kh5tmkfy8utD0Z738gqd3p5aViYYdUtHIyEJ1X4mCMxfCfu15w=="],
"cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], "cross-fetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],

0
guestbook.db Normal file
View File

View File

@ -9,15 +9,17 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/db": "^0.16.1", "@astrojs/db": "0.17.1",
"@astrojs/mdx": "^4.3.3", "@astrojs/mdx": "^4.3.3",
"@astrojs/node": "^9.4.0", "@astrojs/node": "9.4.1",
"@astrojs/rss": "4.0.12", "@astrojs/rss": "4.0.12",
"@fujocoded/astro-dev-only": "0.0.3", "@fujocoded/astro-dev-only": "0.0.4",
"astro": "5.12.3", "astro": "5.13.0",
"astro-breadcrumbs": "^3.3.1", "astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"dompurify": "^3.2.6",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"marked": "^16.1.2",
"node-html-parser": "^7.0.1", "node-html-parser": "^7.0.1",
"sanitize-html": "^2.17.0" "sanitize-html": "^2.17.0"
}, },

View File

@ -5,15 +5,15 @@ interface Props {
imagePath: string; imagePath: string;
alt: string; alt: string;
caption?: string; caption?: string;
class?: string | string[]; class?: string;
} }
const { imagePath, alt, caption, class: className }: Props = Astro.props; const { imagePath, alt, caption, class: className, ...rest }: Props = Astro.props;
const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/**/*.{jpeg,jpg,png,webp,gif}"); const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/**/*.{jpeg,jpg,png,webp,gif}");
const path = imagePath.startsWith("$") ? imagePath.replace("$", "/src/assets") : imagePath; const path = imagePath.startsWith("$") ? imagePath.replace("$", "/src/assets") : imagePath;
if (!images[path]) throw new Error(`"${path}" does not exist in glob: "src/assets/**/*.{jpeg,jpg,png,webp,gif}"`); if (!images[path]) throw new Error(`"${path}" does not exist in glob: "src/assets/**/*.{jpeg,jpg,png,webp,gif}"`);
--- ---
<figure class:list={[className]}> <figure class:list={className} {...rest}>
<Image src={images[path]()} {alt} /> <Image src={images[path]()} {alt} />
{caption && <figcaption>{caption}</figcaption>} {caption && <figcaption>{caption}</figcaption>}
</figure> </figure>

View File

@ -1,7 +1,8 @@
import { defineCollection, reference, z } from "astro:content"; import { defineCollection, reference, z } from "astro:content";
import { glob } from "astro/loaders"; import { glob } from "astro/loaders";
import { rssSchema } from "@astrojs/rss"; import { rssSchema } from "@astrojs/rss";
import MarkdownIt from "markdown-it"; // import MarkdownIt from "markdown-it";
import { marked } from "marked";
import moods from "@/utils/moods"; import moods from "@/utils/moods";
import { ficsLoader } from "@/utils/loader"; import { ficsLoader } from "@/utils/loader";
@ -19,10 +20,7 @@ function slugify(input: string) {
.replace(/-+$/, ""); .replace(/-+$/, "");
} }
const parser = new MarkdownIt({ const parser = marked.use({ gfm: true, breaks: true, });
html: true,
breaks: true,
});
const blog = defineCollection({ const blog = defineCollection({
loader: glob({ pattern: "*.{md,mdx}", base: "./src/content/blog" }), loader: glob({ pattern: "*.{md,mdx}", base: "./src/content/blog" }),
@ -46,14 +44,13 @@ const chapters = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
publishedAt: z.coerce.date(), publishedAt: z.coerce.date(),
notes: z.ostring().transform(notes => parser.renderInline(notes ?? "", {})), notes: z.ostring().transform(async (notes) => await parser.parseInline(notes ?? "")),
lastModified: z.coerce.date().optional(), lastModified: z.coerce.date().optional(),
sortOrder: z.number().default(1), sortOrder: z.number().default(1),
// fic: reference("fics"),
}), }),
}); });
const fics = defineCollection({ const test = defineCollection({
loader: glob({ loader: glob({
pattern: "**/*.{yml,yaml}", pattern: "**/*.{yml,yaml}",
base: source, base: source,
@ -66,19 +63,19 @@ const fics = defineCollection({
title: z.string(), title: z.string(),
series: z.union([z.string(), z.array(z.string())]), series: z.union([z.string(), z.array(z.string())]),
publishedAt: z.coerce.date(), publishedAt: z.coerce.date(),
summary: z.string().transform(summary => parser.renderInline(summary, {})), summary: z.string().transform(async (summary) => await parser.parseInline(summary ?? "")),
characters: z.array(z.string()).optional(), characters: z.array(z.string()).optional(),
ships: z.ostring(), ships: z.ostring(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
notes: z.ostring().transform(notes => parser.renderInline(notes ?? "", {})), notes: z.ostring().transform(async (notes) => await parser.parseInline(notes ?? "")),
lastModified: z.coerce.date().optional(), lastModified: z.coerce.date().optional(),
}), }),
}); });
const test = defineCollection({ const fics = defineCollection({
loader: ficsLoader( loader: ficsLoader(
glob({ glob({
pattern: "**/*.{yml,yaml}", pattern: "**/*.{yml,yaml|toml}",
base: source, base: source,
generateId: ({ entry, data }) => { generateId: ({ entry, data }) => {
if (data.slug) return data.slug as string; if (data.slug) return data.slug as string;
@ -90,14 +87,14 @@ const test = defineCollection({
title: z.string(), title: z.string(),
series: z.union([z.string(), z.array(z.string())]), series: z.union([z.string(), z.array(z.string())]),
publishedAt: z.coerce.date(), publishedAt: z.coerce.date(),
summary: z.string().transform(summary => parser.renderInline(summary, {})), summary: z.string().transform(async (summary) => await parser.parseInline(summary ?? "")),
characters: z.array(z.string()).optional(), characters: z.array(z.string()).optional(),
ships: z.ostring(), ships: z.ostring(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
notes: z.ostring().transform(notes => parser.renderInline(notes ?? "", {})), notes: z.ostring().transform(async (notes) => await parser.parseInline(notes ?? "")),
lastModified: z.coerce.date().optional(), lastModified: z.coerce.date().optional(),
chapters: z.array(reference("chapters")).optional(), chapters: z.array(reference("chapters")).optional(),
}), }),
}); });
export const collections = { blog, fics, chapters, test }; export const collections = { blog, fics, chapters };

View File

@ -0,0 +1,9 @@
title = "this is a test"
series = [
"test",
"fandom 2"
]
publishedAt = 2022-12-22
summary = """
yeller
"""

View File

@ -1,5 +0,0 @@
title: this is a test
series: test
publishedAt: 2020-12-20T19:18:00
summary:
yeller

View File

@ -1,6 +1,6 @@
--- ---
title: ch 1 title: ch 1
publishedAt: 2020-12-20T19:18:00 publishedAt: 2022-11-01
notes: i wrote this in a fugue state while listening to [Waste by Oh Wonder](https://www.youtube.com/watch?v=Ar1grAdGkec) notes: i wrote this in a fugue state while listening to [Waste by Oh Wonder](https://www.youtube.com/watch?v=Ar1grAdGkec)
sortOrder: 1 sortOrder: 1
--- ---

View File

@ -1,6 +1,6 @@
--- ---
title: ch 2 title: chapter 2
publishedAt: 2020-12-20T19:18:00 publishedAt: 2023-12-12
notes: i wrote this in a fugue state while listening to [Waste by Oh Wonder](https://www.youtube.com/watch?v=Ar1grAdGkec) notes: i wrote this in a fugue state while listening to [Waste by Oh Wonder](https://www.youtube.com/watch?v=Ar1grAdGkec)
sortOrder: 2 sortOrder: 2
--- ---

View File

@ -4,6 +4,6 @@ publishedAt: 2020-12-20T19:18:00
summary: summary:
Sanemi survives. He learns to bear the weight of living. Sanemi survives. He learns to bear the weight of living.
-- <hr/>
MAJOR ending spoilers for the manga please don't read if you haven't finished the manga!! MAJOR ending spoilers for the manga please don't read if you haven't finished the manga!!

View File

@ -75,7 +75,7 @@ const { frontmatter } = Astro.props;
li::marker { content: " "; } li::marker { content: " "; }
} }
:global(.avatar) { .avatar {
width: fit-content; width: fit-content;
margin: 0 auto 2rem; margin: 0 auto 2rem;
border-image: var(--borderImage) 5 4 / 10px 8px / 8px repeat; border-image: var(--borderImage) 5 4 / 10px 8px / 8px repeat;
@ -86,7 +86,6 @@ const { frontmatter } = Astro.props;
place-content: center; place-content: center;
width: 80px; width: 80px;
height: auto; height: auto;
image-rendering: pixelated;
} }
} }
</style> </style>

View File

@ -24,7 +24,8 @@ const { title, ficTitle, date, notes, lastModified }: Props = Astro.props;
<main> <main>
<ChapterContent lastModified={lastModified} notes={notes}> <ChapterContent lastModified={lastModified} notes={notes}>
<Fragment slot="title"> <Fragment slot="title">
<h1>{title}</h1> <h1>{ficTitle}</h1>
<h2>{title}</h2>
<time datetime={formatDate(date, true)}> <time datetime={formatDate(date, true)}>
Published on {formatDate(date)} Published on {formatDate(date)}
</time> </time>

View File

@ -5,7 +5,10 @@ import { getCollection, render } from "astro:content";
import { Breadcrumbs } from "astro-breadcrumbs"; import { Breadcrumbs } from "astro-breadcrumbs";
export const getStaticPaths = (async () => { export const getStaticPaths = (async () => {
const chapters = await getCollection("chapters"); const fics = await getCollection("fics");
const chapters = await getCollection("chapters", ({ id }) => {
return fics.find(({ data }) => data.chapters?.some(chapter => chapter.id === id));
});
return chapters.map(chapter => ({ return chapters.map(chapter => ({
params: { params: {
ficId: chapter.id.split("/")[0], ficId: chapter.id.split("/")[0],
@ -19,12 +22,8 @@ const { ficId, chapterId } = Astro.params;
const { chapter } = Astro.props; const { chapter } = Astro.props;
const { Content, remarkPluginFrontmatter } = await render(chapter); const { Content, remarkPluginFrontmatter } = await render(chapter);
const chapters = await getCollection("chapters", ({ id }) => { const chapters = await getCollection("chapters", ({ id }) => id.split("/")[0] === ficId);
return id.split("/")[0] === ficId; const fic = await getCollection("fics", ({ id }) => id === ficId);
});
const fic = await getCollection("fics", ({ id }) => {
return id === ficId;
});
chapters.sort((a, b) => a.data.sortOrder - b.data.sortOrder); chapters.sort((a, b) => a.data.sortOrder - b.data.sortOrder);
const current = chapters.findIndex(chapter => chapter.id === `${ficId}/${chapterId}`); const current = chapters.findIndex(chapter => chapter.id === `${ficId}/${chapterId}`);
const next = current + 1 === chapters.length ? undefined : chapters[current + 1]; const next = current + 1 === chapters.length ? undefined : chapters[current + 1];
@ -66,12 +65,8 @@ const links = [
))} ))}
</select> </select>
</div> </div>
{previous && ( {previous && <a id="previous" href={`/fics/${previous.id}`}>{previous.data.title}</a>}
<a id="previous" href={`/fics/${previous.id}`}>{previous.data.title}</a> {next && <a id="next" href={`/fics/${next.id}`}>{next.data.title}</a>}
)}
{next && (
<a id="next" href={`/fics/${next.id}`}>{next.data.title}</a>
)}
</nav> </nav>
)} )}
</Chapter> </Chapter>

View File

@ -1,7 +1,9 @@
--- ---
import type { GetStaticPaths } from "astro"; import type { GetStaticPaths } from "astro";
import { Font } from "astro:assets"; import { Font } from "astro:assets";
import { getCollection, getEntry, render } from "astro:content"; import { getCollection, getEntries, render } from "astro:content";
import { marked } from "marked";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import formatDate from "@/utils/formatDate"; import formatDate from "@/utils/formatDate";
import ChapterContent from "~/ChapterContent.astro"; import ChapterContent from "~/ChapterContent.astro";
@ -15,21 +17,17 @@ export const getStaticPaths = (async () => {
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths;
const { fic } = Astro.props; const { fic } = Astro.props;
const chapters = await getCollection("chapters", ({ id }) => { const chapters = await getEntries(fic.data.chapters ?? []);
return id.startsWith(fic.id);
});
chapters.length = Math.min(chapters.length, 5); chapters.length = Math.min(chapters.length, 5);
chapters.sort((a, b) => a.data.sortOrder - b.data.sortOrder); chapters.sort((a, b) => a.data.sortOrder - b.data.sortOrder);
const oneshot = chapters.length === 1 && await getEntry(chapters[0]); const { Content } = await render(fic);
const { Content } = oneshot && await render(oneshot); const notes = fic.rendered && await marked.use({ async: true, gfm: true, breaks: true }).parseInline((fic.rendered.metadata!.frontmatter as any)["notes"]);
--- ---
<Layout title={fic.data.title}> <Layout title={fic.data.title}>
<Fragment slot="head"> <Fragment slot="head">
<link rel="alternate" type="application/rss+xml" title={fic.data.title} href={new URL(`/fics/${Astro.params.ficId}/rss.xml`, Astro.site)} /> <link rel="alternate" type="application/rss+xml" title={fic.data.title} href={new URL(`/fics/${Astro.params.ficId}/rss.xml`, Astro.site)} />
<meta name="description" content={fic.data.summary?.substring(0, 150) + "…"} /> <meta name="description" content={fic.data.summary?.substring(0, 150) + "…"} />
{oneshot && ( {fic.body && <Font cssVariable="--serif" preload />}
<Font cssVariable="--serif" preload />
)}
</Fragment> </Fragment>
<main> <main>
@ -39,10 +37,8 @@ const { Content } = oneshot && await render(oneshot);
<dl> <dl>
<dt>Fandom</dt> <dt>Fandom</dt>
{typeof fic.data.series === "object" {typeof fic.data.series === "object"
? (<ul> ? <ul>{fic.data.series.map(fandom => (<li>{fandom}</li>))}</ul>
{fic.data.series.map(fandom => (<li>{fandom}</li>))} : <dd>{fic.data.series}</dd>
</ul>)
: (<dd>{fic.data.series}</dd>)
} }
<dt>Date</dt> <dt>Date</dt>
<dd> <dd>
@ -59,14 +55,12 @@ const { Content } = oneshot && await render(oneshot);
</dl> </dl>
<div class="links"> <div class="links">
{!oneshot && ( {fic.data.chapters && <a class="button" href={`/fics/${chapters[0].id}`}>start reading</a>}
<a class="button" href={`/fics/${chapters[0].id}`}>start reading</a>
)}
<a class="button" href={`/fics/${Astro.params.ficId}/rss.xml`}>rss feed</a> <a class="button" href={`/fics/${Astro.params.ficId}/rss.xml`}>rss feed</a>
</div> </div>
</header> </header>
{!oneshot && ( {fic.data.chapters && (
<h2>chapters</h2> <h2>chapters</h2>
<ul> <ul>
{chapters.map(chapter => ( {chapters.map(chapter => (
@ -76,9 +70,9 @@ const { Content } = oneshot && await render(oneshot);
)} )}
</section> </section>
{oneshot && ( {fic.body && (
<section id="oneshot"> <section id="oneshot">
<ChapterContent lastModified={oneshot.data.lastModified} notes={oneshot.data.notes}> <ChapterContent lastModified={(fic.rendered?.metadata!.frontmatter as any)['lastModified']} notes={notes}>
<Content /> <Content />
</ChapterContent> </ChapterContent>
</section> </section>

View File

@ -1,23 +1,25 @@
import type { APIRoute } from "astro"; import type { APIRoute } from "astro";
import { getCollection } from "astro:content"; import { experimental_AstroContainer as AstroContainer } from "astro/container";
import { getCollection, render } from "astro:content";
import rss, { type RSSFeedItem } from "@astrojs/rss"; import rss, { type RSSFeedItem } from "@astrojs/rss";
import MarkdownIt from "markdown-it";
import { marked } from "marked";
import { parse as htmlParser } from "node-html-parser"; import { parse as htmlParser } from "node-html-parser";
import sanitize from "sanitize-html"; import sanitize from "sanitize-html";
import fixRssImages from "@/utils/fixRssImages"; import fixRssImages from "@/utils/fixRssImages";
const parser = new MarkdownIt(); const parser = marked.use({ gfm: true, breaks: true });
const fics = await getCollection("fics"); const fics = await getCollection("fics");
export const GET: APIRoute = async (context) => { export const GET: APIRoute = async (context) => {
const chapters = await getCollection("chapters", ({ id }) => { const chapters = await getCollection("chapters", ({ id }) => id.split("/")[0] === context.params.ficId);
return id.split("/")[0] === context.params.ficId;
});
const fic = fics.find(({ id }) => id === context.params.ficId); const fic = fics.find(({ id }) => id === context.params.ficId);
const container = await AstroContainer.create();
const feed: RSSFeedItem[] = []; const feed: RSSFeedItem[] = [];
for (const entry of chapters) { for (const entry of chapters) {
const content = parser.render(entry.body!); const { Content } = await render(entry);
const content = await container.renderToString(Content);
const html = htmlParser.parse(content); const html = htmlParser.parse(content);
const images = html.querySelectorAll("img"); const images = html.querySelectorAll("img");
@ -33,12 +35,11 @@ export const GET: APIRoute = async (context) => {
categories: typeof fic?.data.series == "string" ? [fic?.data.series] : [...fic?.data.series!], categories: typeof fic?.data.series == "string" ? [fic?.data.series] : [...fic?.data.series!],
}); });
} }
const summary = await parser.parseInline(fic?.data.summary ?? "");
return rss({ return rss({
title: `${fic?.data.title}`, title: `${fic?.data.title}`,
description: sanitize(parser.render(fic?.data.summary!), { description: sanitize(summary),
allowedTags: sanitize.defaults.allowedTags.concat(["br"]),
}),
site: context.site!, site: context.site!,
items: feed, items: feed,
}); });

View File

@ -1,14 +1,13 @@
--- ---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import dayjs from "dayjs"; import Layout from "@/layouts/Layout.astro";
import utc from "dayjs/plugin/utc"; import formatDate from "@/utils/formatDate";
const fics = await getCollection("fics"); const fics = await getCollection("fics");
const chapters = await getCollection("chapters"); const chapters = await getCollection("chapters");
fics.sort((a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf());
chapters.length = Math.min(chapters.length, 5); chapters.length = Math.min(chapters.length, 5);
chapters.sort((a, b) => a.data.publishedAt.valueOf() - b.data.publishedAt.valueOf()); chapters.sort((a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf());
dayjs.extend(utc);
--- ---
<Layout> <Layout>
<h1>fanfics</h1> <h1>fanfics</h1>
@ -17,15 +16,20 @@ dayjs.extend(utc);
<ul> <ul>
{chapters.map(post => ( {chapters.map(post => (
<li> <li>
<time datetime={dayjs(post.data.publishedAt).utc(true).toISOString()}> <time datetime={formatDate(post.data.publishedAt, true)}>
{dayjs(post.data.publishedAt).utc(true).format("MMMM DD, YYYY")} {formatDate(post.data.publishedAt, false, "MMMM DD, YYYY")}
</time> </time>
{post.id} {fics.some(({ data }) => data.chapters?.some(({ id }) => id === post.id))
{JSON.stringify(fics.filter(fic => fic.id.includes(post.id)))} ? <>
<a href={`/fics/${post.id}`}>{post.data.title}</a> in <a href={`/fics/${post.id}`}>{post.data.title}</a>
<a href={`/fics/${post.id.split("/")[0]}`}> in
{fics.find(({ id }) => post.id.startsWith(id))?.data.title} <a href={`/fics/${post.id.split("/")[0]}`}>
</a> {fics.filter(({ id }) => id === post.id.split("/")[0])[0].data.title}
</a>
</>
: <a href={`/fics/${post.id.split("/")[0]}`}>
{fics.filter(({ id }) => id === post.id.split("/")[0])[0].data.title}
</a>}
</li> </li>
))} ))}
</ul> </ul>

View File

@ -7,7 +7,7 @@ import bulletin from "$/acnl-bulletin.png";
<Layout> <Layout>
<Navbar /> <Navbar />
<main> <main>
<section id="welcome" class="board"> <section id="welcome">
<div class="card"> <div class="card">
<h1>welcome!</h1> <h1>welcome!</h1>
<article> <article>
@ -16,7 +16,7 @@ import bulletin from "$/acnl-bulletin.png";
</div> </div>
</section> </section>
<section id="updates" class="board"> <section id="updates">
<article class="update card"> <article class="update card">
<h1>update title</h1> <h1>update title</h1>
<time datetime="">05/01/25</time> <time datetime="">05/01/25</time>

View File

@ -1,14 +0,0 @@
---
import Layout from "@/layouts/Layout.astro";
import { getCollection } from "astro:content";
const fics = await getCollection("test");
---
<Layout>
{fics.map(fic => (
<article>
<h2>{fic.data.title}</h2>
</article>
))}
</Layout>

View File

@ -14,55 +14,70 @@ async function getAllChapters(metaPath: string) {
export function ficsLoader(loader: Loader) { export function ficsLoader(loader: Loader) {
const oldLoad = loader.load; const oldLoad = loader.load;
loader.load = async (context: LoaderContext) => { loader.load = async (context: LoaderContext) => {
await oldLoad({ context.watcher?.on("all", async (_event, path) => {
...context, if (path.includes("fics")) {
parseData: async (data) => data.data, await resolveFics(oldLoad, context);
}
}); });
await Promise.all( await resolveFics(oldLoad, context);
context.store.values().map(async (value) => {
const loadedPromise = Promise.withResolvers();
getAllChapters(value.filePath as string).then(
async (chapters) => {
const { digest, ...valueWithoutDigest } = value;
const newData = await context.parseData({
id: value.id,
data: {
...valueWithoutDigest.data,
...chapters.length > 1 && { chapters: chapters },
},
});
if (chapters.length === 1) {
// i've committed unspeakable atrocities here
const search = import.meta.glob<MarkdownInstance<any>>(`../content/fics/**/*.md`, { eager: true });
const body = Object.values(search).filter(path => path.file?.includes(chapters[0]))[0];
context.store.set({
...valueWithoutDigest,
data: newData,
body: body.rawContent(),
rendered: {
html: await body.compiledContent(),
metadata: {
headings: body.getHeadings(),
frontmatter: body.frontmatter,
},
},
digest: context.generateDigest(newData),
});
} else {
context.store.set({
...valueWithoutDigest,
data: newData,
digest: context.generateDigest(newData),
});
}
loadedPromise.resolve(chapters);
}
);
return loadedPromise.promise;
})
);
}; };
return loader; return loader;
}
// please don't ask me why i did this. idk either.
async function resolveFics(loader: (oldContext: LoaderContext) => Promise<void>, context: LoaderContext) {
await loader({
...context,
parseData: async (data) => data.data,
});
await Promise.all(
context.store.values().map(async (value) => {
const loadedPromise = Promise.withResolvers();
getAllChapters(value.filePath as string).then(
async (chapters) => {
const { digest, ...valueWithoutDigest } = value;
const newData = await context.parseData({
id: value.id,
data: {
...valueWithoutDigest.data,
...chapters.length > 1 && { chapters: chapters },
},
});
if (chapters.length === 1) {
// i've committed unspeakable atrocities here
const search = import.meta.glob<MarkdownInstance<any>>(`../content/fics/**/*.md`);
// const body = Object.values(search).filter(path => path.file?.includes(chapters[0]))[0];
for (const path in search) {
if (path.includes(chapters[0])) {
const body = await search[path]();
const html = await body.compiledContent();
context.store.set({
...valueWithoutDigest,
data: newData,
body: body.rawContent(),
rendered: {
html,
metadata: {
headings: body.getHeadings(),
frontmatter: body.frontmatter,
},
},
digest: context.generateDigest(newData),
});
}
}
} else {
context.store.set({
...valueWithoutDigest,
data: newData,
digest: context.generateDigest(newData),
});
}
loadedPromise.resolve(chapters);
}
);
return loadedPromise.promise;
})
);
} }