Add version-aware documentation resolution
This commit is contained in:
+19
-1
@@ -126,6 +126,13 @@ export type DocsDocumentationTopic = {
|
||||
i18n_key: string;
|
||||
locale: string;
|
||||
translation_locale: string;
|
||||
version: {
|
||||
resolved: string;
|
||||
minimum?: string | null;
|
||||
maximum_exclusive?: string | null;
|
||||
range: string;
|
||||
fallback: "unversioned" | "matching_range" | string;
|
||||
};
|
||||
conditions: DocsDocumentationCondition[];
|
||||
links: DocsDocumentationLink[];
|
||||
related_modules: string[];
|
||||
@@ -141,6 +148,16 @@ export type DocsDocumentationTopic = {
|
||||
};
|
||||
|
||||
export type DocsContext = {
|
||||
versions: {
|
||||
mode: "installed" | "selected";
|
||||
selected_version?: string | null;
|
||||
status: "installed" | "stable" | "older_supported" | "unsupported" | string;
|
||||
latest_version?: string | null;
|
||||
stable_version?: string | null;
|
||||
supported_versions: string[];
|
||||
installed_versions: Record<string, string>;
|
||||
fallback_policy: string;
|
||||
};
|
||||
actor: {
|
||||
tenant_id?: string;
|
||||
user_id?: string;
|
||||
@@ -193,10 +210,11 @@ export type DocsContext = {
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchDocsContext(settings: ApiSettings, options: { documentationType?: "admin" | "user"; locale?: string } = {}): Promise<DocsContext> {
|
||||
export function fetchDocsContext(settings: ApiSettings, options: { documentationType?: "admin" | "user"; locale?: string; version?: string | null } = {}): Promise<DocsContext> {
|
||||
const params = new URLSearchParams();
|
||||
if (options.documentationType) params.set("type", options.documentationType);
|
||||
if (options.locale) params.set("locale", options.locale);
|
||||
if (options.version) params.set("version", options.version);
|
||||
const query = params.toString();
|
||||
return apiFetch(settings, `/api/v1/docs/context${query ? `?${query}` : ""}`);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
|
||||
const [documentationType, setDocumentationType] = useState<DocumentationType>(() => documentationTypeFromSearch(location.search));
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() => new Set());
|
||||
const locale = localeFromSearch(location.search) ?? language;
|
||||
const selectedVersion = versionFromSearch(location.search);
|
||||
const adminDocs = documentationType === "admin";
|
||||
|
||||
async function load() {
|
||||
@@ -82,7 +83,7 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
|
||||
setError("");
|
||||
setContext(null);
|
||||
try {
|
||||
const nextContext = await fetchDocsContext(settings, { documentationType, locale });
|
||||
const nextContext = await fetchDocsContext(settings, { documentationType, locale, version: selectedVersion });
|
||||
if (sequence !== loadSequence.current) return;
|
||||
if (nextContext.actor.documentation_type !== documentationType) {
|
||||
throw new Error("Documentation response type did not match the requested projection.");
|
||||
@@ -101,7 +102,7 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
|
||||
setDocumentationType((current) => current === nextType ? current : nextType);
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => { void load(); }, [settings.apiBaseUrl, settings.apiKey, settings.accessToken, documentationType, locale]);
|
||||
useEffect(() => { void load(); }, [settings.apiBaseUrl, settings.apiKey, settings.accessToken, documentationType, locale, selectedVersion]);
|
||||
|
||||
const treeNodes = useMemo(() => docsTreeNodes(context, adminDocs), [context, adminDocs]);
|
||||
const pages = useMemo(() => flattenTreePages(treeNodes), [treeNodes]);
|
||||
@@ -140,6 +141,21 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
|
||||
onSelect={selectDocumentationType}
|
||||
canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"}
|
||||
/>
|
||||
<label className="docs-version-selector" title={context?.versions.fallback_policy}>
|
||||
<span>Version</span>
|
||||
<select
|
||||
value={selectedVersion ?? ""}
|
||||
onChange={(event) => selectVersion(event.target.value || null)}
|
||||
>
|
||||
<option value="">Installed versions</option>
|
||||
{selectedVersion && !context?.versions.supported_versions.includes(selectedVersion) &&
|
||||
<option value={selectedVersion}>{selectedVersion} (unsupported)</option>
|
||||
}
|
||||
{(context?.versions.supported_versions ?? []).map((version) => (
|
||||
<option key={version} value={version}>{version}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
|
||||
<ExplorerTree
|
||||
@@ -210,6 +226,13 @@ export default function DocsPage({ settings }: { settings: ApiSettings }) {
|
||||
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
|
||||
}
|
||||
|
||||
function selectVersion(version: string | null) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (version) params.set("version", version);
|
||||
else params.delete("version");
|
||||
navigate(`${location.pathname}?${params.toString()}`, { replace: true });
|
||||
}
|
||||
|
||||
function selectPage(page: DocsPageNode) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
params.set("topic", page.id);
|
||||
@@ -895,6 +918,11 @@ function documentationTypeFromSearch(search: string): DocumentationType {
|
||||
return new URLSearchParams(search).get("type") === "admin" ? "admin" : "user";
|
||||
}
|
||||
|
||||
function versionFromSearch(search: string): string | null {
|
||||
const value = new URLSearchParams(search).get("version")?.trim();
|
||||
return value || null;
|
||||
}
|
||||
|
||||
function localeFromSearch(search: string): string | null {
|
||||
const value = new URLSearchParams(search).get("locale");
|
||||
if (!value) return null;
|
||||
|
||||
Reference in New Issue
Block a user