@drawnagency/primitives 0.1.10 → 0.1.12

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.
Files changed (51) hide show
  1. package/dist/{chunk-32H6Q6CX.js → chunk-2YYC2VJY.js} +1 -1
  2. package/dist/{chunk-XQXZHDNR.js → chunk-PHCEJP7I.js} +1 -1
  3. package/dist/{chunk-6SK5BLG3.js → chunk-Q7OKHD6I.js} +1 -1
  4. package/dist/components/editor/SectionWrapper.d.ts +1 -1
  5. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  6. package/dist/components/editor/StatusDots.d.ts +25 -0
  7. package/dist/components/editor/StatusDots.d.ts.map +1 -0
  8. package/dist/components/editor/StatusPicker.d.ts +1 -1
  9. package/dist/components/editor/StatusPicker.d.ts.map +1 -1
  10. package/dist/components/editor/index.d.ts +1 -0
  11. package/dist/components/editor/index.d.ts.map +1 -1
  12. package/dist/components/shared/SegmentedControl.d.ts +13 -0
  13. package/dist/components/shared/SegmentedControl.d.ts.map +1 -0
  14. package/dist/components/shared/SplitButton.d.ts +17 -0
  15. package/dist/components/shared/SplitButton.d.ts.map +1 -0
  16. package/dist/components/shell/EditorContext.d.ts +2 -0
  17. package/dist/components/shell/EditorContext.d.ts.map +1 -1
  18. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  19. package/dist/hooks/index.d.ts +1 -0
  20. package/dist/hooks/index.d.ts.map +1 -1
  21. package/dist/hooks/useContentLifecycle.d.ts +13 -0
  22. package/dist/hooks/useContentLifecycle.d.ts.map +1 -0
  23. package/dist/hooks/useEditorPublish.d.ts +5 -1
  24. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  25. package/dist/index.js +3 -3
  26. package/dist/lib/dexie.d.ts +8 -1
  27. package/dist/lib/dexie.d.ts.map +1 -1
  28. package/dist/lib/index.js +2 -2
  29. package/dist/lib/registry.d.ts +6 -1
  30. package/dist/lib/registry.d.ts.map +1 -1
  31. package/dist/schemas/index.js +2 -2
  32. package/dist/schemas/site-config.d.ts +2 -2
  33. package/package.json +1 -1
  34. package/src/components/brandguide/DoDontList.tsx +1 -1
  35. package/src/components/editor/SectionWrapper.tsx +44 -2
  36. package/src/components/editor/StatusBadge.tsx +2 -2
  37. package/src/components/editor/StatusDots.tsx +131 -0
  38. package/src/components/editor/StatusPicker.tsx +6 -6
  39. package/src/components/editor/index.ts +1 -0
  40. package/src/components/sections/SectionLayout.tsx +1 -1
  41. package/src/components/shared/Navigation.tsx +3 -3
  42. package/src/components/shared/SegmentedControl.tsx +43 -0
  43. package/src/components/shared/SplitButton.tsx +97 -0
  44. package/src/components/shell/EditorContext.tsx +5 -1
  45. package/src/components/shell/EditorShell.tsx +157 -52
  46. package/src/hooks/index.ts +1 -0
  47. package/src/hooks/useContentLifecycle.ts +34 -0
  48. package/src/hooks/useEditorPublish.ts +230 -66
  49. package/src/lib/dexie.ts +43 -2
  50. package/src/lib/registry.ts +6 -1
  51. package/src/schemas/site-config.ts +1 -1
@@ -24,11 +24,20 @@ interface PublishDeps {
24
24
  siteIndexRef: React.RefObject<SiteIndex>;
25
25
  siteConfig: SiteConfig | null;
26
26
  sections: LoadedSection[];
27
+ deletedSectionIds?: string[];
27
28
  onSuccess: () => void;
28
29
  mediaManifest: MediaManifest;
29
30
  pendingMediaItems: MediaItem[];
30
31
  pendingMediaDeletions: string[];
31
32
  onMediaPublished: (publishedItems: MediaItem[], publishedDeletions: string[]) => void;
33
+ onShasUpdated: (savedSha: string | null, mainSha: string | null) => void;
34
+ }
35
+
36
+ interface GatheredMedia {
37
+ mediaUploads: { item: MediaItem; blobs: { path: string; base64: string }[] }[];
38
+ blobUrlsToRevoke: string[];
39
+ updatedManifest: MediaManifest | undefined;
40
+ hasMediaChanges: boolean;
32
41
  }
