@prenta/admin 0.93.0 → 0.94.0

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 (90) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
  3. package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
  4. package/dist/__tests__/lib/version-diff.test.js +230 -0
  5. package/dist/__tests__/lib/version-diff.test.js.map +1 -0
  6. package/dist/__tests__/lib/version-history.test.d.ts +2 -0
  7. package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
  8. package/dist/__tests__/lib/version-history.test.js +125 -0
  9. package/dist/__tests__/lib/version-history.test.js.map +1 -0
  10. package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
  11. package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
  12. package/dist/__tests__/styles/responsive-layer.test.js +127 -0
  13. package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
  14. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
  15. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
  16. package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
  17. package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
  18. package/dist/__tests__/views/editor-top-bar.render.test.js +29 -0
  19. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  20. package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
  21. package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
  22. package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
  23. package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
  24. package/dist/components/EditorRail.js +3 -3
  25. package/dist/components/EditorRail.js.map +1 -1
  26. package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -1
  27. package/dist/components/page-tab/DirectiveSwitch.js +5 -1
  28. package/dist/components/page-tab/DirectiveSwitch.js.map +1 -1
  29. package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
  30. package/dist/layout/primitives/AdminShell.js +2 -2
  31. package/dist/layout/primitives/AdminShell.js.map +1 -1
  32. package/dist/lib/version-diff.d.ts +50 -0
  33. package/dist/lib/version-diff.d.ts.map +1 -0
  34. package/dist/lib/version-diff.js +294 -0
  35. package/dist/lib/version-diff.js.map +1 -0
  36. package/dist/lib/version-history.d.ts +73 -0
  37. package/dist/lib/version-history.d.ts.map +1 -0
  38. package/dist/lib/version-history.js +126 -0
  39. package/dist/lib/version-history.js.map +1 -0
  40. package/dist/prenta-admin.css +1 -1
  41. package/dist/views/page-editor/EditorCanvas.d.ts +14 -1
  42. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  43. package/dist/views/page-editor/EditorCanvas.js +3 -3
  44. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  45. package/dist/views/page-editor/EditorTopBar.d.ts +8 -2
  46. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  47. package/dist/views/page-editor/EditorTopBar.js +8 -6
  48. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  49. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  50. package/dist/views/page-editor/PageSectionEditor.js +115 -32
  51. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  52. package/dist/views/page-editor/ReviewBar.d.ts +21 -0
  53. package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
  54. package/dist/views/page-editor/ReviewBar.js +22 -0
  55. package/dist/views/page-editor/ReviewBar.js.map +1 -0
  56. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  57. package/dist/views/page-editor/SectionInspector.js +1 -1
  58. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  59. package/dist/views/page-editor/SectionsPanel.js +1 -1
  60. package/dist/views/page-editor/SectionsPanel.js.map +1 -1
  61. package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
  62. package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
  63. package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
  64. package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
  65. package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
  66. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  67. package/dist/views/page-editor/sections/SectionRenderer.js +23 -8
  68. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  69. package/package.json +3 -3
  70. package/src/__tests__/lib/version-diff.test.ts +278 -0
  71. package/src/__tests__/lib/version-history.test.ts +166 -0
  72. package/src/__tests__/styles/responsive-layer.test.ts +152 -0
  73. package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
  74. package/src/__tests__/views/editor-top-bar.render.test.tsx +42 -0
  75. package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
  76. package/src/components/EditorRail.tsx +5 -5
  77. package/src/components/page-tab/DirectiveSwitch.tsx +4 -1
  78. package/src/layout/primitives/AdminShell.tsx +13 -1
  79. package/src/lib/version-diff.ts +376 -0
  80. package/src/lib/version-history.ts +192 -0
  81. package/src/styles/responsive.css +281 -0
  82. package/src/styles/theme.css +8 -0
  83. package/src/views/page-editor/EditorCanvas.tsx +34 -7
  84. package/src/views/page-editor/EditorTopBar.tsx +20 -8
  85. package/src/views/page-editor/PageSectionEditor.tsx +162 -43
  86. package/src/views/page-editor/ReviewBar.tsx +76 -0
  87. package/src/views/page-editor/SectionInspector.tsx +2 -1
  88. package/src/views/page-editor/SectionsPanel.tsx +2 -2
  89. package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
  90. package/src/views/page-editor/sections/SectionRenderer.tsx +53 -12
