From 66248c61bf6eb3457efd6699d58d7388e1050bc7 Mon Sep 17 00:00:00 2001 From: blumlaut <13604413+Blumlaut@users.noreply.github.com> Date: Wed, 10 Dec 2025 16:51:39 +0100 Subject: [PATCH 1/4] Add initial code for new search use searchSite instead of marketingSite --- docusaurus.config.js | 1 + src/theme/SearchBar/index.jsx | 668 ++++++++++++++++++++++++++------- src/theme/SearchBar/styles.css | 367 ++++++++++++++++-- 3 files changed, 882 insertions(+), 154 deletions(-) diff --git a/docusaurus.config.js b/docusaurus.config.js index d53a0873f..c3670161b 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -36,6 +36,7 @@ const config = { customFields: { marketingSite: 'https://zap-hosting.com', + searchSite: 'https://zap-hosting.com' }, // Even if you don't use internalization, you can use this field to set useful diff --git a/src/theme/SearchBar/index.jsx b/src/theme/SearchBar/index.jsx index c12b2cdca..ee818efe1 100644 --- a/src/theme/SearchBar/index.jsx +++ b/src/theme/SearchBar/index.jsx @@ -1,141 +1,545 @@ -import React, { useRef, useCallback, useState } from "react"; -import clsx from "clsx"; -import { useHistory } from "@docusaurus/router"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; -import { usePluginData } from '@docusaurus/useGlobalData'; -import useIsBrowser from "@docusaurus/useIsBrowser"; -import { HighlightSearchResults } from "./HighlightSearchResults"; -const Search = props => { - const initialized = useRef(false); - const searchBarRef = useRef(null); - const [indexReady, setIndexReady] = useState(false); - const history = useHistory(); - const { siteConfig = {} } = useDocusaurusContext(); - const pluginConfig = (siteConfig.plugins || []).find(plugin => Array.isArray(plugin) && typeof plugin[0] === "string" && plugin[0].includes("docusaurus-lunr-search")) - const isBrowser = useIsBrowser(); - const { baseUrl } = siteConfig; - const assetUrl = pluginConfig && pluginConfig[1]?.assetUrl || baseUrl; - const initAlgolia = (searchDocs, searchIndex, DocSearch, options) => { - new DocSearch({ - searchDocs, - searchIndex, - baseUrl, - inputSelector: "#search_input_react", - // Override algolia's default selection event, allowing us to do client-side - // navigation and avoiding a full page refresh. - handleSelected: (_input, _event, suggestion) => { - const url = suggestion.url || "/"; - // Use an anchor tag to parse the absolute url into a relative url - // Alternatively, we can use new URL(suggestion.url) but its not supported in IE - const a = document.createElement("a"); - a.href = url; - _input.setVal(''); // clear value - _event.target.blur(); // remove focus - - // Get the highlight word from the suggestion. - let wordToHighlight = ''; - if (options.highlightResult) { - try { - const matchedLine = suggestion.text || suggestion.subcategory || suggestion.title; - const matchedWordResult = matchedLine.match(new RegExp('\\w*', 'g')); - if (matchedWordResult && matchedWordResult.length > 0) { - const tempDoc = document.createElement('div'); - tempDoc.innerHTML = matchedWordResult[0]; - wordToHighlight = tempDoc.textContent; - } - } catch (e) { - console.log(e); - } - } +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import clsx from 'clsx'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import './styles.css'; + +const MIN_QUERY_LENGTH = 3; +const TAB_ORDER = ['game', 'docs', 'landingPage', 'posts']; +const LABELS = { + en: { + placeholder: 'Search products, games, docs…', + loading: 'Searching…', + empty: 'No matching results.', + failure: 'Search is unavailable. Please try again.', + short: 'Enter at least 3 characters.', + askAiHeading: 'Ask AI', + askAiDescription: 'Get AI-powered answers based on our documentation.', + askAiButton: 'Go!', + askAiGenericError: 'Failed to generate answer. Please try again.', + askAiUnavailable: 'Ask AI is currently unavailable.', + askAiRateLimited: 'Slow down, you\'re asking too many questions, please try again later!', + askAiLoadingText: 'Thinking...', + tabs: { + game: 'Products', + docs: 'Docs', + landingPage: 'Pages', + posts: 'Blog', + }, + }, + de: { + placeholder: 'Produkte, Spiele, Docs suchen…', + loading: 'Suche läuft…', + empty: 'Keine passenden Ergebnisse.', + failure: 'Suche nicht verfügbar. Bitte versuche es erneut.', + short: 'Bitte mindestens 3 Zeichen eingeben.', + askAiHeading: 'Frage die KI', + askAiDescription: 'Erhalte KI-gestützte Antworten basierend auf unserer Dokumentation.', + askAiButton: 'Los!', + askAiGenericError: 'Antwort konnte nicht erstellt werden. Bitte erneut versuchen.', + askAiUnavailable: 'Ask AI ist aktuell nicht verfügbar.', + askAiRateLimited: 'Bitte langsamer – versuche es gleich noch einmal!', + askAiLoadingText: 'Denke nach...', + tabs: { + game: 'Produkte', + docs: 'Docs', + landingPage: 'Seiten', + posts: 'Blog', + }, + }, +}; + +const sanitizeBase = (url) => url.replace(/\/+$/, ''); - history.push(url, { - highlightState: { wordToHighlight }, +const buildTabs = (counts, labels) => { + const entries = []; + const types = counts?.types || {}; + TAB_ORDER.forEach((key) => { + if (typeof labels[key] !== 'string') { + return; + } + const count = types[key] ?? 0; + entries.push({ + key, + count, + label: labels[key], }); - }, - maxHits: options.maxHits }); - }; - - const pluginData = usePluginData('docusaurus-lunr-search'); - const getSearchDoc = () => - process.env.NODE_ENV === "production" - ? fetch(`${assetUrl}${pluginData.fileNames.searchDoc}`).then((content) => content.json()) - : Promise.resolve({}); - - const getLunrIndex = () => - process.env.NODE_ENV === "production" - ? fetch(`${assetUrl}${pluginData.fileNames.lunrIndex}`).then((content) => content.json()) - : Promise.resolve([]); - - const loadAlgolia = () => { - if (!initialized.current) { - Promise.all([ - getSearchDoc(), - getLunrIndex(), - import("./DocSearch"), - import("./algolia.css") - ]).then(([searchDocFile, searchIndex, { default: DocSearch }]) => { - const { searchDocs, options } = searchDocFile; - if (!searchDocs || searchDocs.length === 0) { - return; - } - initAlgolia(searchDocs, searchIndex, DocSearch, options); - setIndexReady(true); - }); - initialized.current = true; + + return entries.filter((tab) => tab.count > 0); +}; + +const normalizeItems = (items) => { + if (Array.isArray(items)) { + return items; } - }; - const toggleSearchIconClick = useCallback( - e => { - if (!searchBarRef.current.contains(e.target)) { - searchBarRef.current.focus(); - } + return []; +}; - props.handleSearchBarToggle && props.handleSearchBarToggle(!props.isSearchBarExpanded); - }, - [props.isSearchBarExpanded] - ); - - let placeholder - if (isBrowser) { - loadAlgolia(); - placeholder = window.navigator.platform.startsWith("Mac") ? - 'Search ⌘+K' : 'Search Ctrl+K' - } - - return ( -
- - - -
- ); +const renderIcon = (icon, title) => { + if (! icon || typeof icon !== 'object') { + return null; + } + + if (icon.type === 'image') { + return {title; + } + + if (icon.type === 'html') { + return ( +