From e32dcacd7bd51a03510444df65fc406195018c6b Mon Sep 17 00:00:00 2001
From: goodroot <9484709+goodroot@users.noreply.github.com>
Date: Fri, 13 Dec 2024 10:54:19 -0800
Subject: [PATCH 1/5] swizzle search, internal only links
---
docusaurus.config.js | 22 +--
package.json | 1 +
src/theme/SearchBar/index.tsx | 279 +++++++++++++++++++++++++++++++++
src/theme/SearchBar/styles.css | 14 ++
yarn.lock | 2 +-
5 files changed, 302 insertions(+), 16 deletions(-)
create mode 100644 src/theme/SearchBar/index.tsx
create mode 100644 src/theme/SearchBar/styles.css
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 2ced3fc3..97eaf20f 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -120,21 +120,13 @@ const config = {
theme: require("./src/internals/prism-github"),
darkTheme: require("./src/internals/prism-dracula"),
},
-
- ...(typeof process.env.ALGOLIA_API_KEY === "string" &&
- typeof process.env.ALGOLIA_APP_ID === "string"
- ? {
- algolia: {
- appId: process.env.ALGOLIA_APP_ID,
- apiKey: process.env.ALGOLIA_API_KEY,
- indexName: "questdb",
- // Disable /search page
- searchPagePath: false,
- contextualSearch: false,
- externalUrlRegex: 'questdb\\.io',
- },
- }
- : {}),
+ algolia: {
+ appId: process.env.ALGOLIA_APP_ID || 'placeholder-app-id',
+ apiKey: process.env.ALGOLIA_API_KEY || 'placeholder-api-key',
+ indexName: "questdb",
+ searchPagePath: false,
+ contextualSearch: false,
+ },
},
presets: [
[
diff --git a/package.json b/package.json
index 67b9d088..3fd1cf52 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@docusaurus/faster": "^3.6.3",
"@docusaurus/theme-mermaid": "^3.6.3",
+ "@docusaurus/theme-search-algolia": "^3.6.3",
"@headlessui/react": "^2.2.0",
"@heroicons/react": "2.2.0",
"@mdx-js/react": "3.1.0",
diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx
new file mode 100644
index 00000000..163d89dc
--- /dev/null
+++ b/src/theme/SearchBar/index.tsx
@@ -0,0 +1,279 @@
+import React, {useCallback, useMemo, useRef, useState} from 'react';
+import {createPortal} from 'react-dom';
+import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react';
+import Head from '@docusaurus/Head';
+import Link from '@docusaurus/Link';
+import {useHistory} from '@docusaurus/router';
+import {
+ isRegexpStringMatch,
+ useSearchLinkCreator,
+} from '@docusaurus/theme-common';
+import {
+ useAlgoliaContextualFacetFilters,
+ useSearchResultUrlProcessor,
+} from '@docusaurus/theme-search-algolia/client';
+import Translate from '@docusaurus/Translate';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import translations from '@theme/SearchTranslations';
+
+import type {AutocompleteState} from '@algolia/autocomplete-core';
+import type {
+ DocSearchModal as DocSearchModalType,
+ DocSearchModalProps,
+} from '@docsearch/react';
+import type {
+ InternalDocSearchHit,
+ StoredDocSearchHit,
+} from '@docsearch/react/dist/esm/types';
+import type {SearchClient} from 'algoliasearch/lite';
+
+type DocSearchProps = Omit<
+ DocSearchModalProps,
+ 'onClose' | 'initialScrollY'
+> & {
+ contextualSearch?: string;
+ externalUrlRegex?: string;
+ searchPagePath: boolean | string;
+};
+
+let DocSearchModal: typeof DocSearchModalType | null = null;
+
+function Hit({
+ hit,
+ children,
+}: {
+ hit: InternalDocSearchHit | StoredDocSearchHit;
+ children: React.ReactNode;
+}) {
+ const history = useHistory();
+
+ const handleClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ history.push(hit.url);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+type ResultsFooterProps = {
+ state: AutocompleteState;
+ onClose: () => void;
+};
+
+function ResultsFooter({state, onClose}: ResultsFooterProps) {
+ const createSearchLink = useSearchLinkCreator();
+
+ return (
+
+
+ {'See all {count} results'}
+
+
+ );
+}
+
+type FacetFilters = Required<
+ Required['searchParameters']
+>['facetFilters'];
+
+function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters {
+ const normalize = (
+ f: FacetFilters,
+ ): readonly string[] | readonly (string | readonly string[])[] =>
+ typeof f === 'string' ? [f] : f;
+ return [...normalize(f1), ...normalize(f2)] as FacetFilters;
+}
+
+function DocSearch({
+ contextualSearch,
+ externalUrlRegex,
+ ...props
+}: DocSearchProps) {
+ const {siteMetadata} = useDocusaurusContext();
+ const processSearchResultUrl = useSearchResultUrlProcessor();
+
+ const contextualSearchFacetFilters =
+ useAlgoliaContextualFacetFilters() as FacetFilters;
+
+ const configFacetFilters: FacetFilters =
+ props.searchParameters?.facetFilters ?? [];
+
+ const facetFilters: FacetFilters = contextualSearch
+ ? // Merge contextual search filters with config filters
+ mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
+ : // ... or use config facetFilters
+ configFacetFilters;
+
+ // We let user override default searchParameters if she wants to
+ const searchParameters: DocSearchProps['searchParameters'] = {
+ ...props.searchParameters,
+ facetFilters,
+ };
+
+ const history = useHistory();
+ const searchContainer = useRef(null);
+ const searchButtonRef = useRef(null);
+ const [isOpen, setIsOpen] = useState(false);
+ const [initialQuery, setInitialQuery] = useState(
+ undefined,
+ );
+
+ const importDocSearchModalIfNeeded = useCallback(() => {
+ if (DocSearchModal) {
+ return Promise.resolve();
+ }
+
+ return Promise.all([
+ import('@docsearch/react/modal') as Promise<
+ typeof import('@docsearch/react')
+ >,
+ import('@docsearch/react/style'),
+ import('./styles.css'),
+ ]).then(([{DocSearchModal: Modal}]) => {
+ DocSearchModal = Modal;
+ });
+ }, []);
+
+ const prepareSearchContainer = useCallback(() => {
+ if (!searchContainer.current) {
+ const divElement = document.createElement('div');
+ searchContainer.current = divElement;
+ document.body.insertBefore(divElement, document.body.firstChild);
+ }
+ }, []);
+
+ const openModal = useCallback(() => {
+ prepareSearchContainer();
+ importDocSearchModalIfNeeded().then(() => setIsOpen(true));
+ }, [importDocSearchModalIfNeeded, prepareSearchContainer]);
+
+ const closeModal = useCallback(() => {
+ setIsOpen(false);
+ searchButtonRef.current?.focus();
+ }, []);
+
+ const handleInput = useCallback(
+ (event: KeyboardEvent) => {
+ if (event.key === 'f' && (event.metaKey || event.ctrlKey)) {
+ // ignore browser's ctrl+f
+ return;
+ }
+ // prevents duplicate key insertion in the modal input
+ event.preventDefault();
+ setInitialQuery(event.key);
+ openModal();
+ },
+ [openModal],
+ );
+
+ const navigator = useRef({
+ navigate({itemUrl}: {itemUrl?: string}) {
+ // Algolia results could contain URL's from other domains which cannot
+ // be served through history and should navigate with window.location
+ if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
+ window.location.href = itemUrl!;
+ } else {
+ history.push(itemUrl!);
+ }
+ },
+ }).current;
+
+ const transformItems = useRef(
+ (items) =>
+ props.transformItems
+ ? // Custom transformItems
+ props.transformItems(items)
+ : // Default transformItems
+ items.map((item) => ({
+ ...item,
+ url: processSearchResultUrl(item.url),
+ })),
+ ).current;
+
+ const resultsFooterComponent: DocSearchProps['resultsFooterComponent'] =
+ useMemo(
+ () =>
+ // eslint-disable-next-line react/no-unstable-nested-components
+ (footerProps: Omit): JSX.Element =>
+ ,
+ [closeModal],
+ );
+
+ const transformSearchClient = useCallback(
+ (searchClient: SearchClient) => {
+ searchClient.addAlgoliaAgent(
+ 'docusaurus',
+ siteMetadata.docusaurusVersion,
+ );
+
+ return searchClient;
+ },
+ [siteMetadata.docusaurusVersion],
+ );
+
+ useDocSearchKeyboardEvents({
+ isOpen,
+ onOpen: openModal,
+ onClose: closeModal,
+ onInput: handleInput,
+ searchButtonRef,
+ });
+
+ return (
+ <>
+
+ {/* This hints the browser that the website will load data from Algolia,
+ and allows it to preconnect to the DocSearch cluster. It makes the first
+ query faster, especially on mobile. */}
+
+
+
+
+
+ {isOpen &&
+ DocSearchModal &&
+ searchContainer.current &&
+ createPortal(
+ ,
+ searchContainer.current,
+ )}
+ >
+ );
+}
+
+export default function SearchBar(): JSX.Element {
+ const {siteConfig} = useDocusaurusContext();
+ return ;
+}
diff --git a/src/theme/SearchBar/styles.css b/src/theme/SearchBar/styles.css
new file mode 100644
index 00000000..fdf8dff9
--- /dev/null
+++ b/src/theme/SearchBar/styles.css
@@ -0,0 +1,14 @@
+:root {
+ --docsearch-primary-color: var(--ifm-color-primary);
+ --docsearch-text-color: var(--ifm-font-color-base);
+}
+
+.DocSearch-Button {
+ margin: 0;
+ transition: all var(--ifm-transition-fast)
+ var(--ifm-transition-timing-default);
+}
+
+.DocSearch-Container {
+ z-index: calc(var(--ifm-z-index-fixed) + 1);
+}
diff --git a/yarn.lock b/yarn.lock
index 5d65c4af..0b72e9a2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1999,7 +1999,7 @@
mermaid ">=10.4"
tslib "^2.6.0"
-"@docusaurus/theme-search-algolia@3.6.3":
+"@docusaurus/theme-search-algolia@3.6.3", "@docusaurus/theme-search-algolia@^3.6.3":
version "3.6.3"
resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.6.3.tgz#1a3331a489f392f5b032c4efc5f431e57eddf7ce"
integrity sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA==
From be6d9fbe5c3dadddff92774a023182dcea5f5504 Mon Sep 17 00:00:00 2001
From: goodroot <9484709+goodroot@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:08:31 -0800
Subject: [PATCH 2/5] try to be relative
---
src/theme/SearchBar/index.tsx | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx
index 163d89dc..2b2e2025 100644
--- a/src/theme/SearchBar/index.tsx
+++ b/src/theme/SearchBar/index.tsx
@@ -45,11 +45,12 @@ function Hit({
hit: InternalDocSearchHit | StoredDocSearchHit;
children: React.ReactNode;
}) {
- const history = useHistory();
-
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
- history.push(hit.url);
+ // Remove any domain part from the URL if it exists
+ const url = hit.url.replace(/^https?:\/\/[^\/]+/, '');
+ // Navigate to the URL in the same tab
+ window.location.assign(url);
};
return (
From c1b5531dcabdeab06f276581b7dfdf7e9c558f63 Mon Sep 17 00:00:00 2001
From: goodroot <9484709+goodroot@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:14:09 -0800
Subject: [PATCH 3/5] debug logging
---
src/theme/SearchBar/index.tsx | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx
index 2b2e2025..e11fd2b6 100644
--- a/src/theme/SearchBar/index.tsx
+++ b/src/theme/SearchBar/index.tsx
@@ -47,9 +47,24 @@ function Hit({
}) {
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
- // Remove any domain part from the URL if it exists
- const url = hit.url.replace(/^https?:\/\/[^\/]+/, '');
- // Navigate to the URL in the same tab
+
+ // Log the original URL for debugging
+ console.log('Original URL:', hit.url);
+
+ // 1. Remove domain if present
+ let url = hit.url.replace(/^https?:\/\/[^\/]+/, '');
+ console.log('After domain removal:', url);
+
+ // 2. Handle double /docs/ if present (in case the URL already has /docs/ and gets another prepended)
+ url = url.replace(/^\/docs\/docs\//, '/docs/');
+ console.log('After fixing double docs:', url);
+
+ // 3. Handle case where /docs/ is missing from documentation pages
+ if (!url.startsWith('/docs/') && !url.match(/^\/(glossary|blog)/)) {
+ url = `/docs${url}`;
+ }
+ console.log('Final URL:', url);
+
window.location.assign(url);
};
From d6ede186dd03b6d150492309e0dfe97adb1cec5e Mon Sep 17 00:00:00 2001
From: goodroot <9484709+goodroot@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:19:34 -0800
Subject: [PATCH 4/5] tidy specific urls
---
src/theme/SearchBar/index.tsx | 15 +++------------
1 file changed, 3 insertions(+), 12 deletions(-)
diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx
index e11fd2b6..0e656074 100644
--- a/src/theme/SearchBar/index.tsx
+++ b/src/theme/SearchBar/index.tsx
@@ -48,22 +48,13 @@ function Hit({
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
- // Log the original URL for debugging
- console.log('Original URL:', hit.url);
-
// 1. Remove domain if present
let url = hit.url.replace(/^https?:\/\/[^\/]+/, '');
- console.log('After domain removal:', url);
-
- // 2. Handle double /docs/ if present (in case the URL already has /docs/ and gets another prepended)
- url = url.replace(/^\/docs\/docs\//, '/docs/');
- console.log('After fixing double docs:', url);
- // 3. Handle case where /docs/ is missing from documentation pages
- if (!url.startsWith('/docs/') && !url.match(/^\/(glossary|blog)/)) {
- url = `/docs${url}`;
+ // 2. For glossary/blog URLs, ensure they don't have /docs/ prefix
+ if (url.match(/\/docs\/(glossary|blog)/)) {
+ url = url.replace('/docs/', '/');
}
- console.log('Final URL:', url);
window.location.assign(url);
};
From 440314146ed9682008152ce5b5d527aabe537c4a Mon Sep 17 00:00:00 2001
From: goodroot <9484709+goodroot@users.noreply.github.com>
Date: Fri, 13 Dec 2024 11:29:02 -0800
Subject: [PATCH 5/5] tidy implementation, raise higher in order
yay first try.gif
---
src/theme/SearchBar/index.tsx | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/theme/SearchBar/index.tsx b/src/theme/SearchBar/index.tsx
index 0e656074..5fcd2988 100644
--- a/src/theme/SearchBar/index.tsx
+++ b/src/theme/SearchBar/index.tsx
@@ -45,22 +45,22 @@ function Hit({
hit: InternalDocSearchHit | StoredDocSearchHit;
children: React.ReactNode;
}) {
+ // Transform URL once and use it in both places
+ const transformUrl = (url: string) => {
+ return (url.startsWith('/docs/glossary/') || url.startsWith('/docs/blog/'))
+ ? url.replace('/docs/', '/')
+ : url;
+ };
+
+ const finalUrl = transformUrl(hit.url);
+
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
-
- // 1. Remove domain if present
- let url = hit.url.replace(/^https?:\/\/[^\/]+/, '');
-
- // 2. For glossary/blog URLs, ensure they don't have /docs/ prefix
- if (url.match(/\/docs\/(glossary|blog)/)) {
- url = url.replace('/docs/', '/');
- }
-
- window.location.assign(url);
+ window.location.assign(finalUrl);
};
return (
-
+
{children}
);