Compare commits
1 Commits
imports-fo
...
docusaurus
Author | SHA1 | Date | |
---|---|---|---|
19f706e7aa |
@ -4,9 +4,12 @@
|
|||||||
* @import * as Preset from "@docusaurus/preset-classic";
|
* @import * as Preset from "@docusaurus/preset-classic";
|
||||||
* @import * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
* @import * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||||
* @import { BuildUrlValues } from "remark-github";
|
* @import { BuildUrlValues } from "remark-github";
|
||||||
|
* @import { ReleasesPluginOptions } from "./releases/plugin.mjs"
|
||||||
*/
|
*/
|
||||||
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
|
import { createDocusaurusConfig } from "@goauthentik/docusaurus-config";
|
||||||
import { createRequire } from "node:module";
|
import { createRequire } from "node:module";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import remarkDirective from "remark-directive";
|
import remarkDirective from "remark-directive";
|
||||||
import remarkGithub, { defaultBuildUrl } from "remark-github";
|
import remarkGithub, { defaultBuildUrl } from "remark-github";
|
||||||
|
|
||||||
@ -15,6 +18,7 @@ import remarkPreviewDirective from "./remark/preview-directive.mjs";
|
|||||||
import remarkSupportDirective from "./remark/support-directive.mjs";
|
import remarkSupportDirective from "./remark/support-directive.mjs";
|
||||||
import remarkVersionDirective from "./remark/version-directive.mjs";
|
import remarkVersionDirective from "./remark/version-directive.mjs";
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,6 +135,12 @@ const config = createDocusaurusConfig({
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
[
|
||||||
|
"./releases/plugin.mjs",
|
||||||
|
/** @type {ReleasesPluginOptions} */ ({
|
||||||
|
docsDirectory: path.join(__dirname, "docs"),
|
||||||
|
}),
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"@docusaurus/plugin-content-docs",
|
"@docusaurus/plugin-content-docs",
|
||||||
{
|
{
|
||||||
|
64
website/releases/plugin.mjs
Normal file
64
website/releases/plugin.mjs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* @file Docusaurus releases plugin.
|
||||||
|
*
|
||||||
|
* @import { LoadContext, Plugin } from "@docusaurus/types"
|
||||||
|
*/
|
||||||
|
import * as fs from "node:fs/promises";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
import { collectReleaseFiles } from "./utils.mjs";
|
||||||
|
|
||||||
|
const PLUGIN_NAME = "ak-releases-plugin";
|
||||||
|
const RELEASES_FILENAME = "releases.gen.json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} ReleasesPluginOptions
|
||||||
|
* @property {string} docsDirectory The path to the documentation directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} AKReleasesPluginData
|
||||||
|
* @property {string} publicPath The URL to the plugin's public directory.
|
||||||
|
* @property {string[]} releases The available versions of the documentation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {LoadContext} loadContext
|
||||||
|
* @param {ReleasesPluginOptions} options
|
||||||
|
* @returns {Promise<Plugin<AKReleasesPluginData>>}
|
||||||
|
*/
|
||||||
|
async function akReleasesPlugin(loadContext, { docsDirectory }) {
|
||||||
|
return {
|
||||||
|
name: PLUGIN_NAME,
|
||||||
|
|
||||||
|
async loadContent() {
|
||||||
|
console.log(`🚀 ${PLUGIN_NAME} loaded`);
|
||||||
|
|
||||||
|
const releases = collectReleaseFiles(docsDirectory).map((release) => release.name);
|
||||||
|
|
||||||
|
const outputPath = path.join(loadContext.siteDir, "static", RELEASES_FILENAME);
|
||||||
|
|
||||||
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
||||||
|
await fs.writeFile(outputPath, JSON.stringify(releases, null, 2), "utf-8");
|
||||||
|
console.log(`✅ ${RELEASES_FILENAME} generated`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {AKReleasesPluginData}
|
||||||
|
*/
|
||||||
|
const content = {
|
||||||
|
releases,
|
||||||
|
publicPath: path.join("/", RELEASES_FILENAME),
|
||||||
|
};
|
||||||
|
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
|
||||||
|
contentLoaded({ content, actions }) {
|
||||||
|
const { setGlobalData } = actions;
|
||||||
|
|
||||||
|
setGlobalData(content);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default akReleasesPlugin;
|
69
website/releases/utils.mjs
Normal file
69
website/releases/utils.mjs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* @file Docusaurus release utils.
|
||||||
|
*
|
||||||
|
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs-types"
|
||||||
|
*/
|
||||||
|
import FastGlob from "fast-glob";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { coerce } from "semver";
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} releasesParentDirectory
|
||||||
|
* @returns {FastGlob.Entry[]}
|
||||||
|
*/
|
||||||
|
export function collectReleaseFiles(releasesParentDirectory) {
|
||||||
|
const releaseFiles = FastGlob.sync("releases/**/v*.{md,mdx}", {
|
||||||
|
cwd: releasesParentDirectory,
|
||||||
|
onlyFiles: true,
|
||||||
|
objectMode: true,
|
||||||
|
})
|
||||||
|
.map((fileEntry) => {
|
||||||
|
return {
|
||||||
|
...fileEntry,
|
||||||
|
path: fileEntry.path.replace(/\.mdx?$/, ""),
|
||||||
|
name: fileEntry.name.replace(/^v/, "").replace(/\.mdx?$/, ""),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aSemVer = coerce(a.name);
|
||||||
|
const bSemVer = coerce(b.name);
|
||||||
|
|
||||||
|
if (aSemVer && bSemVer) {
|
||||||
|
return bSemVer.compare(aSemVer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.name.localeCompare(a.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return releaseFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SUPPORTED_RELEASE_COUNT = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {FastGlob.Entry[]} releaseFiles
|
||||||
|
*/
|
||||||
|
export function createReleaseSidebarEntries(releaseFiles) {
|
||||||
|
/**
|
||||||
|
* @type {SidebarItemConfig[]}
|
||||||
|
*/
|
||||||
|
let sidebarEntries = releaseFiles.map((fileEntry) => {
|
||||||
|
return path.join(fileEntry.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (releaseFiles.length > SUPPORTED_RELEASE_COUNT) {
|
||||||
|
// Then we add the rest of the releases as a category.
|
||||||
|
sidebarEntries = [
|
||||||
|
...sidebarEntries.slice(0, SUPPORTED_RELEASE_COUNT),
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "Previous versions",
|
||||||
|
items: sidebarEntries.slice(SUPPORTED_RELEASE_COUNT),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sidebarEntries;
|
||||||
|
}
|
@ -3,73 +3,20 @@
|
|||||||
*
|
*
|
||||||
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs-types"
|
* @import { SidebarItemConfig } from "@docusaurus/plugin-content-docs-types"
|
||||||
*/
|
*/
|
||||||
import apiReference from "../docs/developer-docs/api/reference/sidebar";
|
import * as path from "node:path";
|
||||||
import { generateVersionDropdown } from "../src/utils.js";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
/**
|
import apiReference from "../docs/developer-docs/api/reference/sidebar";
|
||||||
* @type {SidebarItemConfig[]}
|
import { collectReleaseFiles, createReleaseSidebarEntries } from "../releases/utils.mjs";
|
||||||
*/
|
|
||||||
const releases = [
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||||
"releases/2025/v2025.4",
|
|
||||||
"releases/2025/v2025.2",
|
const releases = collectReleaseFiles(path.join(__dirname, "..", "docs"));
|
||||||
"releases/2024/v2024.12",
|
|
||||||
{
|
|
||||||
type: "category",
|
|
||||||
label: "Previous versions",
|
|
||||||
items: [
|
|
||||||
"releases/2024/v2024.10",
|
|
||||||
"releases/2024/v2024.8",
|
|
||||||
"releases/2024/v2024.6",
|
|
||||||
"releases/2024/v2024.4",
|
|
||||||
"releases/2024/v2024.2",
|
|
||||||
"releases/2023/v2023.10",
|
|
||||||
"releases/2023/v2023.8",
|
|
||||||
"releases/2023/v2023.6",
|
|
||||||
"releases/2023/v2023.5",
|
|
||||||
"releases/2023/v2023.4",
|
|
||||||
"releases/2023/v2023.3",
|
|
||||||
"releases/2023/v2023.2",
|
|
||||||
"releases/2023/v2023.1",
|
|
||||||
"releases/2022/v2022.12",
|
|
||||||
"releases/2022/v2022.11",
|
|
||||||
"releases/2022/v2022.10",
|
|
||||||
"releases/2022/v2022.9",
|
|
||||||
"releases/2022/v2022.8",
|
|
||||||
"releases/2022/v2022.7",
|
|
||||||
"releases/2022/v2022.6",
|
|
||||||
"releases/2022/v2022.5",
|
|
||||||
"releases/2022/v2022.4",
|
|
||||||
"releases/2022/v2022.2",
|
|
||||||
"releases/2022/v2022.1",
|
|
||||||
"releases/2021/v2021.12",
|
|
||||||
"releases/2021/v2021.10",
|
|
||||||
"releases/2021/v2021.9",
|
|
||||||
"releases/2021/v2021.8",
|
|
||||||
"releases/2021/v2021.7",
|
|
||||||
"releases/2021/v2021.6",
|
|
||||||
"releases/2021/v2021.5",
|
|
||||||
"releases/2021/v2021.4",
|
|
||||||
"releases/2021/v2021.3",
|
|
||||||
"releases/2021/v2021.2",
|
|
||||||
"releases/2021/v2021.1",
|
|
||||||
"releases/old/v0.14",
|
|
||||||
"releases/old/v0.13",
|
|
||||||
"releases/old/v0.12",
|
|
||||||
"releases/old/v0.11",
|
|
||||||
"releases/old/v0.10",
|
|
||||||
"releases/old/v0.9",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {SidebarItemConfig[]}
|
* @type {SidebarItemConfig[]}
|
||||||
*/
|
*/
|
||||||
const items = [
|
const items = [
|
||||||
{
|
|
||||||
type: "html",
|
|
||||||
value: generateVersionDropdown(releases),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "doc",
|
type: "doc",
|
||||||
id: "index",
|
id: "index",
|
||||||
@ -796,7 +743,7 @@ const items = [
|
|||||||
slug: "releases",
|
slug: "releases",
|
||||||
description: "Release Notes for recent authentik versions",
|
description: "Release Notes for recent authentik versions",
|
||||||
},
|
},
|
||||||
items: releases,
|
items: createReleaseSidebarEntries(releases),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
231
website/src/components/VersionPicker/index.tsx
Normal file
231
website/src/components/VersionPicker/index.tsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import { usePluginData } from "@docusaurus/useGlobalData";
|
||||||
|
import useIsBrowser from "@docusaurus/useIsBrowser";
|
||||||
|
import type { AKReleasesPluginData } from "@site/releases/plugin.mjs";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React, { memo, useEffect, useMemo, useState } from "react";
|
||||||
|
import { coerce } from "semver";
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
|
const ProductionURL = new URL("https://docs.goauthentik.io");
|
||||||
|
const LocalhostAliases: ReadonlySet<string> = new Set(["localhost", "127.0.0.1"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a semver, create the URL for the version.
|
||||||
|
*/
|
||||||
|
function createVersionURL(semver: string): string {
|
||||||
|
const subdomain = `version-${semver.replace(".", "-")}`;
|
||||||
|
|
||||||
|
return `https://${subdomain}.goauthentik.io`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predicate to determine if a hostname appears to be a prerelease origin.
|
||||||
|
*/
|
||||||
|
function isPrerelease(hostname: string | null): boolean {
|
||||||
|
if (!hostname) return false;
|
||||||
|
|
||||||
|
if (hostname === ProductionURL.hostname) return true;
|
||||||
|
if (hostname.endsWith(".netlify.app")) return true;
|
||||||
|
|
||||||
|
if (LocalhostAliases.has(hostname)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a hostname, parse the semver from the subdomain.
|
||||||
|
*/
|
||||||
|
function parseHostnameSemVer(hostname: string | null): string | null {
|
||||||
|
if (!hostname) return null;
|
||||||
|
|
||||||
|
const [, possibleSemVer] = hostname.match(/version-(.+)\.goauthentik\.io/) || [];
|
||||||
|
|
||||||
|
if (!possibleSemVer) return null;
|
||||||
|
|
||||||
|
const formattedSemVer = possibleSemVer.replace("-", ".");
|
||||||
|
|
||||||
|
if (!coerce(formattedSemVer)) return null;
|
||||||
|
|
||||||
|
return formattedSemVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface VersionDropdownProps {
|
||||||
|
/**
|
||||||
|
* The hostname of the client.
|
||||||
|
*/
|
||||||
|
hostname: string | null;
|
||||||
|
/**
|
||||||
|
* The origin of the prerelease documentation.
|
||||||
|
*
|
||||||
|
* @format url
|
||||||
|
*/
|
||||||
|
prereleaseOrigin: string;
|
||||||
|
/**
|
||||||
|
* The available versions of the documentation.
|
||||||
|
*
|
||||||
|
* @format semver
|
||||||
|
*/
|
||||||
|
releases: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dropdown that shows the available versions of the documentation.
|
||||||
|
*/
|
||||||
|
const VersionDropdown = memo<VersionDropdownProps>(({ hostname, prereleaseOrigin, releases }) => {
|
||||||
|
const prerelease = isPrerelease(hostname);
|
||||||
|
const parsedSemVer = !prerelease ? parseHostnameSemVer(hostname) : null;
|
||||||
|
|
||||||
|
const currentLabel = parsedSemVer || "Pre-Release";
|
||||||
|
|
||||||
|
const endIndex = parsedSemVer ? releases.indexOf(parsedSemVer) : -1;
|
||||||
|
|
||||||
|
const visibleReleases = releases.slice(0, endIndex === -1 ? 3 : endIndex + 3);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="navbar__item dropdown dropdown--hoverable dropdown--right ak-version-selector">
|
||||||
|
<div
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded="false"
|
||||||
|
role="button"
|
||||||
|
className="navbar__link menu__link"
|
||||||
|
>
|
||||||
|
Version: {currentLabel}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="dropdown__menu menu__list-item--collapsed">
|
||||||
|
{!prerelease ? (
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={prereleaseOrigin}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="dropdown__link menu__link"
|
||||||
|
>
|
||||||
|
Pre-Release
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{visibleReleases.map((semVer, idx) => {
|
||||||
|
let label = semVer;
|
||||||
|
|
||||||
|
if (idx === 0) {
|
||||||
|
label += " (Current Release)";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<a
|
||||||
|
href={createVersionURL(semVer)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={clsx("dropdown__link menu__link", {
|
||||||
|
"menu__link--active": semVer === currentLabel,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface VersionPickerLoaderProps {
|
||||||
|
pluginData: AKReleasesPluginData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data-fetching component that loads available versions of the documentation.
|
||||||
|
*
|
||||||
|
* @see {@linkcode VersionPicker} for the component.
|
||||||
|
* @see {@linkcode AKReleasesPluginData} for the plugin data.
|
||||||
|
* @client
|
||||||
|
*/
|
||||||
|
const VersionPickerLoader: React.FC<VersionPickerLoaderProps> = ({ pluginData }) => {
|
||||||
|
const [releases, setReleases] = useState(pluginData.releases);
|
||||||
|
|
||||||
|
const browser = useIsBrowser();
|
||||||
|
|
||||||
|
const prereleaseOrigin = useMemo(() => {
|
||||||
|
if (browser && LocalhostAliases.has(window.location.hostname)) {
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProductionURL.href;
|
||||||
|
}, [browser]);
|
||||||
|
|
||||||
|
const hostname = useMemo(() => {
|
||||||
|
if (!browser) return null;
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
// Query parameter used for debugging.
|
||||||
|
// Note that this doesn't synchronize with Docusaurus's router state.
|
||||||
|
const subdomain = searchParams.get("version");
|
||||||
|
|
||||||
|
if (subdomain) return subdomain;
|
||||||
|
|
||||||
|
return window.location.hostname;
|
||||||
|
}, [browser]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!browser || !prereleaseOrigin) return;
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const updateURL = new URL(pluginData.publicPath, prereleaseOrigin);
|
||||||
|
|
||||||
|
fetch(updateURL, {
|
||||||
|
signal: controller.signal,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch new releases: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data: unknown) => {
|
||||||
|
// We're extra cautious here to be ready if the API shape ever changes.
|
||||||
|
if (!data) throw new Error("Failed to parse releases");
|
||||||
|
|
||||||
|
if (!Array.isArray(data)) throw new Error("Releases must be an array");
|
||||||
|
|
||||||
|
if (!data.every((item) => typeof item === "string"))
|
||||||
|
throw new Error("Releases must be an array of strings");
|
||||||
|
|
||||||
|
setReleases(data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn(`Failed to fetch new releases: ${error}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => controller.abort("unmount");
|
||||||
|
}, [browser, prereleaseOrigin]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VersionDropdown
|
||||||
|
hostname={hostname}
|
||||||
|
prereleaseOrigin={prereleaseOrigin}
|
||||||
|
releases={releases}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component that shows the available versions of the documentation.
|
||||||
|
*
|
||||||
|
* @see {@linkcode VersionPickerLoader} for the data-fetching component.
|
||||||
|
*/
|
||||||
|
export const VersionPicker: React.FC = () => {
|
||||||
|
const pluginData = usePluginData("ak-releases-plugin", undefined, {
|
||||||
|
failfast: true,
|
||||||
|
}) as AKReleasesPluginData;
|
||||||
|
|
||||||
|
if (!pluginData.releases.length) return null;
|
||||||
|
|
||||||
|
return <VersionPickerLoader pluginData={pluginData} />;
|
||||||
|
};
|
27
website/src/components/VersionPicker/styles.css
Normal file
27
website/src/components/VersionPicker/styles.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.theme-doc-sidebar-menu .dropdown.ak-version-selector {
|
||||||
|
--ak-version-selector-padding: calc(var(--ifm-spacing-vertical) / 2);
|
||||||
|
|
||||||
|
width: calc(100% - (var(--ifm-spacing-horizontal) / 2));
|
||||||
|
border-block-end: var(--ifm-hr-height) solid var(--ifm-toc-border-color);
|
||||||
|
padding-block-start: calc(var(--ak-version-selector-padding) / 2);
|
||||||
|
padding-block-end: var(--ak-version-selector-padding);
|
||||||
|
margin-block-end: var(--ak-version-selector-padding);
|
||||||
|
|
||||||
|
.navbar__link.menu__link {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: var(--ifm-font-weight-semibold);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: var(--ifm-color-emphasis-400);
|
||||||
|
filter: var(--ifm-menu-link-sublist-icon-filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown__menu {
|
||||||
|
background: var(--ifm-dropdown-background-color);
|
||||||
|
box-shadow: var(--ifm-global-shadow-lw);
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-200);
|
||||||
|
}
|
||||||
|
}
|
24
website/src/theme/DocSidebarItems/index.tsx
Normal file
24
website/src/theme/DocSidebarItems/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
DocSidebarItemsExpandedStateProvider,
|
||||||
|
useVisibleSidebarItems,
|
||||||
|
} from "@docusaurus/plugin-content-docs/client";
|
||||||
|
import { VersionPicker } from "@site/src/components/VersionPicker/index";
|
||||||
|
import DocSidebarItem from "@theme/DocSidebarItem";
|
||||||
|
import type { Props as DocSidebarItemsProps } from "@theme/DocSidebarItems";
|
||||||
|
import { memo } from "react";
|
||||||
|
|
||||||
|
const DocSidebarItems: React.FC<DocSidebarItemsProps> = ({ items, ...props }) => {
|
||||||
|
const visibleItems = useVisibleSidebarItems(items, props.activePath);
|
||||||
|
const includeVersionPicker = props.level === 1 && props.activePath.startsWith("/docs");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DocSidebarItemsExpandedStateProvider>
|
||||||
|
{includeVersionPicker ? <VersionPicker /> : null}
|
||||||
|
{visibleItems.map((item, index) => (
|
||||||
|
<DocSidebarItem key={index} item={item} index={index} {...props} />
|
||||||
|
))}
|
||||||
|
</DocSidebarItemsExpandedStateProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(DocSidebarItems);
|
3
website/types/docusaurus.d.ts
vendored
3
website/types/docusaurus.d.ts
vendored
@ -21,6 +21,9 @@ declare module "@docusaurus/plugin-content-docs/src/sidebars/types" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module "@docusaurus/plugin-content-docs/client" {
|
declare module "@docusaurus/plugin-content-docs/client" {
|
||||||
|
export * from "@docusaurus/plugin-content-docs/lib/client/docSidebarItemsExpandedState.js";
|
||||||
|
export * from "@docusaurus/plugin-content-docs/lib/client/docsUtils.js";
|
||||||
|
|
||||||
import { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/lib/client/doc.js";
|
import { DocContextValue as BaseDocContextValue } from "@docusaurus/plugin-content-docs/lib/client/doc.js";
|
||||||
import { DocFrontMatter as BaseDocFrontMatter } from "@docusaurus/plugin-content-docs";
|
import { DocFrontMatter as BaseDocFrontMatter } from "@docusaurus/plugin-content-docs";
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user