@byline/host-tanstack-start 1.1.0 → 1.2.1
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/dist/admin-shell/admin-roles/container.js +1 -2
- package/dist/admin-shell/admin-roles/delete.js +1 -1
- package/dist/admin-shell/admin-roles/list.js +1 -2
- package/dist/admin-shell/admin-users/container.js +1 -2
- package/dist/admin-shell/admin-users/delete.js +1 -1
- package/dist/admin-shell/admin-users/list.js +1 -2
- package/dist/admin-shell/chrome/admin-app-bar.js +1 -1
- package/dist/admin-shell/chrome/dashboard.js +1 -1
- package/dist/admin-shell/chrome/drawer-toggle.js +1 -1
- package/dist/admin-shell/chrome/menu-drawer.js +1 -1
- package/dist/admin-shell/chrome/preview-toggle.js +1 -1
- package/dist/admin-shell/chrome/route-error.js +1 -1
- package/dist/admin-shell/chrome/router-pager.d.ts +1 -1
- package/dist/admin-shell/chrome/router-pager.js +1 -1
- package/dist/admin-shell/chrome/th-sortable.js +1 -1
- package/dist/admin-shell/collections/api.js +1 -1
- package/dist/admin-shell/collections/create.js +1 -2
- package/dist/admin-shell/collections/edit.js +1 -2
- package/dist/admin-shell/collections/history.js +82 -6
- package/dist/admin-shell/collections/history.module.js +5 -0
- package/dist/admin-shell/collections/history_module.css +46 -0
- package/dist/admin-shell/collections/list.js +1 -2
- package/dist/admin-shell/collections/preview-link.js +1 -1
- package/dist/admin-shell/collections/restore-version-modal.d.ts +10 -0
- package/dist/admin-shell/collections/restore-version-modal.js +118 -0
- package/dist/admin-shell/collections/restore-version-modal.module.js +10 -0
- package/dist/admin-shell/collections/restore-version-modal_module.css +31 -0
- package/dist/admin-shell/collections/view-menu.js +1 -1
- package/dist/routes/create-admin-account-route.js +1 -2
- package/dist/routes/create-admin-role-edit-route.js +1 -1
- package/dist/routes/create-admin-user-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.js +1 -1
- package/dist/server-fns/auth/current-user.js +3 -8
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/restore-version.d.ts +21 -0
- package/dist/server-fns/collections/restore-version.js +41 -0
- package/dist/server-fns/collections/upload.js +1 -1
- package/package.json +8 -20
- package/src/admin-shell/admin-roles/container.tsx +1 -1
- package/src/admin-shell/admin-roles/delete.tsx +1 -1
- package/src/admin-shell/admin-roles/list.tsx +1 -1
- package/src/admin-shell/admin-users/container.tsx +1 -1
- package/src/admin-shell/admin-users/delete.tsx +1 -1
- package/src/admin-shell/admin-users/list.tsx +1 -1
- package/src/admin-shell/chrome/admin-app-bar.tsx +1 -1
- package/src/admin-shell/chrome/dashboard.tsx +1 -1
- package/src/admin-shell/chrome/drawer-toggle.tsx +1 -1
- package/src/admin-shell/chrome/menu-drawer.tsx +1 -1
- package/src/admin-shell/chrome/preview-toggle.tsx +1 -1
- package/src/admin-shell/chrome/route-error.tsx +1 -1
- package/src/admin-shell/chrome/router-pager.tsx +2 -2
- package/src/admin-shell/chrome/th-sortable.tsx +1 -1
- package/src/admin-shell/collections/api.tsx +1 -1
- package/src/admin-shell/collections/create.tsx +1 -1
- package/src/admin-shell/collections/edit.tsx +1 -1
- package/src/admin-shell/collections/history.module.css +50 -1
- package/src/admin-shell/collections/history.tsx +149 -67
- package/src/admin-shell/collections/list.tsx +1 -1
- package/src/admin-shell/collections/preview-link.tsx +1 -1
- package/src/admin-shell/collections/restore-version-modal.module.css +48 -0
- package/src/admin-shell/collections/restore-version-modal.tsx +131 -0
- package/src/admin-shell/collections/view-menu.tsx +1 -1
- package/src/routes/create-admin-account-route.tsx +1 -1
- package/src/routes/create-admin-role-edit-route.tsx +1 -1
- package/src/routes/create-admin-user-edit-route.tsx +1 -1
- package/src/routes/create-collection-list-route.tsx +1 -1
- package/src/server-fns/auth/current-user.ts +3 -9
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/restore-version.ts +59 -0
- package/src/server-fns/collections/upload.ts +1 -1
|
@@ -11,7 +11,7 @@ import { useParams, useRouterState } from '@tanstack/react-router'
|
|
|
11
11
|
|
|
12
12
|
import type { CollectionAdminConfig, CollectionDefinition, WorkflowStatus } from '@byline/core'
|
|
13
13
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
14
|
-
import { Container, IconButton, Section, Select, Table } from '@byline/ui'
|
|
14
|
+
import { Button, CloseIcon, Container, IconButton, Modal, Section, Select, Table } from '@byline/ui/react'
|
|
15
15
|
import { renderFormatted, StatusBadge } from '@byline/ui/react'
|
|
16
16
|
import cx from 'classnames'
|
|
17
17
|
|
|
@@ -20,6 +20,7 @@ import { RouterPager } from '../chrome/router-pager.js'
|
|
|
20
20
|
import { TableHeadingCellSortable } from '../chrome/th-sortable.js'
|
|
21
21
|
import { formatNumber } from '../chrome/utils.js'
|
|
22
22
|
import styles from './history.module.css'
|
|
23
|
+
import { RestoreVersionModal } from './restore-version-modal.js'
|
|
23
24
|
import { ViewMenu } from './view-menu.js'
|
|
24
25
|
import type { ContentLocaleOption } from './view-menu.js'
|
|
25
26
|
|
|
@@ -27,7 +28,6 @@ import type { ContentLocaleOption } from './view-menu.js'
|
|
|
27
28
|
* Resolve a column value from a document, checking `fields` first (user-defined
|
|
28
29
|
* collection fields) then the root (metadata like status, updated_at).
|
|
29
30
|
*/
|
|
30
|
-
// biome-ignore lint/suspicious/noExplicitAny: collection rows are heterogeneous
|
|
31
31
|
function getColumnValue(document: any, fieldName: string): any {
|
|
32
32
|
if (document.fields && fieldName in document.fields) {
|
|
33
33
|
return document.fields[fieldName]
|
|
@@ -75,7 +75,7 @@ function padRows(value: number) {
|
|
|
75
75
|
key={`empty-row-${
|
|
76
76
|
// biome-ignore lint/suspicious/noArrayIndexKey: we're okay here
|
|
77
77
|
index
|
|
78
|
-
|
|
78
|
+
}`}
|
|
79
79
|
className={cx('byline-coll-history-pad-row', styles.padRow)}
|
|
80
80
|
>
|
|
81
81
|
|
|
@@ -112,6 +112,15 @@ export const HistoryView = ({
|
|
|
112
112
|
versionId: string
|
|
113
113
|
label: string
|
|
114
114
|
} | null>(null)
|
|
115
|
+
const [restoreTarget, setRestoreTarget] = useState<{
|
|
116
|
+
versionId: string
|
|
117
|
+
label: string
|
|
118
|
+
versionNumber: number
|
|
119
|
+
} | null>(null)
|
|
120
|
+
const currentVersionId =
|
|
121
|
+
currentDocument && typeof currentDocument.versionId === 'string'
|
|
122
|
+
? (currentDocument.versionId as string)
|
|
123
|
+
: null
|
|
115
124
|
|
|
116
125
|
function handleOnPageSizeChange(value: string | null): void {
|
|
117
126
|
if (typeof value !== 'string' || value.length === 0) return
|
|
@@ -164,8 +173,8 @@ export const HistoryView = ({
|
|
|
164
173
|
scope="col"
|
|
165
174
|
className={cx('byline-coll-history-col-version', styles.colVersion)}
|
|
166
175
|
/>
|
|
167
|
-
{columns.
|
|
168
|
-
|
|
176
|
+
{columns.flatMap((column) => {
|
|
177
|
+
const cell = (
|
|
169
178
|
<TableHeadingCellSortable
|
|
170
179
|
key={String(column.fieldName)}
|
|
171
180
|
fieldName={String(column.fieldName)}
|
|
@@ -176,6 +185,17 @@ export const HistoryView = ({
|
|
|
176
185
|
className={column.className}
|
|
177
186
|
/>
|
|
178
187
|
)
|
|
188
|
+
if (column.fieldName === 'title') {
|
|
189
|
+
return [
|
|
190
|
+
cell,
|
|
191
|
+
<th
|
|
192
|
+
key="__restore"
|
|
193
|
+
scope="col"
|
|
194
|
+
className={cx('byline-coll-history-col-restore', styles.colRestore)}
|
|
195
|
+
/>,
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
return [cell]
|
|
179
199
|
})}
|
|
180
200
|
</Table.Row>
|
|
181
201
|
</Table.Header>
|
|
@@ -214,84 +234,119 @@ export const HistoryView = ({
|
|
|
214
234
|
</IconButton>
|
|
215
235
|
) : null}
|
|
216
236
|
</Table.Cell>
|
|
217
|
-
{columns.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
237
|
+
{columns.flatMap((column) => {
|
|
238
|
+
const dataCell = (
|
|
239
|
+
<Table.Cell
|
|
240
|
+
key={String(column.fieldName)}
|
|
241
|
+
className={cx({
|
|
242
|
+
'byline-coll-history-cell-right': column.align === 'right',
|
|
243
|
+
[styles.cellRight]: column.align === 'right',
|
|
244
|
+
'byline-coll-history-cell-center': column.align === 'center',
|
|
245
|
+
[styles.cellCenter]: column.align === 'center',
|
|
246
|
+
})}
|
|
247
|
+
>
|
|
248
|
+
{column.fieldName === 'title' ? (
|
|
249
|
+
versionId && currentDocument ? (
|
|
250
|
+
<button
|
|
251
|
+
type="button"
|
|
252
|
+
className={cx(
|
|
253
|
+
'byline-coll-history-title-button',
|
|
254
|
+
styles.titleButton
|
|
255
|
+
)}
|
|
256
|
+
onClick={() =>
|
|
257
|
+
setSelectedVersion({
|
|
258
|
+
versionId,
|
|
259
|
+
label: new Date(document.createdAt).toLocaleString(),
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
>
|
|
263
|
+
{column.formatter
|
|
264
|
+
? renderFormatted(
|
|
244
265
|
getColumnValue(document, column.fieldName as string),
|
|
245
266
|
document,
|
|
246
267
|
column.formatter
|
|
247
268
|
)
|
|
248
|
-
|
|
269
|
+
: resolveDisplayValue(
|
|
249
270
|
getColumnValue(document, column.fieldName as string),
|
|
250
271
|
locale,
|
|
251
272
|
defaultContentLocale
|
|
252
273
|
) || '------'}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
274
|
+
</button>
|
|
275
|
+
) : (
|
|
276
|
+
<Link
|
|
277
|
+
to={'/admin/collections/$collection/$id' as never}
|
|
278
|
+
params={{
|
|
279
|
+
collection,
|
|
280
|
+
id: document.id,
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
{column.formatter
|
|
284
|
+
? renderFormatted(
|
|
264
285
|
getColumnValue(document, column.fieldName as string),
|
|
265
286
|
document,
|
|
266
287
|
column.formatter
|
|
267
288
|
)
|
|
268
|
-
|
|
289
|
+
: resolveDisplayValue(
|
|
269
290
|
getColumnValue(document, column.fieldName as string),
|
|
270
291
|
locale,
|
|
271
292
|
defaultContentLocale
|
|
272
293
|
) || '------'}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
</Link>
|
|
295
|
+
)
|
|
296
|
+
) : column.formatter ? (
|
|
297
|
+
renderFormatted(
|
|
298
|
+
getColumnValue(document, column.fieldName as string),
|
|
299
|
+
document,
|
|
300
|
+
column.formatter
|
|
301
|
+
)
|
|
302
|
+
) : column.fieldName === 'status' && workflowStatuses ? (
|
|
303
|
+
<StatusBadge
|
|
304
|
+
status={document.status}
|
|
305
|
+
workflowStatuses={workflowStatuses}
|
|
306
|
+
/>
|
|
307
|
+
) : (
|
|
308
|
+
resolveDisplayValue(
|
|
309
|
+
getColumnValue(document, column.fieldName as string),
|
|
310
|
+
locale,
|
|
311
|
+
defaultContentLocale
|
|
312
|
+
) || ''
|
|
313
|
+
)}
|
|
314
|
+
</Table.Cell>
|
|
315
|
+
)
|
|
316
|
+
if (column.fieldName === 'title') {
|
|
317
|
+
return [
|
|
318
|
+
dataCell,
|
|
319
|
+
<Table.Cell
|
|
320
|
+
key="__restore"
|
|
321
|
+
className={cx('byline-coll-history-restore-cell', styles.restoreCell)}
|
|
322
|
+
>
|
|
323
|
+
{versionId && versionId !== currentVersionId ? (
|
|
324
|
+
<Button
|
|
325
|
+
type="button"
|
|
326
|
+
variant="outlined"
|
|
327
|
+
size="xs"
|
|
328
|
+
intent="noeffect"
|
|
329
|
+
onClick={() =>
|
|
330
|
+
setRestoreTarget({
|
|
331
|
+
versionId,
|
|
332
|
+
label: new Date(document.createdAt).toLocaleString(),
|
|
333
|
+
versionNumber,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
className={cx(
|
|
337
|
+
'byline-coll-history-restore-button',
|
|
338
|
+
styles.restoreButton
|
|
339
|
+
)}
|
|
340
|
+
title="Restore this version as the current draft"
|
|
341
|
+
>
|
|
342
|
+
Restore
|
|
343
|
+
</Button>
|
|
344
|
+
) : null}
|
|
345
|
+
</Table.Cell>,
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
return [dataCell]
|
|
349
|
+
})}
|
|
295
350
|
</Table.Row>
|
|
296
351
|
)
|
|
297
352
|
})}
|
|
@@ -347,6 +402,33 @@ export const HistoryView = ({
|
|
|
347
402
|
/>
|
|
348
403
|
</Suspense>
|
|
349
404
|
)}
|
|
405
|
+
|
|
406
|
+
<Modal
|
|
407
|
+
isOpen={restoreTarget != null}
|
|
408
|
+
onDismiss={() => setRestoreTarget(null)}
|
|
409
|
+
closeOnOverlayClick={false}
|
|
410
|
+
>
|
|
411
|
+
<Modal.Container className={cx('byline-coll-history-restore-modal', styles.restoreModal)}>
|
|
412
|
+
<Modal.Header
|
|
413
|
+
className={cx('byline-coll-history-restore-modal-head', styles.restoreModalHead)}
|
|
414
|
+
>
|
|
415
|
+
<h3 className="m-0">Restore version</h3>
|
|
416
|
+
<IconButton aria-label="Close" size="xs" onClick={() => setRestoreTarget(null)}>
|
|
417
|
+
<CloseIcon width="14px" height="14px" svgClassName="white-icon" />
|
|
418
|
+
</IconButton>
|
|
419
|
+
</Modal.Header>
|
|
420
|
+
{restoreTarget ? (
|
|
421
|
+
<RestoreVersionModal
|
|
422
|
+
collection={collection}
|
|
423
|
+
documentId={id}
|
|
424
|
+
versionId={restoreTarget.versionId}
|
|
425
|
+
versionLabel={restoreTarget.label}
|
|
426
|
+
versionNumber={restoreTarget.versionNumber}
|
|
427
|
+
onClose={() => setRestoreTarget(null)}
|
|
428
|
+
/>
|
|
429
|
+
) : null}
|
|
430
|
+
</Modal.Container>
|
|
431
|
+
</Modal>
|
|
350
432
|
</>
|
|
351
433
|
)
|
|
352
434
|
}
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
import { useState } from 'react'
|
|
41
41
|
|
|
42
42
|
import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
|
|
43
|
-
import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui'
|
|
43
|
+
import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui/react'
|
|
44
44
|
import cx from 'classnames'
|
|
45
45
|
|
|
46
46
|
import { enablePreviewModeFn } from '../../server-fns/preview/index.js'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RestoreVersionModal — modal body for the "make current" action on the
|
|
3
|
+
* collection history view.
|
|
4
|
+
*
|
|
5
|
+
* Override handles:
|
|
6
|
+
* .byline-coll-restore-content — outer Modal.Content (gap)
|
|
7
|
+
* .byline-coll-restore-body — vertical stack of identity rows
|
|
8
|
+
* .byline-coll-restore-warning — warning paragraph
|
|
9
|
+
* .byline-coll-restore-actions — Cancel/Restore row
|
|
10
|
+
* .byline-coll-restore-button — action buttons (min-width)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.content,
|
|
14
|
+
:global(.byline-coll-restore-content) {
|
|
15
|
+
gap: 0.25rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.body,
|
|
19
|
+
:global(.byline-coll-restore-body) {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.row,
|
|
26
|
+
:global(.byline-coll-restore-row) {
|
|
27
|
+
margin: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.warning,
|
|
31
|
+
:global(.byline-coll-restore-warning) {
|
|
32
|
+
margin-top: 0.75rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.actions,
|
|
36
|
+
:global(.byline-coll-restore-actions) {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: flex-end;
|
|
40
|
+
gap: 0.5rem;
|
|
41
|
+
margin-top: 1.5rem;
|
|
42
|
+
margin-bottom: 1rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.button,
|
|
46
|
+
:global(.byline-coll-restore-button) {
|
|
47
|
+
min-width: 4rem;
|
|
48
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Infonomic Company Limited
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Restore-version modal body.
|
|
13
|
+
*
|
|
14
|
+
* Confirmation dialog for the "make current" action on the history view.
|
|
15
|
+
* Calls the restore-version server fn, invalidates the router, and
|
|
16
|
+
* navigates to the document's edit view so the user lands on the freshly
|
|
17
|
+
* restored draft.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useState } from 'react'
|
|
21
|
+
import { useRouter } from '@tanstack/react-router'
|
|
22
|
+
|
|
23
|
+
import { Alert, Button, LoaderEllipsis, Modal } from '@byline/ui/react'
|
|
24
|
+
import cx from 'classnames'
|
|
25
|
+
|
|
26
|
+
import { restoreDocumentVersion } from '../../server-fns/collections/index.js'
|
|
27
|
+
import { useNavigate } from '../chrome/loose-router.js'
|
|
28
|
+
import styles from './restore-version-modal.module.css'
|
|
29
|
+
|
|
30
|
+
interface RestoreVersionModalProps {
|
|
31
|
+
collection: string
|
|
32
|
+
documentId: string
|
|
33
|
+
versionId: string
|
|
34
|
+
versionLabel: string
|
|
35
|
+
versionNumber: number
|
|
36
|
+
onClose: () => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function RestoreVersionModal({
|
|
40
|
+
collection,
|
|
41
|
+
documentId,
|
|
42
|
+
versionId,
|
|
43
|
+
versionLabel,
|
|
44
|
+
versionNumber,
|
|
45
|
+
onClose,
|
|
46
|
+
}: RestoreVersionModalProps) {
|
|
47
|
+
const navigate = useNavigate()
|
|
48
|
+
const router = useRouter()
|
|
49
|
+
const [error, setError] = useState<string | null>(null)
|
|
50
|
+
const [pending, setPending] = useState(false)
|
|
51
|
+
|
|
52
|
+
async function handleRestore() {
|
|
53
|
+
if (pending) return
|
|
54
|
+
setPending(true)
|
|
55
|
+
setError(null)
|
|
56
|
+
try {
|
|
57
|
+
await restoreDocumentVersion({
|
|
58
|
+
data: { collection, id: documentId, versionId },
|
|
59
|
+
})
|
|
60
|
+
onClose()
|
|
61
|
+
await router.invalidate()
|
|
62
|
+
navigate({
|
|
63
|
+
to: '/admin/collections/$collection/$id' as never,
|
|
64
|
+
params: { collection, id: documentId },
|
|
65
|
+
})
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const code = getErrorCode(err)
|
|
68
|
+
if (code === 'ERR_INVALID_TRANSITION') {
|
|
69
|
+
setError('This version is already the current version of the document.')
|
|
70
|
+
} else if (code === 'ERR_NOT_FOUND') {
|
|
71
|
+
setError('The selected version could not be found. The history may be out of date.')
|
|
72
|
+
} else if (code === 'ERR_FORBIDDEN' || code === 'ERR_UNAUTHENTICATED') {
|
|
73
|
+
setError('You do not have permission to restore versions for this collection.')
|
|
74
|
+
} else {
|
|
75
|
+
setError('Could not restore this version. Please try again.')
|
|
76
|
+
}
|
|
77
|
+
setPending(false)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Modal.Content className={cx('byline-coll-restore-content', styles.content)}>
|
|
83
|
+
<div className={cx('byline-coll-restore-body', styles.body)}>
|
|
84
|
+
{error ? (
|
|
85
|
+
<Alert intent="danger" close={false}>
|
|
86
|
+
{error}
|
|
87
|
+
</Alert>
|
|
88
|
+
) : null}
|
|
89
|
+
<p className={cx('byline-coll-restore-row', styles.row)}>
|
|
90
|
+
<span className="muted">Version:</span> {versionNumber}
|
|
91
|
+
</p>
|
|
92
|
+
<p className={cx('byline-coll-restore-row', styles.row)}>
|
|
93
|
+
<span className="muted">Created:</span> {versionLabel}
|
|
94
|
+
</p>
|
|
95
|
+
<p className={cx('byline-coll-restore-warning', styles.warning)}>
|
|
96
|
+
This will create a new draft version of this document with the content from version{' '}
|
|
97
|
+
{versionNumber}, and that draft will become the current version. The existing versions
|
|
98
|
+
(including any published version) are preserved in history. The restored draft will need
|
|
99
|
+
to be published through the normal workflow.
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
<div className={cx('byline-coll-restore-actions', styles.actions)}>
|
|
103
|
+
<Button
|
|
104
|
+
type="button"
|
|
105
|
+
intent="secondary"
|
|
106
|
+
size="sm"
|
|
107
|
+
onClick={onClose}
|
|
108
|
+
disabled={pending}
|
|
109
|
+
className={cx('byline-coll-restore-button', styles.button)}
|
|
110
|
+
>
|
|
111
|
+
Cancel
|
|
112
|
+
</Button>
|
|
113
|
+
<Button
|
|
114
|
+
size="sm"
|
|
115
|
+
intent="primary"
|
|
116
|
+
onClick={handleRestore}
|
|
117
|
+
disabled={pending}
|
|
118
|
+
className={cx('byline-coll-restore-button', styles.button)}
|
|
119
|
+
>
|
|
120
|
+
{pending === true ? <LoaderEllipsis size={42} /> : 'Restore as Draft'}
|
|
121
|
+
</Button>
|
|
122
|
+
</div>
|
|
123
|
+
</Modal.Content>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getErrorCode(err: unknown): string | null {
|
|
128
|
+
return typeof (err as { code?: unknown })?.code === 'string'
|
|
129
|
+
? (err as { code: string }).code
|
|
130
|
+
: null
|
|
131
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { useEffect } from 'react'
|
|
10
10
|
|
|
11
11
|
import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
|
|
12
|
-
import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui'
|
|
12
|
+
import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
|
|
13
13
|
import cx from 'classnames'
|
|
14
14
|
|
|
15
15
|
import { useNavigate } from '../chrome/loose-router.js'
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createFileRoute } from '@tanstack/react-router'
|
|
10
10
|
|
|
11
|
-
import { Container, Section } from '@byline/ui'
|
|
11
|
+
import { Container, Section } from '@byline/ui/react'
|
|
12
12
|
import { AccountSelfContainer } from '@byline/ui/react'
|
|
13
13
|
|
|
14
14
|
import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createFileRoute, notFound } from '@tanstack/react-router'
|
|
10
10
|
|
|
11
|
-
import { Container, Section } from '@byline/ui'
|
|
11
|
+
import { Container, Section } from '@byline/ui/react'
|
|
12
12
|
|
|
13
13
|
import { RoleContainer } from '../admin-shell/admin-roles/container.js'
|
|
14
14
|
import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { createFileRoute, notFound } from '@tanstack/react-router'
|
|
10
10
|
|
|
11
|
-
import { Container, Section } from '@byline/ui'
|
|
11
|
+
import { Container, Section } from '@byline/ui/react'
|
|
12
12
|
|
|
13
13
|
import { AccountContainer } from '../admin-shell/admin-users/container.js'
|
|
14
14
|
import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
getCollectionDefinition,
|
|
16
16
|
getWorkflowStatuses,
|
|
17
17
|
} from '@byline/core'
|
|
18
|
-
import { useToastManager } from '@byline/ui'
|
|
18
|
+
import { useToastManager } from '@byline/ui/react'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
|
|
21
21
|
import { BreadcrumbsClient } from '../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js'
|
|
@@ -21,11 +21,9 @@
|
|
|
21
21
|
import { createServerFn } from '@tanstack/react-start'
|
|
22
22
|
|
|
23
23
|
import { type Actor, AuthError } from '@byline/auth'
|
|
24
|
-
import { getServerConfig } from '@byline/core'
|
|
25
|
-
import type { PgAdapter } from '@byline/db-postgres'
|
|
26
|
-
import { createAdminUsersRepository } from '@byline/db-postgres/admin'
|
|
27
24
|
|
|
28
25
|
import { getAdminRequestContext } from '../../auth/auth-context.js'
|
|
26
|
+
import { bylineCore } from '../../integrations/byline-core.js'
|
|
29
27
|
|
|
30
28
|
export interface CurrentAdminUser {
|
|
31
29
|
id: string
|
|
@@ -57,9 +55,7 @@ export const getCurrentAdminUser = createServerFn({ method: 'GET' }).handler(
|
|
|
57
55
|
throw new Error('unexpected null actor after getAdminRequestContext')
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
const
|
|
61
|
-
const users = createAdminUsersRepository(db)
|
|
62
|
-
const row = await users.getById(actor.id)
|
|
58
|
+
const row = await bylineCore().adminStore!.adminUsers.getById(actor.id)
|
|
63
59
|
if (!row) {
|
|
64
60
|
// Session resolved to an admin id that no longer exists — force the
|
|
65
61
|
// caller back through sign-in rather than return partial data.
|
|
@@ -97,9 +93,7 @@ export const getCurrentAdminUserSoft = createServerFn({ method: 'GET' }).handler
|
|
|
97
93
|
}
|
|
98
94
|
if (!actor) return null
|
|
99
95
|
|
|
100
|
-
const
|
|
101
|
-
const users = createAdminUsersRepository(db)
|
|
102
|
-
const row = await users.getById(actor.id)
|
|
96
|
+
const row = await bylineCore().adminStore!.adminUsers.getById(actor.id)
|
|
103
97
|
if (!row) return null
|
|
104
98
|
|
|
105
99
|
return {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
10
|
+
|
|
11
|
+
import { ERR_NOT_FOUND, getLogger, getServerConfig } from '@byline/core'
|
|
12
|
+
import type { DocumentLifecycleContext } from '@byline/core/services'
|
|
13
|
+
import { restoreDocumentVersion as restoreDocumentVersionService } from '@byline/core/services'
|
|
14
|
+
|
|
15
|
+
import { getAdminRequestContext } from '../../auth/auth-context.js'
|
|
16
|
+
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Restore a historical document version as the new current version
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export const restoreDocumentVersion = createServerFn({ method: 'POST' })
|
|
23
|
+
.inputValidator((input: { collection: string; id: string; versionId: string }) => input)
|
|
24
|
+
.handler(async ({ data: input }) => {
|
|
25
|
+
const { collection: path, id, versionId } = input
|
|
26
|
+
const logger = getLogger()
|
|
27
|
+
const config = await ensureCollection(path)
|
|
28
|
+
if (!config) {
|
|
29
|
+
throw ERR_NOT_FOUND({
|
|
30
|
+
message: 'Collection not found',
|
|
31
|
+
details: { collectionPath: path },
|
|
32
|
+
}).log(logger)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const serverConfig = getServerConfig()
|
|
36
|
+
const ctx: DocumentLifecycleContext = {
|
|
37
|
+
db: serverConfig.db,
|
|
38
|
+
definition: config.definition,
|
|
39
|
+
collectionId: config.collection.id,
|
|
40
|
+
collectionVersion: config.collection.version,
|
|
41
|
+
collectionPath: path,
|
|
42
|
+
logger,
|
|
43
|
+
defaultLocale: serverConfig.i18n.content.defaultLocale,
|
|
44
|
+
slugifier: serverConfig.slugifier,
|
|
45
|
+
requestContext: await getAdminRequestContext(),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await restoreDocumentVersionService(ctx, {
|
|
49
|
+
documentId: id,
|
|
50
|
+
sourceVersionId: versionId,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
status: 'ok' as const,
|
|
55
|
+
documentId: result.documentId,
|
|
56
|
+
documentVersionId: result.documentVersionId,
|
|
57
|
+
sourceVersionId: result.sourceVersionId,
|
|
58
|
+
}
|
|
59
|
+
})
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import { ERR_NOT_FOUND, ERR_VALIDATION, getServerConfig, getUploadFields } from '@byline/core'
|
|
11
11
|
import { getLogger, withLogContext } from '@byline/core/logger'
|
|
12
12
|
import { uploadField as coreUploadField } from '@byline/core/services'
|
|
13
|
-
import { extractImageMeta, generateImageVariants, isBypassMimeType } from '@byline/
|
|
13
|
+
import { extractImageMeta, generateImageVariants, isBypassMimeType } from '@byline/core/image'
|
|
14
14
|
|
|
15
15
|
import { getAdminRequestContext } from '../../auth/auth-context.js'
|
|
16
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|