@drawnagency/primitives 0.1.106 → 0.1.108
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/{chunk-QLBHERK6.js → chunk-IPKZ5FU6.js} +74 -24
- package/dist/{chunk-Z7VXESB6.js → chunk-JIQBLG6Y.js} +6 -3
- package/dist/components/sections/Container/Container.d.ts.map +1 -1
- package/dist/components/sections/brand-guide.js +1 -1
- package/dist/components/sections/register-schemas.js +2 -2
- package/dist/components/sections/register.js +2 -2
- package/dist/components/shell/EditorShell.d.ts.map +1 -1
- package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
- package/dist/components/shell/SectionTypePicker.d.ts.map +1 -1
- package/dist/hooks/useEditorPublish.d.ts +2 -0
- package/dist/hooks/useEditorPublish.d.ts.map +1 -1
- package/dist/lib/container-ops.d.ts.map +1 -1
- package/dist/lib/dexie.d.ts +25 -8
- package/dist/lib/dexie.d.ts.map +1 -1
- package/dist/lib/dexie.js +19 -17
- package/dist/lib/insert-menu.d.ts +7 -3
- package/dist/lib/insert-menu.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/sections/Container/Container.tsx +12 -2
- package/src/components/sections/IconList/IconList.tsx +75 -22
- package/src/components/shell/EditorShell.tsx +68 -30
- package/src/components/shell/MediaLibraryModal.tsx +42 -5
- package/src/components/shell/SectionTypePicker.tsx +28 -2
- package/src/hooks/useEditorPublish.ts +29 -4
- package/src/lib/container-ops.ts +6 -3
- package/src/lib/dexie.ts +62 -28
- package/src/lib/insert-menu.ts +38 -4
package/src/lib/dexie.ts
CHANGED
|
@@ -50,19 +50,21 @@ interface ContentCacheRow {
|
|
|
50
50
|
savedBranchSha?: string | null;
|
|
51
51
|
changedSectionIds?: string[];
|
|
52
52
|
mainIndex?: SiteIndex | null;
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
53
|
+
// Both sidecars AT THIS SHA. They live HERE and nowhere else, and cacheContent
|
|
54
|
+
// takes them as a required argument, because every way of storing them apart
|
|
55
|
+
// from the sha drifted: the media manifest was a separate singleton row and the
|
|
56
|
+
// document manifest was an optional extra, and both were written only by the
|
|
57
|
+
// cache-miss load path — so every save/publish that re-primed this row advanced
|
|
58
|
+
// the sha and left them behind. A later cache hit then restored pre-upload
|
|
59
|
+
// media (freshly uploaded images rendered "No Image" in the editor while the
|
|
60
|
+
// published site was correct) and an empty document manifest (committed
|
|
61
|
+
// documents read "File missing"). Optional in the TYPE only because Dexie rows
|
|
62
|
+
// are not schema-enforced; readers treat absence as a MISS (getCachedContent),
|
|
63
|
+
// never as "nothing here".
|
|
64
|
+
mediaManifest?: MediaManifest | null;
|
|
57
65
|
documentManifest?: DocumentManifest | null;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
interface MediaManifestRow {
|
|
61
|
-
key: string;
|
|
62
|
-
manifest: MediaManifest;
|
|
63
|
-
updatedAt: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
68
|
interface PendingMediaRow {
|
|
67
69
|
id: string;
|
|
68
70
|
item: MediaItem;
|
|
@@ -101,7 +103,6 @@ class EditorDatabase extends Dexie {
|
|
|
101
103
|
meta!: Dexie.Table<MetaRow, string>;
|
|
102
104
|
siteConfig!: Dexie.Table<SiteConfigRow, string>;
|
|
103
105
|
contentCache!: Dexie.Table<ContentCacheRow, string>;
|
|
104
|
-
mediaManifest!: Dexie.Table<MediaManifestRow, string>;
|
|
105
106
|
pendingMedia!: Dexie.Table<PendingMediaRow, string>;
|
|
106
107
|
pendingMediaDeletions!: Dexie.Table<PendingMediaDeletionRow, string>;
|
|
107
108
|
preservedEdits!: Dexie.Table<PreservedEditRow, string>;
|
|
@@ -223,6 +224,28 @@ class EditorDatabase extends Dexie {
|
|
|
223
224
|
preservedEdits: "id, sectionId",
|
|
224
225
|
pendingDocuments: "id",
|
|
225
226
|
});
|
|
227
|
+
// v11: the media manifest moved onto the sha-keyed contentCache row and the
|
|
228
|
+
// standalone `mediaManifest` store is deleted. It was written by the
|
|
229
|
+
// cache-miss load path ONLY, so every save/publish that re-primed
|
|
230
|
+
// contentCache advanced the cached sha while leaving the manifest at its
|
|
231
|
+
// older value — and the next cache hit served that stale manifest, blanking
|
|
232
|
+
// every image uploaded since. Dropping the store makes the drift
|
|
233
|
+
// unrepresentable. contentCache is cleared because no pre-v11 row carries a
|
|
234
|
+
// manifest; the next load re-fetches and writes both together.
|
|
235
|
+
this.version(11).stores({
|
|
236
|
+
sections: "sectionId",
|
|
237
|
+
siteIndex: "key",
|
|
238
|
+
meta: "key",
|
|
239
|
+
siteConfig: "key",
|
|
240
|
+
contentCache: "key",
|
|
241
|
+
mediaManifest: null,
|
|
242
|
+
pendingMedia: "id",
|
|
243
|
+
pendingMediaDeletions: "id",
|
|
244
|
+
preservedEdits: "id, sectionId",
|
|
245
|
+
pendingDocuments: "id",
|
|
246
|
+
}).upgrade(async (tx) => {
|
|
247
|
+
await tx.table("contentCache").clear();
|
|
248
|
+
});
|
|
226
249
|
}
|
|
227
250
|
}
|
|
228
251
|
|
|
@@ -478,12 +501,28 @@ export async function getDeletedSections(): Promise<string[]> {
|
|
|
478
501
|
return row?.deletedSections ?? [];
|
|
479
502
|
}
|
|
480
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Write the content snapshot for one commit.
|
|
506
|
+
*
|
|
507
|
+
* `manifests` is REQUIRED, and that is the whole point: both sidecars belong to
|
|
508
|
+
* this sha, so every writer that advances the row must restate them. They used to
|
|
509
|
+
* be optional (the media one lived in a separate store entirely), and every
|
|
510
|
+
* save/publish that re-primed this row left them behind at an older sha — a later
|
|
511
|
+
* cache hit then served pre-upload media ("No Image" on images the published site
|
|
512
|
+
* rendered fine) and an empty document manifest ("File missing" on committed
|
|
513
|
+
* documents). Passing them positionally makes forgetting a compile error.
|
|
514
|
+
*/
|
|
481
515
|
export async function cacheContent(
|
|
482
516
|
sha: string,
|
|
483
517
|
sections: LoadedSection[],
|
|
484
518
|
index: SiteIndex,
|
|
485
519
|
siteConfig: Record<string, unknown>,
|
|
486
|
-
|
|
520
|
+
manifests: { media: MediaManifest; documents: DocumentManifest },
|
|
521
|
+
diff?: {
|
|
522
|
+
savedBranchSha?: string | null;
|
|
523
|
+
changedSectionIds?: string[];
|
|
524
|
+
mainIndex?: SiteIndex | null;
|
|
525
|
+
},
|
|
487
526
|
): Promise<void> {
|
|
488
527
|
const now = new Date().toISOString();
|
|
489
528
|
await getDb().contentCache.put({
|
|
@@ -496,7 +535,8 @@ export async function cacheContent(
|
|
|
496
535
|
savedBranchSha: diff?.savedBranchSha ?? null,
|
|
497
536
|
changedSectionIds: diff?.changedSectionIds ?? [],
|
|
498
537
|
mainIndex: diff?.mainIndex ?? null,
|
|
499
|
-
|
|
538
|
+
mediaManifest: manifests.media,
|
|
539
|
+
documentManifest: manifests.documents,
|
|
500
540
|
});
|
|
501
541
|
}
|
|
502
542
|
|
|
@@ -508,7 +548,13 @@ export async function getCachedContent(): Promise<{
|
|
|
508
548
|
savedBranchSha: string | null;
|
|
509
549
|
changedSectionIds: string[];
|
|
510
550
|
mainIndex: SiteIndex | null;
|
|
511
|
-
|
|
551
|
+
/** Both are null ONLY for a row written before the sidecars moved onto it.
|
|
552
|
+
* Callers must treat null as an unusable cache and re-fetch — substituting an
|
|
553
|
+
* empty manifest would blank every image ("No Image") or every committed
|
|
554
|
+
* document ("File missing") on the page. An EMPTY manifest is a legitimate
|
|
555
|
+
* value and is not null. */
|
|
556
|
+
mediaManifest: MediaManifest | null;
|
|
557
|
+
documentManifest: DocumentManifest | null;
|
|
512
558
|
} | null> {
|
|
513
559
|
const row = await getDb().contentCache.get("current");
|
|
514
560
|
if (!row) return null;
|
|
@@ -522,10 +568,8 @@ export async function getCachedContent(): Promise<{
|
|
|
522
568
|
// No stored mainIndex (e.g. legacy or post-publish cache) means "no draft",
|
|
523
569
|
// so the published index equals the current index.
|
|
524
570
|
mainIndex: row.mainIndex ?? row.index,
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
// heals on the next fresh load or save.
|
|
528
|
-
documentManifest: row.documentManifest ?? {},
|
|
571
|
+
mediaManifest: row.mediaManifest ?? null,
|
|
572
|
+
documentManifest: row.documentManifest ?? null,
|
|
529
573
|
};
|
|
530
574
|
}
|
|
531
575
|
|
|
@@ -536,16 +580,6 @@ export async function clearContentCache(): Promise<void> {
|
|
|
536
580
|
await getDb().contentCache.clear();
|
|
537
581
|
}
|
|
538
582
|
|
|
539
|
-
export async function persistMediaManifest(manifest: MediaManifest): Promise<void> {
|
|
540
|
-
const now = new Date().toISOString();
|
|
541
|
-
await getDb().mediaManifest.put({ key: "current", manifest, updatedAt: now });
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
export async function getMediaManifest(): Promise<MediaManifest | null> {
|
|
545
|
-
const row = await getDb().mediaManifest.get("current");
|
|
546
|
-
return row?.manifest ?? null;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
583
|
export async function addPendingMediaItem(
|
|
550
584
|
item: MediaItem,
|
|
551
585
|
localUrls: Record<string, string> = {},
|
package/src/lib/insert-menu.ts
CHANGED
|
@@ -12,9 +12,43 @@ export interface InsertMenuOption {
|
|
|
12
12
|
icon?: ReactNode;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
/** Authored display order for the Add-section menu, most-reached-for first.
|
|
16
|
+
* `container` leads because the picker gives it its own full-width row above a
|
|
17
|
+
* divider. Anything absent — client-repo custom sections and their presets —
|
|
18
|
+
* sorts alphabetically after these. */
|
|
19
|
+
const INSERT_MENU_ORDER = [
|
|
20
|
+
"container",
|
|
21
|
+
"link_heading",
|
|
22
|
+
"sub_heading",
|
|
23
|
+
"sub_sub_heading",
|
|
24
|
+
"prose",
|
|
25
|
+
"media",
|
|
26
|
+
"video",
|
|
27
|
+
"button",
|
|
28
|
+
"colors",
|
|
29
|
+
"document",
|
|
30
|
+
"icon_list",
|
|
31
|
+
`${PRESET_PREFIX}do_dont_list`,
|
|
32
|
+
"dodont_media",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** Sort insert-menu entries into `INSERT_MENU_ORDER`, with unlisted entries
|
|
36
|
+
* (custom sections) alphabetical by label at the end. Pure; returns a copy. */
|
|
37
|
+
export function sortInsertMenuOptions<T extends InsertMenuOption>(options: T[]): T[] {
|
|
38
|
+
return [...options].sort((a, b) => {
|
|
39
|
+
const ai = INSERT_MENU_ORDER.indexOf(a.type);
|
|
40
|
+
const bi = INSERT_MENU_ORDER.indexOf(b.type);
|
|
41
|
+
if (ai !== -1 && bi !== -1) return ai - bi;
|
|
42
|
+
if (ai !== -1) return -1;
|
|
43
|
+
if (bi !== -1) return 1;
|
|
44
|
+
return a.label.localeCompare(b.label);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Every non-spacer section type, plus presets contributed by registered
|
|
49
|
+
* sections, in `INSERT_MENU_ORDER`. Presets appear only when their owning
|
|
50
|
+
* section is registered, so a core-only build (no brand-guide sections) shows
|
|
51
|
+
* no brand-guide presets. */
|
|
18
52
|
export function buildInsertMenuOptions(sections: SectionDefinition[]): InsertMenuOption[] {
|
|
19
53
|
const typeOptions = sections
|
|
20
54
|
.filter((def) => def.type !== "spacer")
|
|
@@ -22,7 +56,7 @@ export function buildInsertMenuOptions(sections: SectionDefinition[]): InsertMen
|
|
|
22
56
|
const presetOptions = sections
|
|
23
57
|
.flatMap((def) => def.presets ?? [])
|
|
24
58
|
.map((p) => ({ type: `${PRESET_PREFIX}${p.id}`, label: p.label, icon: p.icon }));
|
|
25
|
-
return [...typeOptions, ...presetOptions];
|
|
59
|
+
return sortInsertMenuOptions([...typeOptions, ...presetOptions]);
|
|
26
60
|
}
|
|
27
61
|
|
|
28
62
|
/** Resolve an Add-menu selection to ready-to-insert content. `newId` mints ids for
|