Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ GITHUB_TOKEN=your_github_token_here
# This must be set for the discussions section to fetch live data from GitHub
# Create a Classic PAT with read:discussion scope at https://github.com/settings/tokens
DOCUSAURUS_GIT_TOKEN=your_github_token_here
# Algolia SiteSearch Configuration
# Request these from the maintainers after the Algolia crawler is configured.
# The navbar search is enabled only when all three values are set.
# ALGOLIA_INDEX_NAME must be an actual Algolia index name, for example:
# www_recodehive_com_8oew5oqz0y_pages
ALGOLIA_APP_ID=your_algolia_app_id
ALGOLIA_SEARCH_API_KEY=your_algolia_search_api_key
ALGOLIA_INDEX_NAME=your_algolia_index_name

# Shopify Configuration (for Merch Store)
# Get these from: Shopify Admin > Settings > Apps and sales channels > Develop apps
Expand Down
45 changes: 25 additions & 20 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ dotenv.config();

// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)

const algoliaAppId = process.env.ALGOLIA_APP_ID?.trim();
const algoliaSearchApiKey = process.env.ALGOLIA_SEARCH_API_KEY?.trim();
const algoliaIndexName = process.env.ALGOLIA_INDEX_NAME?.trim();

const hasAlgoliaSiteSearch = Boolean(
algoliaAppId && algoliaSearchApiKey && algoliaIndexName,
);

const hasPartialAlgoliaSiteSearchConfig = Boolean(
algoliaAppId || algoliaSearchApiKey || algoliaIndexName,
);

if (hasPartialAlgoliaSiteSearchConfig && !hasAlgoliaSiteSearch) {
console.warn(
"Algolia SiteSearch is partially configured. Set ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY, and the actual Algolia index name in ALGOLIA_INDEX_NAME to enable navbar search.",
);
}

const config: Config = {
title: "recode hive",
tagline: "Learn, Build & Grow with Open Source",
Expand Down Expand Up @@ -206,11 +224,6 @@ const config: Config = {
},
],
},
// Search disabled until Algolia is properly configured
// {
// type: "search",
// position: "right",
// },
{
type: "html",
position: "right",
Expand Down Expand Up @@ -245,21 +258,6 @@ const config: Config = {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
// Disable Algolia search until properly configured
// algolia: {
// appId: "YOUR_APP_ID",
// apiKey: "YOUR_SEARCH_API_KEY",
// indexName: "YOUR_INDEX_NAME",
// contextualSearch: true,
// externalUrlRegex: "external\\.com|domain\\.com",
// replaceSearchResultPathname: {
// from: "/docs/",
// to: "/",
// },
// searchParameters: {},
// searchPagePath: "search",
// insights: false,
// },
} satisfies Preset.ThemeConfig,

markdown: {
Expand Down Expand Up @@ -296,6 +294,13 @@ const config: Config = {
EMAILJS_PUBLIC_KEY: process.env.EMAILJS_PUBLIC_KEY || "",
EMAILJS_SERVICE_ID: process.env.EMAILJS_SERVICE_ID || "",
EMAILJS_TEMPLATE_ID: process.env.EMAILJS_TEMPLATE_ID || "",
algoliaSiteSearch: hasAlgoliaSiteSearch
? {
applicationId: algoliaAppId,
apiKey: algoliaSearchApiKey,
indexName: algoliaIndexName,
}
: null,
hooks: {
onBrokenMarkdownLinks: "warn",
},
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

146 changes: 146 additions & 0 deletions src/components/AlgoliaSiteSearch/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import React, { useEffect } from "react";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";

const containerId = "algolia-sitesearch-navbar";
const siteSearchScriptId = "algolia-sitesearch-script";
const siteSearchStylesheetId = "algolia-sitesearch-stylesheet";
const siteSearchScriptUrl =
"https://unpkg.com/@algolia/sitesearch@latest/dist/search.min.js";
const siteSearchStylesheetUrl =
"https://unpkg.com/@algolia/sitesearch@latest/dist/search.min.css";

type SiteSearchConfig = {
applicationId?: unknown;
apiKey?: unknown;
indexName?: unknown;
};

declare global {
interface Window {
SiteSearch?: {
init: (config: {
container: string;
applicationId: string;
apiKey: string;
indexName: string;
attributes: {
primaryText: string;
secondaryText: string;
tertiaryText: string;
url: string;
image: string;
};
darkMode: boolean;
}) => void;
};
}
}

function toConfigString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}

function ensureSiteSearchStylesheet() {
if (document.getElementById(siteSearchStylesheetId)) {
return;
}

const stylesheet = document.createElement("link");
stylesheet.id = siteSearchStylesheetId;
stylesheet.rel = "stylesheet";
stylesheet.href = siteSearchStylesheetUrl;
document.head.appendChild(stylesheet);
}

function loadSiteSearchScript(): Promise<void> {
if (window.SiteSearch?.init) {
return Promise.resolve();
}

const existingScript = document.getElementById(
siteSearchScriptId,
) as HTMLScriptElement | null;

if (existingScript) {
return new Promise((resolve, reject) => {
existingScript.addEventListener("load", () => resolve(), { once: true });
existingScript.addEventListener("error", reject, { once: true });
});
}

return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.id = siteSearchScriptId;
script.src = siteSearchScriptUrl;
script.async = true;
script.addEventListener("load", () => resolve(), { once: true });
script.addEventListener("error", reject, { once: true });
document.head.appendChild(script);
});
}