33
42
 
34
43
  export function useEditorPublish({
@@ -39,11 +48,13 @@ export function useEditorPublish({
39
48
  siteIndexRef,
40
49
  siteConfig,
41
50
  sections,
51
+ deletedSectionIds,
42
52
  onSuccess,
43
53
  mediaManifest,
44
54
  pendingMediaItems,
45
55
  pendingMediaDeletions,
46
56
  onMediaPublished,
57
+ onShasUpdated,
47
58
  }: PublishDeps) {
48
59
  const [isPublishing, setIsPublishing] = useState(false);
49
60
  const [publishFeedback, setPublishFeedback] = useState<string | null>(null);
@@ -55,7 +66,72 @@ export function useEditorPublish({
55
66
  };
56
67
  }, []);
57
68
 
58
- const handlePublish = useCallback(async () => {
69
+ const showFeedback = useCallback((message: string, duration: number) => {
70
+ setPublishFeedback(message);
71
+ if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
72
+ feedbackTimerRef.current = setTimeout(() => setPublishFeedback(null), duration);
73
+ }, []);
74
+
75
+ async function gatherMediaPayload(): Promise<GatheredMedia> {
76
+ const hasMediaChanges = pendingMediaItems.length > 0 || pendingMediaDeletions.length > 0;
77
+ const mediaUploads: { item: MediaItem; blobs: { path: string; base64: string }[] }[] = [];
78
+ const blobUrlsToRevoke: string[] = [];
79
+
80
+ for (const item of pendingMediaItems) {
81
+ const localUrls = await getPendingMediaLocalUrls(item.id);
82
+ if (!localUrls) continue;
83
+
84
+ for (const url of Object.values(localUrls)) {
85
+ blobUrlsToRevoke.push(url);
86
+ }
87
+
88
+ const blobs: { path: string; base64: string }[] = [];
89
+ const failedBlobFetches: string[] = [];
90
+ const mimeExt: Record<string, string> = {
91
+ "image/gif": "gif", "image/apng": "apng", "video/mp4": "mp4", "video/webm": "webm",
92
+ };
93
+ for (const [key, url] of Object.entries(localUrls)) {
94
+ if (key === "primary" && item.kind === "image") continue;
95
+ try {
96
+ const resp = await fetch(url);
97
+ const blob = await resp.blob();
98
+ const base64 = await blobToBase64(blob);
99
+ if (key === "primary") {
100
+ const ext = mimeExt[item.mimeType] ?? "bin";
101
+ blobs.push({ path: `assets/images/${item.folder}/original.${ext}`, base64 });
102
+ } else {
103
+ blobs.push({ path: `assets/images/${item.folder}/${key}.webp`, base64 });
104
+ }
105
+ } catch {
106
+ failedBlobFetches.push(`${item.id}/${key}`);
107
+ }
108
+ }
109
+
110
+ if (failedBlobFetches.length > 0) {
111
+ throw new Error(`Media upload failed: could not read blob data for ${failedBlobFetches.join(", ")}`);
112
+ }
113
+
114
+ if (blobs.length > 0) {
115
+ mediaUploads.push({ item, blobs });
116
+ }
117
+ }
118
+
119
+ let updatedManifest: MediaManifest | undefined;
120
+ if (hasMediaChanges) {
121
+ const images = { ...mediaManifest.images };
122
+ for (const upload of mediaUploads) {
123
+ images[upload.item.id] = upload.item;
124
+ }
125
+ for (const id of pendingMediaDeletions) {
126
+ delete images[id];
127
+ }
128
+ updatedManifest = { images };
129
+ }
130
+
131
+ return { mediaUploads, blobUrlsToRevoke, updatedManifest, hasMediaChanges };
132
+ }
133
+
134
+ const handleSave = useCallback(async () => {
59
135
  if (!siteConfig) return;
60
136
 
61
137
  setIsPublishing(true);
@@ -67,80 +143,43 @@ export function useEditorPublish({
67
143
 
68
144
  const hasChanges = await hasLocalChanges();
69
145
  const hasMediaChanges = pendingMediaItems.length > 0 || pendingMediaDeletions.length > 0;
70
- if (!hasChanges && !isConfigDirty() && !hasMediaChanges) {
146
+ const hasDeletedSections = (deletedSectionIds?.length ?? 0) > 0;
147
+ if (!hasChanges && !isConfigDirty() && !hasMediaChanges && !hasDeletedSections) {
71
148
  setIsPublishing(false);
72
149
  return;
73
150
  }
74
151
 
75
152
  const dirty = await getDirtySections();
153
+ const { mediaUploads, blobUrlsToRevoke, updatedManifest, hasMediaChanges: mediaChanged } = await gatherMediaPayload();
76
154
 
77
- // Gather pending media for the commit
78
- const mediaUploads: { item: MediaItem; blobs: { path: string; base64: string }[] }[] = [];
79
- const blobUrlsToRevoke: string[] = [];
80
-
81
- for (const item of pendingMediaItems) {
82
- const localUrls = await getPendingMediaLocalUrls(item.id);
83
- if (!localUrls) continue;
84
-
85
- for (const url of Object.values(localUrls)) {
86
- blobUrlsToRevoke.push(url);
155
+ // Build a filtered siteIndex if there are deletions
156
+ let siteIndex = siteIndexRef.current;
157
+ if (deletedSectionIds?.length) {
158
+ const deleteSet = new Set(deletedSectionIds);
159
+ const filteredSections = { ...siteIndex.sections };
160
+ for (const id of deletedSectionIds) {
161
+ delete filteredSections[id];
87
162
  }
88
-
89
- const blobs: { path: string; base64: string }[] = [];
90
- const failedBlobFetches: string[] = [];
91
- const mimeExt: Record<string, string> = {
92
- "image/gif": "gif", "image/apng": "apng", "video/mp4": "mp4", "video/webm": "webm",
163
+ siteIndex = {
164
+ ...siteIndex,
165
+ order: siteIndex.order.filter((id) => !deleteSet.has(id)),
166
+ sections: filteredSections,
93
167
  };
94
- for (const [key, url] of Object.entries(localUrls)) {
95
- if (key === "primary" && item.kind === "image") continue;
96
- try {
97
- const resp = await fetch(url);
98
- const blob = await resp.blob();
99
- const base64 = await blobToBase64(blob);
100
- if (key === "primary") {
101
- const ext = mimeExt[item.mimeType] ?? "bin";
102
- blobs.push({ path: `assets/images/${item.folder}/original.${ext}`, base64 });
103
- } else {
104
- blobs.push({ path: `assets/images/${item.folder}/${key}.webp`, base64 });
105
- }
106
- } catch {
107
- failedBlobFetches.push(`${item.id}/${key}`);
108
- }
109
- }
110
-
111
- if (failedBlobFetches.length > 0) {
112
- throw new Error(`Media upload failed: could not read blob data for ${failedBlobFetches.join(", ")}`);
113
- }
114
-
115
- if (blobs.length > 0) {
116
- mediaUploads.push({ item, blobs });
117
- }
118
- }
119
-
120
- // Build updated manifest
121
- let updatedManifest: MediaManifest | undefined;
122
- if (hasMediaChanges) {
123
- const images = { ...mediaManifest.images };
124
- for (const upload of mediaUploads) {
125
- images[upload.item.id] = upload.item;
126
- }
127
- for (const id of pendingMediaDeletions) {
128
- delete images[id];
129
- }
130
- updatedManifest = { images };
131
168
  }
132
169
 
133
170
  const response = await fetch("/api/save", {
134
171
  method: "POST",
135
172
  headers: { "Content-Type": "application/json" },
136
173
  body: JSON.stringify({
174
+ targetBranch: "saved",
137
175
  sections: dirty.map(({ sectionId, content }) => ({
138
176
  id: sectionId,
139
177
  content,
140
178
  })),
141
- siteIndex: siteIndexRef.current,
179
+ siteIndex,
180
+ ...(deletedSectionIds?.length ? { deletedSectionIds } : {}),
142
181
  ...(isConfigDirty() ? { siteConfig } : {}),
143
- ...(hasMediaChanges ? {
182
+ ...(mediaChanged ? {
144
183
  media: {
145
184
  uploads: mediaUploads.map(({ item, blobs }) => ({ item, blobs })),
146
185
  deletions: pendingMediaDeletions.map((id) => ({
@@ -155,33 +194,158 @@ export function useEditorPublish({
155
194
 
156
195
  if (!response.ok) {
157
196
  const errorBody = await response.json().catch(() => ({}));
158
- throw new Error(errorBody.error || "Publish failed");
197
+ throw new Error(errorBody.error || "Save failed");
159
198
  }
160
199
 
161
200
  const { sha } = await response.json();
162
201
 
163
202
  await discardLocalChanges();
164
203
  await clearPendingMedia();
165
- await cacheContent(sha, sections, siteIndexRef.current, siteConfig);
166
204
  clearConfigDirty();
167
205
  onSuccess();
168
206
  onMediaPublished(pendingMediaItems, pendingMediaDeletions);
207
+ onShasUpdated(sha, null);
169
208
  for (const url of blobUrlsToRevoke) {
170
209
  URL.revokeObjectURL(url);
171
210
  }
172
211
 
173
- setPublishFeedback("Published");
174
- if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
175
- feedbackTimerRef.current = setTimeout(() => setPublishFeedback(null), 3000);
212
+ showFeedback("Saved", 3000);
213
+ } catch (error) {
214
+ console.error("Save failed:", error);
215
+ showFeedback("Save failed", 5000);
216
+ } finally {
217
+ setIsPublishing(false);
218
+ }
219
+ }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback]);
220
+
221
+ const handlePublish = useCallback(async () => {
222
+ setIsPublishing(true);
223
+ setPublishFeedback(null);
224
+
225
+ try {
226
+ const response = await fetch("/api/publish", {
227
+ method: "POST",
228
+ headers: { "Content-Type": "application/json" },
229
+ });
230
+
231
+ if (!response.ok) {
232
+ const errorBody = await response.json().catch(() => ({}));
233
+ throw new Error(errorBody.error || "Publish failed");
234
+ }
235
+
236
+ const { sha } = await response.json();
237
+
238
+ onShasUpdated(null, sha);
239
+ showFeedback("Published", 3000);
176
240
  } catch (error) {
177
241
  console.error("Publish failed:", error);
178
- setPublishFeedback("Publish failed");
179
- if (feedbackTimerRef.current) clearTimeout(feedbackTimerRef.current);
180
- feedbackTimerRef.current = setTimeout(() => setPublishFeedback(null), 5000);
242
+ showFeedback("Publish failed", 5000);
243
+ } finally {
244
+ setIsPublishing(false);
245
+ }
246
+ }, [onShasUpdated, showFeedback]);
247
+
248
+ const handleSaveAndPublish = useCallback(async () => {
249
+ if (!siteConfig) return;
250
+
251
+ setIsPublishing(true);
252
+ setPublishFeedback(null);
253
+
254
+ try {
255
+ cancelPendingFlush();
256
+ await flushNow();
257
+
258
+ const hasChanges = await hasLocalChanges();
259
+ const hasMediaChanges = pendingMediaItems.length > 0 || pendingMediaDeletions.length > 0;
260
+ const hasDeletedSections = (deletedSectionIds?.length ?? 0) > 0;
261
+ const hasLocalEdits = hasChanges || isConfigDirty() || hasMediaChanges || hasDeletedSections;
262
+
263
+ let blobUrlsToRevoke: string[] = [];
264
+
265
+ if (hasLocalEdits) {
266
+ const dirty = await getDirtySections();
267
+ const gathered = await gatherMediaPayload();
268
+ blobUrlsToRevoke = gathered.blobUrlsToRevoke;
269
+
270
+ // Build a filtered siteIndex if there are deletions
271
+ let siteIndex = siteIndexRef.current;
272
+ if (deletedSectionIds?.length) {
273
+ const deleteSet = new Set(deletedSectionIds);
274
+ const filteredSections = { ...siteIndex.sections };
275
+ for (const id of deletedSectionIds) {
276
+ delete filteredSections[id];
277
+ }
278
+ siteIndex = {
279
+ ...siteIndex,
280
+ order: siteIndex.order.filter((id) => !deleteSet.has(id)),
281
+ sections: filteredSections,
282
+ };
283
+ }
284
+
285
+ const saveResponse = await fetch("/api/save", {
286
+ method: "POST",
287
+ headers: { "Content-Type": "application/json" },
288
+ body: JSON.stringify({
289
+ targetBranch: "saved",
290
+ sections: dirty.map(({ sectionId, content }) => ({
291
+ id: sectionId,
292
+ content,
293
+ })),
294
+ siteIndex,
295
+ ...(deletedSectionIds?.length ? { deletedSectionIds } : {}),
296
+ ...(isConfigDirty() ? { siteConfig } : {}),
297
+ ...(gathered.hasMediaChanges ? {
298
+ media: {
299
+ uploads: gathered.mediaUploads.map(({ item, blobs }) => ({ item, blobs })),
300
+ deletions: pendingMediaDeletions.map((id) => ({
301
+ id,
302
+ folder: mediaManifest.images[id]?.folder,
303
+ })).filter((d) => d.folder),
304
+ manifest: gathered.updatedManifest,
305
+ },
306
+ } : {}),
307
+ }),
308
+ });
309
+
310
+ if (!saveResponse.ok) {
311
+ const errorBody = await saveResponse.json().catch(() => ({}));
312
+ throw new Error(errorBody.error || "Save failed");
313
+ }
314
+ }
315
+
316
+ const publishResponse = await fetch("/api/publish", {
317
+ method: "POST",
318
+ headers: { "Content-Type": "application/json" },
319
+ });
320
+
321
+ if (!publishResponse.ok) {
322
+ const errorBody = await publishResponse.json().catch(() => ({}));
323
+ throw new Error(errorBody.error || "Publish failed");
324
+ }
325
+
326
+ const { sha } = await publishResponse.json();
327
+
328
+ if (hasLocalEdits) {
329
+ await discardLocalChanges();
330
+ await clearPendingMedia();
331
+ await cacheContent(sha, sections, siteIndexRef.current, siteConfig);
332
+ clearConfigDirty();
333
+ onSuccess();
334
+ onMediaPublished(pendingMediaItems, pendingMediaDeletions);
335
+ for (const url of blobUrlsToRevoke) {
336
+ URL.revokeObjectURL(url);
337
+ }
338
+ }
339
+
340
+ onShasUpdated(null, sha);
341
+ showFeedback("Published", 3000);
342
+ } catch (error) {
343
+ console.error("Publish failed:", error);
344
+ showFeedback("Publish failed", 5000);
181
345
  } finally {
182
346
  setIsPublishing(false);
183
347
  }
184
- }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished]);
348
+ }, [flushNow, cancelPendingFlush, isConfigDirty, clearConfigDirty, siteIndexRef, siteConfig, sections, deletedSectionIds, onSuccess, mediaManifest, pendingMediaItems, pendingMediaDeletions, onMediaPublished, onShasUpdated, showFeedback]);
185
349
 
186
- return { isPublishing, publishFeedback, handlePublish };
350
+ return { isPublishing, publishFeedback, handleSave, handlePublish, handleSaveAndPublish };
187
351
  }
package/src/lib/dexie.ts CHANGED
@@ -15,12 +15,14 @@ interface SiteIndexRow {
15
15
  key: string;
16
16
  order: string[];
17
17
  sections: Record<string, SectionMeta>;
18
+ deletedSections: string[];
18
19
  updatedAt: string;
19
20
  }
20
21
 
21
22
  interface MetaRow {
22
23
  key: string;
23
24
  lastSavedSha: string | null;
25
+ mainSha: string | null;
24
26
  lastSavedAt: string | null;
25
27
  siteId: string;
26
28
  }
@@ -98,6 +100,16 @@ class EditorDatabase extends Dexie {
98
100
  pendingMedia: "id",
99
101
  pendingMediaDeletions: "id",
100
102
  });
103
+ this.version(5).stores({
104
+ sections: "sectionId",
105
+ siteIndex: "key",
106
+ meta: "key",
107
+ siteConfig: "key",
108
+ contentCache: "key",
109
+ mediaManifest: "key",
110
+ pendingMedia: "id",
111
+ pendingMediaDeletions: "id",
112
+ });
101
113
  }
102
114
  }
103
115
 
@@ -132,6 +144,7 @@ export async function restoreLocalChanges(): Promise<{
132
144
  sections: Record<string, SectionContent>;
133
145
  siteIndex?: SiteIndex;
134
146
  siteConfig?: Record<string, unknown>;
147
+ deletedSections: string[];
135
148
  }> {
136
149
  const sectionRows = await getDb().sections.toArray();
137
150
  const sections: Record<string, SectionContent> = {};
@@ -149,10 +162,11 @@ export async function restoreLocalChanges(): Promise<{
149
162
  sections,
150
163
  siteIndex: { siteId, order: indexRow.order, sections: indexRow.sections },
151
164
  siteConfig: configRow?.config,
165
+ deletedSections: indexRow.deletedSections ?? [],
152
166
  };
153
167
  }
154
168
 
155
- return { sections, siteIndex: undefined, siteConfig: configRow?.config };
169
+ return { sections, siteIndex: undefined, siteConfig: configRow?.config, deletedSections: [] };
156
170
  }
157
171
 
158
172
  export async function discardLocalChanges(): Promise<void> {
@@ -168,7 +182,7 @@ export async function discardLocalChanges(): Promise<void> {
168
182
  });
169
183
  }
170
184
 
171
- export async function persistSiteIndex(index: SiteIndex): Promise<void> {
185
+ export async function persistSiteIndex(index: SiteIndex, deletedSections: string[] = []): Promise<void> {
172
186
  const now = new Date().toISOString();
173
187
  const database = getDb();
174
188
  await database.transaction("rw", [database.siteIndex, database.meta], async () => {
@@ -176,11 +190,13 @@ export async function persistSiteIndex(index: SiteIndex): Promise<void> {
176
190
  key: "current",
177
191
  order: index.order,
178
192
  sections: index.sections,
193
+ deletedSections,
179
194
  updatedAt: now,
180
195
  });
181
196
  await database.meta.put({
182
197
  key: "current",
183
198
  lastSavedSha: null,
199
+ mainSha: null,
184
200
  lastSavedAt: null,
185
201
  siteId: index.siteId,
186
202
  });
@@ -246,11 +262,13 @@ export async function persistAll(
246
262
  key: "current",
247
263
  order: siteIndex.order,
248
264
  sections: siteIndex.sections,
265
+ deletedSections: [],
249
266
  updatedAt: now,
250
267
  });
251
268
  await database.meta.put({
252
269
  key: "current",
253
270
  lastSavedSha: null,
271
+ mainSha: null,
254
272
  lastSavedAt: null,
255
273
  siteId: siteIndex.siteId,
256
274
  });
@@ -267,6 +285,29 @@ export async function persistAll(
267
285
  );
268
286
  }
269
287
 
288
+ export async function updateBranchShas(savedSha: string | null, mainSha: string | null): Promise<void> {
289
+ const database = getDb();
290
+ const existing = await database.meta.get("current");
291
+ if (!existing) return;
292
+ await database.meta.put({
293
+ ...existing,
294
+ lastSavedSha: savedSha,
295
+ mainSha,
296
+ lastSavedAt: new Date().toISOString(),
297
+ });
298
+ }
299
+
300
+ export async function getBranchShas(): Promise<{ savedSha: string | null; mainSha: string | null } | null> {
301
+ const row = await getDb().meta.get("current");
302
+ if (!row) return null;
303
+ return { savedSha: row.lastSavedSha, mainSha: row.mainSha ?? null };
304
+ }
305
+
306
+ export async function getDeletedSections(): Promise<string[]> {
307
+ const row = await getDb().siteIndex.get("current");
308
+ return row?.deletedSections ?? [];
309
+ }
310
+
270
311
  export async function cacheContent(
271
312
  sha: string,
272
313
  sections: LoadedSection[],
@@ -73,11 +73,16 @@ export interface WrapperProps {
73
73
  audiences: Audience[];
74
74
  access: string[];
75
75
  onAccessChange?: (access: string[]) => void;
76
- onStatusChange?: (status: "draft" | "published" | "archived") => void;
76
+ onStatusChange?: (status: "draft" | "live" | "archived") => void;
77
77
  onSectionChange?: (options: Record<string, unknown>) => void;
78
78
  onReorder?: (fromIndex: number, toIndex: number) => void;
79
79
  onRequestInsert?: (index: number) => void;
80
80
  onDelete?: () => void;
81
+ isDeleted?: boolean;
82
+ onUndoDelete?: () => void;
83
+ mainStatus?: string | null;
84
+ contentDiffersFromMain?: boolean;
85
+ isLocalOnly?: boolean;
81
86
  children: React.ReactNode;
82
87
  }
83
88
 
@@ -4,7 +4,7 @@ import { HexColorSchema } from "./shared";
4
4
 
5
5
  export const SectionMetaSchema = z.object({
6
6
  type: z.string(),
7
- status: z.enum(["draft", "published", "archived"]),
7
+ status: z.enum(["draft", "live", "archived"]),
8
8
  access: z.array(z.string()),
9
9
  });
10
10