@@ -0,0 +1,50 @@
1
+ import type { PageSection } from './page-editor-service.js';
2
+ /** One changed field on one section. */
3
+ export interface FieldChange {
4
+ sectionId: string;
5
+ /** Editor-facing section name, captured so the panel needn't re-look it up. */
6
+ sectionName: string;
7
+ /** Human label for the field ("Heading"), from the section registry. */
8
+ field: string;
9
+ /** Formatted previous value; `—` when absent. */
10
+ from: string;
11
+ /** Formatted new value; `—` when absent. */
12
+ to: string;
13
+ }
14
+ /** Changes for one section, in the order the sections appear on the page. */
15
+ export interface SectionChangeGroup {
16
+ sectionId: string;
17
+ sectionName: string;
18
+ changes: FieldChange[];
19
+ }
20
+ /**
21
+ * Render a value as the one line the diff shows.
22
+ *
23
+ * Lossy on purpose — a repeater row expanded in full would bury the change it
24
+ * is reporting. It is used for DISPLAY ONLY; {@link deepEqual} decides whether
25
+ * a field changed, and repeater rows are descended into by
26
+ * {@link compareRepeater} so a collapsed summary never has to stand in for a
27
+ * change it cannot show.
28
+ */
29
+ export declare function formatValue(value: unknown): string;
30
+ /**
31
+ * Compute what changed going FROM `older` TO `newer`.
32
+ *
33
+ * Diffing version *i* against version *i+1* therefore yields what changed *in*
34
+ * version *i* — the framing the history panel needs, where each row answers
35
+ * "what did this save do?" rather than "how does this differ from now?".
36
+ *
37
+ * `older` is optional because the earliest version in a list has nothing
38
+ * before it. That case is no changes, not "everything was added": the oldest
39
+ * snapshot we happen to hold is not the moment the page came into existence,
40
+ * and reporting every field as new would say it was.
41
+ */
42
+ export declare function diffVersions(newer: readonly PageSection[], older: readonly PageSection[] | null | undefined): FieldChange[];
43
+ /**
44
+ * Group changes by section, preserving first-seen order so the panel lists
45
+ * them top-to-bottom in the order they appear on the page.
46
+ */
47
+ export declare function groupChangesBySection(changes: readonly FieldChange[]): SectionChangeGroup[];
48
+ /** The ids the canvas outlines while reviewing a version. */
49
+ export declare function changedSectionIds(changes: readonly FieldChange[]): Set<string>;
50
+ //# sourceMappingURL=version-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-diff.d.ts","sourceRoot":"","sources":["../../src/lib/version-diff.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,+EAA+E;IAC/E,WAAW,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,EAAE,EAAE,MAAM,CAAA;CACX;AAED,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AA8FD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAmBlD;AAuGD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,GAC/C,WAAW,EAAE,CAyEf;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,kBAAkB,EAAE,CAW3F;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAE9E"}
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Field-level diff between two page snapshots.
3
+ *
4
+ * One of the three parts of the page-builder handoff specified as real logic
5
+ * rather than mockup ("the version diff — field-level, computed from full
6
+ * snapshots, and its output drives the changed-section outlines on the
7
+ * canvas"). Nothing here is display-only: the same result feeds the history
8
+ * panel's per-field list AND the set of sections the canvas outlines while
9
+ * reviewing, so the two can never disagree about what changed.
10
+ *
11
+ * Pure over `PageSection[]`, with no React and no fetching, because the diff
12
+ * is asked for in three different places (panel rows, canvas outlines, the
13
+ * review bar's count) and recomputing it from the same inputs has to be free
14
+ * of side effects.
15
+ */
16
+ import { getSectionType, resolveRepeaterField, } from '../views/page-editor/section-types.js';
17
+ /**
18
+ * Labels for the keys that are not content fields and so have no entry in the
19
+ * section registry. Visibility earns its place here: hiding a section is the
20
+ * one structural change a design-owned CMS lets an editor make, so a history
21
+ * that silently dropped it would be missing the only structural edit there is.
22
+ */
23
+ const META_LABELS = {
24
+ visible: 'Visibility',
25
+ name: 'Section name',
26
+ };
27
+ const SETTING_LABELS = {
28
+ backgroundType: 'Background type',
29
+ backgroundColor: 'Background colour',
30
+ paddingY: 'Vertical padding',
31
+ anchorId: 'Anchor id',
32
+ cssClass: 'CSS class',
33
+ };
34
+ const EMPTY = '—';
35
+ /**
36
+ * Label a content key through the section registry — the same definitions the
37
+ * inspector renders from, so the diff reads in the words the editor typed
38
+ * under. Falls back to the raw key for content on a custom or unmanaged type
39
+ * the registry does not describe: a key is uglier than a label but it is true,
40
+ * whereas omitting the row would hide a real change.
41
+ */
42
+ function contentFieldLabel(sectionType, key) {
43
+ const def = getSectionType(sectionType);
44
+ const field = def?.fields.find((f) => f.key === key);
45
+ return field?.label ?? key;
46
+ }
47
+ /**
48
+ * The repeater shape behind a content key, or null when the field is not one.
49
+ *
50
+ * Goes through `resolveRepeaterField` rather than reading `field.fields`
51
+ * directly: `stats` and `gallery` are shorthand presets whose sub-fields are
52
+ * supplied by the registry, not spelled out on the definition, so reading the
53
+ * raw property finds nothing and the diff falls back to raw keys ("value"
54
+ * instead of "Value") on the two repeaters that ship by default.
55
+ */
56
+ function repeaterFor(sectionType, key) {
57
+ const def = getSectionType(sectionType);
58
+ const field = def?.fields.find((f) => f.key === key);
59
+ return field ? resolveRepeaterField(field) : null;
60
+ }
61
+ function isPlainObject(value) {
62
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
63
+ }
64
+ /**
65
+ * Values that all render as `—` and must therefore compare as equal.
66
+ *
67
+ * Without this, tightening the comparison from formatted strings to structure
68
+ * would start reporting `'' → undefined` as a change, emitting rows that read
69
+ * "— → —". Blank is blank however it is spelled.
70
+ */
71
+ function isBlank(value) {
72
+ if (value === null || value === undefined)
73
+ return true;
74
+ if (typeof value === 'string')
75
+ return value.trim() === '';
76
+ if (Array.isArray(value))
77
+ return value.length === 0;
78
+ if (isPlainObject(value))
79
+ return Object.keys(value).length === 0;
80
+ return false;
81
+ }
82
+ /**
83
+ * Structural equality, which is what decides whether a field changed.
84
+ *
85
+ * This deliberately does NOT go through {@link formatValue}. Comparing the
86
+ * display strings is the obvious shortcut and it silently loses real edits:
87
+ * `formatValue` collapses an array to "N items", so retyping a stats row while
88
+ * keeping the row count formats identically on both sides and the field reports
89
+ * no change — no row in the panel, and no outline on the canvas. That is
90
+ * precisely the edit the reviewer is looking for.
91
+ */
92
+ function deepEqual(a, b) {
93
+ if (isBlank(a) && isBlank(b))
94
+ return true;
95
+ if (a === b)
96
+ return true;
97
+ if (Array.isArray(a) && Array.isArray(b)) {
98
+ return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
99
+ }
100
+ if (isPlainObject(a) && isPlainObject(b)) {
101
+ const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
102
+ for (const key of keys)
103
+ if (!deepEqual(a[key], b[key]))
104
+ return false;
105
+ return true;
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * Render a value as the one line the diff shows.
111
+ *
112
+ * Lossy on purpose — a repeater row expanded in full would bury the change it
113
+ * is reporting. It is used for DISPLAY ONLY; {@link deepEqual} decides whether
114
+ * a field changed, and repeater rows are descended into by
115
+ * {@link compareRepeater} so a collapsed summary never has to stand in for a
116
+ * change it cannot show.
117
+ */
118
+ export function formatValue(value) {
119
+ if (value === null || value === undefined)
120
+ return EMPTY;
121
+ if (typeof value === 'string')
122
+ return value.trim() === '' ? EMPTY : value;
123
+ if (typeof value === 'boolean')
124
+ return value ? 'Yes' : 'No';
125
+ if (typeof value === 'number')
126
+ return String(value);
127
+ if (Array.isArray(value)) {
128
+ if (value.length === 0)
129
+ return EMPTY;
130
+ return `${value.length} item${value.length === 1 ? '' : 's'}`;
131
+ }
132
+ if (typeof value === 'object') {
133
+ const record = value;
134
+ for (const key of ['name', 'title', 'label', 'url', 'alt']) {
135
+ const candidate = record[key];
136
+ if (typeof candidate === 'string' && candidate.trim() !== '')
137
+ return candidate;
138
+ }
139
+ const count = Object.keys(record).length;
140
+ return count === 0 ? EMPTY : `${count} field${count === 1 ? '' : 's'}`;
141
+ }
142
+ return String(value);
143
+ }
144
+ /** Visibility reads as a state, not a boolean — "No" is not a useful diff. */
145
+ function formatVisible(value) {
146
+ return value === false ? 'Hidden' : 'Shown';
147
+ }
148
+ function push(out, section, field, from, to) {
149
+ out.push({ sectionId: section.id, sectionName: section.name, field, from, to });
150
+ }
151
+ function compare(out, section, field, before, after, format = formatValue) {
152
+ if (deepEqual(before, after))
153
+ return;
154
+ push(out, section, field, format(before), format(after));
155
+ }
156
+ /**
157
+ * Diff a repeater (stats rows, gallery images, FAQ entries) row by row.
158
+ *
159
+ * A single "2 items → 2 items" row would be worse than useless: it is the one
160
+ * case where the summary cannot express the change, and the reviewer's next
161
+ * question — *which* row — has no answer. Descending gives
162
+ * "Stats · Row 2 · Value" and a real before/after.
163
+ */
164
+ function compareRepeater(out, section, sectionType, key, label, before, after) {
165
+ const repeater = repeaterFor(sectionType, key);
166
+ // The registry's own noun for a row ("Stat", "Image") when it has one.
167
+ const rowNoun = repeater?.rowLabel ?? 'Row';
168
+ const subLabel = (subKey) => repeater?.fields.find((f) => f.key === subKey)?.label ?? subKey;
169
+ for (let i = 0; i < Math.max(before.length, after.length); i++) {
170
+ const rowLabel = `${label} · ${rowNoun} ${i + 1}`;
171
+ const prev = before[i];
172
+ const next = after[i];
173
+ if (prev === undefined) {
174
+ push(out, section, rowLabel, EMPTY, 'Added');
175
+ continue;
176
+ }
177
+ if (next === undefined) {
178
+ push(out, section, rowLabel, 'Present', 'Removed');
179
+ continue;
180
+ }
181
+ if (deepEqual(prev, next))
182
+ continue;
183
+ // Rows are normally objects of sub-fields; anything else (a bare string in
184
+ // a legacy gallery, say) still gets a row rather than being skipped.
185
+ if (!isPlainObject(prev) || !isPlainObject(next)) {
186
+ push(out, section, rowLabel, formatValue(prev), formatValue(next));
187
+ continue;
188
+ }
189
+ for (const subKey of new Set([...Object.keys(prev), ...Object.keys(next)])) {
190
+ compare(out, section, `${rowLabel} · ${subLabel(subKey)}`, prev[subKey], next[subKey]);
191
+ }
192
+ }
193
+ }
194
+ /** One content field: descends into repeaters, otherwise a plain comparison. */
195
+ function compareContentField(out, section, sectionType, key, before, after) {
196
+ if (deepEqual(before, after))
197
+ return;
198
+ const label = contentFieldLabel(sectionType, key);
199
+ const beforeRows = Array.isArray(before) ? before : null;
200
+ const afterRows = Array.isArray(after) ? after : null;
201
+ if (beforeRows && afterRows) {
202
+ compareRepeater(out, section, sectionType, key, label, beforeRows, afterRows);
203
+ return;
204
+ }
205
+ push(out, section, label, formatValue(before), formatValue(after));
206
+ }
207
+ /**
208
+ * Compute what changed going FROM `older` TO `newer`.
209
+ *
210
+ * Diffing version *i* against version *i+1* therefore yields what changed *in*
211
+ * version *i* — the framing the history panel needs, where each row answers
212
+ * "what did this save do?" rather than "how does this differ from now?".
213
+ *
214
+ * `older` is optional because the earliest version in a list has nothing
215
+ * before it. That case is no changes, not "everything was added": the oldest
216
+ * snapshot we happen to hold is not the moment the page came into existence,
217
+ * and reporting every field as new would say it was.
218
+ */
219
+ export function diffVersions(newer, older) {
220
+ if (!older)
221
+ return [];
222
+ const out = [];
223
+ const olderById = new Map(older.map((s) => [s.id, s]));
224
+ for (const next of newer) {
225
+ const prev = olderById.get(next.id);
226
+ if (!prev) {
227
+ out.push({
228
+ sectionId: next.id,
229
+ sectionName: next.name,
230
+ field: 'Section',
231
+ from: EMPTY,
232
+ to: 'Added',
233
+ });
234
+ continue;
235
+ }
236
+ if (next.name !== prev.name) {
237
+ compare(out, next, META_LABELS.name, prev.name, next.name);
238
+ }
239
+ compare(out, next, META_LABELS.visible, prev.visible, next.visible, formatVisible);
240
+ // Union of both key sets: a field cleared to undefined only appears in the
241
+ // older snapshot, and clearing a heading is exactly the kind of edit a
242
+ // history is consulted about.
243
+ const contentKeys = new Set([
244
+ ...Object.keys(prev.content ?? {}),
245
+ ...Object.keys(next.content ?? {}),
246
+ ]);
247
+ for (const key of contentKeys) {
248
+ compareContentField(out, next, next.sectionType, key, prev.content?.[key], next.content?.[key]);
249
+ }
250
+ const settingKeys = new Set([
251
+ ...Object.keys(prev.settings ?? {}),
252
+ ...Object.keys(next.settings ?? {}),
253
+ ]);
254
+ for (const key of settingKeys) {
255
+ compare(out, next, SETTING_LABELS[key] ?? key, prev.settings?.[key], next.settings?.[key]);
256
+ }
257
+ }
258
+ // Sections present before and gone after. Reported against the OLD section's
259
+ // name because the new snapshot has nothing to name it with.
260
+ const newerIds = new Set(newer.map((s) => s.id));
261
+ for (const gone of older) {
262
+ if (newerIds.has(gone.id))
263
+ continue;
264
+ out.push({
265
+ sectionId: gone.id,
266
+ sectionName: gone.name,
267
+ field: 'Section',
268
+ from: 'Present',
269
+ to: 'Removed',
270
+ });
271
+ }
272
+ return out;
273
+ }
274
+ /**
275
+ * Group changes by section, preserving first-seen order so the panel lists
276
+ * them top-to-bottom in the order they appear on the page.
277
+ */
278
+ export function groupChangesBySection(changes) {
279
+ const groups = new Map();
280
+ for (const change of changes) {
281
+ let group = groups.get(change.sectionId);
282
+ if (!group) {
283
+ group = { sectionId: change.sectionId, sectionName: change.sectionName, changes: [] };
284
+ groups.set(change.sectionId, group);
285
+ }
286
+ group.changes.push(change);
287
+ }
288
+ return [...groups.values()];
289
+ }
290
+ /** The ids the canvas outlines while reviewing a version. */
291
+ export function changedSectionIds(changes) {
292
+ return new Set(changes.map((c) => c.sectionId));
293
+ }
294
+ //# sourceMappingURL=version-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-diff.js","sourceRoot":"","sources":["../../src/lib/version-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { type PageSection } from '@prenta/core/page-sections';
2
+ /**
3
+ * Mirrors the `ChangeType` enum in the Prisma schema.
4
+ *
5
+ * `PUBLISH` and `RESTORE` were declared there from the beginning and nothing
6
+ * ever wrote them; `cms-core` now does. Rows written before that read `UPDATE`
7
+ * whatever they actually were, which is why {@link versionKind} labels
8
+ * `UPDATE` as "Saved" rather than "Draft" — the neutral reading is the only
9
+ * one that is true for both old and new rows.
10
+ */
11
+ export type VersionChangeType = 'CREATE' | 'UPDATE' | 'PUBLISH' | 'RESTORE' | 'DELETE';
12
+ export interface VersionAuthor {
13
+ id: string;
14
+ /** Display name, falling back to email and then to a non-blank placeholder. */
15
+ name: string;
16
+ }
17
+ export interface PageVersion {
18
+ id: string;
19
+ changeType: VersionChangeType;
20
+ createdAt: string;
21
+ author: VersionAuthor;
22
+ /** Full page content as of this version — what the canvas renders. */
23
+ sections: PageSection[];
24
+ }
25
+ /** Versions sharing a calendar day, newest day first. */
26
+ export interface VersionDayGroup {
27
+ /** `Today` / `Yesterday` / `Jul 27` — the sticky header text. */
28
+ label: string;
29
+ versions: PageVersion[];
30
+ }
31
+ /** How one change type presents as a pill. */
32
+ export interface VersionKind {
33
+ label: string;
34
+ /** Semantic tone; the panel maps these onto token classes. */
35
+ tone: 'live' | 'draft' | 'neutral' | 'danger';
36
+ }
37
+ export declare function versionKind(changeType: string): VersionKind;
38
+ interface VersionApiRow {
39
+ id: string;
40
+ changeType: string;
41
+ createdAt: string;
42
+ data: Record<string, unknown> | null;
43
+ changedBy?: {
44
+ id: string;
45
+ name?: string | null;
46
+ email?: string | null;
47
+ } | null;
48
+ }
49
+ export declare function toPageVersion(row: VersionApiRow): PageVersion;
50
+ /** Newest first, matching the API's own ordering. */
51
+ export declare function fetchPageVersions(collectionSlug: string, documentId: string): Promise<PageVersion[]>;
52
+ export declare function restorePageVersion(collectionSlug: string, documentId: string, versionId: string): Promise<void>;
53
+ /**
54
+ * `Today` / `Yesterday` / a short date.
55
+ *
56
+ * `now` is a parameter rather than a `Date.now()` call so the label is
57
+ * testable — "relative to today" is untestable when today is whatever the
58
+ * suite happens to run on.
59
+ */
60
+ export declare function dayLabel(iso: string, now?: Date): string;
61
+ export declare function timeLabel(iso: string): string;
62
+ /**
63
+ * Group an already-ordered list into consecutive runs sharing a day label.
64
+ *
65
+ * Consecutive runs, not a keyed bucket: the list arrives newest-first and must
66
+ * stay that way, and a `Map` keyed on the label would silently merge two runs
67
+ * that are not adjacent if the API ever returned an unsorted page.
68
+ */
69
+ export declare function groupVersionsByDay(versions: readonly PageVersion[], now?: Date): VersionDayGroup[];
70
+ /** Two-letter monogram for the row avatar. */
71
+ export declare function initials(name: string): string;
72
+ export {};
73
+ //# sourceMappingURL=version-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-history.d.ts","sourceRoot":"","sources":["../../src/lib/version-history.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAE7E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;AAEtF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,iBAAiB,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,aAAa,CAAA;IACrB,sEAAsE;IACtE,QAAQ,EAAE,WAAW,EAAE,CAAA;CACxB;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,WAAW,EAAE,CAAA;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAA;CAC9C;AAgBD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAE3D;AAED,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACpC,SAAS,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAC/E;AAYD,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,WAAW,CAY7D;AAED,qDAAqD;AACrD,wBAAsB,iBAAiB,CACrC,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAOxB;AAED,wBAAsB,kBAAkB,CACtC,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAMf;AAQD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAepE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI7C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,WAAW,EAAE,EAChC,GAAG,GAAE,IAAiB,GACrB,eAAe,EAAE,CASnB;AAED,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK7C"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Version history data layer for the page editor.
3
+ *
4
+ * History in this editor is a **mode, not a popup**: selecting a version
5
+ * renders it on the canvas, so the panel needs the full section snapshot of
6
+ * every entry, not just its metadata. `GET /collections/:slug/:id/versions`
7
+ * already returns each version's whole `data` blob — the same blob the restore
8
+ * endpoint writes back — so the snapshots are read straight off the list
9
+ * response and no second request per row is needed.
10
+ */
11
+ import { cmsApi } from './api.js';
12
+ import { coerceSections } from '@prenta/core/page-sections';
13
+ const KINDS = {
14
+ // "Published" is the one distinction that matters in a version list: which
15
+ // of these could a visitor have seen?
16
+ PUBLISH: { label: 'Published', tone: 'live' },
17
+ CREATE: { label: 'Created', tone: 'neutral' },
18
+ // NOT "Draft". Every version written before cms-core started recording
19
+ // PUBLISH is an UPDATE regardless of whether it went live, so calling this
20
+ // "Draft" would state something false about historical rows. "Saved" is true
21
+ // of all of them.
22
+ UPDATE: { label: 'Saved', tone: 'draft' },
23
+ RESTORE: { label: 'Restored', tone: 'neutral' },
24
+ DELETE: { label: 'Deleted', tone: 'danger' },
25
+ };
26
+ export function versionKind(changeType) {
27
+ return KINDS[changeType] ?? { label: 'Saved', tone: 'draft' };
28
+ }
29
+ function toAuthor(row) {
30
+ const by = row.changedBy;
31
+ return {
32
+ id: by?.id ?? '',
33
+ // An avatar built from an empty string renders a blank circle, so the
34
+ // fallback has to be a real word rather than ''.
35
+ name: by?.name?.trim() || by?.email?.trim() || 'Unknown',
36
+ };
37
+ }
38
+ export function toPageVersion(row) {
39
+ const data = row.data ?? {};
40
+ return {
41
+ id: row.id,
42
+ changeType: row.changeType ?? 'UPDATE',
43
+ createdAt: row.createdAt,
44
+ author: toAuthor(row),
45
+ // `preserve` matches how the editor reads the live document. Coercing a
46
+ // snapshot more strictly than the current page would make an unmanaged
47
+ // section look like it was deleted in every version that contains one.
48
+ sections: coerceSections(data.sections, { onUnknown: 'preserve' }),
49
+ };
50
+ }
51
+ /** Newest first, matching the API's own ordering. */
52
+ export async function fetchPageVersions(collectionSlug, documentId) {
53
+ const res = await cmsApi(`/collections/${collectionSlug}/${documentId}/versions?pageSize=50`);
54
+ if (res.error)
55
+ throw new Error(res.error);
56
+ const rows = res.data?.versions ?? [];
57
+ return rows.map(toPageVersion);
58
+ }
59
+ export async function restorePageVersion(collectionSlug, documentId, versionId) {
60
+ const res = await cmsApi(`/collections/${collectionSlug}/${documentId}/versions/${versionId}/restore`, { method: 'POST' });
61
+ if (res.error)
62
+ throw new Error(res.error);
63
+ }
64
+ // ─── Presentation helpers ────────────────────────────────────────────
65
+ function startOfDay(date) {
66
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
67
+ }
68
+ /**
69
+ * `Today` / `Yesterday` / a short date.
70
+ *
71
+ * `now` is a parameter rather than a `Date.now()` call so the label is
72
+ * testable — "relative to today" is untestable when today is whatever the
73
+ * suite happens to run on.
74
+ */
75
+ export function dayLabel(iso, now = new Date()) {
76
+ const date = new Date(iso);
77
+ if (Number.isNaN(date.getTime()))
78
+ return 'Unknown date';
79
+ const days = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);
80
+ if (days === 0)
81
+ return 'Today';
82
+ if (days === 1)
83
+ return 'Yesterday';
84
+ return date.toLocaleDateString(undefined, {
85
+ month: 'short',
86
+ day: 'numeric',
87
+ // Drop the year within the current one — "Jul 27, 2026" in a list where
88
+ // every entry is 2026 is four characters of noise per row.
89
+ ...(date.getFullYear() === now.getFullYear() ? {} : { year: 'numeric' }),
90
+ });
91
+ }
92
+ export function timeLabel(iso) {
93
+ const date = new Date(iso);
94
+ if (Number.isNaN(date.getTime()))
95
+ return '';
96
+ return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
97
+ }
98
+ /**
99
+ * Group an already-ordered list into consecutive runs sharing a day label.
100
+ *
101
+ * Consecutive runs, not a keyed bucket: the list arrives newest-first and must
102
+ * stay that way, and a `Map` keyed on the label would silently merge two runs
103
+ * that are not adjacent if the API ever returned an unsorted page.
104
+ */
105
+ export function groupVersionsByDay(versions, now = new Date()) {
106
+ const groups = [];
107
+ for (const version of versions) {
108
+ const label = dayLabel(version.createdAt, now);
109
+ const last = groups[groups.length - 1];
110
+ if (last && last.label === label)
111
+ last.versions.push(version);
112
+ else
113
+ groups.push({ label, versions: [version] });
114
+ }
115
+ return groups;
116
+ }
117
+ /** Two-letter monogram for the row avatar. */
118
+ export function initials(name) {
119
+ const parts = name.trim().split(/\s+/).filter(Boolean);
120
+ if (parts.length === 0)
121
+ return '?';
122
+ if (parts.length === 1)
123
+ return parts[0].slice(0, 2).toUpperCase();
124
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
125
+ }
126
+ //# sourceMappingURL=version-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-history.js","sourceRoot":"","sources":["../../src/lib/version-history.ts"],"names":[],"mappings":"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"}