export default function AlgoliaSiteSearch(): React.ReactElement | null {
const { siteConfig } = useDocusaurusContext();
const config = siteConfig.customFields.algoliaSiteSearch as
| SiteSearchConfig
| undefined;

const applicationId = toConfigString(config?.applicationId);
const apiKey = toConfigString(config?.apiKey);
const indexName = toConfigString(config?.indexName);
const isConfigured = Boolean(applicationId && apiKey && indexName);

useEffect(() => {
if (!isConfigured) {
return;
}

let cancelled = false;

ensureSiteSearchStylesheet();
loadSiteSearchScript()
.then(() => {
if (cancelled || !window.SiteSearch?.init) {
return;
}

const container = document.getElementById(containerId);
if (!container) {
return;
}

container.innerHTML = "";
window.SiteSearch.init({
container: `#${containerId}`,
applicationId,
apiKey,
indexName,
attributes: {
primaryText: "title",
secondaryText: "description",
tertiaryText: "headers",
url: "url",
image: "image",
},
darkMode: document.documentElement.dataset.theme === "dark",
});
})
.catch((error) => {
console.error("Failed to initialize Algolia SiteSearch", error);
});

return () => {
cancelled = true;
};
}, [apiKey, applicationId, indexName, isConfigured]);

if (!isConfigured) {
return null;
}

return (
<div className="navbar__item algolia-sitesearch-navbar">
<div id={containerId} />
</div>
);
}
6 changes: 2 additions & 4 deletions src/theme/Navbar/Content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useThemeConfig, ErrorCauseBoundary } from "@docusaurus/theme-common";
import { splitNavbarItems } from "@docusaurus/theme-common/internal";
import NavbarItem, { type Props as NavbarItemConfig } from "@theme/NavbarItem";
import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle";
import AlgoliaSiteSearch from "@site/src/components/AlgoliaSiteSearch";
// import SearchBar from '@theme/SearchBar';
import NavbarMobileSidebarToggle from "@theme/Navbar/MobileSidebar/Toggle";
import NavbarLogo from "@theme/Navbar/Logo";
Expand Down Expand Up @@ -80,10 +81,6 @@ export default function NavbarContent(): ReactNode {
() => splitNavbarItems(items),
[items],
);
const searchBarItem = useMemo(
() => items.find((item) => item.type === "search"),
[items],
);

return (
<NavbarContentLayout
Expand All @@ -101,6 +98,7 @@ export default function NavbarContent(): ReactNode {
// Ask the user to add the respective navbar items => more flexible
<>
<NavbarItems items={rightItems} />
<AlgoliaSiteSearch />
<NavbarColorModeToggle />
{/* Search component disabled */}
{/* {!searchBarItem && (
Expand Down
Loading