Improved search

This commit is contained in:
Bad Manners 2024-11-07 15:11:48 -03:00
parent 815ca4d528
commit 122cfaf7eb
Signed by: badmanners
GPG key ID: 8C88292CCB075609
18 changed files with 275 additions and 72 deletions

View file

@ -2,6 +2,8 @@
Static website built in Astro + Typescript + TailwindCSS. Static website built in Astro + Typescript + TailwindCSS.
For attributions, see [`licenses.toml`](src/data/licenses.toml).
## Requirements ## Requirements
- Node.js 20+ - Node.js 20+
@ -32,6 +34,7 @@ The following optional environment variables can be set within a `.env` file:
| Name | Type | Description | | 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. | | `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 ### Export story for upload

View file

@ -3,7 +3,7 @@ import tailwindIntegration from "@astrojs/tailwind";
import markdownIntegration from "@astropub/md"; import markdownIntegration from "@astropub/md";
import mdxIntegration from "@astrojs/mdx"; import mdxIntegration from "@astrojs/mdx";
import htaccessIntegration from "astro-htaccess"; import htaccessIntegration from "astro-htaccess";
import pagefindIntegration from "astro-pagefind"; import pagefindIntegration from "./src/integrations/pagefind";
import alpinejsIntegration from "@astrojs/alpinejs"; import alpinejsIntegration from "@astrojs/alpinejs";
import { AI_BOTS } from "./src/data/ai_bots"; import { AI_BOTS } from "./src/data/ai_bots";
@ -17,7 +17,9 @@ export default defineConfig({
markdownIntegration(), markdownIntegration(),
mdxIntegration(), mdxIntegration(),
alpinejsIntegration(), alpinejsIntegration(),
pagefindIntegration(), pagefindIntegration({
forceLanguage: "en",
}),
htaccessIntegration({ htaccessIntegration({
generateHtaccessFile: import.meta.env.APACHE_CONFIG === "true", generateHtaccessFile: import.meta.env.APACHE_CONFIG === "true",
customRules: [ customRules: [

82
package-lock.json generated
View file

@ -15,17 +15,17 @@
"@astrojs/rss": "^4.0.7", "@astrojs/rss": "^4.0.7",
"@astrojs/tailwind": "^5.1.1", "@astrojs/tailwind": "^5.1.1",
"@astropub/md": "^1.0.0", "@astropub/md": "^1.0.0",
"@pagefind/default-ui": "^1.2.0",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/alpinejs": "^3.13.10", "@types/alpinejs": "^3.13.10",
"alpinejs": "^3.14.1", "alpinejs": "^3.14.1",
"astro": "^4.15.11", "astro": "^4.15.11",
"astro-htaccess": "^0.2.0", "astro-htaccess": "^0.2.0",
"astro-pagefind": "^1.6.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"fluid-tailwind": "^1.0.3", "fluid-tailwind": "^1.0.3",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"marked": "^14.1.2", "marked": "^14.1.2",
"pagefind": "^1.1.1", "pagefind": "^1.2.0",
"polywasm": "^0.1.5", "polywasm": "^0.1.5",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"sanitize-html": "^2.13.1", "sanitize-html": "^2.13.1",
@ -44,6 +44,7 @@
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.8", "prettier-plugin-tailwindcss": "^0.6.8",
"sirv": "^3.0.0",
"tsx": "^4.19.1" "tsx": "^4.19.1"
} }
}, },
@ -1573,9 +1574,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@pagefind/darwin-arm64": { "node_modules/@pagefind/darwin-arm64": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.2.0.tgz",
"integrity": "sha512-tZ9tysUmQpFs2EqWG2+E1gc+opDAhSyZSsgKmFzhnWfkK02YHZhvL5XJXEZDqYy3s1FAKhwjTg8XDxneuBlDZQ==", "integrity": "sha512-pHnPL2rm4xbe0LqV376g84hUIsVdy4PK6o2ACveo0DSGoC40eOIwPUPftnUPUinSdDWkkySaL5FT5r9hsXk0ZQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1586,9 +1587,9 @@
] ]
}, },
"node_modules/@pagefind/darwin-x64": { "node_modules/@pagefind/darwin-x64": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.2.0.tgz",
"integrity": "sha512-ChohLQ39dLwaxQv0jIQB/SavP3TM5K5ENfDTqIdzLkmfs3+JlzSDyQKcJFjTHYcCzQOZVeieeGq8PdqvLJxJxQ==", "integrity": "sha512-q2tcnfvcRyx0GnrJoUQJ5bRpiFNtI8DZWM6a4/k8sNJxm2dbM1BnY5hUeo4MbDfpb64Qc1wRMcvBUSOaMKBjfg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1599,15 +1600,15 @@
] ]
}, },
"node_modules/@pagefind/default-ui": { "node_modules/@pagefind/default-ui": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.2.0.tgz",
"integrity": "sha512-ZM0zDatWDnac/VGHhQCiM7UgA4ca8jpjA+VfuTJyHJBaxGqZMQnm4WoTz9E0KFcue1Bh9kxpu7uWFZfwpZZk0A==", "integrity": "sha512-MDSbm34veKpzFP5eJMh/pcPdrOc4FZKUsbpDsbdjSLC2ZeuTjsfDBNu9MGZaNUvGKUdlKk5JozQkVO/dzdSxrQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@pagefind/linux-arm64": { "node_modules/@pagefind/linux-arm64": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.2.0.tgz",
"integrity": "sha512-H5P6wDoCoAbdsWp0Zx0DxnLUrwTGWGLu/VI1rcN2CyFdY2EGSvPQsbGBMrseKRNuIrJDFtxHHHyjZ7UbzaM9EA==", "integrity": "sha512-wVtLOlF9AUrwLovP9ZSEKOYnwIVrrxId4I2Mz02Zxm3wbUIJyx8wHf6LyEf7W7mJ6rEjW5jtavKAbngKCAaicg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -1618,9 +1619,9 @@
] ]
}, },
"node_modules/@pagefind/linux-x64": { "node_modules/@pagefind/linux-x64": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.2.0.tgz",
"integrity": "sha512-yJs7tTYbL2MI3HT+ngs9E1BfUbY9M4/YzA0yEM5xBo4Xl8Yu8Qg2xZTOQ1/F6gwvMrjCUFo8EoACs6LRDhtMrQ==", "integrity": "sha512-Lo5aO2bA++sQTeEWzK5WKr3KU0yzVH5OnTY88apZfkgL4AVfXckH2mrOU8ouYKCLNPseIYTLFEdj0V5xjHQSwQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1631,9 +1632,9 @@
] ]
}, },
"node_modules/@pagefind/windows-x64": { "node_modules/@pagefind/windows-x64": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.2.0.tgz",
"integrity": "sha512-b7/qPqgIl+lMzkQ8fJt51SfguB396xbIIR+VZ3YrL2tLuyifDJ1wL5mEm+ddmHxJ2Fki340paPcDan9en5OmAw==", "integrity": "sha512-tGQcwQAb5Ndv7woc7lhH9iAdxOnTNsgCz8sEBbsASPB2A0uI8BWBmVdf2GFLQkYHqnnqYuun63sa+UOzB7Ah3g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1657,6 +1658,7 @@
"version": "1.0.0-next.28", "version": "1.0.0-next.28",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz",
"integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@popperjs/core": { "node_modules/@popperjs/core": {
@ -2534,20 +2536,6 @@
"astro": "^4.0.0" "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": { "node_modules/astro/node_modules/@astrojs/markdown-remark": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz", "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz",
@ -6136,19 +6124,19 @@
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/pagefind": { "node_modules/pagefind": {
"version": "1.1.1", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.2.0.tgz",
"integrity": "sha512-U2YR0dQN5B2fbIXrLtt/UXNS0yWSSYfePaad1KcBPTi0p+zRtsVjwmoPaMQgTks5DnHNbmDxyJUL5TGaLljK3A==", "integrity": "sha512-sFVv5/x73qCp9KlLHv8/uWDv7rG1tsWcG9MuXc5YTrXIrb8c1Gshm9oc5rMLXNZILXUWai8WczqaK4jjroEzng==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"pagefind": "lib/runner/bin.cjs" "pagefind": "lib/runner/bin.cjs"
}, },
"optionalDependencies": { "optionalDependencies": {
"@pagefind/darwin-arm64": "1.1.1", "@pagefind/darwin-arm64": "1.2.0",
"@pagefind/darwin-x64": "1.1.1", "@pagefind/darwin-x64": "1.2.0",
"@pagefind/linux-arm64": "1.1.1", "@pagefind/linux-arm64": "1.2.0",
"@pagefind/linux-x64": "1.1.1", "@pagefind/linux-x64": "1.2.0",
"@pagefind/windows-x64": "1.1.1" "@pagefind/windows-x64": "1.2.0"
} }
}, },
"node_modules/parse-entities": { "node_modules/parse-entities": {
@ -7225,9 +7213,10 @@
} }
}, },
"node_modules/sirv": { "node_modules/sirv": {
"version": "2.0.4", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@polka/url": "^1.0.0-next.24", "@polka/url": "^1.0.0-next.24",
@ -7235,7 +7224,7 @@
"totalist": "^3.0.0" "totalist": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">=18"
} }
}, },
"node_modules/sisteransi": { "node_modules/sisteransi": {
@ -7672,6 +7661,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"

View file

@ -22,17 +22,17 @@
"@astrojs/rss": "^4.0.7", "@astrojs/rss": "^4.0.7",
"@astrojs/tailwind": "^5.1.1", "@astrojs/tailwind": "^5.1.1",
"@astropub/md": "^1.0.0", "@astropub/md": "^1.0.0",
"@pagefind/default-ui": "^1.2.0",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@types/alpinejs": "^3.13.10", "@types/alpinejs": "^3.13.10",
"alpinejs": "^3.14.1", "alpinejs": "^3.14.1",
"astro": "^4.15.11", "astro": "^4.15.11",
"astro-htaccess": "^0.2.0", "astro-htaccess": "^0.2.0",
"astro-pagefind": "^1.6.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"fluid-tailwind": "^1.0.3", "fluid-tailwind": "^1.0.3",
"github-slugger": "^2.0.0", "github-slugger": "^2.0.0",
"marked": "^14.1.2", "marked": "^14.1.2",
"pagefind": "^1.1.1", "pagefind": "^1.2.0",
"polywasm": "^0.1.5", "polywasm": "^0.1.5",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"sanitize-html": "^2.13.1", "sanitize-html": "^2.13.1",
@ -51,6 +51,7 @@
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.8", "prettier-plugin-tailwindcss": "^0.6.8",
"sirv": "^3.0.0",
"tsx": "^4.19.1" "tsx": "^4.19.1"
} }
} }

View file

@ -8,8 +8,8 @@ date = "2024"
author = { name = "Bad Manners", url = "https://badmanners.xyz", email = "me@badmanners.xyz>" } author = { name = "Bad Manners", url = "https://badmanners.xyz", email = "me@badmanners.xyz>" }
source = "https://git.badmanners.xyz/badmanners/gallery.badmanners.xyz" source = "https://git.badmanners.xyz/badmanners/gallery.badmanners.xyz"
license = { name = "MIT", url = "https://opensource.org/license/mit" } license = { name = "MIT", url = "https://opensource.org/license/mit" }
notes = """All rights reserved. notes = """All rights reserved. \
The MIT License applies only to the source code; see additional copyrights for details.""" The MIT License applies only to the source code; see additional copyrights for details."""
[[copyright.additional]] [[copyright.additional]]
type = "logo" type = "logo"
@ -18,7 +18,7 @@ notes = "The briefcase logo is copyrighted and trademarked by me. All rights res
[[copyright.additional]] [[copyright.additional]]
type = "characters" type = "characters"
notes = """My characters, whether directly attributed to me or unattributed, are copyrighted by me. \ notes = """My characters, whether directly attributed to me or unattributed, are copyrighted by me. \
All rights reserved.""" All rights reserved."""
[[copyright.additional]] [[copyright.additional]]
type = "content" type = "content"
@ -32,7 +32,7 @@ notes = "All rights reserved."
[[copyright.additional]] [[copyright.additional]]
type = "third-party trademarks" type = "third-party trademarks"
notes = """All third-party copyrights, trademarks, and attributed characters belong to their respective owners, \ 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]] [[attributions]]
title = "Noto" title = "Noto"
@ -55,7 +55,7 @@ items = [
"Weasyl", "Weasyl",
] ]
notes = """All third-party copyrights and trademarks belong to their respective owners, \ 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]] [[attributions]]
description = "Edited icons for other websites." description = "Edited icons for other websites."
@ -69,9 +69,9 @@ items = [
"Inkbunny", "Inkbunny",
"SoFurry", "SoFurry",
] ]
notes = """Original icons edited for personal use and released under a permissive license. notes = """Original icons edited for personal use and released under a permissive license. \
All third-party copyrights and trademarks belong to their respective owners, \ 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]] [[attributions]]
author = "Font Awesome" author = "Font Awesome"
@ -106,6 +106,6 @@ type = "icons"
source = "https://github.com/twitter/twemoji" source = "https://github.com/twitter/twemoji"
license = { name = "CC-BY-4.0", url = "https://creativecommons.org/licenses/by/4.0/" } license = { name = "CC-BY-4.0", url = "https://creativecommons.org/licenses/by/4.0/" }
items = [ items = [
"U+1F51E No One Under Eighteen Symbol", "🔞 U+1F51E No One Under Eighteen Symbol",
"U+2728 Sparkles", "U+2728 Sparkles",
] ]

6
src/env.d.ts vendored
View file

@ -5,3 +5,9 @@
declare module "polywasm" { declare module "polywasm" {
export const WebAssembly: typeof globalThis.WebAssembly; export const WebAssembly: typeof globalThis.WebAssembly;
} }
declare module "@pagefind/default-ui" {
declare class PagefindUI {
constructor(arg: any);
}
}

View file

@ -28,6 +28,33 @@ const UI_STRINGS = {
tok: (count: number, nounSingular: string, nounPlural?: string) => tok: (count: number, nounSingular: string, nounPlural?: string) =>
`${(count > 1 && nounPlural) || nounSingular} ${["ala", "wan", "tu"][count] || "mute"}`, `${(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 API functions
"export_story/authors": { "export_story/authors": {
en: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`, en: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`,

View file

@ -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;
}
},
},
};
}

