@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/README.md
CHANGED
|
@@ -31,9 +31,14 @@ Packaging smoke:
|
|
|
31
31
|
## Package exports
|
|
32
32
|
|
|
33
33
|
- `@cedros/data-react`
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
-
|
|
34
|
+
- `CedrosDataProvider`, `useCedrosDataTheme`, `useCedrosDataThemeOptional`
|
|
35
|
+
- `CmsContent`, `getOrCreateVisitorId`, `sanitizeCmsHtml`, `renderCmsMarkdown`
|
|
36
|
+
- theme/page/content types
|
|
37
|
+
- `@cedros/data-react/server`
|
|
38
|
+
- `generatePageMetadata`, `buildPageMetadata`
|
|
39
|
+
- `fetchBlogPost`, `listContentSlugs`, `listBlogSlugs`, `listLearnPathIds`
|
|
40
|
+
- `loadSitemapEntries`
|
|
41
|
+
- all accept `ServerFetchOptions` with optional `apiKey` (falls back to `CEDROS_X_API_KEY` env)
|
|
37
42
|
- `@cedros/data-react/admin`
|
|
38
43
|
- `cedrosDataPlugin`
|
|
39
44
|
- section IDs/groups/types
|
|
@@ -91,6 +96,9 @@ Content:
|
|
|
91
96
|
- `pages`
|
|
92
97
|
- `navigation`
|
|
93
98
|
- `site-settings`
|
|
99
|
+
- `layout` — nav width, link position/style, footer width
|
|
100
|
+
- `tipping`
|
|
101
|
+
- `monetization`
|
|
94
102
|
|
|
95
103
|
Data model:
|
|
96
104
|
- `collections`
|
|
@@ -102,8 +110,44 @@ Operations:
|
|
|
102
110
|
- `data-ops`
|
|
103
111
|
- `history`
|
|
104
112
|
|
|
113
|
+
## Theme system (CedrosDataProvider)
|
|
114
|
+
|
|
115
|
+
`CedrosDataProvider` matches the theme API used by cedros-login and cedros-pay:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { CedrosDataProvider } from "@cedros/data-react";
|
|
119
|
+
|
|
120
|
+
<CedrosDataProvider
|
|
121
|
+
theme="dark"
|
|
122
|
+
themeOverrides={{
|
|
123
|
+
"--cedros-background": "#292524",
|
|
124
|
+
"--cedros-foreground": "#f5f5f4",
|
|
125
|
+
"--cedros-border": "#44403c",
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<SiteLayout ... />
|
|
129
|
+
</CedrosDataProvider>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Props:
|
|
133
|
+
- `theme` — `'light'` | `'dark'` (default: `'light'`)
|
|
134
|
+
- `themeOverrides` — typed `CedrosDataThemeOverrides` with 13 tokens + open-ended `[key: string]`
|
|
135
|
+
- `unstyled` — disables all default className/style (for custom design systems)
|
|
136
|
+
|
|
137
|
+
Hook:
|
|
138
|
+
```tsx
|
|
139
|
+
import { useCedrosDataTheme } from "@cedros/data-react";
|
|
140
|
+
|
|
141
|
+
const { mode, isDark, className, style, unstyled } = useCedrosDataTheme();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Also exports `useCedrosDataThemeOptional()` which returns `null` outside a provider.
|
|
145
|
+
|
|
146
|
+
The provider applies className (`.cedros-dark`) and inline style overrides on the **same element** so overrides always win via CSS specificity — matching cedros-pay/login behavior.
|
|
147
|
+
|
|
105
148
|
## Styling and dark mode
|
|
106
149
|
|
|
150
|
+
- All component styles use `var(--cedros-*, fallback)` — no hardcoded colors, even in dark mode.
|
|
107
151
|
- Admin UIs use tokenized variables aligned with cedros-login/cedros-pay conventions.
|
|
108
152
|
- Dark mode responds to both:
|
|
109
153
|
- `.cedros-dark`
|
|
@@ -117,11 +161,38 @@ import "@cedros/data-react/admin/styles.css";
|
|
|
117
161
|
## Site templates and components
|
|
118
162
|
|
|
119
163
|
Layouts/navigation:
|
|
120
|
-
- `SiteLayout`
|
|
121
|
-
- `TopNav`
|
|
122
|
-
- `SiteFooter`
|
|
164
|
+
- `SiteLayout` — accepts `layout?: SiteLayoutOptions` for nav/footer configuration
|
|
165
|
+
- `TopNav` — accepts `layout?: NavLayoutOptions`
|
|
166
|
+
- `SiteFooter` — accepts `layout?: FooterLayoutOptions`
|
|
123
167
|
- `DashboardShell`
|
|
124
168
|
|
|
169
|
+
### Layout options
|
|
170
|
+
|
|
171
|
+
Layout preferences are managed via the admin "Layout" section (stored as `site_settings/layout`). Pass them to `SiteLayout`:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
<SiteLayout
|
|
175
|
+
siteTitle="My Site"
|
|
176
|
+
navigation={navItems}
|
|
177
|
+
layout={{
|
|
178
|
+
nav: {
|
|
179
|
+
width: "full", // "contained" (default) | "full"
|
|
180
|
+
linkPosition: "center", // "right" (default) | "center"
|
|
181
|
+
linkStyle: "text", // "pill" (default) | "text"
|
|
182
|
+
},
|
|
183
|
+
footer: {
|
|
184
|
+
width: "full", // "contained" (default) | "full"
|
|
185
|
+
},
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
{children}
|
|
189
|
+
</SiteLayout>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **width: "full"** — logo and actions stretch to viewport edges
|
|
193
|
+
- **linkPosition: "center"** — nav links centered between brand and right slot
|
|
194
|
+
- **linkStyle: "text"** — plain text links instead of pill buttons
|
|
195
|
+
|
|
125
196
|
Core page templates:
|
|
126
197
|
- `HomePageTemplate`
|
|
127
198
|
- `ContactPageTemplate`
|
|
@@ -146,13 +217,18 @@ Tipping:
|
|
|
146
217
|
Docs templates:
|
|
147
218
|
- `DocsIndexTemplate`
|
|
148
219
|
- `DocArticleTemplate` (GitBook-style left nav + right TOC)
|
|
220
|
+
- both accept `headless` to skip the built-in `SiteLayout`, or `renderLayout={(content) => ...}` to wrap the docs UI in an existing app shell
|
|
149
221
|
|
|
150
222
|
Content rendering and helpers:
|
|
151
223
|
- `MarkdownContent`
|
|
152
224
|
- `Breadcrumbs`
|
|
153
225
|
- `ContentPagination`
|
|
154
226
|
- `withActiveRouteState`
|
|
227
|
+
- `fetchDocEntry`
|
|
155
228
|
- `buildDocsSidebarSections`
|
|
229
|
+
- `buildDocsTree`
|
|
230
|
+
- `buildHierarchicalDocsSidebarSections`
|
|
231
|
+
- `buildDocsPrevNext`
|
|
156
232
|
- `withActiveDocsSidebar`
|
|
157
233
|
- `prepareBlogIndex`
|
|
158
234
|
- `prepareDocsIndex`
|
|
@@ -248,6 +324,32 @@ Docs/blog templates default to `bodyMarkdown`.
|
|
|
248
324
|
|
|
249
325
|
This keeps markdown as the safe default and avoids unsafe HTML rendering by default.
|
|
250
326
|
|
|
327
|
+
Docs search helpers also inspect optional `bodyMarkdown`, `bodyText`, `bodyHtml`, and `searchText` fields on docs entries, so callers can opt into body/full-text matching without changing the query API.
|
|
328
|
+
|
|
329
|
+
## Server helpers and API key
|
|
330
|
+
|
|
331
|
+
`@cedros/data-react/server` provides Next.js data-fetching helpers that call cedros-data directly:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
import { generatePageMetadata, loadSitemapEntries, fetchBlogPost } from "@cedros/data-react/server";
|
|
335
|
+
|
|
336
|
+
// All helpers accept ServerFetchOptions:
|
|
337
|
+
const meta = await generatePageMetadata("about", {
|
|
338
|
+
serverUrl: process.env.CEDROS_DATA_URL,
|
|
339
|
+
apiKey: process.env.CEDROS_X_API_KEY, // optional, sent as x-api-key header
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The API key resolves from (in order):
|
|
344
|
+
1. `options.apiKey` if explicitly passed
|
|
345
|
+
2. `process.env.CEDROS_X_API_KEY` environment variable
|
|
346
|
+
3. No header sent (unauthenticated — works with direct cedros-data access)
|
|
347
|
+
|
|
348
|
+
Environment variables for server URL resolution:
|
|
349
|
+
1. `options.serverUrl` if explicitly passed
|
|
350
|
+
2. `CEDROS_DATA_URL`
|
|
351
|
+
3. `NEXT_PUBLIC_BACKEND_API_URL`
|
|
352
|
+
|
|
251
353
|
## Default page contract
|
|
252
354
|
|
|
253
355
|
The package exports the default page template contract used by server bootstrap:
|
package/dist/admin/icons.d.ts
CHANGED
package/dist/admin/icons.js
CHANGED
|
@@ -14,5 +14,6 @@ export const Icons = {
|
|
|
14
14
|
dataOps: icon("M4 4h16v16H4z M8 8h8 M8 12h8 M8 16h4"),
|
|
15
15
|
history: icon("M12 8v5l3 2M20 12a8 8 0 11-2.34-5.66"),
|
|
16
16
|
tipping: icon("M12 2a7 7 0 00-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 00-7-7z"),
|
|
17
|
-
monetization: icon("M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6")
|
|
17
|
+
monetization: icon("M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"),
|
|
18
|
+
layout: icon("M4 3h16a1 1 0 011 1v16a1 1 0 01-1 1H4a1 1 0 01-1-1V4a1 1 0 011-1zM3 9h18M9 21V9")
|
|
18
19
|
};
|
package/dist/admin/plugin.js
CHANGED
|
@@ -14,6 +14,7 @@ const MediaSection = lazy(() => import("./sections/media/MediaSection.js"));
|
|
|
14
14
|
const HistorySection = lazy(() => import("./sections/HistorySection.js"));
|
|
15
15
|
const TippingSection = lazy(() => import("./sections/TippingSection.js"));
|
|
16
16
|
const MonetizationSection = lazy(() => import("./sections/MonetizationSection.js"));
|
|
17
|
+
const LayoutSection = lazy(() => import("./sections/LayoutSection.js"));
|
|
17
18
|
export const cedrosDataPlugin = {
|
|
18
19
|
id: "cedros-data",
|
|
19
20
|
name: "Cedros Data",
|
|
@@ -114,6 +115,14 @@ export const cedrosDataPlugin = {
|
|
|
114
115
|
group: CEDROS_DATA_GROUPS.content,
|
|
115
116
|
order: 5,
|
|
116
117
|
requiredPermission: "data:settings:read"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: CEDROS_DATA_SECTIONS.layout,
|
|
121
|
+
label: "Layout",
|
|
122
|
+
icon: Icons.layout,
|
|
123
|
+
group: CEDROS_DATA_GROUPS.content,
|
|
124
|
+
order: 6,
|
|
125
|
+
requiredPermission: "data:settings:read"
|
|
117
126
|
}
|
|
118
127
|
],
|
|
119
128
|
groups: [
|
|
@@ -133,7 +142,8 @@ export const cedrosDataPlugin = {
|
|
|
133
142
|
[CEDROS_DATA_SECTIONS.dataOps]: DataOpsSection,
|
|
134
143
|
[CEDROS_DATA_SECTIONS.history]: HistorySection,
|
|
135
144
|
[CEDROS_DATA_SECTIONS.tipping]: TippingSection,
|
|
136
|
-
[CEDROS_DATA_SECTIONS.monetization]: MonetizationSection
|
|
145
|
+
[CEDROS_DATA_SECTIONS.monetization]: MonetizationSection,
|
|
146
|
+
[CEDROS_DATA_SECTIONS.layout]: LayoutSection
|
|
137
147
|
},
|
|
138
148
|
createPluginContext(hostContext) {
|
|
139
149
|
const custom = readCedrosDataCustomContext(hostContext);
|
|
@@ -11,8 +11,9 @@ export declare const CEDROS_DATA_SECTIONS: {
|
|
|
11
11
|
readonly history: "history";
|
|
12
12
|
readonly tipping: "tipping";
|
|
13
13
|
readonly monetization: "monetization";
|
|
14
|
+
readonly layout: "layout";
|
|
14
15
|
};
|
|
15
|
-
export declare const CEDROS_DATA_SECTION_IDS: readonly ["cedros-data:pages", "cedros-data:navigation", "cedros-data:site-settings", "cedros-data:media", "cedros-data:collections", "cedros-data:schema-designer", "cedros-data:contract-verify", "cedros-data:custom-data", "cedros-data:data-ops", "cedros-data:history", "cedros-data:tipping", "cedros-data:monetization"];
|
|
16
|
+
export declare const CEDROS_DATA_SECTION_IDS: readonly ["cedros-data:pages", "cedros-data:navigation", "cedros-data:site-settings", "cedros-data:media", "cedros-data:collections", "cedros-data:schema-designer", "cedros-data:contract-verify", "cedros-data:custom-data", "cedros-data:data-ops", "cedros-data:history", "cedros-data:tipping", "cedros-data:monetization", "cedros-data:layout"];
|
|
16
17
|
export declare const CEDROS_DATA_GROUPS: {
|
|
17
18
|
readonly content: "Content";
|
|
18
19
|
readonly dataModel: "Data Model";
|
package/dist/admin/sectionIds.js
CHANGED
|
@@ -10,7 +10,8 @@ export const CEDROS_DATA_SECTIONS = {
|
|
|
10
10
|
dataOps: "data-ops",
|
|
11
11
|
history: "history",
|
|
12
12
|
tipping: "tipping",
|
|
13
|
-
monetization: "monetization"
|
|
13
|
+
monetization: "monetization",
|
|
14
|
+
layout: "layout"
|
|
14
15
|
};
|
|
15
16
|
export const CEDROS_DATA_SECTION_IDS = [
|
|
16
17
|
"cedros-data:pages",
|
|
@@ -24,7 +25,8 @@ export const CEDROS_DATA_SECTION_IDS = [
|
|
|
24
25
|
"cedros-data:data-ops",
|
|
25
26
|
"cedros-data:history",
|
|
26
27
|
"cedros-data:tipping",
|
|
27
|
-
"cedros-data:monetization"
|
|
28
|
+
"cedros-data:monetization",
|
|
29
|
+
"cedros-data:layout"
|
|
28
30
|
];
|
|
29
31
|
export const CEDROS_DATA_GROUPS = {
|
|
30
32
|
content: "Content",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { requestJson } from "../api.js";
|
|
4
|
+
import { AdminButton, Card, SelectInput, StatusNotice } from "../components.js";
|
|
5
|
+
const DEFAULT_LAYOUT = {
|
|
6
|
+
navWidth: "contained",
|
|
7
|
+
navLinkPosition: "right",
|
|
8
|
+
navLinkStyle: "pill",
|
|
9
|
+
footerWidth: "contained"
|
|
10
|
+
};
|
|
11
|
+
export default function LayoutSection({ pluginContext }) {
|
|
12
|
+
const canWrite = pluginContext.hasPermission("data:settings:write");
|
|
13
|
+
const [settings, setSettings] = useState(DEFAULT_LAYOUT);
|
|
14
|
+
const [status, setStatus] = useState("");
|
|
15
|
+
const [tone, setTone] = useState("neutral");
|
|
16
|
+
const [loading, setLoading] = useState(false);
|
|
17
|
+
const load = useCallback(async () => {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
setStatus("");
|
|
20
|
+
try {
|
|
21
|
+
const entries = await requestJson(pluginContext, "/entries/query", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: {
|
|
24
|
+
collection_name: "site_settings",
|
|
25
|
+
entry_keys: ["layout"],
|
|
26
|
+
contains: null,
|
|
27
|
+
limit: 1,
|
|
28
|
+
offset: 0
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (entries.length > 0) {
|
|
32
|
+
setSettings(parseLayoutSettings(entries[0].payload));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
setStatus(error.message);
|
|
37
|
+
setTone("error");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
}, [pluginContext]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
void load();
|
|
45
|
+
}, [load]);
|
|
46
|
+
const save = useCallback(async () => {
|
|
47
|
+
if (!canWrite) {
|
|
48
|
+
setStatus("Missing data:settings:write permission.");
|
|
49
|
+
setTone("error");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setLoading(true);
|
|
53
|
+
setStatus("");
|
|
54
|
+
try {
|
|
55
|
+
await requestJson(pluginContext, "/entries/upsert", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: {
|
|
58
|
+
collection_name: "site_settings",
|
|
59
|
+
entry_key: "layout",
|
|
60
|
+
payload: settings
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
setStatus("Layout settings saved.");
|
|
64
|
+
setTone("success");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
setStatus(error.message);
|
|
68
|
+
setTone("error");
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}
|
|
73
|
+
}, [canWrite, pluginContext, settings]);
|
|
74
|
+
const update = useCallback((key, value) => {
|
|
75
|
+
setSettings((prev) => ({ ...prev, [key]: value }));
|
|
76
|
+
}, []);
|
|
77
|
+
return (_jsxs("div", { className: "cedros-data", children: [_jsxs("header", { className: "cedros-data__header", children: [_jsxs("div", { children: [_jsx("h2", { className: "cedros-data__title", children: "Layout" }), _jsx("p", { className: "cedros-data__subtitle", children: "Configure the site navigation bar and footer appearance." }), !canWrite && (_jsx("p", { className: "cedros-data__subtitle", children: "Read-only. Missing data:settings:write permission." }))] }), _jsxs("div", { className: "cedros-data-actions", children: [_jsx(AdminButton, { variant: "secondary", onClick: () => void load(), disabled: loading, children: "Refresh" }), _jsx(AdminButton, { variant: "primary", onClick: () => void save(), disabled: loading || !canWrite, children: "Save" })] })] }), _jsxs("div", { className: "cedros-data-grid cedros-data-grid--two", children: [_jsxs(Card, { title: "Navigation Bar", subtitle: "Controls the top nav header layout.", children: [_jsxs(SelectInput, { label: "Width", value: settings.navWidth, onChange: (e) => update("navWidth", e.target.value), disabled: !canWrite, children: [_jsx("option", { value: "contained", children: "Contained (max-width)" }), _jsx("option", { value: "full", children: "Full width (edge to edge)" })] }), _jsxs(SelectInput, { label: "Link Position", value: settings.navLinkPosition, onChange: (e) => update("navLinkPosition", e.target.value), disabled: !canWrite, children: [_jsx("option", { value: "right", children: "Right (next to actions)" }), _jsx("option", { value: "center", children: "Center" })] }), _jsxs(SelectInput, { label: "Link Style", value: settings.navLinkStyle, onChange: (e) => update("navLinkStyle", e.target.value), disabled: !canWrite, children: [_jsx("option", { value: "pill", children: "Pill buttons" }), _jsx("option", { value: "text", children: "Plain text links" })] })] }), _jsx(Card, { title: "Footer", subtitle: "Controls the site footer layout.", children: _jsxs(SelectInput, { label: "Width", value: settings.footerWidth, onChange: (e) => update("footerWidth", e.target.value), disabled: !canWrite, children: [_jsx("option", { value: "contained", children: "Contained (max-width)" }), _jsx("option", { value: "full", children: "Full width (edge to edge)" })] }) })] }), status && _jsx(StatusNotice, { tone: tone, message: status })] }));
|
|
78
|
+
}
|
|
79
|
+
function parseLayoutSettings(payload) {
|
|
80
|
+
return {
|
|
81
|
+
navWidth: isNavWidth(payload.navWidth) ? payload.navWidth : DEFAULT_LAYOUT.navWidth,
|
|
82
|
+
navLinkPosition: isNavLinkPosition(payload.navLinkPosition)
|
|
83
|
+
? payload.navLinkPosition
|
|
84
|
+
: DEFAULT_LAYOUT.navLinkPosition,
|
|
85
|
+
navLinkStyle: isNavLinkStyle(payload.navLinkStyle)
|
|
86
|
+
? payload.navLinkStyle
|
|
87
|
+
: DEFAULT_LAYOUT.navLinkStyle,
|
|
88
|
+
footerWidth: isFooterWidth(payload.footerWidth)
|
|
89
|
+
? payload.footerWidth
|
|
90
|
+
: DEFAULT_LAYOUT.footerWidth
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function isNavWidth(v) {
|
|
94
|
+
return v === "contained" || v === "full";
|
|
95
|
+
}
|
|
96
|
+
function isNavLinkPosition(v) {
|
|
97
|
+
return v === "center" || v === "right";
|
|
98
|
+
}
|
|
99
|
+
function isNavLinkStyle(v) {
|
|
100
|
+
return v === "text" || v === "pill";
|
|
101
|
+
}
|
|
102
|
+
function isFooterWidth(v) {
|
|
103
|
+
return v === "contained" || v === "full";
|
|
104
|
+
}
|
package/dist/admin/styles.css
CHANGED
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
|
|
23
23
|
.cedros-dark .cedros-data,
|
|
24
24
|
.cedros-admin.cedros-admin--dark .cedros-data {
|
|
25
|
-
--cd-bg: hsl(222.2, 84%, 4.9%);
|
|
26
|
-
--cd-fg: hsl(210, 40%, 98%);
|
|
27
|
-
--cd-muted: hsl(215, 20.2%, 65.1%);
|
|
28
|
-
--cd-muted-bg: hsl(217.2, 32.6%, 17.5%);
|
|
29
|
-
--cd-border: hsl(217.2, 32.6%, 17.5%);
|
|
30
|
-
--cd-accent: hsl(217.2, 32.6%, 17.5%);
|
|
31
|
-
--cd-ring: hsl(212.7, 26.8%, 83.9%);
|
|
32
|
-
--cd-primary: hsl(210, 40%, 98%);
|
|
33
|
-
--cd-primary-fg: hsl(222.2, 47.4%, 11.2%);
|
|
34
|
-
--cd-success: hsl(142, 71%, 45%);
|
|
35
|
-
--cd-error: hsl(0, 84.2%, 60.2%);
|
|
25
|
+
--cd-bg: var(--cedros-background, hsl(222.2, 84%, 4.9%));
|
|
26
|
+
--cd-fg: var(--cedros-foreground, hsl(210, 40%, 98%));
|
|
27
|
+
--cd-muted: var(--cedros-muted-foreground, hsl(215, 20.2%, 65.1%));
|
|
28
|
+
--cd-muted-bg: var(--cedros-muted, hsl(217.2, 32.6%, 17.5%));
|
|
29
|
+
--cd-border: var(--cedros-border, hsl(217.2, 32.6%, 17.5%));
|
|
30
|
+
--cd-accent: var(--cedros-accent, hsl(217.2, 32.6%, 17.5%));
|
|
31
|
+
--cd-ring: var(--cedros-ring, hsl(212.7, 26.8%, 83.9%));
|
|
32
|
+
--cd-primary: var(--cedros-primary, hsl(210, 40%, 98%));
|
|
33
|
+
--cd-primary-fg: var(--cedros-primary-foreground, hsl(222.2, 47.4%, 11.2%));
|
|
34
|
+
--cd-success: var(--cedros-success, hsl(142, 71%, 45%));
|
|
35
|
+
--cd-error: var(--cedros-error, hsl(0, 84.2%, 60.2%));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
.cedros-data__header {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { CedrosDataThemeOverrides, CedrosDataThemeValue, ThemeMode } from "./theme.js";
|
|
3
|
+
export interface CedrosDataProviderProps {
|
|
4
|
+
/** Light or dark mode. */
|
|
5
|
+
theme?: ThemeMode;
|
|
6
|
+
/** CSS variable overrides applied as inline styles. */
|
|
7
|
+
themeOverrides?: CedrosDataThemeOverrides;
|
|
8
|
+
/** When true, no default className or style is applied. */
|
|
9
|
+
unstyled?: boolean;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Theme provider for cedros-data components.
|
|
14
|
+
*
|
|
15
|
+
* Applies `className` and inline `style` on the **same element** so that
|
|
16
|
+
* overrides always win over class-selector-based CSS variable declarations.
|
|
17
|
+
*/
|
|
18
|
+
export declare function CedrosDataProvider({ theme, themeOverrides, unstyled, children, }: CedrosDataProviderProps): React.JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the current theme context.
|
|
21
|
+
*
|
|
22
|
+
* Must be called inside a `<CedrosDataProvider>`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function useCedrosDataTheme(): CedrosDataThemeValue;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the current theme context, or `null` when outside a provider.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useCedrosDataThemeOptional(): CedrosDataThemeValue | null;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
const CedrosDataThemeContext = createContext(null);
|
|
5
|
+
/**
|
|
6
|
+
* Theme provider for cedros-data components.
|
|
7
|
+
*
|
|
8
|
+
* Applies `className` and inline `style` on the **same element** so that
|
|
9
|
+
* overrides always win over class-selector-based CSS variable declarations.
|
|
10
|
+
*/
|
|
11
|
+
export function CedrosDataProvider({ theme = "light", themeOverrides, unstyled = false, children, }) {
|
|
12
|
+
const stableOverrides = useStableOverrides(themeOverrides);
|
|
13
|
+
const value = useMemo(() => {
|
|
14
|
+
const isDark = theme === "dark";
|
|
15
|
+
if (unstyled) {
|
|
16
|
+
return { mode: theme, isDark, className: "", style: {}, unstyled: true, overrides: stableOverrides };
|
|
17
|
+
}
|
|
18
|
+
const className = isDark ? "cedros-dark" : "";
|
|
19
|
+
const style = overridesToStyle(stableOverrides);
|
|
20
|
+
return { mode: theme, isDark, className, style, unstyled: false, overrides: stableOverrides };
|
|
21
|
+
}, [theme, unstyled, stableOverrides]);
|
|
22
|
+
return (_jsx(CedrosDataThemeContext.Provider, { value: value, children: _jsx("div", { className: value.className || undefined, style: value.style, children: children }) }));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the current theme context.
|
|
26
|
+
*
|
|
27
|
+
* Must be called inside a `<CedrosDataProvider>`.
|
|
28
|
+
*/
|
|
29
|
+
export function useCedrosDataTheme() {
|
|
30
|
+
const ctx = useContext(CedrosDataThemeContext);
|
|
31
|
+
if (!ctx) {
|
|
32
|
+
throw new Error("useCedrosDataTheme must be used within CedrosDataProvider");
|
|
33
|
+
}
|
|
34
|
+
return ctx;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the current theme context, or `null` when outside a provider.
|
|
38
|
+
*/
|
|
39
|
+
export function useCedrosDataThemeOptional() {
|
|
40
|
+
return useContext(CedrosDataThemeContext);
|
|
41
|
+
}
|
|
42
|
+
// -- internals ----------------------------------------------------------------
|
|
43
|
+
/** Deep-compare overrides so parent inline objects don't cause re-renders. */
|
|
44
|
+
function useStableOverrides(overrides) {
|
|
45
|
+
const ref = useRef(overrides);
|
|
46
|
+
const [stable, setStable] = useState(overrides);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (shallowEqual(ref.current, overrides))
|
|
49
|
+
return;
|
|
50
|
+
ref.current = overrides;
|
|
51
|
+
setStable(overrides);
|
|
52
|
+
}, [overrides]);
|
|
53
|
+
return stable;
|
|
54
|
+
}
|
|
55
|
+
function shallowEqual(a, b) {
|
|
56
|
+
if (a === b)
|
|
57
|
+
return true;
|
|
58
|
+
if (!a || !b)
|
|
59
|
+
return false;
|
|
60
|
+
const keysA = Object.keys(a);
|
|
61
|
+
const keysB = Object.keys(b);
|
|
62
|
+
if (keysA.length !== keysB.length)
|
|
63
|
+
return false;
|
|
64
|
+
return keysA.every((key) => a[key] === b[key]);
|
|
65
|
+
}
|
|
66
|
+
function overridesToStyle(overrides) {
|
|
67
|
+
if (!overrides)
|
|
68
|
+
return {};
|
|
69
|
+
const style = {};
|
|
70
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
71
|
+
if (value !== undefined) {
|
|
72
|
+
style[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return style;
|
|
76
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ContentType } from "./types.js";
|
|
2
|
+
export declare function collectionNameForContentType(contentType: ContentType): string;
|
|
3
|
+
export declare function collectionNamesForContentType(contentType: ContentType): string[];
|
|
4
|
+
export declare function queryEntriesByContentType<T>(serverUrl: string, contentType: ContentType, buildBody: (collectionName: string) => Record<string, unknown>, apiKey?: string): Promise<T>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { fetchJson } from "./fetch.js";
|
|
2
|
+
const CANONICAL_CONTENT_TYPE_COLLECTIONS = {
|
|
3
|
+
page: "pages",
|
|
4
|
+
blog: "blog",
|
|
5
|
+
docs: "docs",
|
|
6
|
+
learn: "learn",
|
|
7
|
+
project: "projects",
|
|
8
|
+
airdrop: "airdrops",
|
|
9
|
+
};
|
|
10
|
+
const CONTENT_TYPE_COLLECTION_ALIASES = {
|
|
11
|
+
blog: ["blogs"],
|
|
12
|
+
learn: ["courses"],
|
|
13
|
+
};
|
|
14
|
+
export function collectionNameForContentType(contentType) {
|
|
15
|
+
return CANONICAL_CONTENT_TYPE_COLLECTIONS[contentType];
|
|
16
|
+
}
|
|
17
|
+
export function collectionNamesForContentType(contentType) {
|
|
18
|
+
const canonicalName = collectionNameForContentType(contentType);
|
|
19
|
+
const aliases = CONTENT_TYPE_COLLECTION_ALIASES[contentType] ?? [];
|
|
20
|
+
return [canonicalName, ...aliases.filter((alias) => alias !== canonicalName)];
|
|
21
|
+
}
|
|
22
|
+
export async function queryEntriesByContentType(serverUrl, contentType, buildBody, apiKey) {
|
|
23
|
+
return queryEntriesByCollectionNames(serverUrl, collectionNamesForContentType(contentType), buildBody, apiKey);
|
|
24
|
+
}
|
|
25
|
+
async function queryEntriesByCollectionNames(serverUrl, collectionNames, buildBody, apiKey) {
|
|
26
|
+
let lastCollectionNotFoundError;
|
|
27
|
+
const lastCollectionName = collectionNames[collectionNames.length - 1];
|
|
28
|
+
for (const collectionName of collectionNames) {
|
|
29
|
+
try {
|
|
30
|
+
return await fetchJson(serverUrl, "/entries/query", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: buildBody(collectionName),
|
|
33
|
+
apiKey,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!isCollectionNotFoundError(error) || collectionName === lastCollectionName) {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
lastCollectionNotFoundError = error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw (lastCollectionNotFoundError ??
|
|
44
|
+
new Error("@cedros/data-react: no collection names were configured"));
|
|
45
|
+
}
|
|
46
|
+
function isCollectionNotFoundError(error) {
|
|
47
|
+
return error instanceof Error && /collection not found/i.test(error.message);
|
|
48
|
+
}
|
package/dist/react/entries.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveApiKey, resolveServerUrl } from "./fetch.js";
|
|
2
|
+
import { queryEntriesByContentType } from "./contentCollections.js";
|
|
2
3
|
/**
|
|
3
4
|
* Fetches a single blog post by slug, optionally passing visitor_id for metered reads.
|
|
4
5
|
*
|
|
@@ -10,7 +11,6 @@ export async function fetchBlogPost(slug, options) {
|
|
|
10
11
|
const serverUrl = resolveServerUrl(options);
|
|
11
12
|
const apiKey = resolveApiKey(options);
|
|
12
13
|
const body = {
|
|
13
|
-
collection_name: "blog",
|
|
14
14
|
entry_keys: [slug],
|
|
15
15
|
limit: 1,
|
|
16
16
|
offset: 0,
|
|
@@ -18,10 +18,9 @@ export async function fetchBlogPost(slug, options) {
|
|
|
18
18
|
if (options?.visitorId) {
|
|
19
19
|
body.visitor_id = options.visitorId;
|
|
20
20
|
}
|
|
21
|
-
const entries = await
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
21
|
+
const entries = await queryEntriesByContentType(serverUrl, "blog", (collectionName) => ({
|
|
22
|
+
...body,
|
|
23
|
+
collection_name: collectionName,
|
|
24
|
+
}), apiKey);
|
|
26
25
|
return entries.length > 0 ? entries[0] : null;
|
|
27
26
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* These exports are safe to use in React Client Components and
|
|
5
5
|
* do not import any server-only modules.
|
|
6
6
|
*/
|
|
7
|
+
export { CedrosDataProvider, useCedrosDataTheme, useCedrosDataThemeOptional, type CedrosDataProviderProps, } from "./CedrosDataProvider.js";
|
|
8
|
+
export type { ThemeMode, CedrosDataThemeOverrides, CedrosDataThemeValue, } from "./theme.js";
|
|
7
9
|
export { CmsContent, type CmsContentProps } from "./CmsContent.js";
|
|
8
10
|
export { getOrCreateVisitorId } from "./visitor.js";
|
|
9
11
|
export { sanitizeCmsHtml, renderCmsMarkdown } from "./sanitize.js";
|
package/dist/react/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* These exports are safe to use in React Client Components and
|
|
5
5
|
* do not import any server-only modules.
|
|
6
6
|
*/
|
|
7
|
+
export { CedrosDataProvider, useCedrosDataTheme, useCedrosDataThemeOptional, } from "./CedrosDataProvider.js";
|
|
7
8
|
export { CmsContent } from "./CmsContent.js";
|
|
8
9
|
export { getOrCreateVisitorId } from "./visitor.js";
|
|
9
10
|
export { sanitizeCmsHtml, renderCmsMarkdown } from "./sanitize.js";
|
package/dist/react/sitemap.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
|
/** Default change frequency and priority for each content type. */
|
|
12
4
|
const CONTENT_TYPE_DEFAULTS = {
|
|
13
5
|
page: { changeFrequency: "monthly", priority: 0.8 },
|
|
@@ -67,17 +59,12 @@ export async function loadSitemapEntries(options) {
|
|
|
67
59
|
return entries;
|
|
68
60
|
}
|
|
69
61
|
async function fetchCollectionSlugs(serverUrl, contentType, apiKey) {
|
|
70
|
-
const collectionName = CONTENT_TYPE_COLLECTIONS[contentType];
|
|
71
62
|
const defaults = CONTENT_TYPE_DEFAULTS[contentType];
|
|
72
|
-
const records = await
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
offset: 0,
|
|
78
|
-
},
|
|
79
|
-
apiKey,
|
|
80
|
-
});
|
|
63
|
+
const records = await queryEntriesByContentType(serverUrl, contentType, (collectionName) => ({
|
|
64
|
+
collection_name: collectionName,
|
|
65
|
+
limit: 1000,
|
|
66
|
+
offset: 0,
|
|
67
|
+
}), apiKey);
|
|
81
68
|
return records.map((record) => {
|
|
82
69
|
const slug = record.payload.slug ??
|
|
83
70
|
record.payload.route?.replace(/^\//, "") ??
|