From 9a2eb67545f431884d36ac9c011ac92a584cd537 Mon Sep 17 00:00:00 2001 From: Keisuke Umeno <9renpoto@gmail.com> Date: Mon, 23 Mar 2026 15:55:52 +0900 Subject: [PATCH 1/5] chore: add Algolia index sync and update GitHub runners --- .github/workflows/actionlint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index dbcdeb7..60f326f 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -16,7 +16,7 @@ concurrency: jobs: actionlint: - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - uses: actions/checkout@v6 - uses: reviewdog/action-actionlint@v1 From a38fc75755d2cdaa9643442f12407b13c1266d5d Mon Sep 17 00:00:00 2001 From: Keisuke Umeno <9renpoto@gmail.com> Date: Tue, 24 Mar 2026 08:20:58 +0900 Subject: [PATCH 2/5] Apply suggestion from @9renpoto --- .github/workflows/actionlint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 60f326f..dbcdeb7 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -16,7 +16,7 @@ concurrency: jobs: actionlint: - runs-on: ubuntu-slim + runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: reviewdog/action-actionlint@v1 From bbae0024088ca5812234e2f4d84b904335a5456e Mon Sep 17 00:00:00 2001 From: Keisuke Umeno <9renpoto@gmail.com> Date: Tue, 24 Mar 2026 09:50:46 +0900 Subject: [PATCH 3/5] feat: migrate search UI from DocSearch to Algolia autocomplete --- README.md | 59 +++-------- client.ts | 1 - components/Header.tsx | 30 +++--- deno.json | 4 +- deno.lock | 189 ++++++++++++++++++++++++++++++++-- islands/AlgoliaSearch.tsx | 122 ++++++++++++++++++++++ islands/DocSearch.tsx | 59 ----------- routes/_app.tsx | 15 +-- scripts/sync_algolia_index.ts | 5 +- static/styles.css | 97 +++++++++++++---- 10 files changed, 425 insertions(+), 156 deletions(-) create mode 100644 islands/AlgoliaSearch.tsx delete mode 100644 islands/DocSearch.tsx diff --git a/README.md b/README.md index 914cc5f..1ee1349 100644 --- a/README.md +++ b/README.md @@ -32,51 +32,26 @@ deno task preview This launches the prerendered build on port `8000`. -## DocSearch (Algolia) +## Algolia search -This site can integrate [DocSearch](https://docsearch.algolia.com/). +This site uses Algolia search. -There are two ways to use the search box in this app: +1. Create or choose an Algolia index. +2. Generate a search-only API key. +3. Set runtime credentials. +4. Start the app with `deno task start`. -1. **DocSearch program (free)**: apply and wait for domain approval. -2. **Your own Algolia setup**: create your own Algolia index/crawler and use - those credentials (no DocSearch domain approval required). +Runtime environment variables: -Then: - -1. Set the credentials in your environment. -2. Start the app with `deno task start`. - -Environment variables: - -``` -DOCSEARCH_APP_ID=... -DOCSEARCH_API_KEY=... -DOCSEARCH_INDEX_NAME=... +```text +ALGOLIA_APP_ID=... +ALGOLIA_SEARCH_API_KEY=... +ALGOLIA_INDEX_NAME=... ``` -When the three variables are present, a search box is rendered in the header. If +When all three variables are present, a search box is rendered in the header. If they are missing, the UI stays hidden. -### If your domain is not approved by DocSearch - -If you see a message like "Your domain was not approved for DocSearch", the UI -integration in this repository is still valid, but the **DocSearch-hosted -crawler** cannot be used for that domain. - -Use one of these options: - -1. Re-apply after confirming your production domain is publicly accessible and - contains crawlable content. -2. Use your own Algolia crawler/index and set `DOCSEARCH_*` variables from that - index. - -Quick checks after configuration: - -1. Ensure all three env vars are set. -2. Open devtools and verify no `@docsearch/js` initialization errors appear. -3. Confirm your index has records and the API key is search-only for that index. - ### Create your own index in deployment steps This repository includes a deploy-time index sync flow: @@ -97,7 +72,7 @@ Required GitHub secrets/variables: Secrets: ALGOLIA_APP_ID ALGOLIA_ADMIN_API_KEY - DOCSEARCH_INDEX_NAME + ALGOLIA_INDEX_NAME Variables: SITE_URL=https://9renpoto.win @@ -108,7 +83,7 @@ Run locally: ```bash ALGOLIA_APP_ID=... \ ALGOLIA_ADMIN_API_KEY=... \ -DOCSEARCH_INDEX_NAME=... \ +ALGOLIA_INDEX_NAME=... \ SITE_URL=https://9renpoto.win \ deno task algolia:sync ``` @@ -122,9 +97,9 @@ ALGOLIA_DRY_RUN=1 deno task algolia:sync After indexing, set runtime env vars for search UI: ```text -DOCSEARCH_APP_ID= -DOCSEARCH_API_KEY= -DOCSEARCH_INDEX_NAME= +ALGOLIA_APP_ID= +ALGOLIA_SEARCH_API_KEY= +ALGOLIA_INDEX_NAME= ``` [![Made with Fresh](https://fresh.deno.dev/fresh-badge.svg)](https://fresh.deno.dev) diff --git a/client.ts b/client.ts index fd5a428..9b5a7cb 100644 --- a/client.ts +++ b/client.ts @@ -1,3 +1,2 @@ // Import CSS files here for hot module reloading to work. import "./static/styles.css"; -import "@docsearch/css"; diff --git a/components/Header.tsx b/components/Header.tsx index 6bdff55..5cb9d90 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -1,18 +1,18 @@ import IconActivity from "@/components/icons/Activity.tsx"; import Campfire from "@/components/icons/Campfire.tsx"; import IconRss from "@/components/icons/Rss.tsx"; -import DocSearch from "@/islands/DocSearch.tsx"; +import AlgoliaSearch from "@/islands/AlgoliaSearch.tsx"; import HamburgerButton from "../islands/HamburgerButton.tsx"; interface HeaderProps { title: string; - docSearchAppId?: string; - docSearchApiKey?: string; - docSearchIndexName?: string; + algoliaAppId?: string; + algoliaSearchApiKey?: string; + algoliaIndexName?: string; } export function Header( - { title, docSearchAppId, docSearchApiKey, docSearchIndexName }: HeaderProps, + { title, algoliaAppId, algoliaSearchApiKey, algoliaIndexName }: HeaderProps, ) { const menus = [ { name: , href: "/rss.xml" }, @@ -29,12 +29,12 @@ export function Header( diff --git a/deno.json b/deno.json index da20d91..168c695 100644 --- a/deno.json +++ b/deno.json @@ -52,13 +52,13 @@ "@std/path": "jsr:@std/path@^1.0.0", "@std/testing": "jsr:@std/testing@^0.225.3", "@testing-library/dom": "npm:@testing-library/dom@^9.3.4", + "@algolia/autocomplete-js": "npm:@algolia/autocomplete-js@^1.19.4", + "algoliasearch/lite": "npm:algoliasearch@^5/lite", "autoprefixer": "npm:autoprefixer@^10.4.19", "fresh": "jsr:@fresh/core@^2.1.2", "linkedom": "npm:linkedom@^0.18.4", "marked": "https://cdn.jsdelivr.net/npm/marked@4/lib/marked.esm.js", "@types/marked": "npm:@types/marked@^4.0.8", - "@docsearch/css": "npm:@docsearch/css@^3.9.0", - "@docsearch/js": "npm:@docsearch/js@^4.6.0", "preact": "npm:preact@^10.27.2", "preact-render-to-string": "npm:preact-render-to-string@^6.5.4", "tailwindcss": "npm:tailwindcss@^3.4.6", diff --git a/deno.lock b/deno.lock index 7a5ed59..51a3a88 100644 --- a/deno.lock +++ b/deno.lock @@ -32,11 +32,9 @@ "jsr:@std/toml@^1.0.0-rc.3": "1.0.11", "jsr:@std/uuid@^1.0.9": "1.1.0", "jsr:@std/yaml@^1.0.0-rc.1": "1.0.11", + "npm:@algolia/autocomplete-js@^1.19.4": "1.19.7_algoliasearch@5.49.2", "npm:@babel/core@^7.28.0": "7.29.0", "npm:@babel/preset-react@^7.27.1": "7.28.5_@babel+core@7.29.0", - "npm:@docsearch/css@^3.9.0": "3.9.0", - "npm:@docsearch/js@4.6.0": "4.6.0", - "npm:@docsearch/js@^4.6.0": "4.6.0", "npm:@mjackson/node-fetch-server@0.7": "0.7.0", "npm:@opentelemetry/api@^1.9.0": "1.9.0", "npm:@preact/signals@^2.2.1": "2.8.1_preact@10.28.3", @@ -44,6 +42,7 @@ "npm:@prefresh/vite@^2.4.8": "2.4.11_preact@10.28.3_vite@7.3.1__picomatch@4.0.3", "npm:@testing-library/dom@^9.3.4": "9.3.4", "npm:@types/marked@^4.0.8": "4.3.2", + "npm:algoliasearch@5": "5.49.2", "npm:autoprefixer@^10.4.19": "10.4.24_postcss@8.5.6", "npm:esbuild-wasm@~0.25.11": "0.25.12", "npm:esbuild@0.25.7": "0.25.7", @@ -201,6 +200,155 @@ } }, "npm": { + "@algolia/abtesting@1.15.2": { + "integrity": "sha512-rF7vRVE61E0QORw8e2NNdnttcl3jmFMWS9B4hhdga12COe+lMa26bQLfcBn/Nbp9/AF/8gXdaRCPsVns3CnjsA==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/autocomplete-core@1.19.7_algoliasearch@5.49.2": { + "integrity": "sha512-4sqB+Nfo131MkKMledkmsxX08uZHMI4KbscUdsX6U69scchXsgkgENjN9tRVBKHAOn900mBSNEpUdfzKZw8f+A==", + "dependencies": [ + "@algolia/autocomplete-plugin-algolia-insights", + "@algolia/autocomplete-shared" + ] + }, + "@algolia/autocomplete-js@1.19.7_algoliasearch@5.49.2": { + "integrity": "sha512-ExTgOXioDEjM1rpRnxY+3PAQzmlsHM28WwDhdY01VIHLfzr9PoKtIdTWd0N2u6XG8HdaZvDw3PrjGZ3Rno72gg==", + "dependencies": [ + "@algolia/autocomplete-core", + "@algolia/autocomplete-preset-algolia", + "@algolia/autocomplete-shared", + "algoliasearch", + "htm", + "preact" + ] + }, + "@algolia/autocomplete-plugin-algolia-insights@1.19.7_algoliasearch@5.49.2": { + "integrity": "sha512-5nBYyDNUUXThFGrdYeWA2OkoCaIzHzspIjN2gBPRmURdo4xMQgzay4M+J+XgZginlQcJ+UFYkHKbUYQwfAZ3jw==", + "dependencies": [ + "@algolia/autocomplete-shared", + "search-insights" + ] + }, + "@algolia/autocomplete-preset-algolia@1.19.7_algoliasearch@5.49.2": { + "integrity": "sha512-PXou3XXPN18v/lJwpP40JwUjatwKVF1zeevoj2dXo1+TKrFB8XxSiGcBbChBQ6RYUEKYoS+ck8WMbpn4LwyHAw==", + "dependencies": [ + "@algolia/autocomplete-shared", + "algoliasearch" + ] + }, + "@algolia/autocomplete-shared@1.19.7_algoliasearch@5.49.2": { + "integrity": "sha512-Z6nBmZzPX6A0gV0GUrXDwMPhh6AnrWQshebAfZ/oJ5ioxVoPyRl30SLd3kpCrWr/xXXaaFxUq2qthVeBC2LlVg==", + "dependencies": [ + "algoliasearch" + ] + }, + "@algolia/client-abtesting@5.49.2": { + "integrity": "sha512-XyvKCm0RRmovMI/ChaAVjTwpZhXdbgt3iZofK914HeEHLqD1MUFFVLz7M0+Ou7F56UkHXwRbpHwb9xBDNopprQ==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/client-analytics@5.49.2": { + "integrity": "sha512-jq/3qvtmj3NijZlhq7A1B0Cl41GfaBpjJxcwukGsYds6aMSCWrEAJ9pUqw/C9B3hAmILYKl7Ljz3N9SFvekD3Q==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/client-common@5.49.2": { + "integrity": "sha512-bn0biLequn3epobCfjUqCxlIlurLr4RHu7RaE4trgN+RDcUq6HCVC3/yqq1hwbNYpVtulnTOJzcaxYlSr1fnuw==" + }, + "@algolia/client-insights@5.49.2": { + "integrity": "sha512-z14wfFs1T3eeYbCArC8pvntAWsPo9f6hnUGoj8IoRUJTwgJiiySECkm8bmmV47/x0oGHfsVn3kBdjMX0yq0sNA==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/client-personalization@5.49.2": { + "integrity": "sha512-GpRf7yuuAX93+Qt0JGEJZwgtL0MFdjFO9n7dn8s2pA9mTjzl0Sc5+uTk1VPbIAuf7xhCP9Mve+URGb6J+EYxgA==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/client-query-suggestions@5.49.2": { + "integrity": "sha512-HZwApmNkp0DiAjZcLYdQLddcG4Agb88OkojiAHGgcm5DVXobT5uSZ9lmyrbw/tmQBJwgu2CNw4zTyXoIB7YbPA==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/client-search@5.49.2": { + "integrity": "sha512-y1IOpG6OSmTpGg/CT0YBb/EAhR2nsC18QWp9Jy8HO9iGySpcwaTvs5kHa17daP3BMTwWyaX9/1tDTDQshZzXdg==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/ingestion@1.49.2": { + "integrity": "sha512-YYJRjaZ2bqk923HxE4um7j/Cm3/xoSkF2HC2ZweOF8cXL3sqnlndSUYmCaxHFjNPWLaSHk2IfssX6J/tdKTULw==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/monitoring@1.49.2": { + "integrity": "sha512-9WgH+Dha39EQQyGKCHlGYnxW/7W19DIrEbCEbnzwAMpGAv1yTWCHMPXHxYa+LcL3eCp2V/5idD1zHNlIKmHRHg==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/recommend@5.49.2": { + "integrity": "sha512-K7Gp5u+JtVYgaVpBxF5rGiM+Ia8SsMdcAJMTDV93rwh00DKNllC19o1g+PwrDjDvyXNrnTEbofzbTs2GLfFyKA==", + "dependencies": [ + "@algolia/client-common", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, + "@algolia/requester-browser-xhr@5.49.2": { + "integrity": "sha512-3UhYCcWX6fbtN8ABcxZlhaQEwXFh3CsFtARyyadQShHMPe3mJV9Wel4FpJTa+seugRkbezFz0tt6aPTZSYTBuA==", + "dependencies": [ + "@algolia/client-common" + ] + }, + "@algolia/requester-fetch@5.49.2": { + "integrity": "sha512-G94VKSGbsr+WjsDDOBe5QDQ82QYgxvpxRGJfCHZBnYKYsy/jv9qGIDb93biza+LJWizQBUtDj7bZzp3QZyzhPQ==", + "dependencies": [ + "@algolia/client-common" + ] + }, + "@algolia/requester-node-http@5.49.2": { + "integrity": "sha512-UuihBGHafG/ENsrcTGAn5rsOffrCIRuHMOsD85fZGLEY92ate+BMTUqxz60dv5zerh8ZumN4bRm8eW2z9L11jA==", + "dependencies": [ + "@algolia/client-common" + ] + }, "@alloc/quick-lru@5.2.0": { "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==" }, @@ -388,12 +536,6 @@ "@babel/helper-validator-identifier" ] }, - "@docsearch/css@3.9.0": { - "integrity": "sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA==" - }, - "@docsearch/js@4.6.0": { - "integrity": "sha512-9/rbgkm/BgTq46cwxIohvSAz3koOFjnPpg0mwkJItAfzKbQIj+310PvwtgUY1YITDuGCag6yOL50GW2DBkaaBw==" - }, "@esbuild/aix-ppc64@0.25.7": { "integrity": "sha512-uD0kKFHh6ETr8TqEtaAcV+dn/2qnYbH/+8wGEdY70Qf7l1l/jmBUbrmQqwiPKAQE6cOQ7dTj6Xr0HzQDGHyceQ==", "os": ["aix"], @@ -892,6 +1034,25 @@ "@types/marked@4.3.2": { "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==" }, + "algoliasearch@5.49.2": { + "integrity": "sha512-1K0wtDaRONwfhL4h8bbJ9qTjmY6rhGgRvvagXkMBsAOMNr+3Q2SffHECh9DIuNVrMA1JwA0zCwhyepgBZVakng==", + "dependencies": [ + "@algolia/abtesting", + "@algolia/client-abtesting", + "@algolia/client-analytics", + "@algolia/client-common", + "@algolia/client-insights", + "@algolia/client-personalization", + "@algolia/client-query-suggestions", + "@algolia/client-search", + "@algolia/ingestion", + "@algolia/monitoring", + "@algolia/recommend", + "@algolia/requester-browser-xhr", + "@algolia/requester-fetch", + "@algolia/requester-node-http" + ] + }, "ansi-regex@5.0.1": { "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, @@ -1393,6 +1554,9 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "bin": true }, + "htm@3.1.1": { + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" + }, "html-escaper@3.0.3": { "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==" }, @@ -1882,6 +2046,9 @@ "postcss" ] }, + "search-insights@2.17.3": { + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==" + }, "semver@6.3.1": { "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": true @@ -2111,11 +2278,11 @@ "jsr:@std/front-matter@~0.224.3", "jsr:@std/path@1", "jsr:@std/testing@~0.225.3", - "npm:@docsearch/css@^3.9.0", - "npm:@docsearch/js@^4.6.0", + "npm:@algolia/autocomplete-js@^1.19.4", "npm:@preact/signals@^2.3.2", "npm:@testing-library/dom@^9.3.4", "npm:@types/marked@^4.0.8", + "npm:algoliasearch@5", "npm:autoprefixer@^10.4.19", "npm:linkedom@~0.18.4", "npm:preact-render-to-string@^6.5.4", diff --git a/islands/AlgoliaSearch.tsx b/islands/AlgoliaSearch.tsx new file mode 100644 index 0000000..45bbaf5 --- /dev/null +++ b/islands/AlgoliaSearch.tsx @@ -0,0 +1,122 @@ +import { useEffect, useRef } from "preact/hooks"; + +import type { AutocompleteApi } from "@algolia/autocomplete-js"; +import type { LiteClient } from "algoliasearch/lite"; + +interface AlgoliaSearchProps { + appId?: string; + apiKey?: string; + indexName?: string; + placeholder?: string; + containerId?: string; +} + +interface SearchHit { + objectID: string; + title?: string; + snippet?: string; + url?: string; + slug?: string; + [key: string]: unknown; +} + +export default function AlgoliaSearch( + { + appId, + apiKey, + indexName, + placeholder = "Search", + containerId = "algolia-search", + }: AlgoliaSearchProps, +) { + const containerRef = useRef(null); + + useEffect(() => { + if (!appId || !apiKey || !indexName || !containerRef.current) { + return; + } + + let disposed = false; + let cleanup: (() => void) | undefined; + + const setup = async () => { + const [{ liteClient: createClient }, { autocomplete }] = await Promise.all([ + import("algoliasearch/lite"), + import("@algolia/autocomplete-js"), + ]); + + if (disposed || !containerRef.current) { + return; + } + + const client: LiteClient = createClient(appId, apiKey); + const autocompleteApi: AutocompleteApi = autocomplete({ + container: containerRef.current, + placeholder, + detachedMediaQuery: "none", + openOnFocus: true, + panelPlacement: "input-wrapper-width", + getSources({ query }) { + const trimmed = query.trim(); + if (trimmed.length === 0) { + return []; + } + + return [ + { + sourceId: "posts", + async getItems() { + const result = await client.searchForHits({ + requests: [{ indexName, query: trimmed, hitsPerPage: 8 }], + }); + return result.results[0]?.hits ?? []; + }, + getItemUrl({ item }) { + return item.url ?? (item.slug ? `/entry/${item.slug}` : "#"); + }, + templates: { + item({ item, html }) { + const href = item.url ?? (item.slug ? `/entry/${item.slug}` : "#"); + return html` + +
+
${item.title ?? item.slug ?? "Untitled"}
+ ${item.snippet ? html`
${item.snippet}
` : ""} +
+
+ `; + }, + }, + }, + ]; + }, + }); + + cleanup = () => { + autocompleteApi.destroy(); + }; + }; + + setup().catch(() => { + // Keep search UI hidden on runtime import/init failure. + }); + + return () => { + disposed = true; + cleanup?.(); + if (containerRef.current) { + containerRef.current.innerHTML = ""; + } + }; + }, [apiKey, appId, indexName, placeholder]); + + if (!appId || !apiKey || !indexName) { + return null; + } + + return ( +
+ + ); +} diff --git a/islands/DocSearch.tsx b/islands/DocSearch.tsx deleted file mode 100644 index 7c705aa..0000000 --- a/islands/DocSearch.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useEffect } from "preact/hooks"; - -interface DocSearchProps { - appId?: string; - apiKey?: string; - indexName?: string; - placeholder?: string; - containerId?: string; -} - -export default function DocSearch( - { - appId, - apiKey, - indexName, - placeholder = "Search", - containerId = "docsearch", - }: DocSearchProps, -) { - useEffect(() => { - if (!appId || !apiKey || !indexName) { - return; - } - - let cleanup: (() => void) | undefined; - - const setup = async () => { - const docsearchModule = await import("@docsearch/js"); - type DocSearchInit = (options: { - container: string; - appId: string; - apiKey: string; - indices: string[]; - placeholder: string; - }) => () => void; - const docsearch = docsearchModule.default as unknown as DocSearchInit; - - cleanup = docsearch({ - container: `#${containerId}`, - appId, - apiKey, - indices: [indexName], - placeholder, - }); - }; - - setup(); - - return () => { - cleanup?.(); - }; - }, [apiKey, appId, containerId, indexName, placeholder]); - - if (!appId || !apiKey || !indexName) { - return null; - } - - return
; -} diff --git a/routes/_app.tsx b/routes/_app.tsx index 1553e4f..0c3f5db 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -5,9 +5,12 @@ import { Header } from "@/components/Header.tsx"; import { title } from "@/utils/website.ts"; const GA4_MEASUREMENT_ID = Deno.env.get("GA4_MEASUREMENT_ID"); -const DOCSEARCH_APP_ID = Deno.env.get("DOCSEARCH_APP_ID"); -const DOCSEARCH_API_KEY = Deno.env.get("DOCSEARCH_API_KEY"); -const DOCSEARCH_INDEX_NAME = Deno.env.get("DOCSEARCH_INDEX_NAME"); +const ALGOLIA_APP_ID = Deno.env.get("ALGOLIA_APP_ID") ?? + Deno.env.get("DOCSEARCH_APP_ID"); +const ALGOLIA_SEARCH_API_KEY = Deno.env.get("ALGOLIA_SEARCH_API_KEY") ?? + Deno.env.get("DOCSEARCH_API_KEY"); +const ALGOLIA_INDEX_NAME = Deno.env.get("ALGOLIA_INDEX_NAME") ?? + Deno.env.get("DOCSEARCH_INDEX_NAME"); export default function App({ Component }: PageProps) { return ( @@ -16,9 +19,9 @@ export default function App({ Component }: PageProps) {