View file

@ -11,6 +11,16 @@ type Props = {
}; };
const { pageTitle, lang = "en", isAgeRestricted } = Astro.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",
];
--- ---
<html lang={lang}> <html lang={lang}>
@ -24,6 +34,7 @@ const { pageTitle, lang = "en", isAgeRestricted } = Astro.props;
<meta name="msapplication-TileColor" content="#37b340" /> <meta name="msapplication-TileColor" content="#37b340" />
<meta name="theme-color" content="#7DD05A" data-react-helmet="true" /> <meta name="theme-color" content="#7DD05A" data-react-helmet="true" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{fonts.map((font) => <link rel="preload" href={font} as="font" type="font/woff2" crossorigin="anonymous" />)}
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{pageTitle} | Bad Manners</title> <title>{pageTitle} | Bad Manners</title>
<link rel="me" href="https://badmanners.xyz" /> <link rel="me" href="https://badmanners.xyz" />

View file

@ -163,7 +163,9 @@ const returnTo = series
<main <main
class="h-entry mx-auto max-w-6xl rounded-lg bg-stone-50 px-2 pb-10 shadow-sm sm:px-6 md:px-32 lg:px-64 dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none" class="h-entry mx-auto max-w-6xl rounded-lg bg-stone-50 px-2 pb-10 shadow-sm sm:px-6 md:px-32 lg:px-64 dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
data-pagefind-body={props.isDraft ? undefined : ""} data-pagefind-body={props.isDraft ? undefined : ""}
data-pagefind-meta={`type:${props.publishedContentType}`} data-language={t(props.lang, "language/language")}
data-type={props.publishedContentType}
data-pagefind-filter="type[data-type], language[data-language]"
> >
{ {
props.prev || props.next ? ( props.prev || props.next ? (

View file

@ -5,6 +5,7 @@ import GalleryLayout from "@layouts/GalleryLayout.astro";
import UserComponent from "@components/UserComponent.astro"; import UserComponent from "@components/UserComponent.astro";
import { markdownToPlaintext } from "@utils/markdown_to_plaintext"; import { markdownToPlaintext } from "@utils/markdown_to_plaintext";
import { IconNoOneUnder18, IconSquareRSS } from "@components/icons"; import { IconNoOneUnder18, IconSquareRSS } from "@components/icons";
import { t } from "@i18n";
type PostWithPubDate = CollectionEntry<"blog"> & { data: { pubDate: Date } }; type PostWithPubDate = CollectionEntry<"blog"> & { data: { pubDate: Date } };
@ -35,7 +36,7 @@ const posts = await Promise.all(
<ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{ {
posts.map((post, i) => ( posts.map((post, i) => (
<li class="h-entry" lang={post.data.lang}> <li class="h-entry">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/blog/${post.slug}`} href={`/blog/${post.slug}`}
@ -79,6 +80,9 @@ const posts = await Promise.all(
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{post.data.description} {post.data.description}
</p> </p>
<p class="p-language" aria-label="Language">
{t(post.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span>
{post.authors.map((author) => ( {post.authors.map((author) => (

View file

@ -35,7 +35,7 @@ const games = await Promise.all(
<ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="my-6 flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{ {
games.map((game, i) => ( games.map((game, i) => (
<li class="h-entry" lang={game.data.lang}> <li class="h-entry">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/games/${game.slug}`} href={`/games/${game.slug}`}
@ -79,6 +79,9 @@ const games = await Promise.all(
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)} {t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
</p> </p>
<p class="p-language" aria-label="Language">
{t(game.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{game.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{game.authors.length == 1 ? "Author:" : "Authors:"}</span>
{game.authors.map((author) => ( {game.authors.map((author) => (

View file

@ -188,7 +188,7 @@ if (featuredItems.length > MAX_FEATURED_ITEMS) {
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{ {
featuredItems.map((entry) => ( featuredItems.map((entry) => (
<li class="h-entry break-inside-avoid" lang={entry.lang} aria-label={entry.title}> <li class="h-entry break-inside-avoid" aria-label={entry.title}>
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={entry.href} href={entry.href}
@ -235,6 +235,9 @@ if (featuredItems.length > MAX_FEATURED_ITEMS) {
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{entry.altText} {entry.altText}
</p> </p>
<p class="p-language" aria-label="Language">
{t(entry.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{entry.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{entry.authors.length == 1 ? "Author:" : "Authors:"}</span>
{entry.authors.map((author) => ( {entry.authors.map((author) => (
@ -254,7 +257,7 @@ if (featuredItems.length > MAX_FEATURED_ITEMS) {
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{ {
latestItems.map((entry) => ( latestItems.map((entry) => (
<li class="h-entry break-inside-avoid" lang={entry.lang} aria-label={entry.title}> <li class="h-entry break-inside-avoid" aria-label={entry.title}>
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={entry.href} href={entry.href}
@ -301,6 +304,9 @@ if (featuredItems.length > MAX_FEATURED_ITEMS) {
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{entry.altText} {entry.altText}
</p> </p>
<p class="p-language" aria-label="Language">
{t(entry.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{entry.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{entry.authors.length == 1 ? "Author:" : "Authors:"}</span>
{entry.authors.map((author) => ( {entry.authors.map((author) => (

View file

@ -1,11 +1,30 @@
--- ---
import SearchComponent from "astro-pagefind/components/Search";
import GalleryLayout from "@layouts/GalleryLayout.astro"; import GalleryLayout from "@layouts/GalleryLayout.astro";
import "@pagefind/default-ui/css/ui.css";
const bundlePath = `${import.meta.env.BASE_URL}pagefind/`;
--- ---
<script> <script>
import { WebAssembly } from "polywasm"; import { WebAssembly } from "polywasm";
import { PagefindUI } from "@pagefind/default-ui";
globalThis.WebAssembly = globalThis.WebAssembly || WebAssembly; globalThis.WebAssembly = globalThis.WebAssembly || WebAssembly;
function initPageFind() {
const bundlePath = `${import.meta.env.BASE_URL}pagefind/`;
new PagefindUI({
element: "#search",
bundlePath,
resetStyles: false,
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initPageFind);
} else {
initPageFind();
}
</script> </script>
<GalleryLayout pageTitle="Search"> <GalleryLayout pageTitle="Search">
@ -15,5 +34,5 @@ import GalleryLayout from "@layouts/GalleryLayout.astro";
</Fragment> </Fragment>
<h1 class="m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Search</h1> <h1 class="m-2 text-3xl font-semibold text-stone-800 dark:text-stone-100">Search</h1>
<hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden /> <hr class="mb-3 ml-[2px] mt-2 h-[4px] max-w-xs rounded-sm bg-stone-800 dark:bg-stone-100" aria-hidden />
<SearchComponent id="search" className="pagefind-ui my-4" /> <div id="search" class="pagefind-ui pagefind-init my-4" data-pagefind-ui data-bundle-path={bundlePath}></div>
</GalleryLayout> </GalleryLayout>

View file

@ -81,7 +81,7 @@ const totalPages = Math.ceil(page.total / page.size);
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{ {
page.data.map((story, i) => ( page.data.map((story, i) => (
<li class="h-entry break-inside-avoid" lang={story.data.lang}> <li class="h-entry break-inside-avoid">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/stories/${story.slug}`} href={`/stories/${story.slug}`}
@ -125,6 +125,9 @@ const totalPages = Math.ceil(page.total / page.size);
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)} {t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
</p> </p>
<p class="p-language" aria-label="Language">
{t(story.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{story.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{story.authors.length == 1 ? "Author:" : "Authors:"}</span>
{story.authors.map((author) => ( {story.authors.map((author) => (

View file

@ -4,6 +4,7 @@ import { Image } from "astro:assets";
import GalleryLayout from "@layouts/GalleryLayout.astro"; import GalleryLayout from "@layouts/GalleryLayout.astro";
import mapImage from "@assets/images/tlotm_map.jpg"; import mapImage from "@assets/images/tlotm_map.jpg";
import { IconNoOneUnder18 } from "@components/icons"; import { IconNoOneUnder18 } from "@components/icons";
import { t } from "@i18n";
type StoryWithPubDate = CollectionEntry<"stories"> & { data: { pubDate: Date } }; type StoryWithPubDate = CollectionEntry<"stories"> & { data: { pubDate: Date } };
@ -38,7 +39,9 @@ const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summ
<h2 <h2
id="main-chapters" id="main-chapters"
class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100"
data-pagefind-meta="type:series" data-language={t("en", "language/language")}
data-type="series"
data-pagefind-filter="type[data-type], language[data-language]"
> >
Main chapters Main chapters
</h2> </h2>

View file

@ -164,7 +164,7 @@ const totalWorksWithTag = t(
</h2> </h2>
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{props.stories.map((story) => ( {props.stories.map((story) => (
<li class="h-entry break-inside-avoid" lang={story.data.lang}> <li class="h-entry break-inside-avoid">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/stories/${story.slug}`} href={`/stories/${story.slug}`}
@ -211,6 +211,9 @@ const totalWorksWithTag = t(
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)} {t(story.data.lang, "story/warnings", story.data.wordCount, story.data.contentWarning)}
</p> </p>
<p class="p-language" aria-label="Language">
{t(story.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{story.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{story.authors.length == 1 ? "Author:" : "Authors:"}</span>
{story.authors.map((author) => ( {story.authors.map((author) => (
@ -232,7 +235,7 @@ const totalWorksWithTag = t(
</h2> </h2>
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{props.games.map((game) => ( {props.games.map((game) => (
<li class="h-entry break-inside-avoid" lang={game.data.lang}> <li class="h-entry break-inside-avoid">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/games/${game.slug}`} href={`/games/${game.slug}`}
@ -275,6 +278,9 @@ const totalWorksWithTag = t(
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)} {t(game.data.lang, "game/warnings", game.data.platforms, game.data.contentWarning)}
</p> </p>
<p class="p-language" aria-label="Language">
{t(game.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{game.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{game.authors.length == 1 ? "Author:" : "Authors:"}</span>
{game.authors.map((author) => ( {game.authors.map((author) => (
@ -296,7 +302,7 @@ const totalWorksWithTag = t(
</h2> </h2>
<ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal"> <ul class="flex flex-wrap items-start justify-center gap-4 text-center md:justify-normal">
{props.blogPosts.map((post) => ( {props.blogPosts.map((post) => (
<li class="h-entry break-inside-avoid" lang={post.data.lang}> <li class="h-entry break-inside-avoid">
<a <a
class="u-url text-link hover:underline focus:underline" class="u-url text-link hover:underline focus:underline"
href={`/blog/${post.slug}`} href={`/blog/${post.slug}`}
@ -339,6 +345,9 @@ const totalWorksWithTag = t(
<p class="p-summary" aria-label="Summary"> <p class="p-summary" aria-label="Summary">
{post.data.description} {post.data.description}
</p> </p>
<p class="p-language" aria-label="Language">
{t(post.data.lang, "language/language")}
</p>
<div aria-label="Authors"> <div aria-label="Authors">
<span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span> <span>{post.authors.length == 1 ? "Author:" : "Authors:"}</span>
{post.authors.map((author) => ( {post.authors.map((author) => (

View file

@ -28,6 +28,24 @@
} }
} }
/* Pagefind inputs */
.pagefind-ui input::placeholder {
color: theme(colors.stone.500) !important;
opacity: 1 !important;
}
.pagefind-ui input:checked {
background-color: theme(colors.bm.600) !important;
}
@media not print {
.dark .pagefind-ui input::placeholder {
color: theme(colors.stone.400) !important;
opacity: 1 !important;
}
.dark .pagefind-ui input:checked {
background-color: theme(colors.green.800) !important;
}
}
@layer components { @layer components {
.text-link, .text-link,
.pagefind-ui .pagefind-ui__result-link { .pagefind-ui .pagefind-ui__result-link {