@byline/host-tanstack-start 4.16.0 → 4.18.0
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/chrome/admin-menu-drawer.js +14 -14
- package/dist/admin-shell/chrome/dashboard.js +41 -5
- package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
- package/dist/admin-shell/chrome/router-pager.d.ts +7 -0
- package/dist/admin-shell/chrome/router-pager.js +13 -0
- package/dist/admin-shell/chrome/scroll-to-top.d.ts +20 -0
- package/dist/admin-shell/chrome/scroll-to-top.js +9 -0
- package/dist/admin-shell/collections/api.d.ts +2 -2
- package/dist/admin-shell/collections/create.d.ts +2 -2
- package/dist/admin-shell/collections/create.js +1 -0
- package/dist/admin-shell/collections/edit.d.ts +2 -2
- package/dist/admin-shell/collections/edit.js +1 -0
- package/dist/admin-shell/collections/history.d.ts +4 -12
- package/dist/admin-shell/collections/history.js +112 -303
- package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
- package/dist/admin-shell/collections/reindex-button.js +5 -1
- package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
- package/dist/admin-shell/collections/restore-version-modal.js +8 -24
- package/dist/admin-shell/collections/version-history.d.ts +72 -0
- package/dist/admin-shell/collections/version-history.js +259 -0
- package/dist/admin-shell/singletons/history.d.ts +19 -0
- package/dist/admin-shell/singletons/history.js +170 -0
- package/dist/admin-shell/singletons/history.test.d.ts +8 -0
- package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
- package/dist/admin-shell/singletons/preview-link.js +54 -0
- package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
- package/dist/admin-shell/singletons/view-menu.js +116 -0
- package/dist/admin-shell/singletons/view.d.ts +18 -0
- package/dist/admin-shell/singletons/view.js +259 -0
- package/dist/admin-shell/singletons/view.test.d.ts +8 -0
- package/dist/integrations/api-utils.d.ts +18 -4
- package/dist/integrations/api-utils.js +10 -3
- package/dist/routes/create-admin-dashboard-route.js +2 -2
- package/dist/routes/create-collection-api-route.js +3 -3
- package/dist/routes/create-collection-create-route.js +4 -3
- package/dist/routes/create-collection-edit-route.js +4 -3
- package/dist/routes/create-collection-history-route.js +4 -3
- package/dist/routes/create-collection-list-route.js +4 -3
- package/dist/routes/create-singleton-history-route.d.ts +8 -0
- package/dist/routes/create-singleton-history-route.js +99 -0
- package/dist/routes/create-singleton-route.d.ts +35 -0
- package/dist/routes/create-singleton-route.js +130 -0
- package/dist/routes/get-multi-collection-definition.d.ts +10 -0
- package/dist/routes/get-multi-collection-definition.js +6 -0
- package/dist/routes/get-singleton-definition.d.ts +10 -0
- package/dist/routes/get-singleton-definition.js +6 -0
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.js +2 -0
- package/dist/server-fns/actors.d.ts +16 -0
- package/dist/server-fns/actors.js +25 -0
- package/dist/server-fns/admin-document-presentation.d.ts +30 -0
- package/dist/server-fns/admin-document-presentation.js +32 -0
- package/dist/server-fns/collections/actors.d.ts +1 -10
- package/dist/server-fns/collections/actors.js +1 -25
- package/dist/server-fns/collections/get.js +9 -26
- package/dist/server-fns/collections/reorder.js +1 -1
- package/dist/server-fns/collections/scheduled-publication.js +2 -2
- package/dist/server-fns/collections/upload.js +3 -3
- package/dist/server-fns/collections/utils.d.ts +1 -7
- package/dist/server-fns/collections/utils.js +1 -4
- package/dist/server-fns/serialise.d.ts +14 -0
- package/dist/server-fns/serialise.js +4 -0
- package/dist/server-fns/singleton-document-read.d.ts +20 -0
- package/dist/server-fns/singleton-document-read.js +10 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/change-status.d.ts +14 -0
- package/dist/server-fns/singletons/change-status.js +7 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
- package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
- package/dist/server-fns/singletons/copy-to-locale.js +11 -0
- package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
- package/dist/server-fns/singletons/find-by-version.js +9 -0
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
- package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
- package/dist/server-fns/singletons/get.d.ts +24 -0
- package/dist/server-fns/singletons/get.js +6 -0
- package/dist/server-fns/singletons/history.d.ts +31 -0
- package/dist/server-fns/singletons/history.js +22 -0
- package/dist/server-fns/singletons/index.d.ts +19 -0
- package/dist/server-fns/singletons/index.js +12 -0
- package/dist/server-fns/singletons/restore-version.d.ts +14 -0
- package/dist/server-fns/singletons/restore-version.js +7 -0
- package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
- package/dist/server-fns/singletons/schedule-publish.js +10 -0
- package/dist/server-fns/singletons/unpublish.d.ts +12 -0
- package/dist/server-fns/singletons/unpublish.js +7 -0
- package/dist/server-fns/singletons/update.d.ts +14 -0
- package/dist/server-fns/singletons/update.js +14 -0
- package/dist/vitest-jsdom.test.d.ts +8 -0
- package/package.json +24 -18
- package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -13
- package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
- package/src/admin-shell/chrome/dashboard.tsx +43 -9
- package/src/admin-shell/chrome/router-pager.tsx +22 -0
- package/src/admin-shell/chrome/scroll-to-top.test.ts +56 -0
- package/src/admin-shell/chrome/scroll-to-top.ts +28 -0
- package/src/admin-shell/collections/api.tsx +2 -2
- package/src/admin-shell/collections/create.tsx +6 -2
- package/src/admin-shell/collections/edit.tsx +6 -2
- package/src/admin-shell/collections/history.module.css +1 -1
- package/src/admin-shell/collections/history.tsx +162 -442
- package/src/admin-shell/collections/reindex-button.tsx +4 -2
- package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
- package/src/admin-shell/collections/version-history.tsx +349 -0
- package/src/admin-shell/singletons/history.test.tsx +407 -0
- package/src/admin-shell/singletons/history.tsx +185 -0
- package/src/admin-shell/singletons/preview-link.tsx +75 -0
- package/src/admin-shell/singletons/view-menu.tsx +136 -0
- package/src/admin-shell/singletons/view.test.tsx +400 -0
- package/src/admin-shell/singletons/view.tsx +351 -0
- package/src/integrations/api-utils.ts +34 -9
- package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
- package/src/routes/create-admin-dashboard-route.tsx +10 -2
- package/src/routes/create-collection-api-route.tsx +4 -4
- package/src/routes/create-collection-create-route.tsx +5 -8
- package/src/routes/create-collection-edit-route.tsx +5 -4
- package/src/routes/create-collection-history-route.tsx +5 -8
- package/src/routes/create-collection-list-route.tsx +5 -8
- package/src/routes/create-singleton-history-route.test.node.ts +148 -0
- package/src/routes/create-singleton-history-route.tsx +116 -0
- package/src/routes/create-singleton-route.test.node.ts +172 -0
- package/src/routes/create-singleton-route.tsx +176 -0
- package/src/routes/get-multi-collection-definition.ts +16 -0
- package/src/routes/get-singleton-definition.ts +16 -0
- package/src/routes/index.ts +2 -0
- package/src/server-fns/actors.ts +43 -0
- package/src/server-fns/admin-document-presentation.ts +78 -0
- package/src/server-fns/collections/actors.ts +1 -52
- package/src/server-fns/collections/get.ts +10 -49
- package/src/server-fns/collections/reorder.ts +1 -1
- package/src/server-fns/collections/scheduled-publication.ts +7 -1
- package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
- package/src/server-fns/collections/upload.ts +7 -3
- package/src/server-fns/collections/utils.ts +1 -9
- package/src/server-fns/serialise.ts +17 -0
- package/src/server-fns/singleton-document-read.ts +32 -0
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/change-status.ts +19 -0
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
- package/src/server-fns/singletons/copy-to-locale.ts +32 -0
- package/src/server-fns/singletons/find-by-version.ts +23 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
- package/src/server-fns/singletons/get.ts +32 -0
- package/src/server-fns/singletons/history.ts +41 -0
- package/src/server-fns/singletons/index.ts +20 -0
- package/src/server-fns/singletons/restore-version.ts +19 -0
- package/src/server-fns/singletons/schedule-publish.ts +24 -0
- package/src/server-fns/singletons/singletons.test.node.ts +298 -0
- package/src/server-fns/singletons/unpublish.ts +19 -0
- package/src/server-fns/singletons/update.ts +31 -0
- package/src/vitest-jsdom.test.tsx +27 -0
|
@@ -6,57 +6,40 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { useParams, useRouterState } from '@tanstack/react-router'
|
|
9
|
+
import { useParams, useRouter, useRouterState } from '@tanstack/react-router'
|
|
11
10
|
|
|
12
11
|
import { AdminTabs, renderFormatted, StatusBadge } from '@byline/admin/react'
|
|
13
12
|
import { useBylineAdminServices } from '@byline/admin/services'
|
|
14
|
-
import type { CollectionAdminConfig,
|
|
13
|
+
import type { CollectionAdminConfig, MultiCollectionDefinition, WorkflowStatus } from '@byline/core'
|
|
15
14
|
import type { AnyCollectionSchemaTypes } from '@byline/core/zod-schemas'
|
|
16
15
|
import { useTranslation } from '@byline/i18n/react'
|
|
17
|
-
import {
|
|
18
|
-
Button,
|
|
19
|
-
CloseIcon,
|
|
20
|
-
Container,
|
|
21
|
-
IconButton,
|
|
22
|
-
Modal,
|
|
23
|
-
Section,
|
|
24
|
-
Select,
|
|
25
|
-
Table,
|
|
26
|
-
} from '@byline/ui/react'
|
|
16
|
+
import { Button, Container, Section, Table } from '@byline/ui/react'
|
|
27
17
|
import cx from 'clsx'
|
|
28
18
|
|
|
29
19
|
import { getAdminRouteId, getAdminRoutePath } from '../../routes/admin-path.js'
|
|
20
|
+
import { restoreDocumentVersion } from '../../server-fns/collections/index.js'
|
|
30
21
|
import { Link, useNavigate } from '../chrome/loose-router.js'
|
|
31
|
-
import { RouterPager } from '../chrome/router-pager.js'
|
|
32
22
|
import { TableHeadingCellSortable } from '../chrome/th-sortable.js'
|
|
33
|
-
import { formatNumber } from '../chrome/utils.js'
|
|
34
23
|
import { type DocumentHistoryData, DocumentHistoryView } from './document-history.js'
|
|
35
24
|
import styles from './history.module.css'
|
|
36
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
HistoryStats,
|
|
27
|
+
VersionHistoryCore,
|
|
28
|
+
type VersionHistoryData,
|
|
29
|
+
type VersionHistoryRowPresentation,
|
|
30
|
+
} from './version-history.js'
|
|
37
31
|
import { ViewMenu } from './view-menu.js'
|
|
38
32
|
import type { ContentLocaleOption } from './view-menu.js'
|
|
39
33
|
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
function getColumnValue(document: any, fieldName: string): any {
|
|
45
|
-
if (document.fields && fieldName in document.fields) {
|
|
46
|
-
return document.fields[fieldName]
|
|
34
|
+
/** Resolve user fields before root document metadata such as status. */
|
|
35
|
+
function getColumnValue(document: Record<string, unknown>, fieldName: string): unknown {
|
|
36
|
+
if (document.fields && typeof document.fields === 'object' && fieldName in document.fields) {
|
|
37
|
+
return (document.fields as Record<string, unknown>)[fieldName]
|
|
47
38
|
}
|
|
48
39
|
return document[fieldName]
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
// bundle that cannot be resolved by Node during SSR.
|
|
53
|
-
const DiffModal = lazy(() => import('@byline/admin/react').then((m) => ({ default: m.DiffModal })))
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Safely extract a displayable string from a field value that may be a plain
|
|
57
|
-
* string or a locale-keyed object (when locale='all').
|
|
58
|
-
* Falls back to the supplied default content locale, then the first available value.
|
|
59
|
-
*/
|
|
42
|
+
/** Resolve a scalar display value from a per-locale history response. */
|
|
60
43
|
function resolveDisplayValue(
|
|
61
44
|
value: unknown,
|
|
62
45
|
locale: string | undefined,
|
|
@@ -74,40 +57,6 @@ function resolveDisplayValue(
|
|
|
74
57
|
return String(value)
|
|
75
58
|
}
|
|
76
59
|
|
|
77
|
-
function Stats({ total }: { total: number }) {
|
|
78
|
-
return (
|
|
79
|
-
<span className={cx('byline-coll-history-stats', styles.stats)}>
|
|
80
|
-
{formatNumber(total as number, 0)}
|
|
81
|
-
</span>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function padRows(value: number) {
|
|
86
|
-
return Array.from({ length: value }).map((_, index) => (
|
|
87
|
-
<div
|
|
88
|
-
key={`empty-row-${
|
|
89
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: we're okay here
|
|
90
|
-
index
|
|
91
|
-
}`}
|
|
92
|
-
className={cx('byline-coll-history-pad-row', styles.padRow)}
|
|
93
|
-
>
|
|
94
|
-
|
|
95
|
-
</div>
|
|
96
|
-
))
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Maps `event_type` values to audit-strip action labels. Unknown event
|
|
101
|
-
* types fall back to the raw value rather than a missing-key warning.
|
|
102
|
-
*/
|
|
103
|
-
const AUDIT_ACTION_KEYS: Record<string, string> = {
|
|
104
|
-
create: 'collections.history.audit.actionCreate',
|
|
105
|
-
update: 'collections.history.audit.actionUpdate',
|
|
106
|
-
restore: 'collections.history.audit.actionRestore',
|
|
107
|
-
copy_to_locale: 'collections.history.audit.actionCopyToLocale',
|
|
108
|
-
delete_locale: 'collections.history.audit.actionDeleteLocale',
|
|
109
|
-
}
|
|
110
|
-
|
|
111
60
|
export const HistoryView = ({
|
|
112
61
|
collectionDefinition,
|
|
113
62
|
adminConfig,
|
|
@@ -118,21 +67,13 @@ export const HistoryView = ({
|
|
|
118
67
|
contentLocales,
|
|
119
68
|
defaultContentLocale,
|
|
120
69
|
}: {
|
|
121
|
-
collectionDefinition:
|
|
70
|
+
collectionDefinition: MultiCollectionDefinition
|
|
122
71
|
adminConfig?: CollectionAdminConfig
|
|
123
72
|
data: AnyCollectionSchemaTypes['HistoryType'] & {
|
|
124
|
-
/**
|
|
125
|
-
* Audit display labels (the acting user per version), resolved admin-side from each
|
|
126
|
-
* version's `createdBy` id (see docs/07-auth-and-security/02-auditability.md — Workstream 1). Ids
|
|
127
|
-
* absent from the map belong to deleted users.
|
|
128
|
-
*/
|
|
73
|
+
/** Admin-resolved acting-user labels keyed by raw version actor id. */
|
|
129
74
|
actors?: Record<string, { label: string }>
|
|
130
75
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Document-grain audit log for the "Document history" tab (docs/07-auth-and-security/02-auditability.md —
|
|
133
|
-
* Workstream 3): the non-versioned path / available-locales / status
|
|
134
|
-
* changes and the deletion event, with admin-resolved actor labels.
|
|
135
|
-
*/
|
|
76
|
+
/** Collection-only document-grain audit log. */
|
|
136
77
|
auditLog?: DocumentHistoryData
|
|
137
78
|
workflowStatuses?: WorkflowStatus[]
|
|
138
79
|
currentDocument?: Record<string, unknown> | null
|
|
@@ -143,63 +84,150 @@ export const HistoryView = ({
|
|
|
143
84
|
from: getAdminRouteId('collections', '$collection', '$id', 'history') as never,
|
|
144
85
|
}) as { id: string; collection: string }
|
|
145
86
|
const navigate = useNavigate()
|
|
87
|
+
const router = useRouter()
|
|
146
88
|
const { getCollectionDocumentVersion } = useBylineAdminServices()
|
|
147
89
|
const { t } = useTranslation('byline-admin')
|
|
148
90
|
const columns = adminConfig?.columns || []
|
|
149
91
|
const { labels } = collectionDefinition
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
92
|
+
// Preserve the collection view's existing identity-column contract: its
|
|
93
|
+
// restore cell sits immediately after the configured `useAsTitle` column.
|
|
94
|
+
// Singleton history supplies a separate fixed projection in its wrapper.
|
|
153
95
|
const titleFieldName = collectionDefinition.useAsTitle
|
|
154
|
-
const
|
|
96
|
+
const hasRestoreColumn =
|
|
97
|
+
titleFieldName != null && columns.some((column) => column.fieldName === titleFieldName)
|
|
98
|
+
const location = useRouterState({ select: (state) => state.location })
|
|
155
99
|
const locale = (location.search as { locale?: string }).locale
|
|
156
|
-
// Which sub-view is active (docs/07-auth-and-security/02-auditability.md — W3). Absent / anything but
|
|
157
|
-
// 'document' → the content version stream.
|
|
158
100
|
const activeTab =
|
|
159
101
|
(location.search as { tab?: string }).tab === 'document' ? 'document' : 'versions'
|
|
160
|
-
const [selectedVersion, setSelectedVersion] = useState<{
|
|
161
|
-
versionId: string
|
|
162
|
-
label: string
|
|
163
|
-
} | null>(null)
|
|
164
|
-
const [restoreTarget, setRestoreTarget] = useState<{
|
|
165
|
-
versionId: string
|
|
166
|
-
label: string
|
|
167
|
-
versionNumber: number
|
|
168
|
-
} | null>(null)
|
|
169
|
-
const currentVersionId =
|
|
170
|
-
currentDocument && typeof currentDocument.versionId === 'string'
|
|
171
|
-
? (currentDocument.versionId as string)
|
|
172
|
-
: null
|
|
173
102
|
|
|
174
103
|
function handleTabChange(name: string): void {
|
|
175
|
-
// Switching tabs resets pagination; 'versions' (the default) drops the
|
|
176
|
-
// `tab` param entirely to keep the URL clean.
|
|
177
104
|
const params = structuredClone(location.search)
|
|
178
105
|
delete params.page
|
|
179
|
-
if (name === 'document')
|
|
180
|
-
|
|
181
|
-
} else {
|
|
182
|
-
delete params.tab
|
|
183
|
-
}
|
|
106
|
+
if (name === 'document') params.tab = 'document'
|
|
107
|
+
else delete params.tab
|
|
184
108
|
navigate({
|
|
185
109
|
to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
|
|
186
|
-
params: { collection, id }
|
|
110
|
+
params: { collection, id },
|
|
187
111
|
search: params,
|
|
188
112
|
})
|
|
189
113
|
}
|
|
190
114
|
|
|
191
|
-
function
|
|
192
|
-
if (typeof value !== 'string' || value.length === 0) return
|
|
115
|
+
function handlePageSizeChange(pageSize: number): void {
|
|
193
116
|
const params = structuredClone(location.search)
|
|
194
117
|
delete params.page
|
|
195
|
-
params.page_size =
|
|
118
|
+
params.page_size = pageSize
|
|
196
119
|
navigate({
|
|
197
120
|
to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
|
|
198
|
-
params: { collection, id }
|
|
121
|
+
params: { collection, id },
|
|
199
122
|
search: params,
|
|
200
123
|
})
|
|
201
124
|
}
|
|
202
125
|
|
|
126
|
+
const rowPresentation: VersionHistoryRowPresentation = {
|
|
127
|
+
headers: columns.flatMap((column) => {
|
|
128
|
+
const cell = (
|
|
129
|
+
<TableHeadingCellSortable
|
|
130
|
+
key={String(column.fieldName)}
|
|
131
|
+
fieldName={String(column.fieldName)}
|
|
132
|
+
label={column.label}
|
|
133
|
+
sortable={column.sortable}
|
|
134
|
+
scope="col"
|
|
135
|
+
align={column.align}
|
|
136
|
+
className={column.className}
|
|
137
|
+
/>
|
|
138
|
+
)
|
|
139
|
+
return titleFieldName != null && column.fieldName === titleFieldName
|
|
140
|
+
? [
|
|
141
|
+
cell,
|
|
142
|
+
<th
|
|
143
|
+
key="__restore"
|
|
144
|
+
scope="col"
|
|
145
|
+
className={cx('byline-coll-history-col-restore', styles.colRestore)}
|
|
146
|
+
/>,
|
|
147
|
+
]
|
|
148
|
+
: [cell]
|
|
149
|
+
}),
|
|
150
|
+
auditColSpan: columns.length + (hasRestoreColumn ? 1 : 0),
|
|
151
|
+
renderCells: ({
|
|
152
|
+
document,
|
|
153
|
+
versionId,
|
|
154
|
+
versionNumber,
|
|
155
|
+
currentVersionId,
|
|
156
|
+
restoreStatusLabel,
|
|
157
|
+
openCompare,
|
|
158
|
+
openRestore,
|
|
159
|
+
}) =>
|
|
160
|
+
columns.flatMap((column) => {
|
|
161
|
+
const value = getColumnValue(document, String(column.fieldName))
|
|
162
|
+
const dataCell = (
|
|
163
|
+
<Table.Cell
|
|
164
|
+
key={String(column.fieldName)}
|
|
165
|
+
className={cx({
|
|
166
|
+
'byline-coll-history-cell-right': column.align === 'right',
|
|
167
|
+
[styles.cellRight]: column.align === 'right',
|
|
168
|
+
'byline-coll-history-cell-center': column.align === 'center',
|
|
169
|
+
[styles.cellCenter]: column.align === 'center',
|
|
170
|
+
})}
|
|
171
|
+
>
|
|
172
|
+
{titleFieldName != null && column.fieldName === titleFieldName ? (
|
|
173
|
+
versionId && currentDocument ? (
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
className={cx('byline-coll-history-title-button', styles.titleButton)}
|
|
177
|
+
onClick={openCompare}
|
|
178
|
+
>
|
|
179
|
+
{column.formatter
|
|
180
|
+
? renderFormatted(value, document, column.formatter)
|
|
181
|
+
: resolveDisplayValue(value, locale, defaultContentLocale) || '------'}
|
|
182
|
+
</button>
|
|
183
|
+
) : (
|
|
184
|
+
<Link
|
|
185
|
+
to={getAdminRoutePath('collections', '$collection', '$id')}
|
|
186
|
+
params={{ collection, id: document.id }}
|
|
187
|
+
>
|
|
188
|
+
{column.formatter
|
|
189
|
+
? renderFormatted(value, document, column.formatter)
|
|
190
|
+
: resolveDisplayValue(value, locale, defaultContentLocale) || '------'}
|
|
191
|
+
</Link>
|
|
192
|
+
)
|
|
193
|
+
) : column.formatter ? (
|
|
194
|
+
renderFormatted(value, document, column.formatter)
|
|
195
|
+
) : column.fieldName === 'status' && workflowStatuses ? (
|
|
196
|
+
<StatusBadge status={document.status} workflowStatuses={workflowStatuses} />
|
|
197
|
+
) : (
|
|
198
|
+
resolveDisplayValue(value, locale, defaultContentLocale) || ''
|
|
199
|
+
)}
|
|
200
|
+
</Table.Cell>
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return titleFieldName != null && column.fieldName === titleFieldName
|
|
204
|
+
? [
|
|
205
|
+
dataCell,
|
|
206
|
+
<Table.Cell
|
|
207
|
+
key="__restore"
|
|
208
|
+
className={cx('byline-coll-history-restore-cell', styles.restoreCell)}
|
|
209
|
+
>
|
|
210
|
+
{versionId && versionId !== currentVersionId ? (
|
|
211
|
+
<Button
|
|
212
|
+
type="button"
|
|
213
|
+
variant="outlined"
|
|
214
|
+
size="xs"
|
|
215
|
+
intent="noeffect"
|
|
216
|
+
onClick={openRestore}
|
|
217
|
+
className={cx('byline-coll-history-restore-button', styles.restoreButton)}
|
|
218
|
+
title={t('collections.history.restoreButtonTitle', {
|
|
219
|
+
status: restoreStatusLabel,
|
|
220
|
+
})}
|
|
221
|
+
>
|
|
222
|
+
{t('collections.history.restoreButton')}
|
|
223
|
+
</Button>
|
|
224
|
+
) : null}
|
|
225
|
+
</Table.Cell>,
|
|
226
|
+
]
|
|
227
|
+
: [dataCell]
|
|
228
|
+
}),
|
|
229
|
+
}
|
|
230
|
+
|
|
203
231
|
return (
|
|
204
232
|
<>
|
|
205
233
|
<Section>
|
|
@@ -207,8 +235,8 @@ export const HistoryView = ({
|
|
|
207
235
|
<div className={cx('byline-coll-history-head', styles.head)}>
|
|
208
236
|
<h2 className={cx('byline-coll-history-title', styles.title)}>
|
|
209
237
|
{t('collections.history.title', { label: labels.singular })}{' '}
|
|
210
|
-
<
|
|
211
|
-
total={activeTab === 'document' ? (auditLog?.meta.total ?? 0) : data
|
|
238
|
+
<HistoryStats
|
|
239
|
+
total={activeTab === 'document' ? (auditLog?.meta.total ?? 0) : data.meta.total}
|
|
212
240
|
/>
|
|
213
241
|
</h2>
|
|
214
242
|
<ViewMenu
|
|
@@ -231,348 +259,40 @@ export const HistoryView = ({
|
|
|
231
259
|
/>
|
|
232
260
|
</Container>
|
|
233
261
|
</Section>
|
|
234
|
-
|
|
262
|
+
|
|
263
|
+
{activeTab === 'document' ? (
|
|
235
264
|
<DocumentHistoryView
|
|
236
265
|
data={
|
|
237
266
|
auditLog ?? { entries: [], meta: { total: 0, page: 1, pageSize: 0, totalPages: 0 } }
|
|
238
267
|
}
|
|
239
268
|
/>
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
{activeTab !== 'document' && (
|
|
269
|
+
) : (
|
|
243
270
|
<Section>
|
|
244
271
|
<Container>
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
key={String(column.fieldName)}
|
|
267
|
-
fieldName={String(column.fieldName)}
|
|
268
|
-
label={column.label}
|
|
269
|
-
sortable={column.sortable}
|
|
270
|
-
scope="col"
|
|
271
|
-
align={column.align}
|
|
272
|
-
className={column.className}
|
|
273
|
-
/>
|
|
274
|
-
)
|
|
275
|
-
if (titleFieldName != null && column.fieldName === titleFieldName) {
|
|
276
|
-
return [
|
|
277
|
-
cell,
|
|
278
|
-
<th
|
|
279
|
-
key="__restore"
|
|
280
|
-
scope="col"
|
|
281
|
-
className={cx('byline-coll-history-col-restore', styles.colRestore)}
|
|
282
|
-
/>,
|
|
283
|
-
]
|
|
284
|
-
}
|
|
285
|
-
return [cell]
|
|
286
|
-
})}
|
|
287
|
-
</Table.Row>
|
|
288
|
-
</Table.Header>
|
|
289
|
-
|
|
290
|
-
<Table.Body>
|
|
291
|
-
{data?.docs?.map((document, rowIndex) => {
|
|
292
|
-
const versionId = document.versionId
|
|
293
|
-
const { total, page, pageSize, desc } = data.meta
|
|
294
|
-
const versionNumber = desc
|
|
295
|
-
? total - (page - 1) * pageSize - rowIndex
|
|
296
|
-
: (page - 1) * pageSize + rowIndex + 1
|
|
297
|
-
// Audit strip (docs/07-auth-and-security/02-auditability.md — W1): who created this
|
|
298
|
-
// version, via which action. A present-but-unresolved id
|
|
299
|
-
// is a deleted user; an absent id is a row written before
|
|
300
|
-
// audit wiring or an internal-tooling write.
|
|
301
|
-
const actorLabel = document.createdBy
|
|
302
|
-
? (data.actors?.[document.createdBy]?.label ??
|
|
303
|
-
t('collections.history.audit.formerUser'))
|
|
304
|
-
: t('collections.history.audit.unknown')
|
|
305
|
-
const actionKey = document.eventType
|
|
306
|
-
? AUDIT_ACTION_KEYS[document.eventType]
|
|
307
|
-
: undefined
|
|
308
|
-
const actionLabel = actionKey ? t(actionKey) : (document.eventType ?? '')
|
|
309
|
-
// The strip starts on the second column — an empty spacer
|
|
310
|
-
// cell sits under the version-number column, then one cell
|
|
311
|
-
// spans the data columns plus the restore cell appended
|
|
312
|
-
// after the identity column when present.
|
|
313
|
-
const auditColSpan =
|
|
314
|
-
columns.length +
|
|
315
|
-
(titleFieldName != null && columns.some((c) => c.fieldName === titleFieldName)
|
|
316
|
-
? 1
|
|
317
|
-
: 0)
|
|
318
|
-
return (
|
|
319
|
-
<Fragment key={versionId ?? document.id}>
|
|
320
|
-
<Table.Row className={cx('byline-coll-history-row', styles.historyRow)}>
|
|
321
|
-
<Table.Cell
|
|
322
|
-
className={cx('byline-coll-history-version-cell', styles.versionCell)}
|
|
323
|
-
>
|
|
324
|
-
{versionId && currentDocument ? (
|
|
325
|
-
<IconButton
|
|
326
|
-
size="xs"
|
|
327
|
-
variant="outlined"
|
|
328
|
-
intent="noeffect"
|
|
329
|
-
aria-label={t('collections.history.compareAriaLabel')}
|
|
330
|
-
title={t('collections.history.compareTitle')}
|
|
331
|
-
className={cx(
|
|
332
|
-
'byline-coll-history-version-button',
|
|
333
|
-
styles.versionButton
|
|
334
|
-
)}
|
|
335
|
-
onClick={() =>
|
|
336
|
-
setSelectedVersion({
|
|
337
|
-
versionId,
|
|
338
|
-
label: new Date(document.createdAt).toLocaleString(),
|
|
339
|
-
})
|
|
340
|
-
}
|
|
341
|
-
>
|
|
342
|
-
{versionNumber}
|
|
343
|
-
</IconButton>
|
|
344
|
-
) : null}
|
|
345
|
-
</Table.Cell>
|
|
346
|
-
{columns.flatMap((column) => {
|
|
347
|
-
const dataCell = (
|
|
348
|
-
<Table.Cell
|
|
349
|
-
key={String(column.fieldName)}
|
|
350
|
-
className={cx({
|
|
351
|
-
'byline-coll-history-cell-right': column.align === 'right',
|
|
352
|
-
[styles.cellRight]: column.align === 'right',
|
|
353
|
-
'byline-coll-history-cell-center': column.align === 'center',
|
|
354
|
-
[styles.cellCenter]: column.align === 'center',
|
|
355
|
-
})}
|
|
356
|
-
>
|
|
357
|
-
{titleFieldName != null && column.fieldName === titleFieldName ? (
|
|
358
|
-
versionId && currentDocument ? (
|
|
359
|
-
<button
|
|
360
|
-
type="button"
|
|
361
|
-
className={cx(
|
|
362
|
-
'byline-coll-history-title-button',
|
|
363
|
-
styles.titleButton
|
|
364
|
-
)}
|
|
365
|
-
onClick={() =>
|
|
366
|
-
setSelectedVersion({
|
|
367
|
-
versionId,
|
|
368
|
-
label: new Date(document.createdAt).toLocaleString(),
|
|
369
|
-
})
|
|
370
|
-
}
|
|
371
|
-
>
|
|
372
|
-
{column.formatter
|
|
373
|
-
? renderFormatted(
|
|
374
|
-
getColumnValue(document, column.fieldName as string),
|
|
375
|
-
document,
|
|
376
|
-
column.formatter
|
|
377
|
-
)
|
|
378
|
-
: resolveDisplayValue(
|
|
379
|
-
getColumnValue(document, column.fieldName as string),
|
|
380
|
-
locale,
|
|
381
|
-
defaultContentLocale
|
|
382
|
-
) || '------'}
|
|
383
|
-
</button>
|
|
384
|
-
) : (
|
|
385
|
-
<Link
|
|
386
|
-
to={getAdminRoutePath('collections', '$collection', '$id')}
|
|
387
|
-
params={{
|
|
388
|
-
collection,
|
|
389
|
-
id: document.id,
|
|
390
|
-
}}
|
|
391
|
-
>
|
|
392
|
-
{column.formatter
|
|
393
|
-
? renderFormatted(
|
|
394
|
-
getColumnValue(document, column.fieldName as string),
|
|
395
|
-
document,
|
|
396
|
-
column.formatter
|
|
397
|
-
)
|
|
398
|
-
: resolveDisplayValue(
|
|
399
|
-
getColumnValue(document, column.fieldName as string),
|
|
400
|
-
locale,
|
|
401
|
-
defaultContentLocale
|
|
402
|
-
) || '------'}
|
|
403
|
-
</Link>
|
|
404
|
-
)
|
|
405
|
-
) : column.formatter ? (
|
|
406
|
-
renderFormatted(
|
|
407
|
-
getColumnValue(document, column.fieldName as string),
|
|
408
|
-
document,
|
|
409
|
-
column.formatter
|
|
410
|
-
)
|
|
411
|
-
) : column.fieldName === 'status' && workflowStatuses ? (
|
|
412
|
-
<StatusBadge
|
|
413
|
-
status={document.status}
|
|
414
|
-
workflowStatuses={workflowStatuses}
|
|
415
|
-
/>
|
|
416
|
-
) : (
|
|
417
|
-
resolveDisplayValue(
|
|
418
|
-
getColumnValue(document, column.fieldName as string),
|
|
419
|
-
locale,
|
|
420
|
-
defaultContentLocale
|
|
421
|
-
) || ''
|
|
422
|
-
)}
|
|
423
|
-
</Table.Cell>
|
|
424
|
-
)
|
|
425
|
-
if (titleFieldName != null && column.fieldName === titleFieldName) {
|
|
426
|
-
return [
|
|
427
|
-
dataCell,
|
|
428
|
-
<Table.Cell
|
|
429
|
-
key="__restore"
|
|
430
|
-
className={cx(
|
|
431
|
-
'byline-coll-history-restore-cell',
|
|
432
|
-
styles.restoreCell
|
|
433
|
-
)}
|
|
434
|
-
>
|
|
435
|
-
{versionId && versionId !== currentVersionId ? (
|
|
436
|
-
<Button
|
|
437
|
-
type="button"
|
|
438
|
-
variant="outlined"
|
|
439
|
-
size="xs"
|
|
440
|
-
intent="noeffect"
|
|
441
|
-
onClick={() =>
|
|
442
|
-
setRestoreTarget({
|
|
443
|
-
versionId,
|
|
444
|
-
label: new Date(document.createdAt).toLocaleString(),
|
|
445
|
-
versionNumber,
|
|
446
|
-
})
|
|
447
|
-
}
|
|
448
|
-
className={cx(
|
|
449
|
-
'byline-coll-history-restore-button',
|
|
450
|
-
styles.restoreButton
|
|
451
|
-
)}
|
|
452
|
-
title={t('collections.history.restoreButtonTitle')}
|
|
453
|
-
>
|
|
454
|
-
{t('collections.history.restoreButton')}
|
|
455
|
-
</Button>
|
|
456
|
-
) : null}
|
|
457
|
-
</Table.Cell>,
|
|
458
|
-
]
|
|
459
|
-
}
|
|
460
|
-
return [dataCell]
|
|
461
|
-
})}
|
|
462
|
-
</Table.Row>
|
|
463
|
-
<Table.Row
|
|
464
|
-
className={cx('byline-coll-history-audit-row', styles.auditRow)}
|
|
465
|
-
aria-label={t('collections.history.audit.createdBy', {
|
|
466
|
-
label: actorLabel,
|
|
467
|
-
})}
|
|
468
|
-
>
|
|
469
|
-
<Table.Cell
|
|
470
|
-
className={cx(
|
|
471
|
-
'byline-coll-history-audit-spacer-cell',
|
|
472
|
-
styles.auditSpacerCell
|
|
473
|
-
)}
|
|
474
|
-
/>
|
|
475
|
-
<Table.Cell
|
|
476
|
-
colSpan={auditColSpan}
|
|
477
|
-
className={cx('byline-coll-history-audit-cell', styles.auditCell)}
|
|
478
|
-
>
|
|
479
|
-
<span className={cx('byline-coll-history-audit', styles.audit)}>
|
|
480
|
-
{actionLabel}
|
|
481
|
-
{' · '}
|
|
482
|
-
{t('collections.history.audit.createdBy', { label: actorLabel })}
|
|
483
|
-
{' · '}
|
|
484
|
-
{new Date(document.createdAt).toLocaleString()}
|
|
485
|
-
</span>
|
|
486
|
-
</Table.Cell>
|
|
487
|
-
</Table.Row>
|
|
488
|
-
</Fragment>
|
|
489
|
-
)
|
|
490
|
-
})}
|
|
491
|
-
</Table.Body>
|
|
492
|
-
</Table>
|
|
493
|
-
{padRows(6 - (data?.docs?.length ?? 0))}
|
|
494
|
-
</Table.Container>
|
|
495
|
-
<div
|
|
496
|
-
className={cx(
|
|
497
|
-
'byline-coll-history-options byline-coll-history-options-bottom',
|
|
498
|
-
styles.options,
|
|
499
|
-
styles.optionsBottom
|
|
500
|
-
)}
|
|
501
|
-
>
|
|
502
|
-
<Select<string>
|
|
503
|
-
containerClassName={cx('byline-coll-history-page-size', styles.pageSize)}
|
|
504
|
-
id="page_size"
|
|
505
|
-
name="page_size"
|
|
506
|
-
size="sm"
|
|
507
|
-
defaultValue="15"
|
|
508
|
-
items={[
|
|
509
|
-
{ value: '15', label: '15' },
|
|
510
|
-
{ value: '30', label: '30' },
|
|
511
|
-
{ value: '50', label: '50' },
|
|
512
|
-
{ value: '100', label: '100' },
|
|
513
|
-
]}
|
|
514
|
-
onValueChange={handleOnPageSizeChange}
|
|
515
|
-
/>
|
|
516
|
-
<RouterPager
|
|
517
|
-
smoothScrollToTop={true}
|
|
518
|
-
page={data?.meta.page}
|
|
519
|
-
count={data?.meta.totalPages}
|
|
520
|
-
showFirstButton
|
|
521
|
-
showLastButton
|
|
522
|
-
componentName="pagerBottom"
|
|
523
|
-
aria-label={t('collections.list.pagerBottomAriaLabel')}
|
|
524
|
-
/>
|
|
525
|
-
</div>
|
|
272
|
+
<VersionHistoryCore
|
|
273
|
+
data={data as unknown as VersionHistoryData}
|
|
274
|
+
resourceDefinition={collectionDefinition}
|
|
275
|
+
resourcePath={collection}
|
|
276
|
+
documentId={id}
|
|
277
|
+
currentDocument={currentDocument}
|
|
278
|
+
locale={locale}
|
|
279
|
+
rowPresentation={rowPresentation}
|
|
280
|
+
loadHistoricalVersion={getCollectionDocumentVersion}
|
|
281
|
+
onPageSizeChange={handlePageSizeChange}
|
|
282
|
+
restoreVersion={(versionId) =>
|
|
283
|
+
restoreDocumentVersion({ data: { collection, id, versionId } })
|
|
284
|
+
}
|
|
285
|
+
onRestoreComplete={async () => {
|
|
286
|
+
await router.invalidate()
|
|
287
|
+
navigate({
|
|
288
|
+
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
289
|
+
params: { collection, id },
|
|
290
|
+
})
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
526
293
|
</Container>
|
|
527
294
|
</Section>
|
|
528
295
|
)}
|
|
529
|
-
|
|
530
|
-
{selectedVersion && currentDocument && (
|
|
531
|
-
<Suspense fallback={null}>
|
|
532
|
-
<DiffModal
|
|
533
|
-
isOpen={true}
|
|
534
|
-
onDismiss={() => setSelectedVersion(null)}
|
|
535
|
-
collection={collection}
|
|
536
|
-
documentId={id}
|
|
537
|
-
versionId={selectedVersion.versionId}
|
|
538
|
-
versionLabel={selectedVersion.label}
|
|
539
|
-
currentDocument={currentDocument}
|
|
540
|
-
locale={locale}
|
|
541
|
-
loadHistoricalVersion={getCollectionDocumentVersion}
|
|
542
|
-
/>
|
|
543
|
-
</Suspense>
|
|
544
|
-
)}
|
|
545
|
-
|
|
546
|
-
<Modal
|
|
547
|
-
isOpen={restoreTarget != null}
|
|
548
|
-
onDismiss={() => setRestoreTarget(null)}
|
|
549
|
-
closeOnOverlayClick={false}
|
|
550
|
-
>
|
|
551
|
-
<Modal.Container className={cx('byline-coll-history-restore-modal', styles.restoreModal)}>
|
|
552
|
-
<Modal.Header
|
|
553
|
-
className={cx('byline-coll-history-restore-modal-head', styles.restoreModalHead)}
|
|
554
|
-
>
|
|
555
|
-
<h3 className="m-0">{t('collections.history.restoreModalTitle')}</h3>
|
|
556
|
-
<IconButton
|
|
557
|
-
aria-label={t('common.actions.close')}
|
|
558
|
-
size="xs"
|
|
559
|
-
onClick={() => setRestoreTarget(null)}
|
|
560
|
-
>
|
|
561
|
-
<CloseIcon width="14px" height="14px" svgClassName="white-icon" />
|
|
562
|
-
</IconButton>
|
|
563
|
-
</Modal.Header>
|
|
564
|
-
{restoreTarget ? (
|
|
565
|
-
<RestoreVersionModal
|
|
566
|
-
collection={collection}
|
|
567
|
-
documentId={id}
|
|
568
|
-
versionId={restoreTarget.versionId}
|
|
569
|
-
versionLabel={restoreTarget.label}
|
|
570
|
-
versionNumber={restoreTarget.versionNumber}
|
|
571
|
-
onClose={() => setRestoreTarget(null)}
|
|
572
|
-
/>
|
|
573
|
-
) : null}
|
|
574
|
-
</Modal.Container>
|
|
575
|
-
</Modal>
|
|
576
296
|
</>
|
|
577
297
|
)
|
|
578
298
|
}
|