Skip to content

Commit 9d1ce63

Browse files
committed
fix redirect after restore
1 parent 48b810f commit 9d1ce63

4 files changed

Lines changed: 85 additions & 8 deletions

File tree

packages/cmsui/routes/recyclebin-item.test.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ const callLoader = (request: Request, cli: Record<string, unknown>) => {
2828

2929
const callAction = (request: Request, cli: Record<string, unknown>) => {
3030
const context = new RouterContextProvider();
31-
context.set(ploneClientContext, cli as any);
31+
context.set(ploneClientContext, {
32+
config: { apiPath: 'http://example.com/Plone' },
33+
...cli,
34+
} as any);
3235
return action({
3336
request,
3437
params: { id: 'deleted-item' },
@@ -127,13 +130,15 @@ describe('recycle bin item route', () => {
127130

128131
it('restores the item with an optional target path', async () => {
129132
const restoreRecycleBinItem = vi.fn().mockResolvedValue({
130-
data: { restored_item: { '@id': '/restored-item' } },
133+
data: {
134+
restored_item: { '@id': 'http://example.com/Plone/restored-item' },
135+
},
131136
});
132137
const formData = new FormData();
133138
formData.append('_action', 'restore');
134139
formData.append('target_path', 'target-folder');
135140

136-
await callAction(
141+
const result = await callAction(
137142
new Request('http://example.com/@@recyclebin/deleted-item', {
138143
method: 'POST',
139144
body: formData,
@@ -145,6 +150,7 @@ describe('recycle bin item route', () => {
145150
id: 'deleted-item',
146151
data: { target_path: 'target-folder' },
147152
});
153+
expect((result as Response).headers.get('Location')).toBe('/restored-item');
148154
});
149155

150156
it('purges the item', async () => {

packages/cmsui/routes/recyclebin-item.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import { Container } from '@plone/components/quanta';
1414
import { useTranslation } from 'react-i18next';
1515
import { ploneClientContext } from 'seven/app/middleware.server';
1616
import { RecycleBinItemDetails } from '../components/RecycleBin/RecycleBinItemDetails';
17-
import { normalizeRecycleBinItemPaths } from '../components/RecycleBin/utils';
17+
import {
18+
normalizeRecycleBinItemPaths,
19+
stripPortalBasePath,
20+
} from '../components/RecycleBin/utils';
1821

1922
const getErrorMessage = (error: unknown) => {
2023
if (
@@ -30,6 +33,21 @@ const getErrorMessage = (error: unknown) => {
3033
return 'Request failed';
3134
};
3235

36+
const getRestoredItemRedirectUrl = (
37+
restoredUrl: string | undefined,
38+
apiPath: string,
39+
) => {
40+
if (!restoredUrl) return '/@@recyclebin';
41+
42+
const flattenedUrl = flattenToAppURL(restoredUrl);
43+
44+
try {
45+
return stripPortalBasePath(new URL(flattenedUrl).pathname, apiPath);
46+
} catch {
47+
return stripPortalBasePath(flattenedUrl, apiPath);
48+
}
49+
};
50+
3351
export async function loader({
3452
params,
3553
request,
@@ -76,7 +94,7 @@ export async function action({
7694
});
7795
const restoredUrl = result.data.restored_item?.['@id'];
7896
return redirect(
79-
restoredUrl ? flattenToAppURL(restoredUrl) : '/@@recyclebin',
97+
getRestoredItemRedirectUrl(restoredUrl, cli.config.apiPath),
8098
);
8199
}
82100

packages/cmsui/routes/recyclebin.test.tsx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ const callLoader = (request: Request, cli: Record<string, unknown>) => {
2828

2929
const callAction = (request: Request, cli: Record<string, unknown>) => {
3030
const context = new RouterContextProvider();
31-
context.set(ploneClientContext, cli as any);
31+
context.set(ploneClientContext, {
32+
config: { apiPath: 'http://example.com/Plone' },
33+
...cli,
34+
} as any);
3235
return action({
3336
request,
3437
params: {},
@@ -156,7 +159,9 @@ describe('recycle bin route', () => {
156159
});
157160

158161
it('restores selected items', async () => {
159-
const restoreRecycleBinItem = vi.fn().mockResolvedValue({});
162+
const restoreRecycleBinItem = vi.fn().mockResolvedValue({
163+
data: { restored_item: { '@id': 'http://example.com/Plone/restored' } },
164+
});
160165
const formData = new FormData();
161166
formData.append('_action', 'restore-selected');
162167
formData.append('selected_items', 'one');
@@ -180,6 +185,27 @@ describe('recycle bin route', () => {
180185
});
181186
});
182187

188+
it('redirects to the restored item when one selected item is restored', async () => {
189+
const restoreRecycleBinItem = vi.fn().mockResolvedValue({
190+
data: {
191+
restored_item: { '@id': 'http://example.com/Plone/restored-item' },
192+
},
193+
});
194+
const formData = new FormData();
195+
formData.append('_action', 'restore-selected');
196+
formData.append('selected_items', 'one');
197+
198+
const result = await callAction(
199+
new Request('http://example.com/@@recyclebin', {
200+
method: 'POST',
201+
body: formData,
202+
}),
203+
{ restoreRecycleBinItem },
204+
);
205+
206+
expect((result as Response).headers.get('Location')).toBe('/restored-item');
207+
});
208+
183209
it('purges selected items', async () => {
184210
const purgeRecycleBinItem = vi.fn().mockResolvedValue({});
185211
const formData = new FormData();

packages/cmsui/routes/recyclebin.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
getQueryStateFromSearchParams,
2222
normalizeRecycleBinPaths,
23+
stripPortalBasePath,
2324
toRecycleBinQuery,
2425
} from '../components/RecycleBin/utils';
2526

@@ -37,6 +38,21 @@ const getErrorMessage = (error: unknown) => {
3738
return 'Request failed';
3839
};
3940

41+
const getRestoredItemRedirectUrl = (
42+
restoredUrl: string | undefined,
43+
apiPath: string,
44+
) => {
45+
if (!restoredUrl) return '/@@recyclebin';
46+
47+
const flattenedUrl = flattenToAppURL(restoredUrl);
48+
49+
try {
50+
return stripPortalBasePath(new URL(flattenedUrl).pathname, apiPath);
51+
} catch {
52+
return stripPortalBasePath(flattenedUrl, apiPath);
53+
}
54+
};
55+
4056
export async function loader({
4157
request,
4258
context,
@@ -71,6 +87,7 @@ export async function action({
7187
const intent = formData.get('_action');
7288
const selectedItems = formData.getAll('selected_items').map(String);
7389
const failures: Array<{ id: string; message: string }> = [];
90+
const restoredUrls: string[] = [];
7491
let succeeded = 0;
7592

7693
if (intent === 'empty') {
@@ -95,7 +112,11 @@ export async function action({
95112
for (const id of selectedItems) {
96113
try {
97114
if (intent === 'restore-selected') {
98-
await cli.restoreRecycleBinItem({ id, data: {} });
115+
const result = await cli.restoreRecycleBinItem({ id, data: {} });
116+
const restoredUrl = result.data.restored_item?.['@id'];
117+
if (restoredUrl) {
118+
restoredUrls.push(restoredUrl);
119+
}
99120
} else {
100121
await cli.purgeRecycleBinItem({ id });
101122
}
@@ -115,6 +136,12 @@ export async function action({
115136
);
116137
}
117138

139+
if (intent === 'restore-selected' && selectedItems.length === 1) {
140+
return redirect(
141+
getRestoredItemRedirectUrl(restoredUrls[0], cli.config.apiPath),
142+
);
143+
}
144+
118145
return redirect(`/@@recyclebin${url.search}`);
119146
}
120147

0 commit comments

Comments
 (0)