@cedros/data-react 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -6
- package/dist/admin/icons.d.ts +1 -0
- package/dist/admin/icons.js +2 -1
- package/dist/admin/plugin.js +11 -1
- package/dist/admin/sectionIds.d.ts +2 -1
- package/dist/admin/sectionIds.js +4 -2
- package/dist/admin/sections/LayoutSection.d.ts +2 -0
- package/dist/admin/sections/LayoutSection.js +104 -0
- package/dist/admin/styles.css +11 -11
- package/dist/react/CedrosDataProvider.d.ts +28 -0
- package/dist/react/CedrosDataProvider.js +76 -0
- package/dist/react/contentCollections.d.ts +4 -0
- package/dist/react/contentCollections.js +48 -0
- package/dist/react/entries.js +6 -7
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +1 -0
- package/dist/react/sitemap.js +7 -20
- package/dist/react/slugs.js +7 -20
- package/dist/react/theme.d.ts +33 -0
- package/dist/react/theme.js +1 -0
- package/dist/site-templates/DocsTemplates.d.ts +9 -8
- package/dist/site-templates/DocsTemplates.js +32 -17
- package/dist/site-templates/SiteFooter.d.ts +7 -1
- package/dist/site-templates/SiteFooter.js +5 -2
- package/dist/site-templates/SiteLayout.d.ts +9 -1
- package/dist/site-templates/SiteLayout.js +2 -2
- package/dist/site-templates/TopNav.d.ts +11 -1
- package/dist/site-templates/TopNav.js +12 -2
- package/dist/site-templates/contentIndex.d.ts +4 -0
- package/dist/site-templates/contentIndex.js +15 -1
- package/dist/site-templates/docsNavigation.d.ts +30 -1
- package/dist/site-templates/docsNavigation.js +132 -1
- package/dist/site-templates/docsTemplateShell.d.ts +8 -0
- package/dist/site-templates/docsTemplateShell.js +14 -0
- package/dist/site-templates/index.d.ts +4 -4
- package/dist/site-templates/index.js +1 -1
- package/dist/site-templates/styles.css +52 -8
- package/package.json +1 -1
package/dist/react/slugs.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const CONTENT_TYPE_COLLECTIONS = {
|
|
4
|
-
page: "pages",
|
|
5
|
-
blog: "blog",
|
|
6
|
-
docs: "docs",
|
|
7
|
-
learn: "learn",
|
|
8
|
-
project: "projects",
|
|
9
|
-
airdrop: "airdrops",
|
|
10
|
-
};
|
|
1
|
+
import { resolveApiKey, resolveServerUrl } from "./fetch.js";
|
|
2
|
+
import { queryEntriesByContentType } from "./contentCollections.js";
|
|
11
3
|
/**
|
|
12
4
|
* Returns all slugs for a given content type, for use with
|
|
13
5
|
* Next.js `generateStaticParams`.
|
|
@@ -26,16 +18,11 @@ const CONTENT_TYPE_COLLECTIONS = {
|
|
|
26
18
|
export async function listContentSlugs(contentType, options) {
|
|
27
19
|
const serverUrl = resolveServerUrl(options);
|
|
28
20
|
const apiKey = resolveApiKey(options);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
limit: 1000,
|
|
35
|
-
offset: 0,
|
|
36
|
-
},
|
|
37
|
-
apiKey,
|
|
38
|
-
});
|
|
21
|
+
const records = await queryEntriesByContentType(serverUrl, contentType, (collectionName) => ({
|
|
22
|
+
collection_name: collectionName,
|
|
23
|
+
limit: 1000,
|
|
24
|
+
offset: 0,
|
|
25
|
+
}), apiKey);
|
|
39
26
|
return records.map((record) => record.payload.slug ?? record.entry_key);
|
|
40
27
|
}
|
|
41
28
|
/**
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Theme mode selector. */
|
|
2
|
+
export type ThemeMode = "light" | "dark";
|
|
3
|
+
/**
|
|
4
|
+
* Typed theme token overrides using CSS variable names.
|
|
5
|
+
*
|
|
6
|
+
* Matches the `--cedros-*` variable namespace shared across
|
|
7
|
+
* cedros-login, cedros-pay, and cedros-data.
|
|
8
|
+
*/
|
|
9
|
+
export interface CedrosDataThemeOverrides {
|
|
10
|
+
"--cedros-background"?: string;
|
|
11
|
+
"--cedros-foreground"?: string;
|
|
12
|
+
"--cedros-muted"?: string;
|
|
13
|
+
"--cedros-muted-foreground"?: string;
|
|
14
|
+
"--cedros-border"?: string;
|
|
15
|
+
"--cedros-link"?: string;
|
|
16
|
+
"--cedros-primary"?: string;
|
|
17
|
+
"--cedros-primary-foreground"?: string;
|
|
18
|
+
"--cedros-accent"?: string;
|
|
19
|
+
"--cedros-ring"?: string;
|
|
20
|
+
"--cedros-radius"?: string;
|
|
21
|
+
"--cedros-success"?: string;
|
|
22
|
+
"--cedros-error"?: string;
|
|
23
|
+
[key: string]: string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** Value returned by `useCedrosDataTheme()`. */
|
|
26
|
+
export interface CedrosDataThemeValue {
|
|
27
|
+
mode: ThemeMode;
|
|
28
|
+
isDark: boolean;
|
|
29
|
+
className: string;
|
|
30
|
+
style: React.CSSProperties;
|
|
31
|
+
unstyled: boolean;
|
|
32
|
+
overrides?: CedrosDataThemeOverrides;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { SiteNavigationItem } from "./SiteLayout.js";
|
|
2
2
|
import { type DocsIndexEntry } from "./contentIndex.js";
|
|
3
3
|
import { type DocsSidebarSection } from "./docsNavigation.js";
|
|
4
|
+
import { type DocsTemplateShellOptions } from "./docsTemplateShell.js";
|
|
4
5
|
export interface DocsIndexItem extends DocsIndexEntry {
|
|
5
6
|
}
|
|
6
|
-
export interface DocsIndexTemplateProps {
|
|
7
|
-
siteTitle
|
|
8
|
-
navigation
|
|
7
|
+
export interface DocsIndexTemplateProps extends DocsTemplateShellOptions {
|
|
8
|
+
siteTitle?: string;
|
|
9
|
+
navigation?: SiteNavigationItem[];
|
|
9
10
|
docs: DocsIndexItem[];
|
|
10
11
|
title?: string;
|
|
11
12
|
description?: string;
|
|
@@ -22,10 +23,10 @@ export interface DocsIndexTemplateProps {
|
|
|
22
23
|
sidebarSections?: DocsSidebarSection[];
|
|
23
24
|
sidebarTitle?: string;
|
|
24
25
|
}
|
|
25
|
-
export declare function DocsIndexTemplate({ siteTitle, navigation, docs, title, description, basePath, currentPath, query, category, tag, sort, page, pageSize, categories, tags, sidebarSections, sidebarTitle }: DocsIndexTemplateProps): React.JSX.Element;
|
|
26
|
-
export interface DocArticleTemplateProps {
|
|
27
|
-
siteTitle
|
|
28
|
-
navigation
|
|
26
|
+
export declare function DocsIndexTemplate({ siteTitle, navigation, docs, title, description, basePath, currentPath, query, category, tag, sort, page, pageSize, categories, tags, sidebarSections, sidebarTitle, headless, renderLayout }: DocsIndexTemplateProps): React.JSX.Element;
|
|
27
|
+
export interface DocArticleTemplateProps extends DocsTemplateShellOptions {
|
|
28
|
+
siteTitle?: string;
|
|
29
|
+
navigation?: SiteNavigationItem[];
|
|
29
30
|
title: string;
|
|
30
31
|
bodyMarkdown?: string;
|
|
31
32
|
bodyHtml?: string;
|
|
@@ -57,4 +58,4 @@ export interface DocArticleTemplateProps {
|
|
|
57
58
|
};
|
|
58
59
|
editHref?: string;
|
|
59
60
|
}
|
|
60
|
-
export declare function DocArticleTemplate({ siteTitle, navigation, title, bodyMarkdown, bodyHtml, allowUnsafeHtmlFallback, lastUpdated, readingMinutes, basePath, currentPath, searchQuery, docs, sidebarSections, sidebarTitle, breadcrumbs, toc, previousDoc, nextDoc, editHref }: DocArticleTemplateProps): React.JSX.Element;
|
|
61
|
+
export declare function DocArticleTemplate({ siteTitle, navigation, title, bodyMarkdown, bodyHtml, allowUnsafeHtmlFallback, lastUpdated, readingMinutes, basePath, currentPath, searchQuery, docs, sidebarSections, sidebarTitle, breadcrumbs, toc, previousDoc, nextDoc, editHref, headless, renderLayout }: DocArticleTemplateProps): React.JSX.Element;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { SiteLayout } from "./SiteLayout.js";
|
|
3
2
|
import { buildContentListHref, collectFilterValues, prepareDocsIndex } from "./contentIndex.js";
|
|
4
3
|
import { Breadcrumbs, ContentPagination } from "./contentUi.js";
|
|
5
|
-
import { buildDocsSidebarSections, withActiveDocsSidebar } from "./docsNavigation.js";
|
|
4
|
+
import { buildHierarchicalDocsSidebarSections, buildDocsSidebarSections, withActiveDocsSidebar } from "./docsNavigation.js";
|
|
6
5
|
import { DocsSidebar } from "./DocsSidebar.js";
|
|
6
|
+
import { renderDocsTemplateShell } from "./docsTemplateShell.js";
|
|
7
7
|
import { MarkdownContent } from "./MarkdownContent.js";
|
|
8
8
|
import { TocScrollSpy } from "./tocScrollSpy.js";
|
|
9
|
-
export function DocsIndexTemplate({ siteTitle, navigation, docs, title = "Documentation", description = "", basePath = "/docs", currentPath, query = "", category = "", tag = "", sort = "title-asc", page = 1, pageSize = 10, categories, tags, sidebarSections, sidebarTitle = "Docs" }) {
|
|
9
|
+
export function DocsIndexTemplate({ siteTitle, navigation, docs, title = "Documentation", description = "", basePath = "/docs", currentPath, query = "", category = "", tag = "", sort = "title-asc", page = 1, pageSize = 10, categories, tags, sidebarSections, sidebarTitle = "Docs", headless = false, renderLayout }) {
|
|
10
10
|
const normalizedFilters = collectFilterValues(docs);
|
|
11
11
|
const resolvedCategories = categories ?? normalizedFilters.categories;
|
|
12
12
|
const resolvedTags = tags ?? normalizedFilters.tags;
|
|
13
13
|
const activePath = currentPath ?? basePath;
|
|
14
|
-
const resolvedSidebarSections = withActiveDocsSidebar(sidebarSections ??
|
|
14
|
+
const resolvedSidebarSections = withActiveDocsSidebar(sidebarSections ?? buildDefaultDocsSidebarSections(docs, basePath), activePath);
|
|
15
15
|
const result = prepareDocsIndex(docs, {
|
|
16
16
|
query,
|
|
17
17
|
category,
|
|
@@ -20,21 +20,31 @@ export function DocsIndexTemplate({ siteTitle, navigation, docs, title = "Docume
|
|
|
20
20
|
page,
|
|
21
21
|
pageSize
|
|
22
22
|
});
|
|
23
|
-
return (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
return renderDocsTemplateShell((_jsxs("section", { className: "cedros-site__docs-page", children: [_jsx(DocsSidebar, { title: sidebarTitle, basePath: basePath, searchQuery: query, sections: resolvedSidebarSections }), _jsxs("div", { className: "cedros-site__docs-main", children: [_jsxs("section", { className: "cedros-site__card", children: [_jsx("span", { className: "cedros-site__pill", children: "docs" }), _jsx("h1", { className: "cedros-site__title", style: { marginTop: "0.6rem" }, children: title }), description && _jsx("p", { className: "cedros-site__subtitle", children: description }), _jsxs("p", { className: "cedros-site__entry-meta", style: { marginTop: "0.6rem" }, children: [result.totalItems, " results"] })] }), _jsx(DocsIndexControls, { basePath: basePath, query: query, category: category, tag: tag, sort: sort, categories: resolvedCategories, tags: resolvedTags }), result.totalItems === 0 && (_jsxs("section", { className: "cedros-site__card cedros-site__empty-state", children: [_jsx("h2", { className: "cedros-site__title", style: { fontSize: "1.25rem" }, children: "No documentation pages found" }), _jsx("p", { className: "cedros-site__subtitle", style: { marginTop: "0.6rem" }, children: "Try a broader query or clear filters." })] })), result.totalItems > 0 && (_jsxs(_Fragment, { children: [_jsx("section", { className: "cedros-site__content-grid cedros-site__content-grid--docs", children: result.items.map((doc) => (_jsxs("article", { className: "cedros-site__card cedros-site__entry-card", children: [_jsx("h2", { className: "cedros-site__entry-title", children: _jsx("a", { href: `${basePath}/${doc.slug}`, children: doc.title }) }), doc.description && _jsx("p", { className: "cedros-site__subtitle", children: doc.description }), (doc.lastUpdated || doc.category) && (_jsx("p", { className: "cedros-site__entry-meta", children: [doc.category, doc.lastUpdated ? `Updated ${doc.lastUpdated}` : ""]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(" • ") })), doc.tags && doc.tags.length > 0 && (_jsx("div", { className: "cedros-site__tag-list", children: doc.tags.map((entryTag) => (_jsx("a", { href: buildContentListHref(basePath, {
|
|
26
|
+
q: query,
|
|
27
|
+
category,
|
|
28
|
+
tag: entryTag,
|
|
29
|
+
sort
|
|
30
|
+
}), className: "cedros-site__pill", children: entryTag }, entryTag))) }))] }, doc.slug))) }), _jsx(ContentPagination, { basePath: basePath, page: result.page, totalPages: result.totalPages, query: { q: query, category, tag, sort } })] }))] })] })), {
|
|
31
|
+
siteTitle,
|
|
32
|
+
navigation,
|
|
33
|
+
headless,
|
|
34
|
+
renderLayout
|
|
35
|
+
});
|
|
31
36
|
}
|
|
32
|
-
export function DocArticleTemplate({ siteTitle, navigation, title, bodyMarkdown, bodyHtml, allowUnsafeHtmlFallback = false, lastUpdated, readingMinutes, basePath = "/docs", currentPath, searchQuery = "", docs = [], sidebarSections, sidebarTitle = "Docs", breadcrumbs = [], toc = [], previousDoc, nextDoc, editHref }) {
|
|
37
|
+
export function DocArticleTemplate({ siteTitle, navigation, title, bodyMarkdown, bodyHtml, allowUnsafeHtmlFallback = false, lastUpdated, readingMinutes, basePath = "/docs", currentPath, searchQuery = "", docs = [], sidebarSections, sidebarTitle = "Docs", breadcrumbs = [], toc = [], previousDoc, nextDoc, editHref, headless = false, renderLayout }) {
|
|
33
38
|
const activePath = currentPath ?? breadcrumbs[breadcrumbs.length - 1]?.href;
|
|
34
|
-
const resolvedSidebarSections = withActiveDocsSidebar(sidebarSections ??
|
|
35
|
-
return (
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const resolvedSidebarSections = withActiveDocsSidebar(sidebarSections ?? buildDefaultDocsSidebarSections(docs, basePath), activePath);
|
|
40
|
+
return renderDocsTemplateShell((_jsxs("section", { className: "cedros-site__docs-page cedros-site__docs-page--article", children: [_jsx(DocsSidebar, { title: sidebarTitle, basePath: basePath, searchQuery: searchQuery, sections: resolvedSidebarSections }), _jsxs("article", { className: "cedros-site__card cedros-site__article cedros-site__docs-article", children: [_jsx(Breadcrumbs, { trail: breadcrumbs }), _jsxs("div", { className: "cedros-site__article-header", children: [_jsx("span", { className: "cedros-site__pill", children: "docs" }), _jsx("h1", { className: "cedros-site__title", children: title }), (lastUpdated || readingMinutes) && (_jsx("p", { className: "cedros-site__entry-meta", children: [lastUpdated ? `Last updated ${lastUpdated}` : "", readingTime(readingMinutes)]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join(" • ") }))] }), _jsx(MarkdownContent, { bodyMarkdown: bodyMarkdown, bodyHtml: bodyHtml, allowUnsafeHtmlFallback: allowUnsafeHtmlFallback }), (previousDoc || nextDoc || editHref) && (_jsxs("footer", { className: "cedros-site__doc-footer", children: [previousDoc && (_jsxs("a", { href: previousDoc.href, className: "cedros-site__nav-link", children: ["\u2190 ", previousDoc.title] })), nextDoc && (_jsxs("a", { href: nextDoc.href, className: "cedros-site__nav-link", children: [nextDoc.title, " \u2192"] })), editHref && (_jsx("a", { href: editHref, className: "cedros-site__nav-link", children: "Suggest edit" }))] }))] }), toc.length > 0 && (_jsxs("aside", { className: "cedros-site__card cedros-site__toc", children: [_jsx("h2", { className: "cedros-site__toc-title", children: "On this page" }), _jsx(TocScrollSpy, { entries: toc })] }))] })), {
|
|
43
|
+
siteTitle,
|
|
44
|
+
navigation,
|
|
45
|
+
headless,
|
|
46
|
+
renderLayout
|
|
47
|
+
});
|
|
38
48
|
}
|
|
39
49
|
function DocsIndexControls({ basePath, query, category, tag, sort, categories, tags }) {
|
|
40
50
|
return (_jsxs("form", { method: "get", action: basePath, className: "cedros-site__controls cedros-site__card", children: [_jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Search" }), _jsx("input", { type: "search", name: "q", defaultValue: query, placeholder: "Search docs" })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Category" }), _jsxs("select", { name: "category", defaultValue: category, children: [_jsx("option", { value: "", children: "All" }), categories.map((entry) => (_jsx("option", { value: entry, children: entry }, entry)))] })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Tag" }), _jsxs("select", { name: "tag", defaultValue: tag, children: [_jsx("option", { value: "", children: "All" }), tags.map((entry) => (_jsx("option", { value: entry, children: entry }, entry)))] })] }), _jsxs("label", { className: "cedros-site__control", children: [_jsx("span", { children: "Sort" }), _jsxs("select", { name: "sort", defaultValue: sort, children: [_jsx("option", { value: "title-asc", children: "Title A-Z" }), _jsx("option", { value: "title-desc", children: "Title Z-A" }), _jsx("option", { value: "updated-desc", children: "Recently updated" }), _jsx("option", { value: "updated-asc", children: "Least recently updated" })] })] }), _jsxs("div", { className: "cedros-site__control-actions", children: [_jsx("button", { className: "cedros-site__nav-link", type: "submit", children: "Apply" }), _jsx("a", { className: "cedros-site__nav-link", href: basePath, children: "Clear" })] })] }));
|
|
@@ -45,3 +55,8 @@ function readingTime(minutes) {
|
|
|
45
55
|
}
|
|
46
56
|
return `${minutes} min read`;
|
|
47
57
|
}
|
|
58
|
+
function buildDefaultDocsSidebarSections(docs, basePath) {
|
|
59
|
+
return docs.some((doc) => doc.slug.includes("/"))
|
|
60
|
+
? buildHierarchicalDocsSidebarSections(docs, basePath)
|
|
61
|
+
: buildDocsSidebarSections(docs, basePath);
|
|
62
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
/** Layout options for the site footer. */
|
|
3
|
+
export interface FooterLayoutOptions {
|
|
4
|
+
/** Full-width stretches content to viewport edges. Default: "contained". */
|
|
5
|
+
width?: "contained" | "full";
|
|
6
|
+
}
|
|
2
7
|
export interface SiteFooterLink {
|
|
3
8
|
key: string;
|
|
4
9
|
label: string;
|
|
@@ -9,5 +14,6 @@ export interface SiteFooterProps {
|
|
|
9
14
|
note?: string;
|
|
10
15
|
links?: SiteFooterLink[];
|
|
11
16
|
rightSlot?: ReactNode;
|
|
17
|
+
layout?: FooterLayoutOptions;
|
|
12
18
|
}
|
|
13
|
-
export declare function SiteFooter({ siteTitle, note, links, rightSlot }: SiteFooterProps): React.JSX.Element;
|
|
19
|
+
export declare function SiteFooter({ siteTitle, note, links, rightSlot, layout }: SiteFooterProps): React.JSX.Element;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function SiteFooter({ siteTitle, note, links = [], rightSlot }) {
|
|
3
|
-
|
|
2
|
+
export function SiteFooter({ siteTitle, note, links = [], rightSlot, layout }) {
|
|
3
|
+
const footerClasses = layout?.width === "full"
|
|
4
|
+
? "cedros-site__footer cedros-site__footer--full"
|
|
5
|
+
: "cedros-site__footer";
|
|
6
|
+
return (_jsx("footer", { className: footerClasses, children: _jsxs("div", { className: "cedros-site__container cedros-site__footer-inner", children: [_jsxs("div", { className: "cedros-site__footer-brand", children: [_jsx("span", { children: siteTitle }), note && _jsx("span", { children: note })] }), links.length > 0 && (_jsx("nav", { className: "cedros-site__footer-links", "aria-label": "Footer", children: links.map((link) => (_jsx("a", { href: link.route, className: "cedros-site__footer-link", children: link.label }, link.key))) })), rightSlot && _jsx("div", { className: "cedros-site__footer-right", children: rightSlot })] }) }));
|
|
4
7
|
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import type { NavLayoutOptions } from "./TopNav.js";
|
|
3
|
+
import type { FooterLayoutOptions } from "./SiteFooter.js";
|
|
2
4
|
export interface SiteNavigationItem {
|
|
3
5
|
key: string;
|
|
4
6
|
label: string;
|
|
5
7
|
route: string;
|
|
6
8
|
}
|
|
9
|
+
/** Combined layout options for the full site shell. */
|
|
10
|
+
export interface SiteLayoutOptions {
|
|
11
|
+
nav?: NavLayoutOptions;
|
|
12
|
+
footer?: FooterLayoutOptions;
|
|
13
|
+
}
|
|
7
14
|
export interface SiteLayoutProps {
|
|
8
15
|
siteTitle: string;
|
|
9
16
|
navigation: SiteNavigationItem[];
|
|
10
17
|
children: ReactNode;
|
|
11
18
|
brandHref?: string;
|
|
12
19
|
footerNote?: string;
|
|
20
|
+
layout?: SiteLayoutOptions;
|
|
13
21
|
}
|
|
14
|
-
export declare function SiteLayout({ siteTitle, navigation, children, brandHref, footerNote }: SiteLayoutProps): React.JSX.Element;
|
|
22
|
+
export declare function SiteLayout({ siteTitle, navigation, children, brandHref, footerNote, layout }: SiteLayoutProps): React.JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { SiteFooter } from "./SiteFooter.js";
|
|
3
3
|
import { TopNav } from "./TopNav.js";
|
|
4
|
-
export function SiteLayout({ siteTitle, navigation, children, brandHref = "/", footerNote = "Powered by cedros-data" }) {
|
|
5
|
-
return (_jsxs("div", { className: "cedros-site", children: [_jsx(TopNav, { siteTitle: siteTitle, navigation: navigation, brandHref: brandHref }), _jsx("main", { className: "cedros-site__main", children: _jsx("div", { className: "cedros-site__container", children: children }) }), _jsx(SiteFooter, { siteTitle: siteTitle, note: footerNote })] }));
|
|
4
|
+
export function SiteLayout({ siteTitle, navigation, children, brandHref = "/", footerNote = "Powered by cedros-data", layout }) {
|
|
5
|
+
return (_jsxs("div", { className: "cedros-site", children: [_jsx(TopNav, { siteTitle: siteTitle, navigation: navigation, brandHref: brandHref, layout: layout?.nav }), _jsx("main", { className: "cedros-site__main", children: _jsx("div", { className: "cedros-site__container", children: children }) }), _jsx(SiteFooter, { siteTitle: siteTitle, note: footerNote, layout: layout?.footer })] }));
|
|
6
6
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { SiteNavigationItem } from "./SiteLayout.js";
|
|
3
|
+
/** Layout options for the top navigation bar. */
|
|
4
|
+
export interface NavLayoutOptions {
|
|
5
|
+
/** Full-width stretches logo/actions to viewport edges. Default: "contained". */
|
|
6
|
+
width?: "contained" | "full";
|
|
7
|
+
/** Where the main nav links sit. Default: "right". */
|
|
8
|
+
linkPosition?: "center" | "right";
|
|
9
|
+
/** Visual style for nav links. Default: "pill". */
|
|
10
|
+
linkStyle?: "text" | "pill";
|
|
11
|
+
}
|
|
3
12
|
export interface TopNavProps {
|
|
4
13
|
siteTitle: string;
|
|
5
14
|
navigation: SiteNavigationItem[];
|
|
6
15
|
brandHref?: string;
|
|
7
16
|
rightSlot?: ReactNode;
|
|
8
17
|
currentPath?: string;
|
|
18
|
+
layout?: NavLayoutOptions;
|
|
9
19
|
}
|
|
10
|
-
export declare function TopNav({ siteTitle, navigation, brandHref, rightSlot, currentPath }: TopNavProps): React.JSX.Element;
|
|
20
|
+
export declare function TopNav({ siteTitle, navigation, brandHref, rightSlot, currentPath, layout }: TopNavProps): React.JSX.Element;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { withActiveRouteState } from "./routing.js";
|
|
3
|
-
export function TopNav({ siteTitle, navigation, brandHref = "/", rightSlot, currentPath }) {
|
|
3
|
+
export function TopNav({ siteTitle, navigation, brandHref = "/", rightSlot, currentPath, layout }) {
|
|
4
4
|
const activeNavigation = currentPath
|
|
5
5
|
? withActiveRouteState(navigation, currentPath)
|
|
6
6
|
: navigation.map((item) => ({ ...item, isActive: false }));
|
|
7
|
-
|
|
7
|
+
const headerClasses = buildHeaderClasses(layout);
|
|
8
|
+
const linkStyleClass = layout?.linkStyle === "text" ? " cedros-site__nav-link--text" : "";
|
|
9
|
+
return (_jsx("header", { className: headerClasses, children: _jsxs("div", { className: "cedros-site__container cedros-site__header-inner", children: [_jsx("a", { href: brandHref, className: "cedros-site__brand", children: siteTitle }), _jsx("nav", { className: "cedros-site__nav", "aria-label": "Primary", children: activeNavigation.map((item) => (_jsx("a", { href: item.route, className: `cedros-site__nav-link${linkStyleClass}${item.isActive ? " cedros-site__nav-link--active" : ""}`, "aria-current": item.isActive ? "page" : undefined, children: item.label }, item.key))) }), rightSlot && _jsx("div", { className: "cedros-site__header-right", children: rightSlot })] }) }));
|
|
10
|
+
}
|
|
11
|
+
function buildHeaderClasses(layout) {
|
|
12
|
+
let classes = "cedros-site__header";
|
|
13
|
+
if (layout?.width === "full")
|
|
14
|
+
classes += " cedros-site__header--full";
|
|
15
|
+
if (layout?.linkPosition === "center")
|
|
16
|
+
classes += " cedros-site__header--nav-center";
|
|
17
|
+
return classes;
|
|
8
18
|
}
|
|
@@ -40,6 +40,10 @@ export interface DocsIndexEntry {
|
|
|
40
40
|
category?: string;
|
|
41
41
|
tags?: string[];
|
|
42
42
|
lastUpdated?: string;
|
|
43
|
+
bodyMarkdown?: string;
|
|
44
|
+
bodyHtml?: string;
|
|
45
|
+
bodyText?: string;
|
|
46
|
+
searchText?: string;
|
|
43
47
|
}
|
|
44
48
|
export interface DocsIndexQuery {
|
|
45
49
|
query?: string;
|
|
@@ -125,7 +125,7 @@ function matchesDocsQuery(entry, query) {
|
|
|
125
125
|
if (!normalizedQuery) {
|
|
126
126
|
return true;
|
|
127
127
|
}
|
|
128
|
-
const haystack =
|
|
128
|
+
const haystack = buildDocsSearchText(entry);
|
|
129
129
|
return haystack.includes(normalizedQuery);
|
|
130
130
|
}
|
|
131
131
|
function compareBlogEntries(left, right, sort) {
|
|
@@ -159,6 +159,20 @@ function toTimestamp(value) {
|
|
|
159
159
|
const parsed = Date.parse(value ?? "");
|
|
160
160
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
161
161
|
}
|
|
162
|
+
function buildDocsSearchText(entry) {
|
|
163
|
+
return [
|
|
164
|
+
entry.title,
|
|
165
|
+
entry.description,
|
|
166
|
+
entry.category,
|
|
167
|
+
...(entry.tags ?? []),
|
|
168
|
+
entry.searchText,
|
|
169
|
+
entry.bodyText,
|
|
170
|
+
entry.bodyMarkdown,
|
|
171
|
+
entry.bodyHtml
|
|
172
|
+
]
|
|
173
|
+
.join(" ")
|
|
174
|
+
.toLowerCase();
|
|
175
|
+
}
|
|
162
176
|
function paginate(items, pageInput, pageSizeInput) {
|
|
163
177
|
const pageSize = sanitizePositiveInt(pageSizeInput, 10);
|
|
164
178
|
const totalItems = items.length;
|
|
@@ -4,7 +4,7 @@ export interface DocsSidebarItem {
|
|
|
4
4
|
label: string;
|
|
5
5
|
href: string;
|
|
6
6
|
isActive?: boolean;
|
|
7
|
-
depth?:
|
|
7
|
+
depth?: number;
|
|
8
8
|
badge?: string;
|
|
9
9
|
}
|
|
10
10
|
export interface DocsSidebarSection {
|
|
@@ -14,5 +14,34 @@ export interface DocsSidebarSection {
|
|
|
14
14
|
/** When true, section renders collapsed by default in the sidebar. */
|
|
15
15
|
collapsed?: boolean;
|
|
16
16
|
}
|
|
17
|
+
export interface DocsTreeNode<T extends DocsIndexEntry = DocsIndexEntry> {
|
|
18
|
+
key: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
label: string;
|
|
21
|
+
href?: string;
|
|
22
|
+
entry?: T;
|
|
23
|
+
children: DocsTreeNode<T>[];
|
|
24
|
+
}
|
|
25
|
+
export interface DocsTreeSection<T extends DocsIndexEntry = DocsIndexEntry> {
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
nodes: DocsTreeNode<T>[];
|
|
29
|
+
}
|
|
30
|
+
export interface DocsPagerLink<T extends DocsIndexEntry = DocsIndexEntry> {
|
|
31
|
+
title: string;
|
|
32
|
+
href: string;
|
|
33
|
+
entry: T;
|
|
34
|
+
}
|
|
35
|
+
export interface DocsPrevNext<T extends DocsIndexEntry = DocsIndexEntry> {
|
|
36
|
+
previousDoc?: DocsPagerLink<T>;
|
|
37
|
+
nextDoc?: DocsPagerLink<T>;
|
|
38
|
+
}
|
|
39
|
+
export interface FetchDocEntryOptions {
|
|
40
|
+
basePath?: string;
|
|
41
|
+
}
|
|
17
42
|
export declare function buildDocsSidebarSections(docs: DocsIndexEntry[], basePath?: string): DocsSidebarSection[];
|
|
43
|
+
export declare function fetchDocEntry<T extends DocsIndexEntry>(docs: T[], slugOrPath: string, options?: FetchDocEntryOptions): T | undefined;
|
|
44
|
+
export declare function buildDocsTree<T extends DocsIndexEntry>(docs: T[], basePath?: string): DocsTreeSection<T>[];
|
|
45
|
+
export declare function buildHierarchicalDocsSidebarSections<T extends DocsIndexEntry>(docs: T[], basePath?: string): DocsSidebarSection[];
|
|
46
|
+
export declare function buildDocsPrevNext<T extends DocsIndexEntry>(docs: T[], currentSlugOrPath: string, options?: FetchDocEntryOptions): DocsPrevNext<T>;
|
|
18
47
|
export declare function withActiveDocsSidebar(sections: DocsSidebarSection[], currentPath?: string): DocsSidebarSection[];
|
|
@@ -8,7 +8,7 @@ export function buildDocsSidebarSections(docs, basePath = "/docs") {
|
|
|
8
8
|
current.items.push({
|
|
9
9
|
key: doc.slug,
|
|
10
10
|
label: doc.title,
|
|
11
|
-
href:
|
|
11
|
+
href: buildDocHref(basePath, doc.slug)
|
|
12
12
|
});
|
|
13
13
|
grouped.set(sectionKey, current);
|
|
14
14
|
});
|
|
@@ -20,6 +20,52 @@ export function buildDocsSidebarSections(docs, basePath = "/docs") {
|
|
|
20
20
|
}))
|
|
21
21
|
.sort((left, right) => left.label.localeCompare(right.label));
|
|
22
22
|
}
|
|
23
|
+
export function fetchDocEntry(docs, slugOrPath, options = {}) {
|
|
24
|
+
const normalizedLookup = normalizeDocLookup(slugOrPath, options.basePath);
|
|
25
|
+
return docs.find((doc) => {
|
|
26
|
+
const normalizedDocSlug = normalizeDocSlug(doc.slug);
|
|
27
|
+
return normalizedDocSlug === normalizedLookup || (!normalizedLookup && normalizedDocSlug === "index");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export function buildDocsTree(docs, basePath = "/docs") {
|
|
31
|
+
const sections = [];
|
|
32
|
+
const grouped = new Map();
|
|
33
|
+
docs.forEach((doc) => {
|
|
34
|
+
const sectionLabel = doc.category ?? "Overview";
|
|
35
|
+
const sectionKey = sectionLabel.toLowerCase().replace(/\s+/gu, "-");
|
|
36
|
+
const section = grouped.get(sectionKey) ?? { key: sectionKey, label: sectionLabel, nodes: [] };
|
|
37
|
+
if (!grouped.has(sectionKey)) {
|
|
38
|
+
grouped.set(sectionKey, section);
|
|
39
|
+
sections.push(section);
|
|
40
|
+
}
|
|
41
|
+
insertDocTreeNode(section.nodes, doc, basePath);
|
|
42
|
+
});
|
|
43
|
+
return sections;
|
|
44
|
+
}
|
|
45
|
+
export function buildHierarchicalDocsSidebarSections(docs, basePath = "/docs") {
|
|
46
|
+
return buildDocsTree(docs, basePath)
|
|
47
|
+
.map((section) => ({
|
|
48
|
+
key: section.key,
|
|
49
|
+
label: section.label,
|
|
50
|
+
items: flattenDocsTreeNodes(section.nodes)
|
|
51
|
+
}))
|
|
52
|
+
.filter((section) => section.items.length > 0);
|
|
53
|
+
}
|
|
54
|
+
export function buildDocsPrevNext(docs, currentSlugOrPath, options = {}) {
|
|
55
|
+
const current = fetchDocEntry(docs, currentSlugOrPath, options);
|
|
56
|
+
if (!current) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
const currentSlug = normalizeDocSlug(current.slug);
|
|
60
|
+
const currentIndex = docs.findIndex((doc) => normalizeDocSlug(doc.slug) === currentSlug);
|
|
61
|
+
if (currentIndex === -1) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
previousDoc: buildDocsPagerLink(docs[currentIndex - 1], options.basePath),
|
|
66
|
+
nextDoc: buildDocsPagerLink(docs[currentIndex + 1], options.basePath)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
23
69
|
export function withActiveDocsSidebar(sections, currentPath) {
|
|
24
70
|
if (!currentPath) {
|
|
25
71
|
return sections.map((section) => ({
|
|
@@ -48,3 +94,88 @@ export function withActiveDocsSidebar(sections, currentPath) {
|
|
|
48
94
|
})
|
|
49
95
|
}));
|
|
50
96
|
}
|
|
97
|
+
function buildDocHref(basePath, slug) {
|
|
98
|
+
const normalizedBasePath = normalizeRoutePath(basePath);
|
|
99
|
+
const normalizedSlug = normalizeDocSlug(slug);
|
|
100
|
+
if (!normalizedSlug) {
|
|
101
|
+
return normalizedBasePath;
|
|
102
|
+
}
|
|
103
|
+
if (normalizedBasePath === "/") {
|
|
104
|
+
return `/${normalizedSlug}`;
|
|
105
|
+
}
|
|
106
|
+
return `${normalizedBasePath}/${normalizedSlug}`;
|
|
107
|
+
}
|
|
108
|
+
function normalizeDocSlug(slug) {
|
|
109
|
+
return slug.trim().replace(/^\/+|\/+$/gu, "");
|
|
110
|
+
}
|
|
111
|
+
function normalizeDocLookup(value, basePath = "/docs") {
|
|
112
|
+
const normalizedValue = normalizeRoutePath(value).replace(/^\/+/u, "");
|
|
113
|
+
const normalizedBasePath = normalizeRoutePath(basePath).replace(/^\/+/u, "");
|
|
114
|
+
if (!normalizedBasePath) {
|
|
115
|
+
return normalizedValue;
|
|
116
|
+
}
|
|
117
|
+
if (normalizedValue === normalizedBasePath) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
if (normalizedValue.startsWith(`${normalizedBasePath}/`)) {
|
|
121
|
+
return normalizedValue.slice(normalizedBasePath.length + 1);
|
|
122
|
+
}
|
|
123
|
+
return normalizedValue;
|
|
124
|
+
}
|
|
125
|
+
function insertDocTreeNode(nodes, doc, basePath) {
|
|
126
|
+
const segments = normalizedDocSegments(doc.slug);
|
|
127
|
+
let cursor = nodes;
|
|
128
|
+
segments.forEach((segment, index) => {
|
|
129
|
+
const slug = segments.slice(0, index + 1).join("/");
|
|
130
|
+
let node = cursor.find((candidate) => candidate.slug === slug);
|
|
131
|
+
if (!node) {
|
|
132
|
+
node = {
|
|
133
|
+
key: slug || "index",
|
|
134
|
+
slug,
|
|
135
|
+
label: humanizeDocSegment(segment),
|
|
136
|
+
children: []
|
|
137
|
+
};
|
|
138
|
+
cursor.push(node);
|
|
139
|
+
}
|
|
140
|
+
if (index === segments.length - 1) {
|
|
141
|
+
node.entry = doc;
|
|
142
|
+
node.href = buildDocHref(basePath, doc.slug);
|
|
143
|
+
node.label = doc.title;
|
|
144
|
+
}
|
|
145
|
+
cursor = node.children;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function normalizedDocSegments(slug) {
|
|
149
|
+
const normalized = normalizeDocSlug(slug);
|
|
150
|
+
return normalized ? normalized.split("/").filter(Boolean) : ["index"];
|
|
151
|
+
}
|
|
152
|
+
function flattenDocsTreeNodes(nodes, depth = 0) {
|
|
153
|
+
return nodes.flatMap((node) => {
|
|
154
|
+
const items = [];
|
|
155
|
+
if (node.entry && node.href) {
|
|
156
|
+
items.push({
|
|
157
|
+
key: node.key,
|
|
158
|
+
label: node.label,
|
|
159
|
+
href: node.href,
|
|
160
|
+
depth: Math.min(depth, 2)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
items.push(...flattenDocsTreeNodes(node.children, depth + 1));
|
|
164
|
+
return items;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
function buildDocsPagerLink(doc, basePath = "/docs") {
|
|
168
|
+
if (!doc) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
title: doc.title,
|
|
173
|
+
href: buildDocHref(basePath, doc.slug),
|
|
174
|
+
entry: doc
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function humanizeDocSegment(segment) {
|
|
178
|
+
return segment
|
|
179
|
+
.replace(/[-_]+/gu, " ")
|
|
180
|
+
.replace(/\b\w/gu, (char) => char.toUpperCase());
|
|
181
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SiteNavigationItem } from "./SiteLayout.js";
|
|
2
|
+
export interface DocsTemplateShellOptions {
|
|
3
|
+
siteTitle?: string;
|
|
4
|
+
navigation?: SiteNavigationItem[];
|
|
5
|
+
headless?: boolean;
|
|
6
|
+
renderLayout?: (content: React.JSX.Element) => React.JSX.Element;
|
|
7
|
+
}
|
|
8
|
+
export declare function renderDocsTemplateShell(content: React.JSX.Element, options: DocsTemplateShellOptions): React.JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { SiteLayout } from "./SiteLayout.js";
|
|
3
|
+
export function renderDocsTemplateShell(content, options) {
|
|
4
|
+
if (options.renderLayout) {
|
|
5
|
+
return options.renderLayout(content);
|
|
6
|
+
}
|
|
7
|
+
if (options.headless) {
|
|
8
|
+
return content;
|
|
9
|
+
}
|
|
10
|
+
if (options.siteTitle === undefined || options.navigation === undefined) {
|
|
11
|
+
throw new Error("Docs templates require `siteTitle` and `navigation` unless `headless` or `renderLayout` is provided.");
|
|
12
|
+
}
|
|
13
|
+
return (_jsx(SiteLayout, { siteTitle: options.siteTitle, navigation: options.navigation, children: content }));
|
|
14
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { SiteLayout, type SiteLayoutProps, type SiteNavigationItem } from "./SiteLayout.js";
|
|
2
|
-
export { TopNav, type TopNavProps } from "./TopNav.js";
|
|
3
|
-
export { SiteFooter, type SiteFooterProps, type SiteFooterLink } from "./SiteFooter.js";
|
|
1
|
+
export { SiteLayout, type SiteLayoutProps, type SiteLayoutOptions, type SiteNavigationItem } from "./SiteLayout.js";
|
|
2
|
+
export { TopNav, type TopNavProps, type NavLayoutOptions } from "./TopNav.js";
|
|
3
|
+
export { SiteFooter, type SiteFooterProps, type SiteFooterLink, type FooterLayoutOptions } from "./SiteFooter.js";
|
|
4
4
|
export { DashboardShell, type DashboardShellProps, type DashboardNavItem } from "./DashboardShell.js";
|
|
5
5
|
export { DashboardOverviewTemplate, type DashboardOverviewTemplateProps, type DashboardPanel, type DashboardStat } from "./DashboardOverviewTemplate.js";
|
|
6
6
|
export { isRouteActive, normalizeRoutePath, withActiveRouteState, type RouteAwareItem, type RouteMatcherOptions } from "./routing.js";
|
|
7
7
|
export { buildContentListHref, collectFilterValues, collectDimensionValues, matchesFilterDimensions, prepareBlogIndex, prepareDocsIndex, type BlogIndexEntry, type BlogIndexQuery, type DocsIndexEntry, type DocsIndexQuery, type FilterDimension, type FilterDimensionValues, type PaginationResult } from "./contentIndex.js";
|
|
8
|
-
export { buildDocsSidebarSections, withActiveDocsSidebar, type DocsSidebarItem, type DocsSidebarSection } from "./docsNavigation.js";
|
|
8
|
+
export { buildDocsSidebarSections, buildDocsTree, buildHierarchicalDocsSidebarSections, buildDocsPrevNext, fetchDocEntry, withActiveDocsSidebar, type DocsTreeNode, type DocsTreeSection, type DocsPagerLink, type DocsPrevNext, type FetchDocEntryOptions, type DocsSidebarItem, type DocsSidebarSection } from "./docsNavigation.js";
|
|
9
9
|
export { MarkdownContent, type MarkdownContentProps } from "./MarkdownContent.js";
|
|
10
10
|
export { extractTocFromMarkdown, type TocEntry } from "./tocExtractor.js";
|
|
11
11
|
export { DocsSidebar, type DocsSidebarProps } from "./DocsSidebar.js";
|
|
@@ -5,7 +5,7 @@ export { DashboardShell } from "./DashboardShell.js";
|
|
|
5
5
|
export { DashboardOverviewTemplate } from "./DashboardOverviewTemplate.js";
|
|
6
6
|
export { isRouteActive, normalizeRoutePath, withActiveRouteState } from "./routing.js";
|
|
7
7
|
export { buildContentListHref, collectFilterValues, collectDimensionValues, matchesFilterDimensions, prepareBlogIndex, prepareDocsIndex } from "./contentIndex.js";
|
|
8
|
-
export { buildDocsSidebarSections, withActiveDocsSidebar } from "./docsNavigation.js";
|
|
8
|
+
export { buildDocsSidebarSections, buildDocsTree, buildHierarchicalDocsSidebarSections, buildDocsPrevNext, fetchDocEntry, withActiveDocsSidebar } from "./docsNavigation.js";
|
|
9
9
|
export { MarkdownContent } from "./MarkdownContent.js";
|
|
10
10
|
export { extractTocFromMarkdown } from "./tocExtractor.js";
|
|
11
11
|
export { DocsSidebar } from "./DocsSidebar.js";
|