@byline/host-tanstack-start 6.7.2 → 6.9.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/admin-roles/container_module.css +1 -1
- package/dist/admin-shell/admin-roles/delete.js +1 -1
- package/dist/admin-shell/admin-users/container.js +181 -159
- package/dist/admin-shell/admin-users/container.module.js +2 -1
- package/dist/admin-shell/admin-users/container_module.css +5 -1
- package/dist/admin-shell/admin-users/delete.js +1 -1
- package/dist/admin-shell/chrome/admin-app-bar.d.ts +0 -7
- package/dist/admin-shell/collections/api.d.ts +6 -2
- package/dist/admin-shell/collections/api.js +109 -3
- package/dist/admin-shell/collections/api.test.d.ts +8 -0
- package/dist/admin-shell/collections/edit.js +2 -0
- package/dist/admin-shell/collections/preview-link.js +17 -13
- package/dist/admin-shell/collections/view-menu.d.ts +8 -1
- package/dist/admin-shell/collections/view-menu.js +124 -107
- package/dist/admin-shell/collections/view-menu.module.js +1 -0
- package/dist/admin-shell/collections/view-menu_module.css +15 -1
- package/dist/admin-shell/scheduled-publications/list.js +2 -2
- package/dist/admin-shell/singletons/preview-link.js +17 -13
- package/dist/admin-shell/singletons/view-menu.js +78 -63
- package/dist/admin-shell/singletons/view.js +2 -0
- package/dist/integrations/byline-admin-services.js +2 -0
- package/dist/integrations/passkey-admin-services.d.ts +9 -0
- package/dist/integrations/passkey-admin-services.js +53 -0
- package/dist/integrations/session-coordination.d.ts +3 -1
- package/dist/integrations/session-coordination.js +3 -1
- package/dist/integrations/session-middleware.d.ts +3 -1
- package/dist/integrations/session-middleware.js +87 -79
- package/dist/integrations/sign-in-body.d.ts +3 -0
- package/dist/integrations/sign-in-body.js +19 -4
- package/dist/integrations/sign-in-middleware.js +30 -5
- package/dist/server-fns/auth/passkey-admission.d.ts +24 -0
- package/dist/server-fns/auth/passkey-admission.js +88 -0
- package/dist/server-fns/auth/passkeys.d.ts +96 -0
- package/dist/server-fns/auth/passkeys.js +238 -0
- package/dist/server-fns/auth/reconcile-locale.d.ts +20 -0
- package/dist/server-fns/auth/reconcile-locale.js +16 -0
- package/dist/server-fns/auth/sign-in.js +1 -15
- package/dist/server-fns/collections/get.d.ts +1 -0
- package/dist/server-fns/collections/get.js +4 -0
- package/package.json +11 -11
- package/src/admin-shell/admin-roles/container.module.css +1 -1
- package/src/admin-shell/admin-roles/delete.tsx +1 -1
- package/src/admin-shell/admin-users/container.module.css +7 -1
- package/src/admin-shell/admin-users/container.tsx +22 -2
- package/src/admin-shell/admin-users/delete.tsx +1 -1
- package/src/admin-shell/chrome/admin-app-bar.tsx +2 -2
- package/src/admin-shell/chrome/dashboard.test.tsx +2 -2
- package/src/admin-shell/collections/api.test.tsx +223 -0
- package/src/admin-shell/collections/api.tsx +98 -3
- package/src/admin-shell/collections/edit.tsx +9 -1
- package/src/admin-shell/collections/preview-link.tsx +13 -12
- package/src/admin-shell/collections/view-menu.module.css +29 -3
- package/src/admin-shell/collections/view-menu.tsx +114 -96
- package/src/admin-shell/scheduled-publications/list.tsx +3 -4
- package/src/admin-shell/singletons/history.test.tsx +1 -1
- package/src/admin-shell/singletons/preview-link.tsx +13 -12
- package/src/admin-shell/singletons/view-menu.tsx +65 -59
- package/src/admin-shell/singletons/view.test.tsx +4 -4
- package/src/admin-shell/singletons/view.tsx +3 -1
- package/src/integrations/byline-admin-services.ts +3 -0
- package/src/integrations/passkey-admin-services.ts +85 -0
- package/src/integrations/session-client.test.node.ts +73 -0
- package/src/integrations/session-coordination.ts +3 -1
- package/src/integrations/session-middleware.ts +86 -72
- package/src/integrations/sign-in-body.test.node.ts +31 -1
- package/src/integrations/sign-in-body.ts +27 -3
- package/src/integrations/sign-in-middleware.ts +42 -9
- package/src/server-fns/auth/passkey-admission.test.node.ts +173 -0
- package/src/server-fns/auth/passkey-admission.ts +125 -0
- package/src/server-fns/auth/passkeys.test.node.ts +247 -0
- package/src/server-fns/auth/passkeys.ts +252 -0
- package/src/server-fns/auth/reconcile-locale.ts +40 -0
- package/src/server-fns/auth/sign-in.ts +1 -32
- package/src/server-fns/collections/get.ts +3 -0
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ReactNode } from 'react'
|
|
9
10
|
import { useEffect } from 'react'
|
|
10
11
|
|
|
11
12
|
import type { CollectionAdminConfig, PreviewDocument } from '@byline/core'
|
|
12
13
|
import { useTranslation } from '@byline/i18n/react'
|
|
13
|
-
import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
|
|
14
|
+
import { Button, HistoryIcon, IconButton, Label, Select, Tooltip } from '@byline/ui/react'
|
|
14
15
|
import cx from 'clsx'
|
|
15
16
|
|
|
16
17
|
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
@@ -51,6 +52,7 @@ export const ViewMenu = ({
|
|
|
51
52
|
defaultContentLocale,
|
|
52
53
|
adminConfig,
|
|
53
54
|
doc,
|
|
55
|
+
dataActions,
|
|
54
56
|
}: {
|
|
55
57
|
/** Collection path (e.g. "docs", "news"). */
|
|
56
58
|
collection: string
|
|
@@ -78,6 +80,12 @@ export const ViewMenu = ({
|
|
|
78
80
|
* entirely (e.g. on the create route, where no doc exists yet).
|
|
79
81
|
*/
|
|
80
82
|
doc?: PreviewDocument
|
|
83
|
+
/**
|
|
84
|
+
* Controls that act on the data selected by the locale and depth controls
|
|
85
|
+
* (the API view's Copy / Download JSON). Rendered first, in the same group
|
|
86
|
+
* as those controls, so they wrap together ahead of the view navigation.
|
|
87
|
+
*/
|
|
88
|
+
dataActions?: ReactNode
|
|
81
89
|
}) => {
|
|
82
90
|
const navigate = useNavigate()
|
|
83
91
|
const { t } = useTranslation('byline-admin')
|
|
@@ -141,103 +149,113 @@ export const ViewMenu = ({
|
|
|
141
149
|
|
|
142
150
|
return (
|
|
143
151
|
<div className={cx('byline-view-menu', styles.root)}>
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
152
|
+
<div className={cx('byline-view-menu-data', styles.group)}>
|
|
153
|
+
{dataActions}
|
|
154
|
+
<Label
|
|
155
|
+
className={cx('muted byline-view-menu-label', styles.label)}
|
|
156
|
+
id="contentLocaleLabel"
|
|
157
|
+
htmlFor="contentLocale"
|
|
158
|
+
label={t('collections.viewMenu.contentLocaleLabel')}
|
|
159
|
+
/>
|
|
160
|
+
<Select<string>
|
|
161
|
+
name="contentLocale"
|
|
162
|
+
id="contentLocale"
|
|
163
|
+
className={cx('byline-view-menu-locale-select', styles.localeSelect)}
|
|
164
|
+
size="xs"
|
|
165
|
+
variant="outlined"
|
|
166
|
+
value={locale ?? defaultContentLocale}
|
|
167
|
+
items={[
|
|
168
|
+
...(activeView !== 'edit'
|
|
169
|
+
? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
|
|
170
|
+
: []),
|
|
171
|
+
...contentLocales.map((loc) => ({ value: loc.code, label: loc.label })),
|
|
172
|
+
]}
|
|
173
|
+
onValueChange={handleLocaleChange}
|
|
174
|
+
/>
|
|
175
|
+
{activeView === 'api' && (
|
|
176
|
+
<>
|
|
177
|
+
<Label
|
|
178
|
+
className={cx('muted byline-view-menu-label', styles.label)}
|
|
179
|
+
id="populateDepthLabel"
|
|
180
|
+
htmlFor="populateDepth"
|
|
181
|
+
label={t('collections.viewMenu.depthLabel')}
|
|
182
|
+
/>
|
|
183
|
+
<Select<string>
|
|
184
|
+
name="populateDepth"
|
|
185
|
+
id="populateDepth"
|
|
186
|
+
className={cx('byline-view-menu-depth-select', styles.depthSelect)}
|
|
187
|
+
size="xs"
|
|
188
|
+
variant="outlined"
|
|
189
|
+
value={String(depth ?? 0)}
|
|
190
|
+
items={[
|
|
191
|
+
{ value: '0', label: '0' },
|
|
192
|
+
{ value: '1', label: '1' },
|
|
193
|
+
{ value: '2', label: '2' },
|
|
194
|
+
{ value: '3', label: '3' },
|
|
195
|
+
]}
|
|
196
|
+
onValueChange={handleDepthChange}
|
|
197
|
+
/>
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
<div className={cx('byline-view-menu-nav', styles.group)}>
|
|
202
|
+
{doc && (
|
|
203
|
+
<PreviewLink
|
|
204
|
+
collectionPath={collection}
|
|
205
|
+
doc={doc}
|
|
206
|
+
adminConfig={adminConfig}
|
|
207
|
+
locale={locale}
|
|
208
|
+
className={cx('byline-view-menu-icon-button', styles.iconButton)}
|
|
172
209
|
/>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
className={cx('byline-view-menu-
|
|
210
|
+
)}
|
|
211
|
+
<Tooltip text={t('collections.breadcrumbs.history')} side="bottom" delay={0}>
|
|
212
|
+
<IconButton
|
|
213
|
+
className={cx('byline-view-menu-icon-button', styles.iconButton)}
|
|
177
214
|
size="xs"
|
|
178
|
-
variant=
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
className={cx('byline-view-menu-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
})
|
|
223
|
-
}
|
|
224
|
-
>
|
|
225
|
-
{t('common.actions.edit')}
|
|
226
|
-
</Button>
|
|
227
|
-
<Button
|
|
228
|
-
size="xs"
|
|
229
|
-
variant={activeView === 'api' ? 'filled' : 'outlined'}
|
|
230
|
-
className={cx('byline-view-menu-button', styles.button)}
|
|
231
|
-
onClick={() =>
|
|
232
|
-
navigate({
|
|
233
|
-
to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
|
|
234
|
-
params: { collection, id: documentId },
|
|
235
|
-
search: locale ? { locale } : {},
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
>
|
|
239
|
-
{t('collections.viewMenu.apiButton')}
|
|
240
|
-
</Button>
|
|
215
|
+
variant={activeView === 'history' ? 'filled' : 'text'}
|
|
216
|
+
aria-label={t('collections.breadcrumbs.history')}
|
|
217
|
+
onClick={() =>
|
|
218
|
+
navigate({
|
|
219
|
+
to: getAdminRoutePath('collections', '$collection', '$id', 'history'),
|
|
220
|
+
params: { collection, id: documentId },
|
|
221
|
+
search: locale ? { locale } : {},
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
>
|
|
225
|
+
<HistoryIcon
|
|
226
|
+
className={cx('byline-view-menu-icon-button-icon', styles.iconButtonIcon)}
|
|
227
|
+
/>
|
|
228
|
+
</IconButton>
|
|
229
|
+
</Tooltip>
|
|
230
|
+
<Button
|
|
231
|
+
size="xs"
|
|
232
|
+
variant={activeView === 'edit' ? 'filled' : 'outlined'}
|
|
233
|
+
className={cx('byline-view-menu-button', styles.button)}
|
|
234
|
+
onClick={() =>
|
|
235
|
+
navigate({
|
|
236
|
+
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
237
|
+
params: { collection, id: documentId },
|
|
238
|
+
search: locale ? { locale } : {},
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
>
|
|
242
|
+
{t('common.actions.edit')}
|
|
243
|
+
</Button>
|
|
244
|
+
<Button
|
|
245
|
+
size="xs"
|
|
246
|
+
variant={activeView === 'api' ? 'filled' : 'outlined'}
|
|
247
|
+
className={cx('byline-view-menu-button', styles.button)}
|
|
248
|
+
onClick={() =>
|
|
249
|
+
navigate({
|
|
250
|
+
to: getAdminRoutePath('collections', '$collection', '$id', 'api'),
|
|
251
|
+
params: { collection, id: documentId },
|
|
252
|
+
search: locale ? { locale } : {},
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
>
|
|
256
|
+
{t('collections.viewMenu.apiButton')}
|
|
257
|
+
</Button>
|
|
258
|
+
</div>
|
|
241
259
|
</div>
|
|
242
260
|
)
|
|
243
261
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { useDocumentMutationState } from '../document-mutation-state.js'
|
|
3
|
-
|
|
4
|
-
;('use client')
|
|
1
|
+
'use client'
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
@@ -32,6 +29,8 @@ import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
|
32
29
|
import { cancelCollectionDocumentScheduledPublish } from '../../server-fns/collections/index.js'
|
|
33
30
|
import { Link, useNavigate } from '../chrome/loose-router.js'
|
|
34
31
|
import { RouterPager } from '../chrome/router-pager.js'
|
|
32
|
+
import { DocumentMutationNotice } from '../document-mutation-notice.js'
|
|
33
|
+
import { useDocumentMutationState } from '../document-mutation-state.js'
|
|
35
34
|
import styles from './list.module.css'
|
|
36
35
|
import type { ScheduledPublicationListResponse } from '../../server-fns/collections/index.js'
|
|
37
36
|
|
|
@@ -372,7 +372,7 @@ describe('singleton version history', () => {
|
|
|
372
372
|
<HistoryView
|
|
373
373
|
collectionDefinition={collectionDefinition}
|
|
374
374
|
adminConfig={{
|
|
375
|
-
|
|
375
|
+
path: 'articles',
|
|
376
376
|
columns: [{ fieldName: 'title', label: 'Title' }],
|
|
377
377
|
}}
|
|
378
378
|
data={data as never}
|
|
@@ -12,7 +12,7 @@ import { useState } from 'react'
|
|
|
12
12
|
|
|
13
13
|
import type { SingletonAdminConfig, SingletonPreviewDocument } from '@byline/core'
|
|
14
14
|
import { useTranslation } from '@byline/i18n/react'
|
|
15
|
-
import { ExternalLinkIcon, IconButton, useToastManager } from '@byline/ui/react'
|
|
15
|
+
import { ExternalLinkIcon, IconButton, Tooltip, useToastManager } from '@byline/ui/react'
|
|
16
16
|
import cx from 'clsx'
|
|
17
17
|
|
|
18
18
|
import { enablePreviewModeFn } from '../../server-fns/preview/index.js'
|
|
@@ -60,16 +60,17 @@ export function SingletonPreviewLink({
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
return (
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
<Tooltip text={t('collections.preview.title')} side="bottom" delay={0}>
|
|
64
|
+
<IconButton
|
|
65
|
+
className={cx('byline-preview-link', className)}
|
|
66
|
+
size="xs"
|
|
67
|
+
variant="text"
|
|
68
|
+
disabled={busy}
|
|
69
|
+
onClick={handleClick}
|
|
70
|
+
aria-label={t('collections.preview.openAriaLabel')}
|
|
71
|
+
>
|
|
72
|
+
<ExternalLinkIcon width="20px" height="20px" className="byline-preview-link-icon" />
|
|
73
|
+
</IconButton>
|
|
74
|
+
</Tooltip>
|
|
74
75
|
)
|
|
75
76
|
}
|
|
@@ -12,7 +12,7 @@ import { useEffect } from 'react'
|
|
|
12
12
|
|
|
13
13
|
import type { SingletonAdminConfig, SingletonPreviewDocument } from '@byline/core'
|
|
14
14
|
import { useTranslation } from '@byline/i18n/react'
|
|
15
|
-
import { Button, HistoryIcon, IconButton, Label, Select } from '@byline/ui/react'
|
|
15
|
+
import { Button, HistoryIcon, IconButton, Label, Select, Tooltip } from '@byline/ui/react'
|
|
16
16
|
import cx from 'clsx'
|
|
17
17
|
|
|
18
18
|
import { getAdminRoutePath } from '../../routes/admin-path.js'
|
|
@@ -71,66 +71,72 @@ export function SingletonViewMenu({
|
|
|
71
71
|
|
|
72
72
|
return (
|
|
73
73
|
<div className={cx('byline-view-menu', collectionStyles.root)}>
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<Select<string>
|
|
81
|
-
name="contentLocale"
|
|
82
|
-
id="contentLocale"
|
|
83
|
-
className={cx('byline-view-menu-locale-select', collectionStyles.localeSelect)}
|
|
84
|
-
size="xs"
|
|
85
|
-
variant="outlined"
|
|
86
|
-
value={locale ?? defaultContentLocale}
|
|
87
|
-
items={[
|
|
88
|
-
...(activeView === 'history'
|
|
89
|
-
? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
|
|
90
|
-
: []),
|
|
91
|
-
...contentLocales.map((entry) => ({ value: entry.code, label: entry.label })),
|
|
92
|
-
]}
|
|
93
|
-
onValueChange={handleLocaleChange}
|
|
94
|
-
/>
|
|
95
|
-
{adminConfig?.preview != null && doc != null && (
|
|
96
|
-
<SingletonPreviewLink
|
|
97
|
-
doc={doc}
|
|
98
|
-
adminConfig={adminConfig}
|
|
99
|
-
locale={locale}
|
|
100
|
-
className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
|
|
74
|
+
<div className={cx('byline-view-menu-data', collectionStyles.group)}>
|
|
75
|
+
<Label
|
|
76
|
+
className={cx('muted byline-view-menu-label', collectionStyles.label)}
|
|
77
|
+
id="contentLocaleLabel"
|
|
78
|
+
htmlFor="contentLocale"
|
|
79
|
+
label={t('collections.viewMenu.contentLocaleLabel')}
|
|
101
80
|
/>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<HistoryIcon
|
|
117
|
-
className={cx('byline-view-menu-icon-button-icon', collectionStyles.iconButtonIcon)}
|
|
81
|
+
<Select<string>
|
|
82
|
+
name="contentLocale"
|
|
83
|
+
id="contentLocale"
|
|
84
|
+
className={cx('byline-view-menu-locale-select', collectionStyles.localeSelect)}
|
|
85
|
+
size="xs"
|
|
86
|
+
variant="outlined"
|
|
87
|
+
value={locale ?? defaultContentLocale}
|
|
88
|
+
items={[
|
|
89
|
+
...(activeView === 'history'
|
|
90
|
+
? [{ value: 'all', label: t('collections.viewMenu.localeAll') }]
|
|
91
|
+
: []),
|
|
92
|
+
...contentLocales.map((entry) => ({ value: entry.code, label: entry.label })),
|
|
93
|
+
]}
|
|
94
|
+
onValueChange={handleLocaleChange}
|
|
118
95
|
/>
|
|
119
|
-
</
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
96
|
+
</div>
|
|
97
|
+
<div className={cx('byline-view-menu-nav', collectionStyles.group)}>
|
|
98
|
+
{adminConfig?.preview != null && doc != null && (
|
|
99
|
+
<SingletonPreviewLink
|
|
100
|
+
doc={doc}
|
|
101
|
+
adminConfig={adminConfig}
|
|
102
|
+
locale={locale}
|
|
103
|
+
className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
106
|
+
<Tooltip text={t('collections.breadcrumbs.history')} side="bottom" delay={0}>
|
|
107
|
+
<IconButton
|
|
108
|
+
className={cx('byline-view-menu-icon-button', collectionStyles.iconButton)}
|
|
109
|
+
size="xs"
|
|
110
|
+
variant={activeView === 'history' ? 'filled' : 'text'}
|
|
111
|
+
aria-label={t('collections.breadcrumbs.history')}
|
|
112
|
+
onClick={() =>
|
|
113
|
+
navigate({
|
|
114
|
+
to: getAdminRoutePath('singletons', '$singleton', 'history'),
|
|
115
|
+
params: { singleton },
|
|
116
|
+
search: locale ? { locale } : {},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<HistoryIcon
|
|
121
|
+
className={cx('byline-view-menu-icon-button-icon', collectionStyles.iconButtonIcon)}
|
|
122
|
+
/>
|
|
123
|
+
</IconButton>
|
|
124
|
+
</Tooltip>
|
|
125
|
+
<Button
|
|
126
|
+
size="xs"
|
|
127
|
+
variant={activeView === 'edit' ? 'filled' : 'outlined'}
|
|
128
|
+
className={cx('byline-view-menu-button', collectionStyles.button)}
|
|
129
|
+
onClick={() =>
|
|
130
|
+
navigate({
|
|
131
|
+
to: getAdminRoutePath('singletons', '$singleton'),
|
|
132
|
+
params: { singleton },
|
|
133
|
+
search: locale ? { locale } : {},
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
>
|
|
137
|
+
{t('common.actions.edit')}
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
134
140
|
</div>
|
|
135
141
|
)
|
|
136
142
|
}
|
|
@@ -142,7 +142,7 @@ const singleton = defineSingleton({
|
|
|
142
142
|
|
|
143
143
|
const adminConfig = {
|
|
144
144
|
singleton: true as const,
|
|
145
|
-
|
|
145
|
+
path: singleton.path,
|
|
146
146
|
preview: { url: () => '/preview-settings' },
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -181,7 +181,7 @@ let root: Root
|
|
|
181
181
|
function view(
|
|
182
182
|
currentDocument: typeof loadedDocument | null,
|
|
183
183
|
locale = 'en',
|
|
184
|
-
config: typeof adminConfig | { singleton: true;
|
|
184
|
+
config: typeof adminConfig | { singleton: true; path: string } = adminConfig
|
|
185
185
|
) {
|
|
186
186
|
return (
|
|
187
187
|
<I18nProvider
|
|
@@ -208,7 +208,7 @@ function view(
|
|
|
208
208
|
function render(
|
|
209
209
|
currentDocument: typeof loadedDocument | null,
|
|
210
210
|
locale = 'en',
|
|
211
|
-
config: typeof adminConfig | { singleton: true;
|
|
211
|
+
config: typeof adminConfig | { singleton: true; path: string } = adminConfig
|
|
212
212
|
) {
|
|
213
213
|
act(() => root.render(view(currentDocument, locale, config)))
|
|
214
214
|
}
|
|
@@ -332,7 +332,7 @@ describe('SingletonView', () => {
|
|
|
332
332
|
})
|
|
333
333
|
|
|
334
334
|
it('never derives a singleton preview from its internal stored path', () => {
|
|
335
|
-
render(loadedDocument, 'en', { singleton: true,
|
|
335
|
+
render(loadedDocument, 'en', { singleton: true, path: singleton.path })
|
|
336
336
|
|
|
337
337
|
expect(container.querySelector('.byline-view-menu')).not.toBeNull()
|
|
338
338
|
expect(container.querySelector('.byline-preview-link')).toBeNull()
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { useRouter } from '@tanstack/react-router'
|
|
12
12
|
|
|
13
13
|
import { FormRenderer } from '@byline/admin/react'
|
|
14
|
-
import type { SingletonAdminConfig, SingletonDefinition } from '@byline/core'
|
|
14
|
+
import type { SingletonAdminConfig, SingletonDefinition, UnavailableBlock } from '@byline/core'
|
|
15
15
|
import {
|
|
16
16
|
getDefaultStatus,
|
|
17
17
|
getWorkflow,
|
|
@@ -82,6 +82,7 @@ export function SingletonView({
|
|
|
82
82
|
? workflowStatuses[currentIndex + 1]
|
|
83
83
|
: undefined
|
|
84
84
|
const publishedVersion = document?._publishedVersion ?? null
|
|
85
|
+
const unavailableBlocks = document?._unavailableBlocks as UnavailableBlock[] | undefined
|
|
85
86
|
const restoreWarnings = document?._restoreWarnings as string[] | undefined
|
|
86
87
|
const scheduledPublicationEnabled = document?._scheduledPublicationEnabled === true
|
|
87
88
|
const canSchedulePublication = document?._canSchedulePublication === true
|
|
@@ -441,6 +442,7 @@ export function SingletonView({
|
|
|
441
442
|
contentLocales={document == null ? undefined : contentLocales}
|
|
442
443
|
publishedVersion={document == null ? null : publishedVersion}
|
|
443
444
|
restoreWarnings={restoreWarnings}
|
|
445
|
+
unavailableBlocks={unavailableBlocks}
|
|
444
446
|
nextStatus={document == null ? undefined : nextStatus}
|
|
445
447
|
workflowStatuses={document == null ? undefined : workflowStatuses}
|
|
446
448
|
onCancel={() => navigate({ to: getAdminRoutePath() })}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { adminSignIn } from '../server-fns/auth/index.js'
|
|
37
37
|
import { getCollectionDocumentVersion as serverGetCollectionDocumentVersion } from '../server-fns/collections/get.js'
|
|
38
38
|
import { setAdminLocaleFn } from '../server-fns/i18n/index.js'
|
|
39
|
+
import { passkeyAdminServices } from './passkey-admin-services.js'
|
|
39
40
|
import { acceptSession, coordinateAuthAction, notifySessionAction } from './session-coordination.js'
|
|
40
41
|
|
|
41
42
|
/**
|
|
@@ -70,6 +71,8 @@ export const bylineAdminServices: BylineAdminServices = {
|
|
|
70
71
|
return result
|
|
71
72
|
},
|
|
72
73
|
|
|
74
|
+
passkeys: passkeyAdminServices,
|
|
75
|
+
|
|
73
76
|
// Account self-service
|
|
74
77
|
updateAccount,
|
|
75
78
|
changeAccountPassword,
|
|
@@ -0,0 +1,85 @@
|
|
|
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 type { PasskeyAdminServices } from '@byline/admin/services'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getPasskeyCapability,
|
|
13
|
+
passkeyEnrollment,
|
|
14
|
+
passkeyEnrollmentOptions,
|
|
15
|
+
passkeyList,
|
|
16
|
+
passkeyManagementContext,
|
|
17
|
+
passkeyPasswordProof,
|
|
18
|
+
passkeyProof,
|
|
19
|
+
passkeyProofOptions,
|
|
20
|
+
passkeyRemove,
|
|
21
|
+
passkeyRename,
|
|
22
|
+
passkeySignIn,
|
|
23
|
+
passkeySignInOptions,
|
|
24
|
+
} from '../server-fns/auth/passkeys.js'
|
|
25
|
+
import {
|
|
26
|
+
acceptSession,
|
|
27
|
+
coordinateAuthAction,
|
|
28
|
+
flagSessionChanged,
|
|
29
|
+
notifySessionAction,
|
|
30
|
+
requireUnchanged,
|
|
31
|
+
sessionChangedError,
|
|
32
|
+
sessionSnapshot,
|
|
33
|
+
} from './session-coordination.js'
|
|
34
|
+
|
|
35
|
+
export const passkeyAdminServices: PasskeyAdminServices = {
|
|
36
|
+
capability: () => getPasskeyCapability(),
|
|
37
|
+
captureLogin: (signIn = false) => {
|
|
38
|
+
const snapshot = sessionSnapshot()
|
|
39
|
+
const assertCurrent = () => {
|
|
40
|
+
if (!signIn) return requireUnchanged(snapshot)
|
|
41
|
+
// Explicit reauthentication may begin on an already flagged page, but any
|
|
42
|
+
// subsequent account change still invalidates this particular ceremony.
|
|
43
|
+
const current = sessionSnapshot()
|
|
44
|
+
if (current.epoch !== snapshot.epoch || current.sessionId !== snapshot.sessionId)
|
|
45
|
+
throw sessionChangedError()
|
|
46
|
+
}
|
|
47
|
+
assertCurrent()
|
|
48
|
+
return { expectedSessionId: snapshot.sessionId, assertCurrent }
|
|
49
|
+
},
|
|
50
|
+
managementContext: async (binding) => {
|
|
51
|
+
binding.assertCurrent()
|
|
52
|
+
if (!binding.expectedSessionId) throw sessionChangedError()
|
|
53
|
+
const actor = await passkeyManagementContext({
|
|
54
|
+
data: { expectedSessionId: binding.expectedSessionId },
|
|
55
|
+
})
|
|
56
|
+
binding.assertCurrent()
|
|
57
|
+
return actor
|
|
58
|
+
},
|
|
59
|
+
signInOptions: passkeySignInOptions,
|
|
60
|
+
signIn: (args, binding) =>
|
|
61
|
+
coordinateAuthAction(async () => {
|
|
62
|
+
binding.assertCurrent()
|
|
63
|
+
const result = await passkeySignIn(args)
|
|
64
|
+
acceptSession(result.sessionId)
|
|
65
|
+
notifySessionAction()
|
|
66
|
+
return result
|
|
67
|
+
}),
|
|
68
|
+
passwordProof: passkeyPasswordProof,
|
|
69
|
+
proofOptions: passkeyProofOptions,
|
|
70
|
+
proof: passkeyProof,
|
|
71
|
+
enrollmentOptions: passkeyEnrollmentOptions,
|
|
72
|
+
enrollment: passkeyEnrollment,
|
|
73
|
+
list: passkeyList,
|
|
74
|
+
rename: passkeyRename,
|
|
75
|
+
remove: async (args) => {
|
|
76
|
+
// The mutation middleware queues its fetch, including cookie application;
|
|
77
|
+
// it releases the queue before a pre-handler renewal can acquire it.
|
|
78
|
+
const result = await passkeyRemove(args)
|
|
79
|
+
if (result.signedOut) {
|
|
80
|
+
flagSessionChanged()
|
|
81
|
+
notifySessionAction()
|
|
82
|
+
}
|
|
83
|
+
return result
|
|
84
|
+
},
|
|
85
|
+
}
|