@byline/host-tanstack-start 4.15.0 → 4.17.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.
Files changed (183) hide show
  1. package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
  2. package/dist/admin-shell/chrome/admin-menu-drawer.js +17 -5
  3. package/dist/admin-shell/chrome/dashboard.js +41 -5
  4. package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
  5. package/dist/admin-shell/collections/api.d.ts +2 -2
  6. package/dist/admin-shell/collections/create.d.ts +2 -2
  7. package/dist/admin-shell/collections/create.js +1 -0
  8. package/dist/admin-shell/collections/edit.d.ts +2 -2
  9. package/dist/admin-shell/collections/edit.js +1 -0
  10. package/dist/admin-shell/collections/history.d.ts +4 -12
  11. package/dist/admin-shell/collections/history.js +112 -303
  12. package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
  13. package/dist/admin-shell/collections/reindex-button.js +5 -1
  14. package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
  15. package/dist/admin-shell/collections/restore-version-modal.js +8 -24
  16. package/dist/admin-shell/collections/version-history.d.ts +72 -0
  17. package/dist/admin-shell/collections/version-history.js +259 -0
  18. package/dist/admin-shell/singletons/history.d.ts +19 -0
  19. package/dist/admin-shell/singletons/history.js +170 -0
  20. package/dist/admin-shell/singletons/history.test.d.ts +8 -0
  21. package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
  22. package/dist/admin-shell/singletons/preview-link.js +54 -0
  23. package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
  24. package/dist/admin-shell/singletons/view-menu.js +116 -0
  25. package/dist/admin-shell/singletons/view.d.ts +18 -0
  26. package/dist/admin-shell/singletons/view.js +259 -0
  27. package/dist/admin-shell/singletons/view.test.d.ts +8 -0
  28. package/dist/integrations/analytics-agent.d.ts +20 -0
  29. package/dist/integrations/analytics-agent.js +57 -0
  30. package/dist/integrations/analytics-events.d.ts +51 -0
  31. package/dist/integrations/analytics-events.js +129 -0
  32. package/dist/integrations/api-utils.d.ts +18 -4
  33. package/dist/integrations/api-utils.js +10 -3
  34. package/dist/integrations/serve-analytics-agent.d.ts +8 -0
  35. package/dist/integrations/serve-analytics-agent.js +11 -0
  36. package/dist/routes/analytics-range.d.ts +12 -0
  37. package/dist/routes/analytics-range.js +35 -0
  38. package/dist/routes/create-admin-analytics-route.d.ts +8 -0
  39. package/dist/routes/create-admin-analytics-route.js +108 -0
  40. package/dist/routes/create-admin-dashboard-route.js +2 -2
  41. package/dist/routes/create-admin-layout-route.js +10 -4
  42. package/dist/routes/create-collection-api-route.js +3 -3
  43. package/dist/routes/create-collection-create-route.js +4 -3
  44. package/dist/routes/create-collection-edit-route.js +4 -3
  45. package/dist/routes/create-collection-history-route.js +4 -3
  46. package/dist/routes/create-collection-list-route.js +4 -3
  47. package/dist/routes/create-singleton-history-route.d.ts +8 -0
  48. package/dist/routes/create-singleton-history-route.js +99 -0
  49. package/dist/routes/create-singleton-route.d.ts +35 -0
  50. package/dist/routes/create-singleton-route.js +130 -0
  51. package/dist/routes/get-multi-collection-definition.d.ts +10 -0
  52. package/dist/routes/get-multi-collection-definition.js +6 -0
  53. package/dist/routes/get-singleton-definition.d.ts +10 -0
  54. package/dist/routes/get-singleton-definition.js +6 -0
  55. package/dist/routes/index.d.ts +3 -0
  56. package/dist/routes/index.js +3 -0
  57. package/dist/server-fns/actors.d.ts +16 -0
  58. package/dist/server-fns/actors.js +25 -0
  59. package/dist/server-fns/admin-document-presentation.d.ts +30 -0
  60. package/dist/server-fns/admin-document-presentation.js +32 -0
  61. package/dist/server-fns/analytics/index.d.ts +9 -0
  62. package/dist/server-fns/analytics/index.js +2 -0
  63. package/dist/server-fns/analytics/maintenance.d.ts +22 -0
  64. package/dist/server-fns/analytics/maintenance.js +42 -0
  65. package/dist/server-fns/analytics/queries.d.ts +25 -0
  66. package/dist/server-fns/analytics/queries.js +54 -0
  67. package/dist/server-fns/collections/actors.d.ts +1 -10
  68. package/dist/server-fns/collections/actors.js +1 -25
  69. package/dist/server-fns/collections/get.js +9 -26
  70. package/dist/server-fns/collections/reorder.js +1 -1
  71. package/dist/server-fns/collections/scheduled-publication.js +2 -2
  72. package/dist/server-fns/collections/upload.js +3 -3
  73. package/dist/server-fns/collections/utils.d.ts +1 -7
  74. package/dist/server-fns/collections/utils.js +1 -4
  75. package/dist/server-fns/serialise.d.ts +14 -0
  76. package/dist/server-fns/serialise.js +4 -0
  77. package/dist/server-fns/singleton-document-read.d.ts +20 -0
  78. package/dist/server-fns/singleton-document-read.js +10 -0
  79. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
  80. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
  81. package/dist/server-fns/singletons/change-status.d.ts +14 -0
  82. package/dist/server-fns/singletons/change-status.js +7 -0
  83. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
  84. package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
  85. package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
  86. package/dist/server-fns/singletons/copy-to-locale.js +11 -0
  87. package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
  88. package/dist/server-fns/singletons/find-by-version.js +9 -0
  89. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
  90. package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
  91. package/dist/server-fns/singletons/get.d.ts +24 -0
  92. package/dist/server-fns/singletons/get.js +6 -0
  93. package/dist/server-fns/singletons/history.d.ts +31 -0
  94. package/dist/server-fns/singletons/history.js +22 -0
  95. package/dist/server-fns/singletons/index.d.ts +19 -0
  96. package/dist/server-fns/singletons/index.js +12 -0
  97. package/dist/server-fns/singletons/restore-version.d.ts +14 -0
  98. package/dist/server-fns/singletons/restore-version.js +7 -0
  99. package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
  100. package/dist/server-fns/singletons/schedule-publish.js +10 -0
  101. package/dist/server-fns/singletons/unpublish.d.ts +12 -0
  102. package/dist/server-fns/singletons/unpublish.js +7 -0
  103. package/dist/server-fns/singletons/update.d.ts +14 -0
  104. package/dist/server-fns/singletons/update.js +14 -0
  105. package/dist/vite/analytics-dev.d.ts +22 -0
  106. package/dist/vite/analytics-dev.js +28 -0
  107. package/dist/vite/index.d.ts +1 -0
  108. package/dist/vite/index.js +1 -0
  109. package/dist/vitest-jsdom.test.d.ts +8 -0
  110. package/package.json +28 -16
  111. package/src/admin-shell/chrome/admin-menu-drawer.tsx +19 -5
  112. package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
  113. package/src/admin-shell/chrome/dashboard.tsx +43 -9
  114. package/src/admin-shell/collections/api.tsx +2 -2
  115. package/src/admin-shell/collections/create.tsx +6 -2
  116. package/src/admin-shell/collections/edit.tsx +6 -2
  117. package/src/admin-shell/collections/history.module.css +1 -1
  118. package/src/admin-shell/collections/history.tsx +162 -442
  119. package/src/admin-shell/collections/reindex-button.tsx +4 -2
  120. package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
  121. package/src/admin-shell/collections/version-history.tsx +349 -0
  122. package/src/admin-shell/singletons/history.test.tsx +407 -0
  123. package/src/admin-shell/singletons/history.tsx +185 -0
  124. package/src/admin-shell/singletons/preview-link.tsx +75 -0
  125. package/src/admin-shell/singletons/view-menu.tsx +136 -0
  126. package/src/admin-shell/singletons/view.test.tsx +400 -0
  127. package/src/admin-shell/singletons/view.tsx +351 -0
  128. package/src/integrations/analytics-agent.tsx +91 -0
  129. package/src/integrations/analytics-events.test.node.ts +245 -0
  130. package/src/integrations/analytics-events.ts +224 -0
  131. package/src/integrations/api-utils.ts +34 -9
  132. package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
  133. package/src/integrations/serve-analytics-agent.ts +19 -0
  134. package/src/routes/analytics-range.ts +55 -0
  135. package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
  136. package/src/routes/create-admin-analytics-route.tsx +109 -0
  137. package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
  138. package/src/routes/create-admin-dashboard-route.tsx +10 -2
  139. package/src/routes/create-admin-layout-route.tsx +15 -8
  140. package/src/routes/create-collection-api-route.tsx +4 -4
  141. package/src/routes/create-collection-create-route.tsx +5 -8
  142. package/src/routes/create-collection-edit-route.tsx +5 -4
  143. package/src/routes/create-collection-history-route.tsx +5 -8
  144. package/src/routes/create-collection-list-route.tsx +5 -8
  145. package/src/routes/create-singleton-history-route.test.node.ts +148 -0
  146. package/src/routes/create-singleton-history-route.tsx +116 -0
  147. package/src/routes/create-singleton-route.test.node.ts +172 -0
  148. package/src/routes/create-singleton-route.tsx +176 -0
  149. package/src/routes/get-multi-collection-definition.ts +16 -0
  150. package/src/routes/get-singleton-definition.ts +16 -0
  151. package/src/routes/index.ts +3 -0
  152. package/src/server-fns/actors.ts +43 -0
  153. package/src/server-fns/admin-document-presentation.ts +78 -0
  154. package/src/server-fns/analytics/index.ts +23 -0
  155. package/src/server-fns/analytics/maintenance.ts +54 -0
  156. package/src/server-fns/analytics/queries.ts +86 -0
  157. package/src/server-fns/collections/actors.ts +1 -52
  158. package/src/server-fns/collections/get.ts +10 -49
  159. package/src/server-fns/collections/reorder.ts +1 -1
  160. package/src/server-fns/collections/scheduled-publication.ts +7 -1
  161. package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
  162. package/src/server-fns/collections/upload.ts +7 -3
  163. package/src/server-fns/collections/utils.ts +1 -9
  164. package/src/server-fns/serialise.ts +17 -0
  165. package/src/server-fns/singleton-document-read.ts +32 -0
  166. package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
  167. package/src/server-fns/singletons/change-status.ts +19 -0
  168. package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
  169. package/src/server-fns/singletons/copy-to-locale.ts +32 -0
  170. package/src/server-fns/singletons/find-by-version.ts +23 -0
  171. package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
  172. package/src/server-fns/singletons/get.ts +32 -0
  173. package/src/server-fns/singletons/history.ts +41 -0
  174. package/src/server-fns/singletons/index.ts +20 -0
  175. package/src/server-fns/singletons/restore-version.ts +19 -0
  176. package/src/server-fns/singletons/schedule-publish.ts +24 -0
  177. package/src/server-fns/singletons/singletons.test.node.ts +298 -0
  178. package/src/server-fns/singletons/unpublish.ts +19 -0
  179. package/src/server-fns/singletons/update.ts +31 -0
  180. package/src/vite/analytics-dev.test.node.ts +126 -0
  181. package/src/vite/analytics-dev.ts +56 -0
  182. package/src/vite/index.ts +1 -0
  183. package/src/vitest-jsdom.test.tsx +27 -0
