Skip to content

Commit c795e02

Browse files
adrians5jclaude
andauthored
fix(website-builder): leave a deleted revision and order revisions by version (#6006)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9f56c27 commit c795e02

5 files changed

Lines changed: 72 additions & 7 deletions

File tree

‎packages/app-website-builder/src/presentation/pages/PageEditor/Revisions/RevisionListItem.tsx‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ const getIcon = (revision: PageRevision) => {
7171

7272
interface RevisionListItemProps {
7373
item: RevisionListPresenter.ItemViewModel;
74+
onDeleted?: (revision: PageRevision) => void;
7475
}
7576

76-
const RevisionListItem = ({ item }: RevisionListItemProps) => {
77+
const RevisionListItem = ({ item, onDeleted }: RevisionListItemProps) => {
7778
const { revision } = item;
7879
const { createRevision, editRevision, deleteRevision, unpublishRevision, publishRevision } =
7980
useRevision({
80-
revision
81+
revision,
82+
onDeleted
8183
});
8284

8385
const { icon, text: tooltipText } = getIcon(revision);

‎packages/app-website-builder/src/presentation/pages/PageEditor/Revisions/RevisionsList.tsx‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
22
import { List, OverlayLoader } from "@webiny/admin-ui";
3+
import { useRouter } from "@webiny/app";
34
import RevisionListItem from "./RevisionListItem.js";
45
import type { Page } from "~/domain/Page/index.js";
6+
import type { PageRevision } from "~/domain/PageRevision/index.js";
7+
import { Routes } from "~/routes.js";
58
import { useRevisionList } from "./useRevisionList.js";
9+
import { usePageEditorDrawer } from "./usePageEditorDrawer.js";
610

711
interface IRevisionsListProps {
812
page: Pick<Page, "id">;
@@ -11,14 +15,45 @@ interface IRevisionsListProps {
1115
export const RevisionsList = (props: IRevisionsListProps) => {
1216
const { page } = props;
1317
const { vm } = useRevisionList(page.id);
18+
const { goToRoute } = useRouter();
19+
const { openRevisionList } = usePageEditorDrawer();
20+
21+
/**
22+
* Deleting the revision that is open in the editor would leave the editor on a revision that
23+
* no longer exists, so we move to the newest remaining one, or back to the list if none is left.
24+
*/
25+
const onDeleted = useCallback(
26+
(revision: PageRevision) => {
27+
if (revision.id !== page.id) {
28+
return;
29+
}
30+
31+
openRevisionList(false);
32+
33+
const next = vm.revisions.find(item => item.revision.id !== revision.id);
34+
if (!next) {
35+
goToRoute(Routes.Pages.List);
36+
return;
37+
}
38+
39+
goToRoute(Routes.Pages.Editor, {
40+
id: next.revision.id
41+
});
42+
},
43+
[page.id, vm.revisions, openRevisionList]
44+
);
1445

1546
return (
1647
<div className={"relative"}>
1748
{(vm.isLoading || vm.isMutating) && <OverlayLoader />}
1849
{vm.revisions.length ? (
1950
<List data-testid={"cms.content-form.revisions"}>
2051
{vm.revisions.map(item => (
21-
<RevisionListItem item={item} key={item.revision.id} />
52+
<RevisionListItem
53+
item={item}
54+
key={item.revision.id}
55+
onDeleted={onDeleted}
56+
/>
2257
))}
2358
</List>
2459
) : (

‎packages/app-website-builder/src/presentation/pages/PageEditor/Revisions/useRevision.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@ import { usePageEditorDrawer } from "./usePageEditorDrawer.js";
1818

1919
export interface UseRevisionProps {
2020
revision: PageRevision;
21+
/**
22+
* Called once the revision is deleted, so the list can move the editor away from a revision
23+
* that no longer exists.
24+
*/
25+
onDeleted?: (revision: PageRevision) => void;
2126
}
2227

2328
export const useRevision = (props: UseRevisionProps) => {
24-
const { revision } = props;
29+
const { revision, onDeleted } = props;
2530

2631
const { goToRoute } = useRouter();
2732
const { openRevisionList } = usePageEditorDrawer();
@@ -59,6 +64,8 @@ export const useRevision = (props: UseRevisionProps) => {
5964
showSuccessToast({
6065
title: `Revision #${revision.version} was deleted successfully!`
6166
});
67+
68+
onDeleted?.(revision);
6269
} catch (ex) {
6370
showWarningToast({
6471
title: "Could not delete the revision.",
@@ -67,7 +74,7 @@ export const useRevision = (props: UseRevisionProps) => {
6774
}
6875
}
6976
});
70-
}, [revision.id, revision.version]);
77+
}, [revision.id, revision.version, onDeleted]);
7178

7279
const publishRevision = useCallback(() => {
7380
showDialog({

‎packages/app-website-builder/src/presentation/pages/RevisionList/RevisionListPresenter.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,25 @@ describe("RevisionListPresenter", () => {
121121
expect(presenter.vm.isEmpty).toBeFalse();
122122
});
123123

124+
it("should order the revisions by version, even when an older one was saved later", () => {
125+
const presenter = createPresenter();
126+
127+
// Publishing or editing an older revision moves its savedOn past the newer ones.
128+
revisionsCache.addItems([
129+
createRevision("page-1#0002", "page-1", 2, "2026-09-21T00:00:00.000Z"),
130+
createRevision("page-1#0001", "page-1", 1, "2026-09-23T00:00:00.000Z"),
131+
createRevision("page-1#0003", "page-1", 3, "2026-09-22T00:00:00.000Z")
132+
]);
133+
134+
presenter.init({ entryId: "page-1" });
135+
136+
expect(presenter.vm.revisions.map(item => item.revision.id)).toEqual([
137+
"page-1#0003",
138+
"page-1#0002",
139+
"page-1#0001"
140+
]);
141+
});
142+
124143
it("should not report an empty list while loading", () => {
125144
loadingRepository.isLoading.mockReturnValue(true);
126145
const presenter = createPresenter();

‎packages/app-website-builder/src/presentation/pages/RevisionList/RevisionListPresenter.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ class RevisionListPresenterImpl implements PresenterAbstraction.Interface {
6161
.getItems()
6262
.filter(revision => revision.entryId === this.entryId);
6363

64-
return orderBy(revisions, ["savedOn"], ["desc"]).map(revision => this.getItemVm(revision));
64+
// Ordered by version rather than savedOn: publishing or editing an older revision moves its
65+
// savedOn past the newer ones, which would shuffle the list.
66+
return orderBy(revisions, ["version"], ["desc"]).map(revision => this.getItemVm(revision));
6567
};
6668

6769
private getItemVm = (revision: PageRevision): PresenterAbstraction.ItemViewModel => {

0 commit comments

Comments
 (0)