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",
"dependencies": {
"@astrojs/db": "^0.16.1",
"@astrojs/db": "0.17.1",
"@astrojs/mdx": "^4.3.3",
"@astrojs/node": "^9.4.0",
"@astrojs/node": "9.4.1",
"@astrojs/rss": "4.0.12",
"@fujocoded/astro-dev-only": "0.0.3",
"astro": "5.12.3",
"@fujocoded/astro-dev-only": "0.0.4",
"astro": "5.13.0",
"astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13",
"dompurify": "^3.2.6",
"markdown-it": "^14.1.0",
"marked": "^16.1.2",
"node-html-parser": "^7.0.1",
"sanitize-html": "^2.17.0",
},
@ -26,7 +28,7 @@
"packages": {
"@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=="],
@ -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/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=="],
@ -106,7 +108,7 @@
"@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=="],
@ -268,6 +270,8 @@
"@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/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=="],
"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=="],
@ -408,6 +412,8 @@
"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=="],
"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=="],
"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-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=="],
"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=="],
"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"
},
"dependencies": {
"@astrojs/db": "^0.16.1",
"@astrojs/db": "0.17.1",
"@astrojs/mdx": "^4.3.3",
"@astrojs/node": "^9.4.0",
"@astrojs/node": "9.4.1",
"@astrojs/rss": "4.0.12",
"@fujocoded/astro-dev-only": "0.0.3",
"astro": "5.12.3",
"@fujocoded/astro-dev-only": "0.0.4",
"astro": "5.13.0",
"astro-breadcrumbs": "^3.3.1",
"dayjs": "^1.11.13",
"dompurify": "^3.2.6",
"markdown-it": "^14.1.0",
"marked": "^16.1.2",
"node-html-parser": "^7.0.1",
"sanitize-html": "^2.17.0"
},

View File

@ -5,15 +5,15 @@ interface Props {
imagePath: string;
alt: 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 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}"`);
---
<figure class:list={[className]}>
<figure class:list={className} {...rest}>
<Image src={images[path]()} {alt} />
{caption && <figcaption>{caption}</figcaption>}
</figure>

View File

@ -1,7 +1,8 @@
import { defineCollection, reference, z } from "astro:content";
import { glob } from "astro/loaders";
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 { ficsLoader } from "@/utils/loader";
@ -19,10 +20,7 @@ function slugify(input: string) {
.replace(/-+$/, "");
}
const parser = new MarkdownIt({
html: true,
breaks: true,
});
const parser = marked.use({ gfm: true, breaks: true, });
const blog = defineCollection({
loader: glob({ pattern: "*.{md,mdx}", base: "./src/content/blog" }),
@ -46,14 +44,13 @@ const chapters = defineCollection({
schema: z.object({
title: z.string(),
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(),
sortOrder: z.number().default(1),
// fic: reference("fics"),
}),
});
const fics = defineCollection({
const test = defineCollection({
loader: glob({
pattern: "**/*.{yml,yaml}",
base: source,
@ -66,19 +63,19 @@ const fics = defineCollection({
title: z.string(),
series: z.union([z.string(), z.array(z.string())]),
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(),
ships: z.ostring(),
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(),
}),
});
const test = defineCollection({
const fics = defineCollection({
loader: ficsLoader(
glob({
pattern: "**/*.{yml,yaml}",
pattern: "**/*.{yml,yaml|toml}",
base: source,
generateId: ({ entry, data }) => {
if (data.slug) return data.slug as string;
@ -90,14 +87,14 @@ const test = defineCollection({
title: z.string(),
series: z.union([z.string(), z.array(z.string())]),
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(),
ships: z.ostring(),
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(),
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
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)
sortOrder: 1
---

View File

@ -1,6 +1,6 @@
---
title: ch 2
publishedAt: 2020-12-20T19:18:00
title: chapter 2
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)
sortOrder: 2
---

View File

@ -4,6 +4,6 @@ publishedAt: 2020-12-20T19:18:00
summary:
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!!

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,25 @@
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 MarkdownIt from "markdown-it";
import { marked } from "marked";
import { parse as htmlParser } from "node-html-parser";
import sanitize from "sanitize-html";
import fixRssImages from "@/utils/fixRssImages";
const parser = new MarkdownIt();
const parser = marked.use({ gfm: true, breaks: true });
const fics = await getCollection("fics");
export const GET: APIRoute = async (context) => {
const chapters = await getCollection("chapters", ({ id }) => {
return id.split("/")[0] === context.params.ficId;
});
const chapters = await getCollection("chapters", ({ id }) => id.split("/")[0] === context.params.ficId);
const fic = fics.find(({ id }) => id === context.params.ficId);
const container = await AstroContainer.create();
const feed: RSSFeedItem[] = [];
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 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!],
});
}
const summary = await parser.parseInline(fic?.data.summary ?? "");
return rss({
title: `${fic?.data.title}`,
description: sanitize(parser.render(fic?.data.summary!), {
allowedTags: sanitize.defaults.allowedTags.concat(["br"]),
}),
description: sanitize(summary),
site: context.site!,
items: feed,
});

View File

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

View File

@ -7,7 +7,7 @@ import bulletin from "$/acnl-bulletin.png";
<Layout>
<Navbar />
<main>
<section id="welcome" class="board">
<section id="welcome">
<div class="card">
<h1>welcome!</h1>
<article>
@ -16,7 +16,7 @@ import bulletin from "$/acnl-bulletin.png";
</div>
</section>
<section id="updates" class="board">
<section id="updates">
<article class="update card">
<h1>update title</h1>
<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) {
const oldLoad = loader.load;
loader.load = async (context: LoaderContext) => {
await oldLoad({
...context,
parseData: async (data) => data.data,
context.watcher?.on("all", async (_event, path) => {
if (path.includes("fics")) {
await resolveFics(oldLoad, context);
}
});
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`, { 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;
})
);
await resolveFics(oldLoad, context);
};
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;
})
);
}