From 4ec69a75c06865b48b627582fac5c12ecbfcc019 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 1 Aug 2026 16:00:32 -0400 Subject: [PATCH] feat: page history overlay --- backend/locales/en.json | 20 + frontend/src/assets/icons.generated.js | 6 +- .../src/components/PageHistoryOverlay.vue | 400 +++++++++++++++++- frontend/src/components/PageSourceOverlay.vue | 15 +- .../components/PageVersionSourceDialog.vue | 125 ++++++ frontend/src/components/TreeBrowserDialog.vue | 77 ++-- .../src/components/shared/WConfirmDialog.vue | 42 +- 7 files changed, 633 insertions(+), 52 deletions(-) create mode 100644 frontend/src/components/PageVersionSourceDialog.vue diff --git a/backend/locales/en.json b/backend/locales/en.json index 82e10affd..271fdb83b 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -1983,20 +1983,40 @@ "history.action.deleted": "Deleted", "history.action.moved": "Moved", "history.action.updated": "Updated", + "history.branchFailed": "Failed to create a page from this version.", + "history.branchOff": "Branch off from here", + "history.branchReason": "Branched off from the version of {date}", + "history.branchSuccess": "New page created from this version.", "history.changedFields": "Changed: {fields}", "history.current": "Current", + "history.downloadFailed": "Failed to download this version.", + "history.downloadVersion": "Download Version", "history.emptyPage": "Nothing", + "history.inline": "Inline", "history.loadFailed": "Failed to load the page history.", "history.none": "No history has been recorded for this page yet.", "history.pickA": "Compare from this version", "history.pickB": "Compare to this version", + "history.restore": "Restore", "history.restore.confirmButton": "Restore", "history.restore.confirmText": "Are you sure you want to restore this page content as it was on {date}? This version will be copied on top of the current history. As such, newer versions will still be preserved.", "history.restore.confirmTitle": "Restore page version?", "history.restore.success": "Page version restored succesfully!", + "history.restoreConfirm": "Restore the page content as it was on **{date}**?", + "history.restoreConfirmHint": "The page keeps its current title, tags and settings, and the state it is in now is kept in the history.", + "history.restoreFailed": "Failed to restore this version.", + "history.restoreReason": "Restored the content from {date}", + "history.restoreSuccess": "Page content restored successfully.", "history.sameContent": "These two versions have the same content — only metadata changed.", + "history.setAsSource": "Set as Differencing Source", + "history.setAsTarget": "Set as Differencing Target", + "history.sideBySide": "Side-by-Side", + "history.sourceCopied": "Source copied to the clipboard.", "history.title": "Page History", "history.unknownAuthor": "Unknown", + "history.versionActions": "Version Actions", + "history.versionId": "Version ID {id}", + "history.viewSource": "View Source", "iconPicker.allSets": "All sets", "iconPicker.icons": "Icons", "iconPicker.image": "Image", diff --git a/frontend/src/assets/icons.generated.js b/frontend/src/assets/icons.generated.js index d7c0d4eb6..2f20ded62 100644 --- a/frontend/src/assets/icons.generated.js +++ b/frontend/src/assets/icons.generated.js @@ -5,7 +5,7 @@ never waits on (or depends on) the icon service. Regenerate with `npm run icons` after adding or removing an icon; `check-icons.mjs` fails the build if this drifts. - 252 icons. + 256 icons. */ export const BUNDLED_ICONS = { "la:angle-double-right": {"body":"","width":32,"height":32}, @@ -40,6 +40,7 @@ export const BUNDLED_ICONS = { "la:clock": {"body":"","width":32,"height":32}, "la:cloud-upload-alt": {"body":"","width":32,"height":32}, "la:code": {"body":"","width":32,"height":32}, + "la:code-branch": {"body":"","width":32,"height":32}, "la:cog": {"body":"","width":32,"height":32}, "la:comments": {"body":"","width":32,"height":32}, "la:copy": {"body":"","width":32,"height":32}, @@ -131,6 +132,7 @@ export const BUNDLED_ICONS = { "la:traffic-light": {"body":"","width":32,"height":32}, "la:trash": {"body":"","width":32,"height":32}, "la:trash-alt": {"body":"","width":32,"height":32}, + "la:undo": {"body":"","width":32,"height":32}, "la:undo-alt": {"body":"","width":32,"height":32}, "la:unlink": {"body":"","width":32,"height":32}, "la:upload": {"body":"","width":32,"height":32}, @@ -220,6 +222,8 @@ export const BUNDLED_ICONS = { "mdi:information": {"body":"","width":24,"height":24}, "mdi:information-box": {"body":"","width":24,"height":24}, "mdi:keyboard-variant": {"body":"","width":24,"height":24}, + "mdi:letter-a-box": {"body":"","width":24,"height":24}, + "mdi:letter-b-box": {"body":"","width":24,"height":24}, "mdi:lightbulb-outline": {"body":"","width":24,"height":24}, "mdi:line-scan": {"body":"","width":24,"height":24}, "mdi:link-variant": {"body":"","width":24,"height":24}, diff --git a/frontend/src/components/PageHistoryOverlay.vue b/frontend/src/components/PageHistoryOverlay.vue index 59105c490..dca029052 100644 --- a/frontend/src/components/PageHistoryOverlay.vue +++ b/frontend/src/components/PageHistoryOverlay.vue @@ -3,11 +3,40 @@ {{ t('history.title') }} - {{ pageStore.title }} + + {{ pageStore.title }} + + + + + /{{ version.path }} - -
{{ version.reason }}
-
- {{ t('history.changedFields', { fields: version.changedFields.join(', ') }) }} -
+ + + + + + + + + {{ t('history.setAsSource') }} + + + + + + {{ t('history.setAsTarget') }} + + + + + + + {{ t('history.viewSource') }} + + + + + + {{ t('history.downloadVersion') }} + + + + +
+ +
+ +
{{ version.reason }}
+
+ {{ t('history.changedFields', { fields: version.changedFields.join(', ') }) }} +
+
{{ t('history.none') }}
@@ -109,9 +206,10 @@
{{ sideLabel(sideA) }}
{{ sideCaption(sideA) }}
+ + - -
B
@@ -135,15 +233,31 @@ + + diff --git a/frontend/src/components/TreeBrowserDialog.vue b/frontend/src/components/TreeBrowserDialog.vue index abf41bc9b..a310c502c 100644 --- a/frontend/src/components/TreeBrowserDialog.vue +++ b/frontend/src/components/TreeBrowserDialog.vue @@ -14,7 +14,7 @@ {{ t('pageRenameDialog.title') }}
-
+
@@ -33,22 +33,29 @@
- - - - - - - {{ item.title }} - - - + + + + + + + + + {{ item.title }} + + + +
{{ currentFolderPath }}
@@ -514,9 +521,25 @@ onMounted(async () => { @use 'sass:color'; .page-save-dialog { + /* + The header draws its separator as an OUTSET box-shadow, which is painted with the header's own + background -- and a later sibling's background is painted after it. So the tinted tree column + covered that 1px line while the untinted file list left it showing, and the two columns looked as + though they started at different heights. + + Positioning the header puts it above both: a positioned element paints over its in-flow siblings, + so the line survives across the full width. + */ + .card-header { + position: relative; + } + &-browser { height: 300px; max-height: 90vh; + /* -> Belt and braces with the scroll areas inside: whatever either column ends up holding, the + browser cannot spill over the fields and buttons below it */ + overflow: hidden; border-bottom: 1px solid #fff; @at-root .body--light & { @@ -525,16 +548,20 @@ onMounted(async () => { @at-root .body--dark & { border-bottom-color: $dark-3; } + } - > .col-4 { - height: 300px; + /* + Tinted so the tree reads as a column of its own rather than running into the file list beside it. - @at-root .body--light & { - background-color: $blue-grey-1; - } - @at-root .body--dark & { - background-color: $dark-4; - } + This was a `> .col-4` rule, which the layout migration left pointing at a class that no longer + exists -- the columns are Tailwind fractions now -- so the pane had been plain white since. + */ + &-tree { + @at-root .body--light & { + background-color: $blue-grey-1; + } + @at-root .body--dark & { + background-color: $dark-4; } } diff --git a/frontend/src/components/shared/WConfirmDialog.vue b/frontend/src/components/shared/WConfirmDialog.vue index f4f3234a1..8b61f92c4 100644 --- a/frontend/src/components/shared/WConfirmDialog.vue +++ b/frontend/src/components/shared/WConfirmDialog.vue @@ -5,7 +5,20 @@ {{ title }} -
{{ message }}
+
+ +
+ + +
{{ caption }}