@prenta/admin 0.93.0 → 0.95.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.
- package/CHANGELOG.md +152 -0
- package/dist/__tests__/lib/page-editor-service-api.test.js +37 -0
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
- package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-diff.test.js +230 -0
- package/dist/__tests__/lib/version-diff.test.js.map +1 -0
- package/dist/__tests__/lib/version-history.test.d.ts +2 -0
- package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-history.test.js +125 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +127 -0
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +29 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/section-edit-form.render.test.d.ts +2 -0
- package/dist/__tests__/views/section-edit-form.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-edit-form.render.test.js +124 -0
- package/dist/__tests__/views/section-edit-form.render.test.js.map +1 -0
- package/dist/__tests__/views/section-inspector-media.test.js +1 -1
- package/dist/__tests__/views/section-inspector-media.test.js.map +1 -1
- package/dist/__tests__/views/section-inspector-repeater.render.test.js +2 -1
- package/dist/__tests__/views/section-inspector-repeater.render.test.js.map +1 -1
- package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
- package/dist/components/CanvasEditTip.js +1 -1
- package/dist/components/CanvasEditTip.js.map +1 -1
- package/dist/components/EditorRail.js +3 -3
- package/dist/components/EditorRail.js.map +1 -1
- package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -1
- package/dist/components/page-tab/DirectiveSwitch.js +5 -1
- package/dist/components/page-tab/DirectiveSwitch.js.map +1 -1
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +2 -2
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/lib/version-diff.d.ts +50 -0
- package/dist/lib/version-diff.d.ts.map +1 -0
- package/dist/lib/version-diff.js +294 -0
- package/dist/lib/version-diff.js.map +1 -0
- package/dist/lib/version-history.d.ts +73 -0
- package/dist/lib/version-history.d.ts.map +1 -0
- package/dist/lib/version-history.js +126 -0
- package/dist/lib/version-history.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/EditorCanvas.d.ts +14 -1
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +3 -3
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +8 -2
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +8 -6
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +129 -40
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/ReviewBar.d.ts +21 -0
- package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
- package/dist/views/page-editor/ReviewBar.js +22 -0
- package/dist/views/page-editor/ReviewBar.js.map +1 -0
- package/dist/views/page-editor/SectionEditForm.d.ts +42 -0
- package/dist/views/page-editor/SectionEditForm.d.ts.map +1 -0
- package/dist/views/page-editor/SectionEditForm.js +48 -0
- package/dist/views/page-editor/SectionEditForm.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts +0 -20
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +6 -224
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts +10 -1
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +11 -5
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
- package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
- package/dist/views/page-editor/section-fields.d.ts +43 -0
- package/dist/views/page-editor/section-fields.d.ts.map +1 -0
- package/dist/views/page-editor/section-fields.js +236 -0
- package/dist/views/page-editor/section-fields.js.map +1 -0
- package/dist/views/page-editor/sections/ArticleBodySection.js +1 -1
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +23 -8
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +11 -9
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/lib/page-editor-service-api.test.ts +44 -0
- package/src/__tests__/lib/version-diff.test.ts +278 -0
- package/src/__tests__/lib/version-history.test.ts +166 -0
- package/src/__tests__/styles/responsive-layer.test.ts +152 -0
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +42 -0
- package/src/__tests__/views/section-edit-form.render.test.tsx +162 -0
- package/src/__tests__/views/section-inspector-media.test.ts +1 -1
- package/src/__tests__/views/section-inspector-repeater.render.test.tsx +2 -1
- package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
- package/src/components/CanvasEditTip.tsx +3 -3
- package/src/components/EditorRail.tsx +5 -5
- package/src/components/page-tab/DirectiveSwitch.tsx +4 -1
- package/src/layout/primitives/AdminShell.tsx +13 -1
- package/src/lib/version-diff.ts +376 -0
- package/src/lib/version-history.ts +192 -0
- package/src/styles/responsive.css +281 -0
- package/src/styles/theme.css +8 -0
- package/src/views/page-editor/EditorCanvas.tsx +34 -7
- package/src/views/page-editor/EditorTopBar.tsx +20 -8
- package/src/views/page-editor/PageSectionEditor.tsx +182 -65
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionEditForm.tsx +161 -0
- package/src/views/page-editor/SectionInspector.tsx +9 -553
- package/src/views/page-editor/SectionsPanel.tsx +72 -16
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/section-fields.tsx +563 -0
- package/src/views/page-editor/sections/ArticleBodySection.tsx +1 -1
- package/src/views/page-editor/sections/SectionRenderer.tsx +53 -12
- package/src/views/post-editor/PostEditorCanvas.tsx +1 -1
- package/src/views/post-editor/PostSectionEditor.tsx +18 -26
|
@@ -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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"}
|