@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.
- package/CHANGELOG.md +111 -0
- 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/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/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 +115 -32
- 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/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +1 -1
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +1 -1
- 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/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/package.json +3 -3
- 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/version-history-panel.render.test.tsx +195 -0
- 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 +162 -43
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionInspector.tsx +2 -1
- package/src/views/page-editor/SectionsPanel.tsx +2 -2
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/sections/SectionRenderer.tsx +53 -12
|
@@ -10,7 +10,14 @@ import { SeoEditorPane } from '../../components/seo/SeoEditorPane.js'
|
|
|
10
10
|
import { EditorRail, RailHeader } from '../../components/EditorRail.js'
|
|
11
11
|
import { EditorRailTabs, type RailTab, type RailTabId } from '../../components/EditorRailTabs.js'
|
|
12
12
|
import { ErrorBoundary } from '../../components/ErrorBoundary.js'
|
|
13
|
-
import {
|
|
13
|
+
import { VersionHistoryPanel } from './VersionHistoryPanel.js'
|
|
14
|
+
import { ReviewBar } from './ReviewBar.js'
|
|
15
|
+
import {
|
|
16
|
+
fetchPageVersions,
|
|
17
|
+
restorePageVersion,
|
|
18
|
+
type PageVersion,
|
|
19
|
+
} from '../../lib/version-history.js'
|
|
20
|
+
import { changedSectionIds, diffVersions } from '../../lib/version-diff.js'
|
|
14
21
|
import {
|
|
15
22
|
emptyPage,
|
|
16
23
|
fetchPageForEditor,
|
|
@@ -221,6 +228,12 @@ export function PageSectionEditor({
|
|
|
221
228
|
/** Active rail tab. Replaces the old boolean `seoOpen` right-hand aside. */
|
|
222
229
|
const [railTab, setRailTab] = useState<RailTabId>('sections')
|
|
223
230
|
const [historyOpen, setHistoryOpen] = useState(false)
|
|
231
|
+
const [versions, setVersions] = useState<PageVersion[]>([])
|
|
232
|
+
const [versionsLoading, setVersionsLoading] = useState(false)
|
|
233
|
+
const [versionsError, setVersionsError] = useState<string | null>(null)
|
|
234
|
+
/** Index into `versions`; 0 is the newest, which is the current content. */
|
|
235
|
+
const [versionIndex, setVersionIndex] = useState(0)
|
|
236
|
+
const [restoring, setRestoring] = useState(false)
|
|
224
237
|
const [confirmPublish, setConfirmPublish] = useState(false)
|
|
225
238
|
const [scheduleOpen, setScheduleOpen] = useState(false)
|
|
226
239
|
const [leaveTarget, setLeaveTarget] = useState<string | null>(null)
|
|
@@ -325,6 +338,105 @@ export function PageSectionEditor({
|
|
|
325
338
|
|
|
326
339
|
const activeRailTab = railTabs.find((tab) => tab.id === railTab) ?? railTabs[0]!
|
|
327
340
|
|
|
341
|
+
// ─── Version history ─────────────────────────────────────────────────
|
|
342
|
+
// History is a MODE, not a dialog: it overlays the rail and drives the
|
|
343
|
+
// canvas, so the version being reviewed is editor state rather than
|
|
344
|
+
// something owned by a panel that can be unmounted.
|
|
345
|
+
|
|
346
|
+
const loadVersions = useCallback(async (pageId: string) => {
|
|
347
|
+
setVersionsLoading(true)
|
|
348
|
+
setVersionsError(null)
|
|
349
|
+
try {
|
|
350
|
+
setVersions(await fetchPageVersions(PAGES_COLLECTION, pageId))
|
|
351
|
+
} catch (err) {
|
|
352
|
+
setVersions([])
|
|
353
|
+
setVersionsError(err instanceof Error ? err.message : 'Failed to load version history')
|
|
354
|
+
} finally {
|
|
355
|
+
setVersionsLoading(false)
|
|
356
|
+
}
|
|
357
|
+
}, [])
|
|
358
|
+
|
|
359
|
+
const openHistory = useCallback(() => {
|
|
360
|
+
if (!page?.id) return
|
|
361
|
+
setHistoryOpen(true)
|
|
362
|
+
// Always land on "current" so opening history never silently swaps the
|
|
363
|
+
// canvas out from under the editor.
|
|
364
|
+
setVersionIndex(0)
|
|
365
|
+
void loadVersions(page.id)
|
|
366
|
+
}, [page?.id, loadVersions])
|
|
367
|
+
|
|
368
|
+
const closeHistory = useCallback(() => {
|
|
369
|
+
setHistoryOpen(false)
|
|
370
|
+
setVersionIndex(0)
|
|
371
|
+
}, [])
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Index 0 is the current content, which the canvas is already showing — so
|
|
375
|
+
* selecting it is "back to current", not a review. Only an older index puts
|
|
376
|
+
* the editor into review mode.
|
|
377
|
+
*/
|
|
378
|
+
const reviewing = historyOpen && versionIndex > 0
|
|
379
|
+
const reviewVersion = reviewing ? (versions[versionIndex] ?? null) : null
|
|
380
|
+
|
|
381
|
+
// What that version's save CHANGED — diffed against the one before it, which
|
|
382
|
+
// is the question the outlines on the canvas answer.
|
|
383
|
+
const reviewChanges = useMemo(
|
|
384
|
+
() =>
|
|
385
|
+
reviewVersion
|
|
386
|
+
? diffVersions(reviewVersion.sections, versions[versionIndex + 1]?.sections)
|
|
387
|
+
: [],
|
|
388
|
+
[reviewVersion, versions, versionIndex],
|
|
389
|
+
)
|
|
390
|
+
const reviewChangedIds = useMemo(() => changedSectionIds(reviewChanges), [reviewChanges])
|
|
391
|
+
|
|
392
|
+
const handleRestoreVersion = useCallback(
|
|
393
|
+
async (version: PageVersion) => {
|
|
394
|
+
if (!page?.id) return
|
|
395
|
+
setRestoring(true)
|
|
396
|
+
try {
|
|
397
|
+
await restorePageVersion(PAGES_COLLECTION, page.id, version.id)
|
|
398
|
+
// Re-read rather than trusting the snapshot we already hold: the
|
|
399
|
+
// restore goes through the normal update path, so hooks and derived
|
|
400
|
+
// fields may have altered what actually landed.
|
|
401
|
+
const restored = await fetchPageForEditor(page.id)
|
|
402
|
+
acceptPage(restored)
|
|
403
|
+
setSelectedId(null)
|
|
404
|
+
setDirty(false)
|
|
405
|
+
setSaveState('idle')
|
|
406
|
+
setValidation(null)
|
|
407
|
+
closeHistory()
|
|
408
|
+
void loadVersions(page.id)
|
|
409
|
+
toast.success('Version restored')
|
|
410
|
+
} catch (err) {
|
|
411
|
+
toast.error(err instanceof Error ? err.message : 'Failed to restore version')
|
|
412
|
+
} finally {
|
|
413
|
+
setRestoring(false)
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
[page?.id, acceptPage, closeHistory, loadVersions],
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
// ─── Edit permission ─────────────────────────────────────────────────
|
|
420
|
+
/**
|
|
421
|
+
* ADR 0002: a Page-Builder-owned page (it has a `_layout`) compiles its
|
|
422
|
+
* sections from the builder layout on every save, so editing them here would
|
|
423
|
+
* be overwritten. The server independently refuses such edits with 409.
|
|
424
|
+
*/
|
|
425
|
+
const readOnly = page?.builderOwned ?? false
|
|
426
|
+
/**
|
|
427
|
+
* The single gate every writing surface reads.
|
|
428
|
+
*
|
|
429
|
+
* Hoisted above the callbacks rather than derived at render time, because
|
|
430
|
+
* the two that were left behind were the ones that mattered: the site
|
|
431
|
+
* preview's in-place edit handlers close over `canEdit` (the raw role) and
|
|
432
|
+
* so kept accepting edits while a past version was under review — and while
|
|
433
|
+
* a builder-owned page was supposedly read-only. The iframe is a second
|
|
434
|
+
* editing surface, and a lock that only covers the rail is not a lock.
|
|
435
|
+
*/
|
|
436
|
+
const effectiveCanEdit = canEdit && !readOnly && !reviewing
|
|
437
|
+
/** Restoring is not editing: it is allowed precisely while reviewing. */
|
|
438
|
+
const canRestoreVersion = canEdit && !readOnly
|
|
439
|
+
|
|
328
440
|
// ─── Load ──────────────────────────────────────────────────────────
|
|
329
441
|
useEffect(() => {
|
|
330
442
|
let cancelled = false
|
|
@@ -610,24 +722,6 @@ export function PageSectionEditor({
|
|
|
610
722
|
onNavigate('/pages')
|
|
611
723
|
}, [page?.id, onNavigate])
|
|
612
724
|
|
|
613
|
-
// After a version restore the server has already written the restored
|
|
614
|
-
// data — refetch so the editor reflects it instead of the stale state.
|
|
615
|
-
const handleRestored = useCallback(() => {
|
|
616
|
-
if (!page?.id) return
|
|
617
|
-
fetchPageForEditor(page.id)
|
|
618
|
-
.then((p) => {
|
|
619
|
-
acceptPage(p)
|
|
620
|
-
setSelectedId(null)
|
|
621
|
-
setRailTab('sections')
|
|
622
|
-
setDirty(false)
|
|
623
|
-
setSaveState('idle')
|
|
624
|
-
setValidation(null)
|
|
625
|
-
})
|
|
626
|
-
.catch(() => {
|
|
627
|
-
toast.error('Version restored — refresh the page to see it.')
|
|
628
|
-
})
|
|
629
|
-
}, [page?.id])
|
|
630
|
-
|
|
631
725
|
const handleSeoSaved = useCallback(() => {
|
|
632
726
|
reloadAudit()
|
|
633
727
|
if (!page?.id) return
|
|
@@ -656,25 +750,28 @@ export function PageSectionEditor({
|
|
|
656
750
|
setPendingFocusPath(fieldPath ?? null)
|
|
657
751
|
}, [])
|
|
658
752
|
|
|
753
|
+
// `effectiveCanEdit`, not `canEdit`: the site preview is a second editing
|
|
754
|
+
// surface, so a role check alone let it write through both the version-review
|
|
755
|
+
// lock and the builder-owned lock the rest of the editor honours.
|
|
659
756
|
const handleCanvasEdit = useCallback(
|
|
660
757
|
(edit: { sectionId: string; fieldPath: string; value: string }) => {
|
|
661
|
-
if (!
|
|
758
|
+
if (!effectiveCanEdit) return
|
|
662
759
|
setSuppressSectionsEcho((n) => n + 1)
|
|
663
760
|
mutateSections((s) =>
|
|
664
761
|
updateSectionContentAtPath(s, edit.sectionId, edit.fieldPath, edit.value),
|
|
665
762
|
)
|
|
666
763
|
setSelectedId(edit.sectionId)
|
|
667
764
|
},
|
|
668
|
-
[
|
|
765
|
+
[effectiveCanEdit, mutateSections],
|
|
669
766
|
)
|
|
670
767
|
|
|
671
768
|
const handleCanvasRequestMedia = useCallback(
|
|
672
769
|
(request: { sectionId: string; fieldPath: string }) => {
|
|
673
|
-
if (!
|
|
770
|
+
if (!effectiveCanEdit) return
|
|
674
771
|
setSelectedId(request.sectionId)
|
|
675
772
|
setCanvasMediaTarget(request)
|
|
676
773
|
},
|
|
677
|
-
[
|
|
774
|
+
[effectiveCanEdit],
|
|
678
775
|
)
|
|
679
776
|
|
|
680
777
|
// After a canvas field click, focus the matching inspector input once painted.
|
|
@@ -807,12 +904,12 @@ export function PageSectionEditor({
|
|
|
807
904
|
|
|
808
905
|
const selected = page.sections.find((s) => s.id === selectedId) ?? null
|
|
809
906
|
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
//
|
|
814
|
-
|
|
815
|
-
const
|
|
907
|
+
// `readOnly` / `effectiveCanEdit` / `canRestoreVersion` are computed above,
|
|
908
|
+
// with the hooks, so the callbacks defined there share this one gate.
|
|
909
|
+
|
|
910
|
+
// The canvas shows the reviewed snapshot, but `page.sections` stays the
|
|
911
|
+
// editor's own content throughout — nothing about review mode writes to it.
|
|
912
|
+
const canvasSections = reviewVersion ? reviewVersion.sections : page.sections
|
|
816
913
|
|
|
817
914
|
// Sections whose type has no registered definition (seed/code-owned, e.g.
|
|
818
915
|
// `opal-*`). They are preserved verbatim on save but can't be edited here.
|
|
@@ -857,7 +954,8 @@ export function PageSectionEditor({
|
|
|
857
954
|
onViewOnSite={page.id ? handleViewOnSite : undefined}
|
|
858
955
|
sitePreview={sitePreview.active}
|
|
859
956
|
onToggleSitePreview={page.id && page.slug ? sitePreview.toggle : undefined}
|
|
860
|
-
|
|
957
|
+
historyOpen={historyOpen}
|
|
958
|
+
onHistory={page.id ? () => (historyOpen ? closeHistory() : openHistory()) : undefined}
|
|
861
959
|
onOpenSettings={() => setSettingsOpen(true)}
|
|
862
960
|
onOpenSeo={handleToggleSeo}
|
|
863
961
|
seoOpen={railTab === 'seo'}
|
|
@@ -926,7 +1024,7 @@ export function PageSectionEditor({
|
|
|
926
1024
|
)}
|
|
927
1025
|
|
|
928
1026
|
<ReflowContext.Provider value={reflowContext}>
|
|
929
|
-
<div className="relative flex flex-1 overflow-hidden">
|
|
1027
|
+
<div className="prenta-editor-body relative flex flex-1 overflow-hidden">
|
|
930
1028
|
<EditorRail collapsedLabel={activeRailTab.label}>
|
|
931
1029
|
{({ width, dragging, collapse }) => (
|
|
932
1030
|
<>
|
|
@@ -992,6 +1090,23 @@ export function PageSectionEditor({
|
|
|
992
1090
|
/>
|
|
993
1091
|
)}
|
|
994
1092
|
</div>
|
|
1093
|
+
|
|
1094
|
+
{/* Overlays the rail rather than replacing its contents, so
|
|
1095
|
+
closing history returns to exactly the tab and scroll
|
|
1096
|
+
position it was opened from. */}
|
|
1097
|
+
{historyOpen && (
|
|
1098
|
+
<VersionHistoryPanel
|
|
1099
|
+
versions={versions}
|
|
1100
|
+
loading={versionsLoading}
|
|
1101
|
+
error={versionsError}
|
|
1102
|
+
selectedIndex={versionIndex}
|
|
1103
|
+
onSelect={setVersionIndex}
|
|
1104
|
+
onClose={closeHistory}
|
|
1105
|
+
onRestore={handleRestoreVersion}
|
|
1106
|
+
restoring={restoring}
|
|
1107
|
+
canRestore={canRestoreVersion}
|
|
1108
|
+
/>
|
|
1109
|
+
)}
|
|
995
1110
|
</>
|
|
996
1111
|
)}
|
|
997
1112
|
</EditorRail>
|
|
@@ -1012,7 +1127,7 @@ export function PageSectionEditor({
|
|
|
1012
1127
|
/>
|
|
1013
1128
|
) : (
|
|
1014
1129
|
<EditorCanvas
|
|
1015
|
-
sections={
|
|
1130
|
+
sections={canvasSections}
|
|
1016
1131
|
selectedId={selectedId}
|
|
1017
1132
|
breakpoint={viewport}
|
|
1018
1133
|
zoom={zoom}
|
|
@@ -1021,11 +1136,25 @@ export function PageSectionEditor({
|
|
|
1021
1136
|
onSelect={handleSelectSection}
|
|
1022
1137
|
structuralTypes={structuralTypes}
|
|
1023
1138
|
sitePreviewAvailable={sitePreviewAvailable && !sitePreview.active}
|
|
1139
|
+
reviewing={reviewing}
|
|
1140
|
+
changedSectionIds={reviewChangedIds}
|
|
1141
|
+
reviewBar={
|
|
1142
|
+
reviewVersion ? (
|
|
1143
|
+
<ReviewBar
|
|
1144
|
+
version={reviewVersion}
|
|
1145
|
+
changedCount={reviewChangedIds.size}
|
|
1146
|
+
onRestore={() => void handleRestoreVersion(reviewVersion)}
|
|
1147
|
+
onBackToCurrent={() => setVersionIndex(0)}
|
|
1148
|
+
restoring={restoring}
|
|
1149
|
+
canRestore={canRestoreVersion}
|
|
1150
|
+
/>
|
|
1151
|
+
) : null
|
|
1152
|
+
}
|
|
1024
1153
|
/>
|
|
1025
1154
|
)}
|
|
1026
1155
|
</div>
|
|
1027
1156
|
|
|
1028
|
-
{selected && railTab === 'sections' && (
|
|
1157
|
+
{selected && railTab === 'sections' && !reviewing && (
|
|
1029
1158
|
<SectionInspector
|
|
1030
1159
|
section={selected}
|
|
1031
1160
|
canEdit={effectiveCanEdit}
|
|
@@ -1040,16 +1169,6 @@ export function PageSectionEditor({
|
|
|
1040
1169
|
</ReflowContext.Provider>
|
|
1041
1170
|
</div>
|
|
1042
1171
|
|
|
1043
|
-
{page.id && (
|
|
1044
|
-
<VersionHistory
|
|
1045
|
-
collectionSlug="pages"
|
|
1046
|
-
documentId={page.id}
|
|
1047
|
-
open={historyOpen}
|
|
1048
|
-
onClose={() => setHistoryOpen(false)}
|
|
1049
|
-
onRestore={handleRestored}
|
|
1050
|
-
/>
|
|
1051
|
-
)}
|
|
1052
|
-
|
|
1053
1172
|
<PageSettingsModal
|
|
1054
1173
|
open={settingsOpen}
|
|
1055
1174
|
page={page}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { History, Loader2 } from 'lucide-react'
|
|
4
|
+
import { dayLabel, timeLabel, type PageVersion } from '../../lib/version-history.js'
|
|
5
|
+
|
|
6
|
+
export interface ReviewBarProps {
|
|
7
|
+
version: PageVersion
|
|
8
|
+
changedCount: number
|
|
9
|
+
onRestore: () => void
|
|
10
|
+
onBackToCurrent: () => void
|
|
11
|
+
restoring: boolean
|
|
12
|
+
canRestore: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The strip above the canvas while a past version is on it.
|
|
17
|
+
*
|
|
18
|
+
* Occupies the same slot as the editing tip and the structure notice, and
|
|
19
|
+
* replaces both rather than stacking with them: everything that slot normally
|
|
20
|
+
* says is about editing the current page, which is precisely what you cannot
|
|
21
|
+
* do here. It also has to be unmissable — the canvas below looks like a
|
|
22
|
+
* perfectly ordinary editable page, and the only thing separating "reviewing
|
|
23
|
+
* history" from "editing" is this bar.
|
|
24
|
+
*/
|
|
25
|
+
export function ReviewBar({
|
|
26
|
+
version,
|
|
27
|
+
changedCount,
|
|
28
|
+
onRestore,
|
|
29
|
+
onBackToCurrent,
|
|
30
|
+
restoring,
|
|
31
|
+
canRestore,
|
|
32
|
+
}: ReviewBarProps) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
// `status`, not a bare div: entering review mode is a change of what the
|
|
36
|
+
// whole canvas means, and a screen-reader user gets no equivalent of the
|
|
37
|
+
// amber colour that announces it to everyone else.
|
|
38
|
+
role="status"
|
|
39
|
+
className="border-warning/35 bg-warning/10 text-foreground mx-auto mb-3 flex w-fit max-w-full flex-wrap items-center gap-2.5 rounded-full border py-1.5 pr-1.5 pl-3.5 text-[11.5px]"
|
|
40
|
+
>
|
|
41
|
+
<History className="text-warning h-3.5 w-3.5 shrink-0" aria-hidden />
|
|
42
|
+
<span className="min-w-0">
|
|
43
|
+
Viewing{' '}
|
|
44
|
+
<strong className="font-semibold">
|
|
45
|
+
{dayLabel(version.createdAt)} {timeLabel(version.createdAt)}
|
|
46
|
+
</strong>{' '}
|
|
47
|
+
by <strong className="font-semibold">{version.author.name}</strong>
|
|
48
|
+
{changedCount > 0 && (
|
|
49
|
+
<>
|
|
50
|
+
{' · '}
|
|
51
|
+
{changedCount} section{changedCount === 1 ? '' : 's'} changed
|
|
52
|
+
</>
|
|
53
|
+
)}
|
|
54
|
+
</span>
|
|
55
|
+
|
|
56
|
+
{canRestore && (
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
onClick={onRestore}
|
|
60
|
+
disabled={restoring}
|
|
61
|
+
className="border-border bg-card text-foreground hover:bg-accent inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
62
|
+
>
|
|
63
|
+
{restoring && <Loader2 className="h-3 w-3 animate-spin" aria-hidden />}
|
|
64
|
+
Restore
|
|
65
|
+
</button>
|
|
66
|
+
)}
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={onBackToCurrent}
|
|
70
|
+
className="border-border bg-card text-foreground hover:bg-accent shrink-0 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors"
|
|
71
|
+
>
|
|
72
|
+
Back to current
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -51,7 +51,8 @@ interface SectionInspectorProps {
|
|
|
51
51
|
const LABEL = 'text-foreground mb-1 block text-xs font-medium'
|
|
52
52
|
const INPUT =
|
|
53
53
|
'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'
|
|
54
|
-
const INSPECTOR_SHELL =
|
|
54
|
+
const INSPECTOR_SHELL =
|
|
55
|
+
'prenta-editor-inspector bg-card border-border flex h-full flex-col border-l'
|
|
55
56
|
|
|
56
57
|
function InspectorHeaderActions({
|
|
57
58
|
onClose,
|
|
@@ -305,7 +305,7 @@ function SectionRow({
|
|
|
305
305
|
disabled={!canEdit}
|
|
306
306
|
aria-pressed={!section.visible}
|
|
307
307
|
aria-label={section.visible ? `Hide ${section.name}` : `Show ${section.name}`}
|
|
308
|
-
className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1 disabled:opacity-40"
|
|
308
|
+
className="prenta-touch-target text-muted-foreground hover:text-foreground shrink-0 rounded p-1 disabled:opacity-40"
|
|
309
309
|
>
|
|
310
310
|
{section.visible ? (
|
|
311
311
|
<Eye className="h-4 w-4" aria-hidden />
|
|
@@ -321,7 +321,7 @@ function SectionRow({
|
|
|
321
321
|
type="button"
|
|
322
322
|
disabled={!canEdit}
|
|
323
323
|
aria-label={`More actions for ${section.name}`}
|
|
324
|
-
className="text-muted-foreground hover:text-foreground shrink-0 rounded p-1 disabled:opacity-40"
|
|
324
|
+
className="prenta-touch-target text-muted-foreground hover:text-foreground shrink-0 rounded p-1 disabled:opacity-40"
|
|
325
325
|
>
|
|
326
326
|
<MoreVertical className="h-4 w-4" aria-hidden />
|
|
327
327
|
</button>
|