@odla-ai/chapter 0.40.1 → 0.40.3
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 +9 -4
- package/dist/{chunk-4K2Q5JBC.js → chunk-O22RRPBT.js} +232 -84
- package/dist/chunk-O22RRPBT.js.map +1 -0
- package/dist/index.cjs +24 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +24 -9
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +6 -2
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/worker/index.cjs +73 -19
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +73 -19
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4K2Q5JBC.js.map +0 -1
package/README.md
CHANGED
|
@@ -546,9 +546,11 @@ builderLibrary: {
|
|
|
546
546
|
|
|
547
547
|
Every resource has exactly one scope: `{ kind: "chapter", chapterId }`,
|
|
548
548
|
`{ kind: "built_not_found" }`, or `{ kind: "global" }`. There are no groups or
|
|
549
|
-
audience arrays. A Chapter can author only its own scope.
|
|
550
|
-
|
|
551
|
-
|
|
549
|
+
audience arrays. A Chapter can author only its own scope. Built Not Found can
|
|
550
|
+
author material for itself, every Chapter, or exactly one Chapter from its
|
|
551
|
+
configured source list. A Chapter's admins and agents see its own published
|
|
552
|
+
material plus global material, never a sibling Chapter or Built Not Found-only
|
|
553
|
+
material. Built Not Found admins see every configured
|
|
552
554
|
Chapter source and can promote a Chapter resource to a global copy; the server
|
|
553
555
|
records the source Chapter, source resource/document revisions, actor, and time.
|
|
554
556
|
|
|
@@ -556,7 +558,10 @@ Resources are bounded documents/notes, HTTPS links/inspiration, or canonical
|
|
|
556
558
|
Brand `{ bookId, assetId }` references. Asset URLs, bytes, storage credentials,
|
|
557
559
|
HTML, and executable content are not part of the contract. The standard admin
|
|
558
560
|
adds a visibly private **Builder Library** tab separate from **Member content**.
|
|
559
|
-
|
|
561
|
+
It is a compact CMS inventory with search and type/status filters, type-specific
|
|
562
|
+
add/edit fields, previews, explicit Draft/Published/Archived state, and a
|
|
563
|
+
visibility picker whose Chapter choices come only from checked-in network
|
|
564
|
+
configuration. Saving a library item never creates or changes a member page.
|
|
560
565
|
|
|
561
566
|
An authenticated admin or site-building agent reads the bounded, published-only
|
|
562
567
|
manifest from `/api/admin/builder-library/manifest`. On Built Not Found, pass a
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-IAWBE5PX.js";
|
|
8
8
|
|
|
9
9
|
// src/ui/admin.tsx
|
|
10
|
-
import { useEffect as useEffect11, useMemo as
|
|
10
|
+
import { useEffect as useEffect11, useMemo as useMemo3, useRef, useState as useState17 } from "preact/hooks";
|
|
11
11
|
import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
|
|
12
12
|
import { CrmClient } from "@odla-ai/crm";
|
|
13
13
|
|
|
@@ -1688,37 +1688,101 @@ function availabilitySection(options = {}) {
|
|
|
1688
1688
|
}
|
|
1689
1689
|
|
|
1690
1690
|
// src/ui/admin-builder-library.tsx
|
|
1691
|
-
import { useEffect as useEffect8, useState as useState13 } from "preact/hooks";
|
|
1691
|
+
import { useEffect as useEffect8, useMemo as useMemo2, useState as useState13 } from "preact/hooks";
|
|
1692
1692
|
import { jsx as jsx18, jsxs as jsxs15 } from "preact/jsx-runtime";
|
|
1693
|
-
var
|
|
1694
|
-
var
|
|
1693
|
+
var KINDS = ["document", "note", "link", "inspiration", "image", "file"];
|
|
1694
|
+
var KIND_LABEL = { document: "Document", note: "Note", link: "Link", inspiration: "Inspiration", image: "Image", file: "File" };
|
|
1695
|
+
var STATUS_LABEL = { draft: "Draft", published: "Published", archived: "Archived" };
|
|
1696
|
+
var GRID = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 360px), 1fr))", gap: 16, alignItems: "start" };
|
|
1697
|
+
var STACK = { display: "flex", flexDirection: "column", gap: 10 };
|
|
1698
|
+
var ROW = { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" };
|
|
1699
|
+
var META = { display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap", color: "var(--ui-text-faint)", fontSize: 13 };
|
|
1700
|
+
var ITEM = { width: "100%", textAlign: "left", padding: 12, borderRadius: 10, border: "1px solid var(--ui-border)", background: "var(--ui-surface)", color: "inherit", cursor: "pointer" };
|
|
1701
|
+
var emptyDocument = () => ({ resources: [] });
|
|
1702
|
+
var localKey = (id) => `local:${id}`;
|
|
1703
|
+
var sourceKey = (sourceId, id) => `source:${sourceId}:${id}`;
|
|
1704
|
+
var cleanId = (value) => value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
|
|
1705
|
+
var scopeLabel = (scope) => scope.kind === "chapter" ? `Only ${scope.chapterId}` : scope.kind === "built_not_found" ? "Built Not Found only" : "All chapters";
|
|
1706
|
+
var contentSummary = (item) => item.summary || item.bodyMarkdown?.replace(/[#*_>`-]/g, "").trim().slice(0, 140) || item.url || item.brandAsset?.filename || item.brandAsset?.assetId || "No description yet";
|
|
1707
|
+
function localResource(kind, chapterId, mode) {
|
|
1708
|
+
const suffix = Date.now().toString(36);
|
|
1709
|
+
return {
|
|
1710
|
+
id: `untitled-${kind}-${suffix}`,
|
|
1711
|
+
kind,
|
|
1712
|
+
title: `Untitled ${KIND_LABEL[kind].toLowerCase()}`,
|
|
1713
|
+
tags: [],
|
|
1714
|
+
scope: mode === "hub" ? { kind: "built_not_found" } : { kind: "chapter", chapterId },
|
|
1715
|
+
status: "draft",
|
|
1716
|
+
revision: 1,
|
|
1717
|
+
...kind === "document" || kind === "note" ? { bodyMarkdown: "Start writing\u2026" } : {},
|
|
1718
|
+
...kind === "link" || kind === "inspiration" ? { url: "https://example.com" } : {},
|
|
1719
|
+
...kind === "image" || kind === "file" ? { brandAsset: { bookId: "brand-book", assetId: `replace-${suffix}` } } : {},
|
|
1720
|
+
provenance: { createdBy: "server", createdAt: 0, updatedBy: "server", updatedAt: 0 }
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function ResourcePreview({ item }) {
|
|
1724
|
+
return /* @__PURE__ */ jsxs15("div", { className: "card", style: { padding: 14 }, children: [
|
|
1725
|
+
/* @__PURE__ */ jsx18("div", { className: "card-label", children: "Preview" }),
|
|
1726
|
+
/* @__PURE__ */ jsx18("h3", { style: { margin: "6px 0" }, children: item.title || "Untitled" }),
|
|
1727
|
+
item.summary ? /* @__PURE__ */ jsx18("p", { style: { margin: "0 0 10px" }, children: item.summary }) : null,
|
|
1728
|
+
item.bodyMarkdown ? /* @__PURE__ */ jsx18("pre", { style: { margin: 0, whiteSpace: "pre-wrap", font: "inherit", lineHeight: 1.55 }, children: item.bodyMarkdown }) : null,
|
|
1729
|
+
item.url ? /* @__PURE__ */ jsx18("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url }) : null,
|
|
1730
|
+
item.brandAsset ? /* @__PURE__ */ jsxs15("div", { style: META, children: [
|
|
1731
|
+
"Brand asset \xB7 ",
|
|
1732
|
+
item.brandAsset.bookId,
|
|
1733
|
+
" / ",
|
|
1734
|
+
item.brandAsset.assetId,
|
|
1735
|
+
item.brandAsset.filename ? ` \xB7 ${item.brandAsset.filename}` : ""
|
|
1736
|
+
] }) : null
|
|
1737
|
+
] });
|
|
1738
|
+
}
|
|
1695
1739
|
function BuilderLibraryAdmin({ ctx, initialData }) {
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1740
|
+
const remote = useAdminResource(ctx.getToken, "/api/admin/builder-library");
|
|
1741
|
+
const seed = initialData ?? remote.data;
|
|
1742
|
+
const [resources, setResources] = useState13(() => [...seed?.local?.document.resources ?? []]);
|
|
1743
|
+
const [selectedKey, setSelectedKey] = useState13(() => seed?.local?.document.resources[0]?.id ? localKey(seed.local.document.resources[0].id) : null);
|
|
1744
|
+
const [query, setQuery] = useState13("");
|
|
1745
|
+
const [kindFilter, setKindFilter] = useState13("all");
|
|
1746
|
+
const [statusFilter, setStatusFilter] = useState13("all");
|
|
1747
|
+
const [newKind, setNewKind] = useState13("document");
|
|
1698
1748
|
const [saveError, setSaveError] = useState13(null);
|
|
1699
1749
|
const [saving, setSaving] = useState13(false);
|
|
1700
1750
|
useEffect8(() => {
|
|
1701
|
-
if (
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1751
|
+
if (!remote.data) return;
|
|
1752
|
+
const next = [...remote.data.local?.document.resources ?? []];
|
|
1753
|
+
setResources(next);
|
|
1754
|
+
setSelectedKey((current) => current && next.some((item) => localKey(item.id) === current) ? current : next[0] ? localKey(next[0].id) : null);
|
|
1755
|
+
}, [remote.data]);
|
|
1756
|
+
const data = initialData ?? remote.data;
|
|
1757
|
+
const entries = useMemo2(() => data ? [
|
|
1758
|
+
...resources.map((item) => ({ key: localKey(item.id), resource: item, local: true, sourceId: data.mode === "hub" ? "Built Not Found" : "This chapter", sourceRevision: data.local?.revision ?? 0 })),
|
|
1759
|
+
...data.sources.flatMap((source) => source.document.resources.map((item) => ({ key: sourceKey(source.sourceId, item.id), resource: item, local: false, sourceId: source.sourceId, sourceRevision: source.revision })))
|
|
1760
|
+
] : [], [data, resources]);
|
|
1761
|
+
const visible = entries.filter(({ resource, sourceId }) => {
|
|
1762
|
+
const haystack = `${resource.title} ${resource.summary ?? ""} ${resource.tags.join(" ")} ${sourceId}`.toLowerCase();
|
|
1763
|
+
return (!query.trim() || haystack.includes(query.toLowerCase().trim())) && (kindFilter === "all" || resource.kind === kindFilter) && (statusFilter === "all" || resource.status === statusFilter);
|
|
1764
|
+
});
|
|
1765
|
+
const selected = entries.find((entry) => entry.key === selectedKey) ?? visible[0] ?? null;
|
|
1766
|
+
if (!data && remote.loading) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(AdminNote, { children: "Loading Builder Library\u2026" }) });
|
|
1767
|
+
if (remote.error || !data) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsxs15(AdminNote, { children: [
|
|
1706
1768
|
"Builder Library could not be loaded: ",
|
|
1707
|
-
|
|
1769
|
+
remote.error ?? "unknown error"
|
|
1708
1770
|
] }) });
|
|
1771
|
+
const update = (change) => {
|
|
1772
|
+
if (!selected?.local) return;
|
|
1773
|
+
setResources((items) => items.map((item) => item.id === selected.resource.id ? { ...item, ...change } : item));
|
|
1774
|
+
};
|
|
1775
|
+
const add = () => {
|
|
1776
|
+
const item = localResource(newKind, data.chapterTargets[0]?.chapterId ?? "chapter", data.mode);
|
|
1777
|
+
setResources((items) => [...items, item]);
|
|
1778
|
+
setSelectedKey(localKey(item.id));
|
|
1779
|
+
};
|
|
1709
1780
|
const save = async () => {
|
|
1710
1781
|
setSaveError(null);
|
|
1711
|
-
let document2;
|
|
1712
|
-
try {
|
|
1713
|
-
document2 = JSON.parse(draft);
|
|
1714
|
-
} catch (error) {
|
|
1715
|
-
setSaveError([`JSON is invalid: ${error instanceof Error ? error.message : String(error)}`]);
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
1782
|
setSaving(true);
|
|
1719
1783
|
try {
|
|
1720
|
-
await adminFetch(ctx.getToken, "/api/admin/builder-library", { method: "PUT", body: JSON.stringify({ expectedRevision: data.local?.revision ?? 0, document:
|
|
1721
|
-
|
|
1784
|
+
await adminFetch(ctx.getToken, "/api/admin/builder-library", { method: "PUT", body: JSON.stringify({ expectedRevision: data.local?.revision ?? 0, document: { ...data.local?.document ?? emptyDocument(), resources } }) });
|
|
1785
|
+
remote.refresh();
|
|
1722
1786
|
} catch (error) {
|
|
1723
1787
|
const structured = error;
|
|
1724
1788
|
setSaveError(structured.errors ?? [error instanceof Error ? error.message : String(error)]);
|
|
@@ -1726,79 +1790,163 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
|
|
|
1726
1790
|
setSaving(false);
|
|
1727
1791
|
}
|
|
1728
1792
|
};
|
|
1729
|
-
const promote = async (
|
|
1793
|
+
const promote = async (entry) => {
|
|
1730
1794
|
setSaveError(null);
|
|
1731
1795
|
try {
|
|
1732
|
-
await adminFetch(ctx.getToken, "/api/admin/builder-library/promote", { method: "POST", body: JSON.stringify({ chapterId, resourceId, expectedRevision: data.local?.revision ?? 0 }) });
|
|
1733
|
-
|
|
1796
|
+
await adminFetch(ctx.getToken, "/api/admin/builder-library/promote", { method: "POST", body: JSON.stringify({ chapterId: entry.sourceId, resourceId: entry.resource.id, expectedRevision: data.local?.revision ?? 0 }) });
|
|
1797
|
+
remote.refresh();
|
|
1734
1798
|
} catch (error) {
|
|
1735
1799
|
setSaveError([error instanceof Error ? error.message : String(error)]);
|
|
1736
1800
|
}
|
|
1737
1801
|
};
|
|
1802
|
+
const scopeValue = selected?.resource.scope.kind === "chapter" ? `chapter:${selected.resource.scope.chapterId}` : selected?.resource.scope.kind ?? "built_not_found";
|
|
1738
1803
|
return /* @__PURE__ */ jsxs15(AdminPage, { children: [
|
|
1739
|
-
/* @__PURE__ */ jsxs15(Panel, { children: [
|
|
1740
|
-
|
|
1741
|
-
/* @__PURE__ */ jsx18("
|
|
1742
|
-
|
|
1743
|
-
/* @__PURE__ */
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1804
|
+
/* @__PURE__ */ jsxs15(Panel, { title: /* @__PURE__ */ jsxs15("span", { children: [
|
|
1805
|
+
"Builder Library ",
|
|
1806
|
+
/* @__PURE__ */ jsx18("span", { className: "muted", children: "\xB7 private" })
|
|
1807
|
+
] }), actions: /* @__PURE__ */ jsxs15("div", { style: ROW, children: [
|
|
1808
|
+
/* @__PURE__ */ jsx18("select", { "aria-label": "New resource type", value: newKind, onChange: (event) => setNewKind(event.currentTarget.value), children: KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }),
|
|
1809
|
+
/* @__PURE__ */ jsx18("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
|
|
1810
|
+
] }), children: [
|
|
1811
|
+
/* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: "Documents, links, and Brand assets used by site-building agents. Nothing here appears on a member page." }),
|
|
1812
|
+
/* @__PURE__ */ jsxs15("div", { style: ROW, children: [
|
|
1813
|
+
/* @__PURE__ */ jsx18("input", { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
|
|
1814
|
+
/* @__PURE__ */ jsxs15("select", { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
|
|
1815
|
+
/* @__PURE__ */ jsx18("option", { value: "all", children: "All types" }),
|
|
1816
|
+
KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind))
|
|
1747
1817
|
] }),
|
|
1748
|
-
/* @__PURE__ */ jsxs15("
|
|
1749
|
-
/* @__PURE__ */ jsx18("
|
|
1750
|
-
/* @__PURE__ */ jsx18("
|
|
1818
|
+
/* @__PURE__ */ jsxs15("select", { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
|
|
1819
|
+
/* @__PURE__ */ jsx18("option", { value: "all", children: "All statuses" }),
|
|
1820
|
+
/* @__PURE__ */ jsx18("option", { value: "draft", children: "Draft" }),
|
|
1821
|
+
/* @__PURE__ */ jsx18("option", { value: "published", children: "Published" }),
|
|
1822
|
+
/* @__PURE__ */ jsx18("option", { value: "archived", children: "Archived" })
|
|
1823
|
+
] })
|
|
1824
|
+
] })
|
|
1825
|
+
] }),
|
|
1826
|
+
/* @__PURE__ */ jsxs15("div", { style: GRID, "data-builder-library-cms": true, children: [
|
|
1827
|
+
/* @__PURE__ */ jsx18(Panel, { title: `${visible.length} resource${visible.length === 1 ? "" : "s"}`, children: /* @__PURE__ */ jsx18("div", { style: { ...STACK, maxHeight: "70vh", overflow: "auto" }, children: visible.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "No resources match these filters." }) : visible.map((entry) => /* @__PURE__ */ jsxs15("button", { type: "button", style: { ...ITEM, ...selected?.key === entry.key ? { borderColor: "var(--ui-accent)", boxShadow: "0 0 0 1px var(--ui-accent)" } : {} }, onClick: () => setSelectedKey(entry.key), children: [
|
|
1828
|
+
/* @__PURE__ */ jsxs15("div", { style: { ...ROW, justifyContent: "space-between" }, children: [
|
|
1829
|
+
/* @__PURE__ */ jsx18("strong", { children: entry.resource.title }),
|
|
1830
|
+
/* @__PURE__ */ jsx18("span", { className: "badge", children: KIND_LABEL[entry.resource.kind] })
|
|
1751
1831
|
] }),
|
|
1752
|
-
/* @__PURE__ */
|
|
1753
|
-
|
|
1754
|
-
/* @__PURE__ */ jsx18("span", { children:
|
|
1832
|
+
/* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: "6px 0", lineHeight: 1.4 }, children: contentSummary(entry.resource) }),
|
|
1833
|
+
/* @__PURE__ */ jsxs15("div", { style: META, children: [
|
|
1834
|
+
/* @__PURE__ */ jsx18("span", { children: STATUS_LABEL[entry.resource.status] }),
|
|
1835
|
+
/* @__PURE__ */ jsx18("span", { children: "\xB7" }),
|
|
1836
|
+
/* @__PURE__ */ jsx18("span", { children: scopeLabel(entry.resource.scope) }),
|
|
1837
|
+
/* @__PURE__ */ jsx18("span", { children: "\xB7" }),
|
|
1838
|
+
/* @__PURE__ */ jsx18("span", { children: entry.sourceId })
|
|
1755
1839
|
] })
|
|
1756
|
-
] }),
|
|
1757
|
-
/* @__PURE__ */
|
|
1840
|
+
] }, entry.key)) }) }),
|
|
1841
|
+
/* @__PURE__ */ jsxs15(Panel, { title: selected ? selected.resource.title : "Select a resource", actions: selected?.local ? /* @__PURE__ */ jsxs15("div", { style: ROW, children: [
|
|
1842
|
+
/* @__PURE__ */ jsx18("button", { className: "btn secondary", type: "button", disabled: saving, onClick: () => update({ status: selected.resource.status === "archived" ? "draft" : "archived" }), children: selected.resource.status === "archived" ? "Restore draft" : "Archive" }),
|
|
1843
|
+
/* @__PURE__ */ jsx18("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save changes" })
|
|
1844
|
+
] }) : null, children: [
|
|
1845
|
+
!selected ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "Add a resource or choose one from the list." }) : selected.local ? /* @__PURE__ */ jsx18(LocalEditor, { selected: selected.resource, data, scopeValue, saving, update, setResources, setSelectedKey }) : /* @__PURE__ */ jsxs15("div", { style: STACK, children: [
|
|
1846
|
+
/* @__PURE__ */ jsxs15("div", { style: META, children: [
|
|
1847
|
+
"Read only \xB7 supplied by ",
|
|
1848
|
+
selected.sourceId,
|
|
1849
|
+
" \xB7 source revision ",
|
|
1850
|
+
selected.sourceRevision
|
|
1851
|
+
] }),
|
|
1852
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1853
|
+
/* @__PURE__ */ jsx18("span", { className: "badge", children: STATUS_LABEL[selected.resource.status] }),
|
|
1854
|
+
" ",
|
|
1855
|
+
/* @__PURE__ */ jsx18("span", { className: "badge", children: scopeLabel(selected.resource.scope) })
|
|
1856
|
+
] }),
|
|
1857
|
+
/* @__PURE__ */ jsx18(ResourcePreview, { item: selected.resource }),
|
|
1858
|
+
data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */ jsx18("button", { className: "btn secondary", type: "button", onClick: () => void promote(selected), children: "Copy to all chapters" }) : null
|
|
1859
|
+
] }),
|
|
1860
|
+
saveError ? /* @__PURE__ */ jsxs15("div", { className: "notice danger", role: "alert", children: [
|
|
1861
|
+
/* @__PURE__ */ jsx18("strong", { children: "Fix these items before saving:" }),
|
|
1862
|
+
/* @__PURE__ */ jsx18("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx18("li", { children: error }, error)) })
|
|
1863
|
+
] }) : null
|
|
1864
|
+
] })
|
|
1865
|
+
] })
|
|
1866
|
+
] });
|
|
1867
|
+
}
|
|
1868
|
+
function LocalEditor({ selected, data, scopeValue, update, setResources, setSelectedKey }) {
|
|
1869
|
+
return /* @__PURE__ */ jsxs15("div", { style: STACK, children: [
|
|
1870
|
+
/* @__PURE__ */ jsxs15("div", { style: META, children: [
|
|
1871
|
+
"Local \xB7 resource revision ",
|
|
1872
|
+
selected.revision,
|
|
1873
|
+
" \xB7 library revision ",
|
|
1874
|
+
data.local?.revision ?? 0
|
|
1758
1875
|
] }),
|
|
1759
|
-
|
|
1760
|
-
/* @__PURE__ */ jsxs15("
|
|
1761
|
-
|
|
1762
|
-
"
|
|
1876
|
+
/* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
1877
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx18("span", { children: "Title" }),
|
|
1879
|
+
/* @__PURE__ */ jsx18("input", { value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) })
|
|
1763
1880
|
] }),
|
|
1764
|
-
/* @__PURE__ */ jsxs15("
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1881
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1882
|
+
/* @__PURE__ */ jsx18("span", { children: "ID" }),
|
|
1883
|
+
/* @__PURE__ */ jsx18("input", { value: selected.id, onInput: (event) => {
|
|
1884
|
+
const id = cleanId(event.currentTarget.value);
|
|
1885
|
+
setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
|
|
1886
|
+
setSelectedKey(localKey(id));
|
|
1887
|
+
} })
|
|
1768
1888
|
] }),
|
|
1769
|
-
|
|
1770
|
-
/* @__PURE__ */ jsx18("
|
|
1771
|
-
/* @__PURE__ */
|
|
1772
|
-
|
|
1773
|
-
" \
|
|
1774
|
-
|
|
1775
|
-
" \xB7 ",
|
|
1776
|
-
scopeLabel(item)
|
|
1777
|
-
] }),
|
|
1778
|
-
item.summary ? /* @__PURE__ */ jsx18("p", { children: item.summary }) : null,
|
|
1779
|
-
data.mode === "hub" && item.scope.kind === "chapter" ? /* @__PURE__ */ jsx18("button", { className: "btn", type: "button", onClick: () => void promote(source.sourceId, item.id), children: "Make global" }) : null
|
|
1780
|
-
] }, item.id)) })
|
|
1781
|
-
] }, source.sourceId)),
|
|
1782
|
-
/* @__PURE__ */ jsxs15(Panel, { children: [
|
|
1783
|
-
/* @__PURE__ */ jsxs15("div", { className: "card-label", children: [
|
|
1784
|
-
data.mode === "hub" ? "Built Not Found" : "This chapter",
|
|
1785
|
-
" \xB7 editable"
|
|
1889
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1890
|
+
/* @__PURE__ */ jsx18("span", { children: "Type" }),
|
|
1891
|
+
/* @__PURE__ */ jsx18("select", { value: selected.kind, onChange: (event) => {
|
|
1892
|
+
const kind = event.currentTarget.value;
|
|
1893
|
+
update({ kind, bodyMarkdown: kind === "document" || kind === "note" ? selected.bodyMarkdown ?? "Start writing\u2026" : void 0, url: kind === "link" || kind === "inspiration" ? selected.url ?? "https://example.com" : void 0, brandAsset: kind === "image" || kind === "file" ? selected.brandAsset ?? { bookId: "brand-book", assetId: "replace-me" } : void 0 });
|
|
1894
|
+
}, children: KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind)) })
|
|
1786
1895
|
] }),
|
|
1787
|
-
/* @__PURE__ */ jsxs15("
|
|
1788
|
-
"
|
|
1789
|
-
|
|
1896
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1897
|
+
/* @__PURE__ */ jsx18("span", { children: "Status" }),
|
|
1898
|
+
/* @__PURE__ */ jsxs15("select", { value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
|
|
1899
|
+
/* @__PURE__ */ jsx18("option", { value: "draft", children: "Draft" }),
|
|
1900
|
+
/* @__PURE__ */ jsx18("option", { value: "published", children: "Published" }),
|
|
1901
|
+
/* @__PURE__ */ jsx18("option", { value: "archived", children: "Archived" })
|
|
1902
|
+
] })
|
|
1790
1903
|
] }),
|
|
1791
|
-
/* @__PURE__ */ jsx18("p", { className: "meeting-note", children: "Use documents/notes, HTTPS links/inspiration, or canonical Brand book and asset IDs. The server owns revisions, actor history, and timestamps." }),
|
|
1792
1904
|
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1793
|
-
/* @__PURE__ */ jsx18("span", { children: "
|
|
1794
|
-
/* @__PURE__ */
|
|
1905
|
+
/* @__PURE__ */ jsx18("span", { children: "Visible to" }),
|
|
1906
|
+
/* @__PURE__ */ jsxs15("select", { value: scopeValue, onChange: (event) => {
|
|
1907
|
+
const value = event.currentTarget.value;
|
|
1908
|
+
update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
|
|
1909
|
+
}, children: [
|
|
1910
|
+
data.mode === "hub" ? /* @__PURE__ */ jsx18("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
|
|
1911
|
+
data.mode === "hub" ? /* @__PURE__ */ jsx18("option", { value: "global", children: "All chapters" }) : null,
|
|
1912
|
+
data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs15("option", { value: `chapter:${chapterId}`, children: [
|
|
1913
|
+
"Only ",
|
|
1914
|
+
chapterId
|
|
1915
|
+
] }, chapterId))
|
|
1916
|
+
] })
|
|
1795
1917
|
] }),
|
|
1796
|
-
|
|
1797
|
-
/* @__PURE__ */ jsx18("
|
|
1798
|
-
/* @__PURE__ */ jsx18("
|
|
1799
|
-
] })
|
|
1800
|
-
|
|
1801
|
-
|
|
1918
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1919
|
+
/* @__PURE__ */ jsx18("span", { children: "Tags" }),
|
|
1920
|
+
/* @__PURE__ */ jsx18("input", { value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) })
|
|
1921
|
+
] })
|
|
1922
|
+
] }),
|
|
1923
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1924
|
+
/* @__PURE__ */ jsx18("span", { children: "Summary" }),
|
|
1925
|
+
/* @__PURE__ */ jsx18("textarea", { rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) })
|
|
1926
|
+
] }),
|
|
1927
|
+
selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1928
|
+
/* @__PURE__ */ jsx18("span", { children: "Markdown" }),
|
|
1929
|
+
/* @__PURE__ */ jsx18("textarea", { rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) })
|
|
1930
|
+
] }) : null,
|
|
1931
|
+
selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1932
|
+
/* @__PURE__ */ jsx18("span", { children: "HTTPS URL" }),
|
|
1933
|
+
/* @__PURE__ */ jsx18("input", { type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) })
|
|
1934
|
+
] }) : null,
|
|
1935
|
+
selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
|
|
1936
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1937
|
+
/* @__PURE__ */ jsx18("span", { children: "Brand book ID" }),
|
|
1938
|
+
/* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
|
|
1939
|
+
] }),
|
|
1940
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1941
|
+
/* @__PURE__ */ jsx18("span", { children: "Asset ID" }),
|
|
1942
|
+
/* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
|
|
1943
|
+
] }),
|
|
1944
|
+
/* @__PURE__ */ jsxs15("label", { className: "field", children: [
|
|
1945
|
+
/* @__PURE__ */ jsx18("span", { children: "Filename (optional)" }),
|
|
1946
|
+
/* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) })
|
|
1947
|
+
] })
|
|
1948
|
+
] }) : null,
|
|
1949
|
+
/* @__PURE__ */ jsx18(ResourcePreview, { item: selected })
|
|
1802
1950
|
] });
|
|
1803
1951
|
}
|
|
1804
1952
|
function builderLibrarySection() {
|
|
@@ -1895,7 +2043,7 @@ function emailSection(options = {}) {
|
|
|
1895
2043
|
// src/ui/admin-member-content.tsx
|
|
1896
2044
|
import { useEffect as useEffect9, useState as useState15 } from "preact/hooks";
|
|
1897
2045
|
import { jsx as jsx20, jsxs as jsxs17 } from "preact/jsx-runtime";
|
|
1898
|
-
var
|
|
2046
|
+
var emptyDraft = () => ({ pages: [], upsellOverrides: [] });
|
|
1899
2047
|
function accessLabel(page) {
|
|
1900
2048
|
return page.audience.kind === "all-members" ? "All members" : `Tiers: ${page.audience.tierIds.join(", ")}`;
|
|
1901
2049
|
}
|
|
@@ -1904,12 +2052,12 @@ function MemberContentAdmin({
|
|
|
1904
2052
|
initialData
|
|
1905
2053
|
}) {
|
|
1906
2054
|
const resource = useAdminResource(ctx.getToken, "/api/admin/member-content");
|
|
1907
|
-
const [draft, setDraft] = useState15(JSON.stringify(initialData?.chapter?.document ??
|
|
2055
|
+
const [draft, setDraft] = useState15(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
|
|
1908
2056
|
const [saveError, setSaveError] = useState15(null);
|
|
1909
2057
|
const [saving, setSaving] = useState15(false);
|
|
1910
2058
|
useEffect9(() => {
|
|
1911
2059
|
if (!resource.data) return;
|
|
1912
|
-
setDraft(JSON.stringify(resource.data.chapter?.document ??
|
|
2060
|
+
setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft(), null, 2));
|
|
1913
2061
|
}, [resource.data]);
|
|
1914
2062
|
const loaded = initialData ?? resource.data;
|
|
1915
2063
|
if (!loaded && resource.loading) return /* @__PURE__ */ jsx20(AdminPage, { children: /* @__PURE__ */ jsx20(AdminNote, { children: "Loading member content\u2026" }) });
|
|
@@ -2323,7 +2471,7 @@ function Authed(props) {
|
|
|
2323
2471
|
renderWorkspaceNav
|
|
2324
2472
|
} = props;
|
|
2325
2473
|
const { getToken, signOut } = useClerkAuth();
|
|
2326
|
-
const client =
|
|
2474
|
+
const client = useMemo3(
|
|
2327
2475
|
() => new CrmClient({
|
|
2328
2476
|
basePath: crmBasePath,
|
|
2329
2477
|
headers: async () => {
|
|
@@ -2384,11 +2532,11 @@ function Authed(props) {
|
|
|
2384
2532
|
}
|
|
2385
2533
|
function ChapterAdmin(props) {
|
|
2386
2534
|
const copy = props.chapter?.copy ?? DEFAULT_CHAPTER_COPY;
|
|
2387
|
-
const defaults =
|
|
2535
|
+
const defaults = useMemo3(
|
|
2388
2536
|
() => props.chapter ? defaultAdminWorkspaces(props.chapter) : [],
|
|
2389
2537
|
[props.chapter]
|
|
2390
2538
|
);
|
|
2391
|
-
const workspaces =
|
|
2539
|
+
const workspaces = useMemo3(() => {
|
|
2392
2540
|
if (typeof props.workspaces === "function") return props.workspaces(defaults);
|
|
2393
2541
|
return props.workspaces ?? props.sections ?? defaults;
|
|
2394
2542
|
}, [props.workspaces, props.sections, defaults]);
|
|
@@ -2574,4 +2722,4 @@ export {
|
|
|
2574
2722
|
defaultAdminSections,
|
|
2575
2723
|
meetingsSection
|
|
2576
2724
|
};
|
|
2577
|
-
//# sourceMappingURL=chunk-
|
|
2725
|
+
//# sourceMappingURL=chunk-O22RRPBT.js.map
|