@@ -25,7 +25,7 @@
25
25
  import type React from 'react'
26
26
  import { useState } from 'react'
27
27
 
28
- import type { ListActionComponentProps } from '@byline/core'
28
+ import { documentAbilityKey, type ListActionComponentProps } from '@byline/core'
29
29
  import { useTranslation } from '@byline/i18n/react'
30
30
  import { Button, useToastManager } from '@byline/ui/react'
31
31
 
@@ -37,7 +37,9 @@ export function ReindexButton({
37
37
  }: ListActionComponentProps): React.JSX.Element | null {
38
38
  const { t } = useTranslation('byline-admin')
39
39
  const toastManager = useToastManager()
40
- const canReindex = useAbility(`collections.${collectionPath}.reindex`)
40
+ const canReindex = useAbility(
41
+ documentAbilityKey({ kind: 'collection', path: collectionPath }, 'reindex')
42
+ )
41
43
  const [pending, setPending] = useState(false)
42
44
 
43
45
  if (!canReindex) return null
@@ -12,42 +12,35 @@
12
12
  * Restore-version modal body.
13
13
  *
14
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.
15
+ * The resource wrapper injects restore and post-restore navigation so this
16
+ * confirmation body works for both collection documents and singletons.
18
17
  */
19
18
 
20
19
  import { useState } from 'react'
21
- import { useRouter } from '@tanstack/react-router'
22
20
 
23
21
  import { useTranslation } from '@byline/i18n/react'
24
22
  import { Alert, Button, LoaderEllipsis, Modal } from '@byline/ui/react'
25
23
  import cx from 'clsx'
26
24
 
27
- import { getAdminRoutePath } from '../../routes/admin-path.js'
28
- import { restoreDocumentVersion } from '../../server-fns/collections/index.js'
29
- import { useNavigate } from '../chrome/loose-router.js'
30
25
  import styles from './restore-version-modal.module.css'
31
26
 
32
27
  interface RestoreVersionModalProps {
33
- collection: string
34
- documentId: string
35
- versionId: string
36
28
  versionLabel: string
37
29
  versionNumber: number
30
+ restoreStatusLabel: string
38
31
  onClose: () => void
32
+ restoreVersion: () => Promise<unknown>
33
+ onRestoreComplete: () => void | Promise<void>
39
34
  }
40
35
 
41
36
  export function RestoreVersionModal({
42
- collection,
43
- documentId,
44
- versionId,
45
37
  versionLabel,
46
38
  versionNumber,
39
+ restoreStatusLabel,
47
40
  onClose,
41
+ restoreVersion,
42
+ onRestoreComplete,
48
43
  }: RestoreVersionModalProps) {
49
- const navigate = useNavigate()
50
- const router = useRouter()
51
44
  const { t } = useTranslation('byline-admin')
52
45
  const [error, setError] = useState<string | null>(null)
53
46
  const [pending, setPending] = useState(false)
@@ -57,15 +50,9 @@ export function RestoreVersionModal({
57
50
  setPending(true)
58
51
  setError(null)
59
52
  try {
60
- await restoreDocumentVersion({
61
- data: { collection, id: documentId, versionId },
62
- })
53
+ await restoreVersion()
63
54
  onClose()
64
- await router.invalidate()
65
- navigate({
66
- to: getAdminRoutePath('collections', '$collection', '$id'),
67
- params: { collection, id: documentId },
68
- })
55
+ await onRestoreComplete()
69
56
  } catch (err) {
70
57
  const code = getErrorCode(err)
71
58
  if (code === 'ERR_INVALID_TRANSITION') {
@@ -96,7 +83,10 @@ export function RestoreVersionModal({
96
83
  <span className="muted">{t('collections.restore.createdLabel')}</span> {versionLabel}
97
84
  </p>
98
85
  <p className={cx('byline-coll-restore-warning', styles.warning)}>
99
- {t('collections.restore.warning', { version: versionNumber })}
86
+ {t('collections.restore.warning', {
87
+ version: versionNumber,
88
+ status: restoreStatusLabel,
89
+ })}
100
90
  </p>
101
91
  </div>
102
92
  <div className={cx('byline-coll-restore-actions', styles.actions)}>
@@ -127,7 +117,11 @@ export function RestoreVersionModal({
127
117
  disabled={pending}
128
118
  className={cx('byline-coll-restore-button', styles.button)}
129
119
  >
130
- {pending === true ? <LoaderEllipsis size={42} /> : t('collections.restore.confirmButton')}
120
+ {pending === true ? (
121
+ <LoaderEllipsis size={42} />
122
+ ) : (
123
+ t('collections.restore.confirmButton', { status: restoreStatusLabel })
124
+ )}
131
125
  </Button>
132
126
  </div>
133
127
  </Modal.Content>
@@ -0,0 +1,349 @@
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 { Fragment, lazy, type ReactNode, Suspense, useState } from 'react'
10
+
11
+ import { type CollectionDefinition, getDefaultStatus, getWorkflowStatuses } from '@byline/core'
12
+ import { useTranslation } from '@byline/i18n/react'
13
+ import { CloseIcon, IconButton, Modal, Select, Table } from '@byline/ui/react'
14
+ import cx from 'clsx'
15
+
16
+ import { RouterPager } from '../chrome/router-pager.js'
17
+ import { formatNumber } from '../chrome/utils.js'
18
+ import styles from './history.module.css'
19
+ import { RestoreVersionModal } from './restore-version-modal.js'
20
+
21
+ // Lazy-load DiffModal because react-diff-viewer-continued uses a web worker
22
+ // bundle that cannot be resolved by Node during SSR.
23
+ const DiffModal = lazy(() => import('@byline/admin/react').then((m) => ({ default: m.DiffModal })))
24
+
25
+ interface VersionHistoryDocument extends Record<string, unknown> {
26
+ id: string
27
+ versionId?: string
28
+ status: string
29
+ createdAt: string
30
+ createdBy?: string
31
+ eventType?: string
32
+ fields?: Record<string, unknown>
33
+ }
34
+
35
+ export interface VersionHistoryData {
36
+ docs: VersionHistoryDocument[]
37
+ meta: {
38
+ total: number
39
+ page: number
40
+ pageSize: number
41
+ totalPages: number
42
+ order?: string
43
+ desc?: boolean
44
+ }
45
+ /** Admin-resolved acting-user labels keyed by the raw version actor id. */
46
+ actors?: Record<string, { label: string }>
47
+ }
48
+
49
+ interface VersionHistoryRowContext {
50
+ document: VersionHistoryDocument
51
+ versionId: string | undefined
52
+ versionNumber: number
53
+ currentVersionId: string | null
54
+ actorLabel: string
55
+ actionLabel: string
56
+ restoreStatusLabel: string
57
+ openCompare: () => void
58
+ openRestore: () => void
59
+ }
60
+
61
+ /**
62
+ * Resource-specific table columns. Collection history projects configured
63
+ * columns; singleton history supplies its fixed status/time/actor/restore
64
+ * presentation so restore never depends on a `useAsTitle` field.
65
+ */
66
+ export interface VersionHistoryRowPresentation {
67
+ headers: ReactNode
68
+ auditColSpan: number
69
+ renderCells: (context: VersionHistoryRowContext) => ReactNode
70
+ }
71
+
72
+ export type HistoricalVersionLoader = (
73
+ resourcePath: string,
74
+ documentId: string,
75
+ versionId: string,
76
+ locale: string | undefined
77
+ ) => Promise<Record<string, unknown>>
78
+
79
+ const AUDIT_ACTION_KEYS: Record<string, string> = {
80
+ create: 'collections.history.audit.actionCreate',
81
+ update: 'collections.history.audit.actionUpdate',
82
+ restore: 'collections.history.audit.actionRestore',
83
+ copy_to_locale: 'collections.history.audit.actionCopyToLocale',
84
+ delete_locale: 'collections.history.audit.actionDeleteLocale',
85
+ }
86
+
87
+ export function HistoryStats({ total }: { total: number }) {
88
+ return (
89
+ <span className={cx('byline-coll-history-stats', styles.stats)}>{formatNumber(total, 0)}</span>
90
+ )
91
+ }
92
+
93
+ function padRows(value: number) {
94
+ return Array.from({ length: Math.max(0, value) }).map((_, index) => (
95
+ <div
96
+ key={`empty-row-${
97
+ // biome-ignore lint/suspicious/noArrayIndexKey: filler rows have no stable identity
98
+ index
99
+ }`}
100
+ className={cx('byline-coll-history-pad-row', styles.padRow)}
101
+ >
102
+ &nbsp;
103
+ </div>
104
+ ))
105
+ }
106
+
107
+ export function VersionHistoryCore({
108
+ data,
109
+ resourceDefinition,
110
+ resourcePath,
111
+ documentId,
112
+ currentDocument,
113
+ locale,
114
+ rowPresentation,
115
+ loadHistoricalVersion,
116
+ onPageSizeChange,
117
+ restoreVersion,
118
+ onRestoreComplete,
119
+ }: {
120
+ data: VersionHistoryData
121
+ resourceDefinition: CollectionDefinition
122
+ resourcePath: string
123
+ documentId: string
124
+ currentDocument?: Record<string, unknown> | null
125
+ locale?: string
126
+ rowPresentation: VersionHistoryRowPresentation
127
+ loadHistoricalVersion: HistoricalVersionLoader
128
+ onPageSizeChange: (pageSize: number) => void
129
+ restoreVersion: (versionId: string) => Promise<unknown>
130
+ onRestoreComplete: () => void | Promise<void>
131
+ }) {
132
+ const { t } = useTranslation('byline-admin')
133
+ const [selectedVersion, setSelectedVersion] = useState<{
134
+ versionId: string
135
+ label: string
136
+ } | null>(null)
137
+ const [restoreTarget, setRestoreTarget] = useState<{
138
+ versionId: string
139
+ label: string
140
+ versionNumber: number
141
+ } | null>(null)
142
+ const currentVersionId =
143
+ currentDocument && typeof currentDocument.versionId === 'string'
144
+ ? currentDocument.versionId
145
+ : null
146
+ const restoreStatus = getDefaultStatus(resourceDefinition)
147
+ const restoreStatusLabel =
148
+ getWorkflowStatuses(resourceDefinition).find((status) => status.name === restoreStatus)
149
+ ?.label ?? restoreStatus
150
+
151
+ return (
152
+ <>
153
+ <div className={cx('byline-coll-history-options', styles.options)}>
154
+ <RouterPager
155
+ page={data.meta.page}
156
+ count={data.meta.totalPages}
157
+ showFirstButton
158
+ showLastButton
159
+ componentName="pagerTop"
160
+ aria-label={t('collections.list.pagerTopAriaLabel')}
161
+ />
162
+ </div>
163
+ <Table.Container className={cx('byline-coll-history-table-wrap', styles.tableWrap)}>
164
+ <Table>
165
+ <Table.Header>
166
+ <Table.Row>
167
+ <th
168
+ scope="col"
169
+ className={cx('byline-coll-history-col-version', styles.colVersion)}
170
+ />
171
+ {rowPresentation.headers}
172
+ </Table.Row>
173
+ </Table.Header>
174
+
175
+ <Table.Body>
176
+ {data.docs.map((document, rowIndex) => {
177
+ const versionId = document.versionId
178
+ const { total, page, pageSize, desc } = data.meta
179
+ const versionNumber = desc
180
+ ? total - (page - 1) * pageSize - rowIndex
181
+ : (page - 1) * pageSize + rowIndex + 1
182
+ // A present-but-unresolved id is a deleted user; an absent id
183
+ // belongs to a row written before audit wiring or by internal tooling.
184
+ const actorLabel = document.createdBy
185
+ ? (data.actors?.[document.createdBy]?.label ??
186
+ t('collections.history.audit.formerUser'))
187
+ : t('collections.history.audit.unknown')
188
+ const actionKey = document.eventType
189
+ ? AUDIT_ACTION_KEYS[document.eventType]
190
+ : undefined
191
+ const actionLabel = actionKey ? t(actionKey) : (document.eventType ?? '')
192
+ const versionLabel = new Date(document.createdAt).toLocaleString()
193
+ const openCompare = () => {
194
+ if (versionId == null) return
195
+ setSelectedVersion({ versionId, label: versionLabel })
196
+ }
197
+ const openRestore = () => {
198
+ if (versionId == null || versionId === currentVersionId) return
199
+ setRestoreTarget({ versionId, label: versionLabel, versionNumber })
200
+ }
201
+
202
+ return (
203
+ <Fragment key={versionId ?? document.id}>
204
+ <Table.Row className={cx('byline-coll-history-row', styles.historyRow)}>
205
+ <Table.Cell
206
+ className={cx('byline-coll-history-version-cell', styles.versionCell)}
207
+ >
208
+ {versionId && currentDocument ? (
209
+ <IconButton
210
+ size="xs"
211
+ variant="outlined"
212
+ intent="noeffect"
213
+ aria-label={t('collections.history.compareAriaLabel')}
214
+ title={t('collections.history.compareTitle')}
215
+ className={cx('byline-coll-history-version-button', styles.versionButton)}
216
+ onClick={openCompare}
217
+ >
218
+ {versionNumber}
219
+ </IconButton>
220
+ ) : null}
221
+ </Table.Cell>
222
+ {rowPresentation.renderCells({
223
+ document,
224
+ versionId,
225
+ versionNumber,
226
+ currentVersionId,
227
+ actorLabel,
228
+ actionLabel,
229
+ restoreStatusLabel,
230
+ openCompare,
231
+ openRestore,
232
+ })}
233
+ </Table.Row>
234
+ <Table.Row
235
+ className={cx('byline-coll-history-audit-row', styles.auditRow)}
236
+ aria-label={t('collections.history.audit.createdBy', {
237
+ label: actorLabel,
238
+ })}
239
+ >
240
+ <Table.Cell
241
+ className={cx(
242
+ 'byline-coll-history-audit-spacer-cell',
243
+ styles.auditSpacerCell
244
+ )}
245
+ />
246
+ <Table.Cell
247
+ colSpan={rowPresentation.auditColSpan}
248
+ className={cx('byline-coll-history-audit-cell', styles.auditCell)}
249
+ >
250
+ <span className={cx('byline-coll-history-audit', styles.audit)}>
251
+ {actionLabel}
252
+ {' · '}
253
+ {t('collections.history.audit.createdBy', { label: actorLabel })}
254
+ {' · '}
255
+ {versionLabel}
256
+ </span>
257
+ </Table.Cell>
258
+ </Table.Row>
259
+ </Fragment>
260
+ )
261
+ })}
262
+ </Table.Body>
263
+ </Table>
264
+ {padRows(6 - data.docs.length)}
265
+ </Table.Container>
266
+ <div
267
+ className={cx(
268
+ 'byline-coll-history-options byline-coll-history-options-bottom',
269
+ styles.options,
270
+ styles.optionsBottom
271
+ )}
272
+ >
273
+ <Select<string>
274
+ containerClassName={cx('byline-coll-history-page-size', styles.pageSize)}
275
+ id="page_size"
276
+ name="page_size"
277
+ size="sm"
278
+ defaultValue="15"
279
+ items={[
280
+ { value: '15', label: '15' },
281
+ { value: '30', label: '30' },
282
+ { value: '50', label: '50' },
283
+ { value: '100', label: '100' },
284
+ ]}
285
+ onValueChange={(value) => {
286
+ if (typeof value !== 'string' || value.length === 0) return
287
+ onPageSizeChange(Number.parseInt(value, 10))
288
+ }}
289
+ />
290
+ <RouterPager
291
+ smoothScrollToTop={true}
292
+ page={data.meta.page}
293
+ count={data.meta.totalPages}
294
+ showFirstButton
295
+ showLastButton
296
+ componentName="pagerBottom"
297
+ aria-label={t('collections.list.pagerBottomAriaLabel')}
298
+ />
299
+ </div>
300
+
301
+ {selectedVersion && currentDocument && (
302
+ <Suspense fallback={null}>
303
+ <DiffModal
304
+ isOpen={true}
305
+ onDismiss={() => setSelectedVersion(null)}
306
+ collection={resourcePath}
307
+ documentId={documentId}
308
+ versionId={selectedVersion.versionId}
309
+ versionLabel={selectedVersion.label}
310
+ currentDocument={currentDocument}
311
+ locale={locale}
312
+ loadHistoricalVersion={loadHistoricalVersion}
313
+ />
314
+ </Suspense>
315
+ )}
316
+
317
+ <Modal
318
+ isOpen={restoreTarget != null}
319
+ onDismiss={() => setRestoreTarget(null)}
320
+ closeOnOverlayClick={false}
321
+ >
322
+ <Modal.Container className={cx('byline-coll-history-restore-modal', styles.restoreModal)}>
323
+ <Modal.Header
324
+ className={cx('byline-coll-history-restore-modal-head', styles.restoreModalHead)}
325
+ >
326
+ <h3 className="m-0">{t('collections.history.restoreModalTitle')}</h3>
327
+ <IconButton
328
+ aria-label={t('common.actions.close')}
329
+ size="xs"
330
+ onClick={() => setRestoreTarget(null)}
331
+ >
332
+ <CloseIcon width="14px" height="14px" svgClassName="white-icon" />
333
+ </IconButton>
334
+ </Modal.Header>
335
+ {restoreTarget ? (
336
+ <RestoreVersionModal
337
+ versionLabel={restoreTarget.label}
338
+ versionNumber={restoreTarget.versionNumber}
339
+ restoreStatusLabel={restoreStatusLabel}
340
+ onClose={() => setRestoreTarget(null)}
341
+ restoreVersion={() => restoreVersion(restoreTarget.versionId)}
342
+ onRestoreComplete={onRestoreComplete}
343
+ />
344
+ ) : null}
345
+ </Modal.Container>
346
+ </Modal>
347
+ </>
348
+ )
349
+ }