@pramen/cms-editor 0.0.39 → 0.0.41
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/dist/index.html +1 -1
- package/dist/main.0jsv1nns.js +251 -0
- package/package.json +1 -1
- package/src/app-context.tsx +4 -0
- package/src/components.tsx +36 -9
- package/src/routes/_layout.tsx +5 -1
- package/src/routes/home.tsx +16 -2
- package/dist/main.v8ynk2fc.js +0 -251
package/package.json
CHANGED
package/src/app-context.tsx
CHANGED
|
@@ -13,6 +13,10 @@ declare global {
|
|
|
13
13
|
/** Runtime config set by the host's /config.js (see @pramen/cms-editor build). */
|
|
14
14
|
PRAMEN_CMS_EDITOR?: {
|
|
15
15
|
signInUrl?: string;
|
|
16
|
+
/** Hide the Pages tab for deployments that use collections only — no block/page
|
|
17
|
+
* building. The tab is otherwise always shown and lands on an empty list, which
|
|
18
|
+
* reads as "the CMS is broken" rather than "this site has no pages". */
|
|
19
|
+
hidePages?: boolean;
|
|
16
20
|
/** Extra top-nav links to companion tools the host serves (e.g. a curation page).
|
|
17
21
|
* Rendered as plain external `<a>` links after the built-in tabs. */
|
|
18
22
|
extraNav?: { label: string; href: string }[];
|
package/src/components.tsx
CHANGED
|
@@ -187,23 +187,45 @@ function cellText(v: unknown): string {
|
|
|
187
187
|
return String(v);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/** Page size for collection lists. collectionList defaults to 100 server-side, so a
|
|
191
|
+
* request without an explicit limit silently truncates — and the header then reports the
|
|
192
|
+
* truncated count as if it were the total, which reads as "that is all there is". */
|
|
193
|
+
const COLLECTION_PAGE_SIZE = 50;
|
|
194
|
+
|
|
190
195
|
export function CollectionList({ api, def, onOpen, onNew, onError }: { api: Api; def: CollectionMeta; onOpen: (id: string) => void; onNew: () => void; onError: (s: string) => void }) {
|
|
191
196
|
const [rows, setRows] = useState<Record<string, unknown>[]>([]);
|
|
197
|
+
const [offset, setOffset] = useState(0);
|
|
198
|
+
const [hasMore, setHasMore] = useState(false);
|
|
192
199
|
const [loading, setLoading] = useState(true);
|
|
200
|
+
|
|
201
|
+
const load = useCallback(
|
|
202
|
+
(off: number) => {
|
|
203
|
+
setLoading(true);
|
|
204
|
+
return api
|
|
205
|
+
.call<Record<string, unknown>[]>("collectionList", { collection: def.slug, limit: COLLECTION_PAGE_SIZE, offset: off })
|
|
206
|
+
.then((r) => {
|
|
207
|
+
setRows((prev) => (off === 0 ? r : [...prev, ...r]));
|
|
208
|
+
// A full page means there is probably more; a short one is definitely the end.
|
|
209
|
+
setHasMore(r.length === COLLECTION_PAGE_SIZE);
|
|
210
|
+
setOffset(off + r.length);
|
|
211
|
+
})
|
|
212
|
+
.catch((e) => onError(errMsg(e)))
|
|
213
|
+
.finally(() => setLoading(false));
|
|
214
|
+
},
|
|
215
|
+
[api, def.slug, onError],
|
|
216
|
+
);
|
|
217
|
+
|
|
193
218
|
useEffect(() => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
.finally(() => { if (live) setLoading(false); });
|
|
200
|
-
return () => { live = false; };
|
|
201
|
-
}, [api, def.slug, onError]);
|
|
219
|
+
setRows([]);
|
|
220
|
+
setOffset(0);
|
|
221
|
+
setHasMore(false);
|
|
222
|
+
void load(0);
|
|
223
|
+
}, [load]);
|
|
202
224
|
|
|
203
225
|
const labelOf = (col: string) => def.fields.find((f) => f.name === col)?.label ?? col;
|
|
204
226
|
return (
|
|
205
227
|
<>
|
|
206
|
-
<Hero lead={def.pluralLabel} em={rows.length === 0 ? "None yet" : rows.length === 1 ? `1 ${def.label.toLowerCase()}` : `${rows.length} ${def.pluralLabel.toLowerCase()}`}>
|
|
228
|
+
<Hero lead={def.pluralLabel} em={rows.length === 0 ? "None yet" : rows.length === 1 ? `1 ${def.label.toLowerCase()}` : `${rows.length}${hasMore ? "+" : ""} ${def.pluralLabel.toLowerCase()}`}>
|
|
207
229
|
<Cta text="Let's" em={`add a ${def.label.toLowerCase()}`}>
|
|
208
230
|
<Button className="shrink-0" onPress={onNew}>+ New {def.label.toLowerCase()}</Button>
|
|
209
231
|
</Cta>
|
|
@@ -225,6 +247,11 @@ export function CollectionList({ api, def, onOpen, onNew, onError }: { api: Api;
|
|
|
225
247
|
{!loading && rows.length === 0 ? <p className="text-fg-subtle">No {def.pluralLabel.toLowerCase()} yet. Create one.</p> : null}
|
|
226
248
|
{loading ? <p className="text-fg-subtle">Loading…</p> : null}
|
|
227
249
|
</div>
|
|
250
|
+
{hasMore && !loading ? (
|
|
251
|
+
<div className="mt-3.5 text-center">
|
|
252
|
+
<Button variant="secondary" size="sm" onPress={() => void load(offset)}>Load more</Button>
|
|
253
|
+
</div>
|
|
254
|
+
) : null}
|
|
228
255
|
</div>
|
|
229
256
|
</>
|
|
230
257
|
);
|
package/src/routes/_layout.tsx
CHANGED
|
@@ -27,6 +27,8 @@ export default function RootLayout() {
|
|
|
27
27
|
|
|
28
28
|
// Host-configured links to companion tools (e.g. a curation page), from /config.js.
|
|
29
29
|
const extraNav = typeof window !== "undefined" ? window.PRAMEN_CMS_EDITOR?.extraNav ?? [] : [];
|
|
30
|
+
// Collections-only deployments hide the block/page builder entirely.
|
|
31
|
+
const hidePages = typeof window !== "undefined" ? window.PRAMEN_CMS_EDITOR?.hidePages === true : false;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<>
|
|
@@ -36,7 +38,9 @@ export default function RootLayout() {
|
|
|
36
38
|
</span>
|
|
37
39
|
<span className="flex-1" />
|
|
38
40
|
<nav className="flex items-center gap-0.5">
|
|
39
|
-
|
|
41
|
+
{hidePages ? null : (
|
|
42
|
+
<Button variant="ghost" size="sm" className={tabCls("pages")} onPress={() => navigate("home")}>Pages</Button>
|
|
43
|
+
)}
|
|
40
44
|
{collections.map((c) => (
|
|
41
45
|
<Button key={c.slug} variant="ghost" size="sm" className={tabCls(`col:${c.slug}`)} onPress={() => navigate("collection", { params: { slug: c.slug } })}>
|
|
42
46
|
{c.icon ? `${c.icon} ` : ""}{c.pluralLabel}
|
package/src/routes/home.tsx
CHANGED
|
@@ -9,17 +9,31 @@ import type { BlockType, Page } from "../types";
|
|
|
9
9
|
export default createPage()
|
|
10
10
|
.route("/")
|
|
11
11
|
.render(function Home() {
|
|
12
|
-
const { api, setError } = useApp();
|
|
12
|
+
const { api, setError, collections } = useApp();
|
|
13
13
|
const navigate = useNavigate();
|
|
14
14
|
const [pages, setPages] = useState<Page[]>([]);
|
|
15
15
|
const [blockTypes, setBlockTypes] = useState<BlockType[]>([]);
|
|
16
16
|
|
|
17
|
+
// Collections-only deployment: `/` is the Pages list, so land on the first
|
|
18
|
+
// collection instead. Without this, hiding the tab still leaves the landing page
|
|
19
|
+
// showing an empty page list — and still fetching pages, which errors outright on a
|
|
20
|
+
// deployment that never spread cmsSchema.
|
|
21
|
+
const hidePages = typeof window !== "undefined" && window.PRAMEN_CMS_EDITOR?.hidePages === true;
|
|
22
|
+
const firstCollection = collections[0]?.slug;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (hidePages && firstCollection) navigate("collection", { params: { slug: firstCollection }, replace: true });
|
|
26
|
+
}, [hidePages, firstCollection, navigate]);
|
|
27
|
+
|
|
17
28
|
const refreshPages = () => api.listPages().then(setPages).catch((e) => setError(errMsg(e)));
|
|
18
29
|
useEffect(() => {
|
|
30
|
+
if (hidePages) return;
|
|
19
31
|
refreshPages();
|
|
20
32
|
api.listBlockTypes().then(setBlockTypes).catch((e) => setError(errMsg(e)));
|
|
21
33
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
22
|
-
}, [api]);
|
|
34
|
+
}, [api, hidePages]);
|
|
35
|
+
|
|
36
|
+
if (hidePages && firstCollection) return null;
|
|
23
37
|
|
|
24
38
|
return (
|
|
25
39
|
<PageList
|