From 122cfaf7ebe141f82c5626b3bb20e7863970da00 Mon Sep 17 00:00:00 2001 From: Bad Manners Date: Thu, 7 Nov 2024 15:11:48 -0300 Subject: [PATCH 01/10] Improved search --- README.md | 3 + astro.config.mjs | 6 +- package-lock.json | 82 +++++++--------- package.json | 5 +- src/data/licenses.toml | 20 ++-- src/env.d.ts | 6 ++ src/i18n/index.ts | 27 ++++++ src/integrations/pagefind.ts | 96 +++++++++++++++++++ src/layouts/BaseLayout.astro | 11 +++ src/layouts/PublishedContentLayout.astro | 4 +- src/pages/blog/index.astro | 6 +- src/pages/games/index.astro | 5 +- src/pages/index.astro | 10 +- src/pages/search.astro | 23 ++++- src/pages/stories/[...page].astro | 5 +- .../stories/the-lost-of-the-marshes.astro | 5 +- src/pages/tags/[slug].astro | 15 ++- src/styles/base.css | 18 ++++ 18 files changed, 275 insertions(+), 72 deletions(-) create mode 100644 src/integrations/pagefind.ts diff --git a/README.md b/README.md index 8cfe710..3d94ae1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Static website built in Astro + Typescript + TailwindCSS. +For attributions, see [`licenses.toml`](src/data/licenses.toml). + ## Requirements - Node.js 20+ @@ -32,6 +34,7 @@ The following optional environment variables can be set within a `.env` file: | Name | Type | Description | | ---------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `APACHE_CONFIG` | boolean | If set to true, generates an `.htaccess` Apache config file at the root of the output directory. | | `PUBLISH_DRAFTS` | boolean | If set to true, includes drafts in the production build. Published drafts still won't be directly indexed by any other pages. | ### Export story for upload diff --git a/astro.config.mjs b/astro.config.mjs index def2e7f..dfd9618 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -3,7 +3,7 @@ import tailwindIntegration from "@astrojs/tailwind"; import markdownIntegration from "@astropub/md"; import mdxIntegration from "@astrojs/mdx"; import htaccessIntegration from "astro-htaccess"; -import pagefindIntegration from "astro-pagefind"; +import pagefindIntegration from "./src/integrations/pagefind"; import alpinejsIntegration from "@astrojs/alpinejs"; import { AI_BOTS } from "./src/data/ai_bots"; @@ -17,7 +17,9 @@ export default defineConfig({ markdownIntegration(), mdxIntegration(), alpinejsIntegration(), - pagefindIntegration(), + pagefindIntegration({ + forceLanguage: "en", + }), htaccessIntegration({ generateHtaccessFile: import.meta.env.APACHE_CONFIG === "true", customRules: [ diff --git a/package-lock.json b/package-lock.json index e168a9c..8111914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,17 +15,17 @@ "@astrojs/rss": "^4.0.7", "@astrojs/tailwind": "^5.1.1", "@astropub/md": "^1.0.0", + "@pagefind/default-ui": "^1.2.0", "@tailwindcss/typography": "^0.5.15", "@types/alpinejs": "^3.13.10", "alpinejs": "^3.14.1", "astro": "^4.15.11", "astro-htaccess": "^0.2.0", - "astro-pagefind": "^1.6.0", "clsx": "^2.1.1", "fluid-tailwind": "^1.0.3", "github-slugger": "^2.0.0", "marked": "^14.1.2", - "pagefind": "^1.1.1", + "pagefind": "^1.2.0", "polywasm": "^0.1.5", "reading-time": "^1.5.0", "sanitize-html": "^2.13.1", @@ -44,6 +44,7 @@ "prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-tailwindcss": "^0.6.8", + "sirv": "^3.0.0", "tsx": "^4.19.1" } }, @@ -1573,9 +1574,9 @@ "license": "MIT" }, "node_modules/@pagefind/darwin-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.1.1.tgz", - "integrity": "sha512-tZ9tysUmQpFs2EqWG2+E1gc+opDAhSyZSsgKmFzhnWfkK02YHZhvL5XJXEZDqYy3s1FAKhwjTg8XDxneuBlDZQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.2.0.tgz", + "integrity": "sha512-pHnPL2rm4xbe0LqV376g84hUIsVdy4PK6o2ACveo0DSGoC40eOIwPUPftnUPUinSdDWkkySaL5FT5r9hsXk0ZQ==", "cpu": [ "arm64" ], @@ -1586,9 +1587,9 @@ ] }, "node_modules/@pagefind/darwin-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.1.1.tgz", - "integrity": "sha512-ChohLQ39dLwaxQv0jIQB/SavP3TM5K5ENfDTqIdzLkmfs3+JlzSDyQKcJFjTHYcCzQOZVeieeGq8PdqvLJxJxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.2.0.tgz", + "integrity": "sha512-q2tcnfvcRyx0GnrJoUQJ5bRpiFNtI8DZWM6a4/k8sNJxm2dbM1BnY5hUeo4MbDfpb64Qc1wRMcvBUSOaMKBjfg==", "cpu": [ "x64" ], @@ -1599,15 +1600,15 @@ ] }, "node_modules/@pagefind/default-ui": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.1.1.tgz", - "integrity": "sha512-ZM0zDatWDnac/VGHhQCiM7UgA4ca8jpjA+VfuTJyHJBaxGqZMQnm4WoTz9E0KFcue1Bh9kxpu7uWFZfwpZZk0A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.2.0.tgz", + "integrity": "sha512-MDSbm34veKpzFP5eJMh/pcPdrOc4FZKUsbpDsbdjSLC2ZeuTjsfDBNu9MGZaNUvGKUdlKk5JozQkVO/dzdSxrQ==", "license": "MIT" }, "node_modules/@pagefind/linux-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.1.1.tgz", - "integrity": "sha512-H5P6wDoCoAbdsWp0Zx0DxnLUrwTGWGLu/VI1rcN2CyFdY2EGSvPQsbGBMrseKRNuIrJDFtxHHHyjZ7UbzaM9EA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.2.0.tgz", + "integrity": "sha512-wVtLOlF9AUrwLovP9ZSEKOYnwIVrrxId4I2Mz02Zxm3wbUIJyx8wHf6LyEf7W7mJ6rEjW5jtavKAbngKCAaicg==", "cpu": [ "arm64" ], @@ -1618,9 +1619,9 @@ ] }, "node_modules/@pagefind/linux-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.1.1.tgz", - "integrity": "sha512-yJs7tTYbL2MI3HT+ngs9E1BfUbY9M4/YzA0yEM5xBo4Xl8Yu8Qg2xZTOQ1/F6gwvMrjCUFo8EoACs6LRDhtMrQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.2.0.tgz", + "integrity": "sha512-Lo5aO2bA++sQTeEWzK5WKr3KU0yzVH5OnTY88apZfkgL4AVfXckH2mrOU8ouYKCLNPseIYTLFEdj0V5xjHQSwQ==", "cpu": [ "x64" ], @@ -1631,9 +1632,9 @@ ] }, "node_modules/@pagefind/windows-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.1.1.tgz", - "integrity": "sha512-b7/qPqgIl+lMzkQ8fJt51SfguB396xbIIR+VZ3YrL2tLuyifDJ1wL5mEm+ddmHxJ2Fki340paPcDan9en5OmAw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.2.0.tgz", + "integrity": "sha512-tGQcwQAb5Ndv7woc7lhH9iAdxOnTNsgCz8sEBbsASPB2A0uI8BWBmVdf2GFLQkYHqnnqYuun63sa+UOzB7Ah3g==", "cpu": [ "x64" ], @@ -1657,6 +1658,7 @@ "version": "1.0.0-next.28", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, "license": "MIT" }, "node_modules/@popperjs/core": { @@ -2534,20 +2536,6 @@ "astro": "^4.0.0" } }, - "node_modules/astro-pagefind": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/astro-pagefind/-/astro-pagefind-1.6.0.tgz", - "integrity": "sha512-U/WuE0ktkZkoFJf6yopWO4DjIJ3+wrnopE2L3kUYiyqNTJpqmp13bFLR8gir6B+KzQ5dsXQtJZYTQtKJg1FxIA==", - "license": "MIT", - "dependencies": { - "@pagefind/default-ui": "^1.0.3", - "pagefind": "^1.0.3", - "sirv": "^2.0.3" - }, - "peerDependencies": { - "astro": "^2.0.4 || ^3.0.0 || ^4.0.0" - } - }, "node_modules/astro/node_modules/@astrojs/markdown-remark": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz", @@ -6136,19 +6124,19 @@ "license": "BlueOak-1.0.0" }, "node_modules/pagefind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.1.1.tgz", - "integrity": "sha512-U2YR0dQN5B2fbIXrLtt/UXNS0yWSSYfePaad1KcBPTi0p+zRtsVjwmoPaMQgTks5DnHNbmDxyJUL5TGaLljK3A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.2.0.tgz", + "integrity": "sha512-sFVv5/x73qCp9KlLHv8/uWDv7rG1tsWcG9MuXc5YTrXIrb8c1Gshm9oc5rMLXNZILXUWai8WczqaK4jjroEzng==", "license": "MIT", "bin": { "pagefind": "lib/runner/bin.cjs" }, "optionalDependencies": { - "@pagefind/darwin-arm64": "1.1.1", - "@pagefind/darwin-x64": "1.1.1", - "@pagefind/linux-arm64": "1.1.1", - "@pagefind/linux-x64": "1.1.1", - "@pagefind/windows-x64": "1.1.1" + "@pagefind/darwin-arm64": "1.2.0", + "@pagefind/darwin-x64": "1.2.0", + "@pagefind/linux-arm64": "1.2.0", + "@pagefind/linux-x64": "1.2.0", + "@pagefind/windows-x64": "1.2.0" } }, "node_modules/parse-entities": { @@ -7225,9 +7213,10 @@ } }, "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", + "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", + "dev": true, "license": "MIT", "dependencies": { "@polka/url": "^1.0.0-next.24", @@ -7235,7 +7224,7 @@ "totalist": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, "node_modules/sisteransi": { @@ -7672,6 +7661,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/package.json b/package.json index 24d6bec..d76ceb1 100644 --- a/package.json +++ b/package.json @@ -22,17 +22,17 @@ "@astrojs/rss": "^4.0.7", "@astrojs/tailwind": "^5.1.1", "@astropub/md": "^1.0.0", + "@pagefind/default-ui": "^1.2.0", "@tailwindcss/typography": "^0.5.15", "@types/alpinejs": "^3.13.10", "alpinejs": "^3.14.1", "astro": "^4.15.11", "astro-htaccess": "^0.2.0", - "astro-pagefind": "^1.6.0", "clsx": "^2.1.1", "fluid-tailwind": "^1.0.3", "github-slugger": "^2.0.0", "marked": "^14.1.2", - "pagefind": "^1.1.1", + "pagefind": "^1.2.0", "polywasm": "^0.1.5", "reading-time": "^1.5.0", "sanitize-html": "^2.13.1", @@ -51,6 +51,7 @@ "prettier": "^3.3.3", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-tailwindcss": "^0.6.8", + "sirv": "^3.0.0", "tsx": "^4.19.1" } } diff --git a/src/data/licenses.toml b/src/data/licenses.toml index d62c1c9..15b7b14 100644 --- a/src/data/licenses.toml +++ b/src/data/licenses.toml @@ -8,8 +8,8 @@ date = "2024" author = { name = "Bad Manners", url = "https://badmanners.xyz", email = "me@badmanners.xyz>" } source = "https://git.badmanners.xyz/badmanners/gallery.badmanners.xyz" license = { name = "MIT", url = "https://opensource.org/license/mit" } -notes = """All rights reserved. -The MIT License applies only to the source code; see additional copyrights for details.""" +notes = """All rights reserved. \ + The MIT License applies only to the source code; see additional copyrights for details.""" [[copyright.additional]] type = "logo" @@ -18,7 +18,7 @@ notes = "The briefcase logo is copyrighted and trademarked by me. All rights res [[copyright.additional]] type = "characters" notes = """My characters, whether directly attributed to me or unattributed, are copyrighted by me. \ -All rights reserved.""" + All rights reserved.""" [[copyright.additional]] type = "content" @@ -32,7 +32,7 @@ notes = "All rights reserved." [[copyright.additional]] type = "third-party trademarks" notes = """All third-party copyrights, trademarks, and attributed characters belong to their respective owners, \ -and I'm not affiliated with any of them.""" + and I'm not affiliated with any of them.""" [[attributions]] title = "Noto" @@ -55,7 +55,7 @@ items = [ "Weasyl", ] notes = """All third-party copyrights and trademarks belong to their respective owners, \ -and I'm not affiliated with any of them.""" + and I'm not affiliated with any of them.""" [[attributions]] description = "Edited icons for other websites." @@ -69,9 +69,9 @@ items = [ "Inkbunny", "SoFurry", ] -notes = """Original icons edited for personal use and released under a permissive license. -All third-party copyrights and trademarks belong to their respective owners, \ -and I'm not affiliated with any of them.""" +notes = """Original icons edited for personal use and released under a permissive license. \ + All third-party copyrights and trademarks belong to their respective owners, \ + and I'm not affiliated with any of them.""" [[attributions]] author = "Font Awesome" @@ -106,6 +106,6 @@ type = "icons" source = "https://github.com/twitter/twemoji" license = { name = "CC-BY-4.0", url = "https://creativecommons.org/licenses/by/4.0/" } items = [ - "U+1F51E No One Under Eighteen Symbol", - "U+2728 Sparkles", + "πŸ”ž U+1F51E No One Under Eighteen Symbol", + "✨ U+2728 Sparkles", ] diff --git a/src/env.d.ts b/src/env.d.ts index 1cb3011..e27f673 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -5,3 +5,9 @@ declare module "polywasm" { export const WebAssembly: typeof globalThis.WebAssembly; } + +declare module "@pagefind/default-ui" { + declare class PagefindUI { + constructor(arg: any); + } +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index af585f2..4ec70c1 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -28,6 +28,33 @@ const UI_STRINGS = { tok: (count: number, nounSingular: string, nounPlural?: string) => `${(count > 1 && nounPlural) || nounSingular} ${["ala", "wan", "tu"][count] || "mute"}`, }, + // Language functions + "language/language": { + en: (language?: Lang) => { + if (!language || language === "en") { + return "English"; + } + switch (language) { + case "tok": + return "toki pona"; + default: + let unknown: never = language; + throw new Error(`Unknown language ${unknown}`); + } + }, + tok: (language?: Lang) => { + if (!language || language === "tok") { + return "toki pona"; + } + switch (language) { + case "en": + return "toki Inli"; + default: + let unknown: never = language; + throw new Error(`Unknown language ${unknown}`); + } + }, + }, // export-story API functions "export_story/authors": { en: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`, diff --git a/src/integrations/pagefind.ts b/src/integrations/pagefind.ts new file mode 100644 index 0000000..bf1607c --- /dev/null +++ b/src/integrations/pagefind.ts @@ -0,0 +1,96 @@ +import type { AstroIntegration } from "astro"; +import { fileURLToPath } from "node:url"; +import { join } from "node:path"; +import sirv from "sirv"; +import * as pf from "pagefind"; + +type PagefindConfig = pf.PagefindServiceConfig; + +export default function pagefind(config: PagefindConfig = {}): AstroIntegration { + let outDir: string; + let assets: string | null; + return { + name: "pagefind", + hooks: { + "astro:config:setup": ({ config, logger }) => { + if (config.output === "server") { + logger.warn( + "Output type `server` does not produce static *.html pages in its output and thus will not work with astro-pagefind integration.", + ); + return; + } + + if (config.adapter?.name.startsWith("@astrojs/vercel")) { + outDir = fileURLToPath(new URL(".vercel/output/static/", config.root)); + } else if (config.adapter?.name === "@astrojs/cloudflare") { + outDir = fileURLToPath(new URL(config.base?.replace(/^\//, ""), config.outDir)); + } else if (config.adapter?.name === "@astrojs/node" && config.output === "hybrid") { + outDir = fileURLToPath(config.build.client!); + } else { + outDir = fileURLToPath(config.outDir); + } + if (config.build.assetsPrefix) { + assets = null; + } else { + assets = config.build.assets; + } + }, + "astro:server:setup": ({ server, logger }) => { + if (!outDir) { + logger.warn( + "astro-pagefind couldn't reliably determine the output directory. Search assets will not be served.", + ); + return; + } + + const serve = sirv(outDir, { + dev: true, + etag: true, + }); + server.middlewares.use((req, res, next) => { + if (req.url?.startsWith("/pagefind/") || (assets && req.url?.startsWith(`/${assets}/`))) { + serve(req, res, next); + } else { + next(); + } + }); + }, + "astro:build:done": async ({ logger }) => { + if (!outDir) { + logger.warn( + "astro-pagefind couldn't reliably determine the output directory. Search index will not be built.", + ); + return; + } + + const { index, errors: createIndexErrors } = await pf.createIndex(config); + if (createIndexErrors.length) { + logger.warn( + `astro-pagefind errored when creating index. Search index will not be built.\n\n${createIndexErrors.join("\n")}`, + ); + await pf.close(); + return; + } + const { page_count, errors: addDirectoryErrors } = await index!.addDirectory({ path: outDir }); + if (addDirectoryErrors.length) { + logger.warn( + `astro-pagefind errored when adding the output directory. Search index will not be built.\n\n${addDirectoryErrors.join("\n")}`, + ); + await pf.close(); + return; + } + logger.info(`astro-pagefind has indexed ${page_count} page(s).`); + const { errors: writeFilesErrors } = await index!.writeFiles({ + outputPath: join(outDir, "pagefind"), + }); + if (writeFilesErrors.length) { + logger.warn( + `astro-pagefind errored when writing files. Search index will not be built.\n\n${writeFilesErrors.join("\n")}`, + ); + await pf.close(); + return; + } + }, + }, + }; +} diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 9f3f873..a0131fd 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -11,6 +11,16 @@ type Props = { }; const { pageTitle, lang = "en", isAgeRestricted } = Astro.props; +const fonts = [ + "/fonts/noto-sans-latin-ext-wght-normal.woff2", + "/fonts/noto-sans-latin-wght-normal.woff2", + "/fonts/noto-sans-latin-ext-wght-italic.woff2", + "/fonts/noto-sans-latin-wght-italic.woff2", + "/fonts/noto-serif-latin-ext-wght-normal.woff2", + "/fonts/noto-serif-latin-wght-normal.woff2", + "/fonts/noto-serif-latin-ext-wght-italic.woff2", + "/fonts/noto-serif-latin-wght-italic.woff2", +]; --- @@ -24,6 +34,7 @@ const { pageTitle, lang = "en", isAgeRestricted } = Astro.props; + {fonts.map((font) => )} {pageTitle} | Bad Manners diff --git a/src/layouts/PublishedContentLayout.astro b/src/layouts/PublishedContentLayout.astro index 41a7e15..5219135 100644 --- a/src/layouts/PublishedContentLayout.astro +++ b/src/layouts/PublishedContentLayout.astro @@ -163,7 +163,9 @@ const returnTo = series
{ props.prev || props.next ? ( diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index b8cf9ba..b4425c0 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -5,6 +5,7 @@ import GalleryLayout from "@layouts/GalleryLayout.astro"; import UserComponent from "@components/UserComponent.astro"; import { markdownToPlaintext } from "@utils/markdown_to_plaintext"; import { IconNoOneUnder18, IconSquareRSS } from "@components/icons"; +import { t } from "@i18n"; type PostWithPubDate = CollectionEntry<"blog"> & { data: { pubDate: Date } }; @@ -35,7 +36,7 @@ const posts = await Promise.all(