Skip to content

Commit e87b97c

Browse files
committed
Add CMSUI History route scaffold (#30)
Register an @@history route in cmsui that lists content revisions via the plone.restapi @history endpoint and supports reverting to a previous version. Add a History toolbar button in publicui linking to the route, plus en/de/it locale strings and towncrier news fragments.
1 parent 7ac6df6 commit e87b97c

8 files changed

Lines changed: 118 additions & 1 deletion

File tree

packages/cmsui/config/routes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ export default function install(config: ConfigType) {
4949
},
5050
],
5151
},
52+
{
53+
type: 'prefix',
54+
path: '@@history',
55+
children: [
56+
{
57+
type: 'route',
58+
path: '*',
59+
file: '@plone/cmsui/routes/history.tsx',
60+
},
61+
],
62+
},
5263
{
5364
type: 'prefix',
5465
path: 'controlpanel',

packages/cmsui/locales/de/common.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
"signInTo": "Bei {{site}} anmelden",
7272
"forgotPassword": "Passwort vergessen? <a>Neues Passwort anfordern</a>",
7373
"returnToHome": "Zur Startseite zurückkehren"
74+
},
75+
"history": {
76+
"label": "Verlauf",
77+
"revert": "Zurücksetzen"
7478
}
7579
}
7680
}

packages/cmsui/locales/en/common.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@
119119
"signInTo": "Sign in to {{site}}",
120120
"forgotPassword": "Forgot password? Request a <a>new password</a>",
121121
"returnToHome": "Return to home page"
122+
},
123+
"history": {
124+
"label": "History",
125+
"revert": "Revert"
122126
}
123127
}
124128
}

packages/cmsui/locales/it/common.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
"signInTo": "Accedi a {{site}}",
110110
"forgotPassword": "Password dimenticata? <a>Richiedi una nuova password</a>",
111111
"returnToHome": "Torna alla home page"
112+
},
113+
"history": {
114+
"label": "Cronologia",
115+
"revert": "Ripristina"
112116
}
113117
}
114118
}

packages/cmsui/news/30.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a History route (`@@history`) that lists content revisions and supports reverting to a previous version.

packages/cmsui/routes/history.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
2+
import {
3+
data,
4+
RouterContextProvider,
5+
useFetcher,
6+
useLoaderData,
7+
} from 'react-router';
8+
import { requireAuthCookie } from '@plone/react-router';
9+
import {
10+
ploneClientContext,
11+
ploneContentContext,
12+
} from '@plone/aurora/app/middleware.server';
13+
import { flattenToAppURL } from '@plone/helpers';
14+
import { useTranslation } from 'react-i18next';
15+
import type { GetHistoryResponse } from '@plone/types';
16+
17+
export async function loader({
18+
request,
19+
context,
20+
}: LoaderFunctionArgs<RouterContextProvider>) {
21+
await requireAuthCookie(request);
22+
23+
const cli = context.get(ploneClientContext);
24+
const content = context.get(ploneContentContext);
25+
const contentPath = content?.['@id'] ?? '/';
26+
27+
const { data: history } = await cli.getHistory({ path: contentPath });
28+
29+
return data(flattenToAppURL({ content, history }));
30+
}
31+
32+
export async function action({
33+
request,
34+
context,
35+
}: ActionFunctionArgs<RouterContextProvider>) {
36+
await requireAuthCookie(request);
37+
38+
const cli = context.get(ploneClientContext);
39+
const content = context.get(ploneContentContext);
40+
const contentPath = content?.['@id'] ?? '/';
41+
42+
const formData = await request.formData();
43+
const version = Number(formData.get('version'));
44+
45+
await cli.revertHistory({ path: contentPath, data: { version } });
46+
47+
return data({ ok: true });
48+
}
49+
50+
export default function History() {
51+
const { content, history } = useLoaderData<typeof loader>();
52+
const { t } = useTranslation();
53+
const fetcher = useFetcher();
54+
55+
// TODO: replace this list with the quanta Table component and format the
56+
// timestamp with @internationalized/date. See Volto's History.jsx for the
57+
// full UX (version comparison/diff, state transitions).
58+
return (
59+
<main>
60+
<h1>{t('cmsui.history.label')}</h1>
61+
<p>{content['@id']}</p>
62+
<ul>
63+
{(history as GetHistoryResponse).map((entry, index) => (
64+
<li key={index}>
65+
<b>{entry.actor?.fullname}</b> {entry.transition_title} {entry.time}
66+
{entry.comments ? <em> ({entry.comments})</em> : null}
67+
{'version' in entry && entry.may_revert ? (
68+
<fetcher.Form method="post" style={{ display: 'inline' }}>
69+
<input type="hidden" name="version" value={entry.version} />
70+
<button type="submit">{t('cmsui.history.revert')}</button>
71+
</fetcher.Form>
72+
) : null}
73+
</li>
74+
))}
75+
</ul>
76+
</main>
77+
);
78+
}

packages/publicui/news/30.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a History button to the toolbar linking to the `@@history` route.

packages/publicui/routes/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import i18next from '@plone/aurora/app/i18next.server';
2626
import { ploneContentContext } from '@plone/aurora/app/middleware.server';
2727
import type { RootLoader } from '@plone/aurora/app/root';
28-
import { FolderIcon } from '@plone/components/Icons';
28+
import { FolderIcon, HistoryIcon } from '@plone/components/Icons';
2929
import Pencil from '@plone/components/icons/pencil.svg?react';
3030
import SlotRenderer from '@plone/layout/slots/SlotRenderer';
3131
import Toolbar from '@plone/layout/components/Toolbar/Toolbar';
@@ -150,6 +150,20 @@ export default function Index() {
150150
<FolderIcon />
151151
</Link>
152152
</Plug>
153+
<Plug
154+
pluggable="toolbar-top"
155+
id="button-history"
156+
// @ts-expect-error this is currently typed as never[]
157+
dependencies={[location.pathname]}
158+
>
159+
<Link
160+
className="secondary"
161+
aria-label="History"
162+
href={`/@@history${location.pathname.replace(/^\/$/, '')}`}
163+
>
164+
<HistoryIcon />
165+
</Link>
166+
</Plug>
153167
{showToolbar && <Toolbar />}
154168
<div id="main">
155169
<div className={clsx(styles.app, 'app-slot')}>

0 commit comments

Comments
 (0)