@byline/admin 4.19.0 → 5.1.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/fields/field-services-context.d.ts +1 -1
- package/dist/fields/field-services-types.d.ts +5 -2
- package/dist/forms/available-locales-widget.d.ts +3 -2
- package/dist/forms/available-locales-widget.js +4 -2
- package/dist/forms/document-actions.d.ts +3 -2
- package/dist/forms/document-actions.js +32 -14
- package/dist/forms/form-renderer.d.ts +12 -3
- package/dist/forms/form-renderer.js +98 -8
- package/dist/forms/form-renderer.module.js +1 -0
- package/dist/forms/form-renderer_module.css +12 -0
- package/dist/forms/form-status-display.d.ts +3 -2
- package/dist/forms/form-status-display.js +5 -2
- package/dist/forms/path-widget.d.ts +2 -1
- package/dist/forms/path-widget.js +7 -2
- package/dist/forms/scheduled-publication-control.d.ts +2 -1
- package/dist/forms/scheduled-publication-control.js +12 -8
- package/dist/forms/tree-placement-widget.d.ts +5 -1
- package/dist/forms/tree-placement-widget.js +14 -7
- package/dist/forms/upload-executor.d.ts +2 -2
- package/dist/modules/admin-account/components/change-password.js +18 -0
- package/dist/modules/admin-account/service.d.ts +2 -6
- package/dist/modules/admin-account/service.js +2 -1
- package/dist/modules/admin-users/repository.d.ts +10 -1
- package/dist/modules/auth/index.d.ts +3 -1
- package/dist/modules/auth/index.js +1 -0
- package/dist/modules/auth/jwt-session-provider.d.ts +8 -2
- package/dist/modules/auth/jwt-session-provider.js +222 -70
- package/dist/modules/auth/login-sessions-repository.d.ts +23 -0
- package/dist/modules/auth/login-sessions-repository.js +1 -0
- package/dist/modules/auth/refresh-tokens-repository.d.ts +12 -9
- package/dist/modules/auth/resolve-actor.d.ts +3 -0
- package/dist/modules/auth/resolve-actor.js +5 -2
- package/dist/modules/auth/sign-in-rate-limiter.d.ts +48 -0
- package/dist/modules/auth/sign-in-rate-limiter.js +229 -0
- package/dist/store.d.ts +15 -2
- package/package.json +17 -17
- package/src/fields/field-services-types.ts +10 -2
- package/src/forms/available-locales-widget.tsx +5 -2
- package/src/forms/document-actions.tsx +55 -20
- package/src/forms/form-renderer-submit.test.tsx +131 -2
- package/src/forms/form-renderer.module.css +20 -0
- package/src/forms/form-renderer.tsx +122 -7
- package/src/forms/form-status-display.tsx +6 -1
- package/src/forms/path-widget.tsx +8 -3
- package/src/forms/scheduled-publication-control.tsx +16 -7
- package/src/forms/tree-placement-widget.tsx +34 -7
- package/src/modules/admin-account/components/change-password.test.tsx +72 -0
- package/src/modules/admin-account/components/change-password.tsx +15 -4
- package/src/modules/admin-account/service.ts +8 -7
- package/src/modules/admin-users/repository.ts +10 -1
- package/src/modules/auth/index.ts +13 -1
- package/src/modules/auth/jwt-session-provider.ts +276 -91
- package/src/modules/auth/login-sessions-repository.ts +25 -0
- package/src/modules/auth/refresh-tokens-repository.ts +12 -9
- package/src/modules/auth/resolve-actor.ts +10 -1
- package/src/modules/auth/sign-in-rate-limiter.ts +240 -0
- package/src/store.ts +22 -2
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import { type ReactNode } from 'react';
|
|
9
|
-
import type { BylineFieldServices } from './field-services-types';
|
|
9
|
+
import type { BylineFieldServices } from './field-services-types.js';
|
|
10
10
|
interface BylineFieldServicesProviderProps {
|
|
11
11
|
services: BylineFieldServices;
|
|
12
12
|
children: ReactNode;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StructuralMutationReceipt } from '@byline/core';
|
|
1
2
|
/**
|
|
2
3
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
4
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -64,6 +65,7 @@ export interface TreeAncestor {
|
|
|
64
65
|
path?: string;
|
|
65
66
|
}
|
|
66
67
|
export interface PlaceTreeNodeInput {
|
|
68
|
+
expectedRevision: number;
|
|
67
69
|
collection: string;
|
|
68
70
|
documentId: string;
|
|
69
71
|
/** The new parent; `null` makes the document a root node. */
|
|
@@ -75,12 +77,13 @@ export interface PlaceTreeNodeInput {
|
|
|
75
77
|
/** Place / move a document within its collection's tree. */
|
|
76
78
|
export type PlaceTreeNodeFn = (input: PlaceTreeNodeInput) => Promise<{
|
|
77
79
|
orderKey: string;
|
|
78
|
-
}>;
|
|
80
|
+
} & StructuralMutationReceipt>;
|
|
79
81
|
/** Remove a document from the tree (back to the unplaced state). */
|
|
80
82
|
export type RemoveFromTreeFn = (input: {
|
|
81
83
|
collection: string;
|
|
82
84
|
documentId: string;
|
|
83
|
-
|
|
85
|
+
expectedRevision: number;
|
|
86
|
+
}) => Promise<StructuralMutationReceipt>;
|
|
84
87
|
/** Resolve a document's ancestor chain, root-first, hydrated with titles. */
|
|
85
88
|
export type GetTreeAncestorsFn = (input: {
|
|
86
89
|
collection: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { type ReconciledLocaleState, reconcileLocaleState } from './available-locales-reconcile';
|
|
1
|
+
export { type ReconciledLocaleState, reconcileLocaleState } from './available-locales-reconcile.js';
|
|
2
2
|
/** A content locale to render a checkbox for. */
|
|
3
3
|
export interface AvailableLocalesWidgetLocale {
|
|
4
4
|
code: string;
|
|
5
5
|
label: string;
|
|
6
6
|
}
|
|
7
7
|
export interface AvailableLocalesWidgetProps {
|
|
8
|
+
disabled?: boolean;
|
|
8
9
|
/** All configured content locales — one checkbox each (code + display label). */
|
|
9
10
|
contentLocales: ReadonlyArray<AvailableLocalesWidgetLocale>;
|
|
10
11
|
/**
|
|
@@ -26,4 +27,4 @@ export interface AvailableLocalesWidgetProps {
|
|
|
26
27
|
* Stable override handles: `.byline-form-available-locales`,
|
|
27
28
|
* `.byline-form-available-locales-list`.
|
|
28
29
|
*/
|
|
29
|
-
export declare const AvailableLocalesWidget: ({ contentLocales, availableVersionLocales, }: AvailableLocalesWidgetProps) => import("react").JSX.Element | null;
|
|
30
|
+
export declare const AvailableLocalesWidget: ({ disabled: mutationsBlocked, contentLocales, availableVersionLocales, }: AvailableLocalesWidgetProps) => import("react").JSX.Element | null;
|
|
@@ -7,7 +7,7 @@ import clsx from "clsx";
|
|
|
7
7
|
import { reconcileLocaleState } from "./available-locales-reconcile.js";
|
|
8
8
|
import available_locales_widget_module from "./available-locales-widget.module.js";
|
|
9
9
|
import { useFormContext, useSystemAvailableLocales } from "./form-context.js";
|
|
10
|
-
const AvailableLocalesWidget = ({ contentLocales, availableVersionLocales })=>{
|
|
10
|
+
const AvailableLocalesWidget = ({ disabled: mutationsBlocked = false, contentLocales, availableVersionLocales })=>{
|
|
11
11
|
const { t } = useTranslation('byline-admin');
|
|
12
12
|
const { setSystemAvailableLocales } = useFormContext();
|
|
13
13
|
const advertised = useSystemAvailableLocales();
|
|
@@ -18,6 +18,7 @@ const AvailableLocalesWidget = ({ contentLocales, availableVersionLocales })=>{
|
|
|
18
18
|
availableVersionLocales
|
|
19
19
|
]);
|
|
20
20
|
const toggle = useCallback((code, checked)=>{
|
|
21
|
+
if (mutationsBlocked) return;
|
|
21
22
|
const next = new Set(advertised);
|
|
22
23
|
if (checked) next.add(code);
|
|
23
24
|
else next.delete(code);
|
|
@@ -25,6 +26,7 @@ const AvailableLocalesWidget = ({ contentLocales, availableVersionLocales })=>{
|
|
|
25
26
|
...next
|
|
26
27
|
]);
|
|
27
28
|
}, [
|
|
29
|
+
mutationsBlocked,
|
|
28
30
|
advertised,
|
|
29
31
|
setSystemAvailableLocales
|
|
30
32
|
]);
|
|
@@ -52,7 +54,7 @@ const AvailableLocalesWidget = ({ contentLocales, availableVersionLocales })=>{
|
|
|
52
54
|
label: label,
|
|
53
55
|
intent: intent,
|
|
54
56
|
checked: checked,
|
|
55
|
-
disabled: disabled,
|
|
57
|
+
disabled: mutationsBlocked || disabled,
|
|
56
58
|
onCheckedChange: (value)=>toggle(code, true === value)
|
|
57
59
|
}, code);
|
|
58
60
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PublishedVersionInfo } from './form-renderer';
|
|
1
|
+
import type { PublishedVersionInfo } from './form-renderer.js';
|
|
2
2
|
import type { ScheduledPublicationState } from './scheduled-publication-state.js';
|
|
3
3
|
/**
|
|
4
4
|
* Shape of a content-locale option as consumed by the Copy-to-Locale
|
|
@@ -9,7 +9,8 @@ export interface DocumentActionsLocaleOption {
|
|
|
9
9
|
code: string;
|
|
10
10
|
label: string;
|
|
11
11
|
}
|
|
12
|
-
export declare function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate, sourceTitle, onCopyToLocale, sourceLocale, contentLocales, hasUnsavedChanges, onUnsavedChanges, onDeleteLocale, defaultLocale, availableLocales, scheduledPublicationState, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, }: {
|
|
12
|
+
export declare function DocumentActions({ disabled, publishedVersion, onUnpublish, onDelete, onDuplicate, sourceTitle, onCopyToLocale, sourceLocale, contentLocales, hasUnsavedChanges, onUnsavedChanges, onDeleteLocale, defaultLocale, availableLocales, scheduledPublicationState, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, }: {
|
|
13
|
+
disabled?: boolean;
|
|
13
14
|
publishedVersion?: PublishedVersionInfo | null;
|
|
14
15
|
onUnpublish?: () => Promise<void>;
|
|
15
16
|
onDelete?: () => Promise<void>;
|
|
@@ -6,7 +6,7 @@ import { Button, Checkbox, CloseIcon, DeleteIcon, Dropdown, EllipsisIcon, IconBu
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import document_actions_module from "./document-actions.module.js";
|
|
8
8
|
const DUPLICATE_TITLE_SUFFIX = ' (copy)';
|
|
9
|
-
function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate, sourceTitle, onCopyToLocale, sourceLocale, contentLocales, hasUnsavedChanges, onUnsavedChanges, onDeleteLocale, defaultLocale, availableLocales, scheduledPublicationState, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication }) {
|
|
9
|
+
function DocumentActions({ disabled = false, publishedVersion, onUnpublish, onDelete, onDuplicate, sourceTitle, onCopyToLocale, sourceLocale, contentLocales, hasUnsavedChanges, onUnsavedChanges, onDeleteLocale, defaultLocale, availableLocales, scheduledPublicationState, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication }) {
|
|
10
10
|
const { t } = useTranslation('byline-admin');
|
|
11
11
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
12
12
|
const [showDuplicateConfirm, setShowDuplicateConfirm] = useState(false);
|
|
@@ -51,30 +51,35 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
51
51
|
}
|
|
52
52
|
const hasAnyAction = schedulingActions.length > 0 || copyToLocaleAvailable || deleteLocaleAvailable || null != onDuplicate || null != onDelete;
|
|
53
53
|
const handleOnDelete = ()=>{
|
|
54
|
+
if (disabled) return;
|
|
54
55
|
setShowDeleteConfirm(false);
|
|
55
|
-
if (onDelete) onDelete();
|
|
56
|
+
if (onDelete) onDelete().catch(()=>{});
|
|
56
57
|
};
|
|
57
58
|
const handleOnDuplicate = async ()=>{
|
|
59
|
+
if (disabled) return;
|
|
58
60
|
if (!onDuplicate) return;
|
|
59
61
|
setDuplicateBusy(true);
|
|
60
62
|
try {
|
|
61
63
|
await onDuplicate();
|
|
62
64
|
setShowDuplicateConfirm(false);
|
|
63
|
-
} finally{
|
|
65
|
+
} catch {} finally{
|
|
64
66
|
setDuplicateBusy(false);
|
|
65
67
|
}
|
|
66
68
|
};
|
|
67
69
|
const handleOpenDuplicate = ()=>{
|
|
70
|
+
if (disabled) return;
|
|
68
71
|
if (hasUnsavedChanges) return void onUnsavedChanges?.();
|
|
69
72
|
setShowDuplicateConfirm(true);
|
|
70
73
|
};
|
|
71
74
|
const handleOpenCopyToLocale = ()=>{
|
|
75
|
+
if (disabled) return;
|
|
72
76
|
if (hasUnsavedChanges) return void onUnsavedChanges?.();
|
|
73
77
|
setCopyTargetLocale(availableTargetLocales[0]?.code ?? '');
|
|
74
78
|
setCopyOverwrite(false);
|
|
75
79
|
setShowCopyToLocaleConfirm(true);
|
|
76
80
|
};
|
|
77
81
|
const handleOnCopyToLocale = async ()=>{
|
|
82
|
+
if (disabled) return;
|
|
78
83
|
if (!onCopyToLocale || !copyTargetLocale) return;
|
|
79
84
|
setCopyToLocaleBusy(true);
|
|
80
85
|
try {
|
|
@@ -83,17 +88,19 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
83
88
|
overwrite: copyOverwrite
|
|
84
89
|
});
|
|
85
90
|
setShowCopyToLocaleConfirm(false);
|
|
86
|
-
} finally{
|
|
91
|
+
} catch {} finally{
|
|
87
92
|
setCopyToLocaleBusy(false);
|
|
88
93
|
}
|
|
89
94
|
};
|
|
90
95
|
const handleOpenDeleteLocale = ()=>{
|
|
96
|
+
if (disabled) return;
|
|
91
97
|
if (hasUnsavedChanges) return void onUnsavedChanges?.();
|
|
92
98
|
const preferred = deletableLocales.find((loc)=>loc.code === sourceLocale)?.code;
|
|
93
99
|
setDeleteTargetLocale(preferred ?? deletableLocales[0]?.code ?? '');
|
|
94
100
|
setShowDeleteLocaleConfirm(true);
|
|
95
101
|
};
|
|
96
102
|
const handleOnDeleteLocale = async ()=>{
|
|
103
|
+
if (disabled) return;
|
|
97
104
|
if (!onDeleteLocale || !deleteTargetLocale) return;
|
|
98
105
|
setDeleteLocaleBusy(true);
|
|
99
106
|
try {
|
|
@@ -101,7 +108,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
101
108
|
targetLocale: deleteTargetLocale
|
|
102
109
|
});
|
|
103
110
|
setShowDeleteLocaleConfirm(false);
|
|
104
|
-
} finally{
|
|
111
|
+
} catch {} finally{
|
|
105
112
|
setDeleteLocaleBusy(false);
|
|
106
113
|
}
|
|
107
114
|
};
|
|
@@ -113,6 +120,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
113
120
|
hasAnyAction && /*#__PURE__*/ jsxs(Dropdown.Root, {
|
|
114
121
|
children: [
|
|
115
122
|
/*#__PURE__*/ jsx(Dropdown.Trigger, {
|
|
123
|
+
disabled: disabled,
|
|
116
124
|
render: /*#__PURE__*/ jsx(IconButton, {
|
|
117
125
|
variant: "text",
|
|
118
126
|
intent: "noeffect",
|
|
@@ -134,6 +142,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
134
142
|
schedulingActions.length > 0 && /*#__PURE__*/ jsxs(Fragment, {
|
|
135
143
|
children: [
|
|
136
144
|
schedulingActions.map((action)=>/*#__PURE__*/ jsx(Dropdown.Item, {
|
|
145
|
+
disabled: disabled,
|
|
137
146
|
onClick: action.onSelect,
|
|
138
147
|
children: /*#__PURE__*/ jsx("div", {
|
|
139
148
|
className: clsx('byline-form-actions-item', document_actions_module.item),
|
|
@@ -141,6 +150,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
141
150
|
className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
|
|
142
151
|
children: /*#__PURE__*/ jsx("button", {
|
|
143
152
|
type: "button",
|
|
153
|
+
disabled: disabled,
|
|
144
154
|
children: action.label
|
|
145
155
|
})
|
|
146
156
|
})
|
|
@@ -150,6 +160,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
150
160
|
]
|
|
151
161
|
}),
|
|
152
162
|
copyToLocaleAvailable && /*#__PURE__*/ jsx(Dropdown.Item, {
|
|
163
|
+
disabled: disabled,
|
|
153
164
|
onClick: handleOpenCopyToLocale,
|
|
154
165
|
children: /*#__PURE__*/ jsx("div", {
|
|
155
166
|
className: clsx('byline-form-actions-item', document_actions_module.item),
|
|
@@ -157,12 +168,14 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
157
168
|
className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
|
|
158
169
|
children: /*#__PURE__*/ jsx("button", {
|
|
159
170
|
type: "button",
|
|
171
|
+
disabled: disabled,
|
|
160
172
|
children: t('documentActions.copyToLocaleMenuItem')
|
|
161
173
|
})
|
|
162
174
|
})
|
|
163
175
|
})
|
|
164
176
|
}),
|
|
165
177
|
deleteLocaleAvailable && /*#__PURE__*/ jsx(Dropdown.Item, {
|
|
178
|
+
disabled: disabled,
|
|
166
179
|
onClick: handleOpenDeleteLocale,
|
|
167
180
|
children: /*#__PURE__*/ jsx("div", {
|
|
168
181
|
className: clsx('byline-form-actions-item', document_actions_module.item),
|
|
@@ -170,12 +183,14 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
170
183
|
className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
|
|
171
184
|
children: /*#__PURE__*/ jsx("button", {
|
|
172
185
|
type: "button",
|
|
186
|
+
disabled: disabled,
|
|
173
187
|
children: t('documentActions.deleteLocale.menuItem')
|
|
174
188
|
})
|
|
175
189
|
})
|
|
176
190
|
})
|
|
177
191
|
}),
|
|
178
192
|
onDuplicate && /*#__PURE__*/ jsx(Dropdown.Item, {
|
|
193
|
+
disabled: disabled,
|
|
179
194
|
onClick: handleOpenDuplicate,
|
|
180
195
|
children: /*#__PURE__*/ jsx("div", {
|
|
181
196
|
className: clsx('byline-form-actions-item', document_actions_module.item),
|
|
@@ -183,6 +198,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
183
198
|
className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
|
|
184
199
|
children: /*#__PURE__*/ jsx("button", {
|
|
185
200
|
type: "button",
|
|
201
|
+
disabled: disabled,
|
|
186
202
|
children: t('common.actions.duplicate')
|
|
187
203
|
})
|
|
188
204
|
})
|
|
@@ -192,6 +208,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
192
208
|
children: [
|
|
193
209
|
/*#__PURE__*/ jsx(Dropdown.Separator, {}),
|
|
194
210
|
/*#__PURE__*/ jsx(Dropdown.Item, {
|
|
211
|
+
disabled: disabled,
|
|
195
212
|
onClick: ()=>{
|
|
196
213
|
setShowDeleteConfirm(true);
|
|
197
214
|
},
|
|
@@ -286,6 +303,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
286
303
|
minWidth: '80px'
|
|
287
304
|
},
|
|
288
305
|
intent: "danger",
|
|
306
|
+
disabled: disabled,
|
|
289
307
|
onClick: handleOnDelete,
|
|
290
308
|
children: t('common.actions.delete')
|
|
291
309
|
})
|
|
@@ -397,7 +415,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
397
415
|
onClick: ()=>{
|
|
398
416
|
if (!duplicateBusy) setShowDuplicateConfirm(false);
|
|
399
417
|
},
|
|
400
|
-
disabled: duplicateBusy,
|
|
418
|
+
disabled: disabled || duplicateBusy,
|
|
401
419
|
children: t('common.actions.cancel')
|
|
402
420
|
}),
|
|
403
421
|
/*#__PURE__*/ jsx(Button, {
|
|
@@ -407,7 +425,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
407
425
|
},
|
|
408
426
|
intent: "primary",
|
|
409
427
|
onClick: handleOnDuplicate,
|
|
410
|
-
disabled: duplicateBusy,
|
|
428
|
+
disabled: disabled || duplicateBusy,
|
|
411
429
|
children: duplicateBusy ? t('documentActions.duplicate.busyButton') : t('common.actions.duplicate')
|
|
412
430
|
})
|
|
413
431
|
]
|
|
@@ -499,7 +517,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
499
517
|
onValueChange: (value)=>{
|
|
500
518
|
if (null != value) setCopyTargetLocale(value);
|
|
501
519
|
},
|
|
502
|
-
disabled: copyToLocaleBusy
|
|
520
|
+
disabled: disabled || copyToLocaleBusy
|
|
503
521
|
})
|
|
504
522
|
]
|
|
505
523
|
}),
|
|
@@ -513,7 +531,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
513
531
|
name: "overwrite",
|
|
514
532
|
label: t('documentActions.copyToLocale.overwriteLabel'),
|
|
515
533
|
checked: copyOverwrite,
|
|
516
|
-
disabled: copyToLocaleBusy,
|
|
534
|
+
disabled: disabled || copyToLocaleBusy,
|
|
517
535
|
helpText: t('documentActions.copyToLocale.overwriteHelp'),
|
|
518
536
|
onCheckedChange: (value)=>{
|
|
519
537
|
setCopyOverwrite(true === value);
|
|
@@ -540,7 +558,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
540
558
|
onClick: ()=>{
|
|
541
559
|
if (!copyToLocaleBusy) setShowCopyToLocaleConfirm(false);
|
|
542
560
|
},
|
|
543
|
-
disabled: copyToLocaleBusy,
|
|
561
|
+
disabled: disabled || copyToLocaleBusy,
|
|
544
562
|
children: t('common.actions.cancel')
|
|
545
563
|
}),
|
|
546
564
|
/*#__PURE__*/ jsx(Button, {
|
|
@@ -550,7 +568,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
550
568
|
},
|
|
551
569
|
intent: "primary",
|
|
552
570
|
onClick: handleOnCopyToLocale,
|
|
553
|
-
disabled: copyToLocaleBusy || !copyTargetLocale,
|
|
571
|
+
disabled: disabled || copyToLocaleBusy || !copyTargetLocale,
|
|
554
572
|
children: copyToLocaleBusy ? t('documentActions.copyToLocale.busyButton') : t('documentActions.copyToLocale.confirmButton')
|
|
555
573
|
})
|
|
556
574
|
]
|
|
@@ -620,7 +638,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
620
638
|
onValueChange: (value)=>{
|
|
621
639
|
if (null != value) setDeleteTargetLocale(value);
|
|
622
640
|
},
|
|
623
|
-
disabled: deleteLocaleBusy
|
|
641
|
+
disabled: disabled || deleteLocaleBusy
|
|
624
642
|
})
|
|
625
643
|
]
|
|
626
644
|
}),
|
|
@@ -650,7 +668,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
650
668
|
onClick: ()=>{
|
|
651
669
|
if (!deleteLocaleBusy) setShowDeleteLocaleConfirm(false);
|
|
652
670
|
},
|
|
653
|
-
disabled: deleteLocaleBusy,
|
|
671
|
+
disabled: disabled || deleteLocaleBusy,
|
|
654
672
|
children: t('common.actions.cancel')
|
|
655
673
|
}),
|
|
656
674
|
/*#__PURE__*/ jsx(Button, {
|
|
@@ -660,7 +678,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
|
|
|
660
678
|
},
|
|
661
679
|
intent: "danger",
|
|
662
680
|
onClick: handleOnDeleteLocale,
|
|
663
|
-
disabled: deleteLocaleBusy || !deleteTargetLocale,
|
|
681
|
+
disabled: disabled || deleteLocaleBusy || !deleteTargetLocale,
|
|
664
682
|
children: deleteLocaleBusy ? t('documentActions.deleteLocale.busyButton') : t('documentActions.deleteLocale.confirmButton')
|
|
665
683
|
})
|
|
666
684
|
]
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
import { type ReactNode } from 'react';
|
|
9
9
|
import type { Field, FormAdminConfig, WorkflowStatus } from '@byline/core';
|
|
10
10
|
import type { DocumentPatch } from '@byline/core/patches';
|
|
11
|
-
import { type DocumentActionsLocaleOption } from './document-actions';
|
|
12
|
-
import { type ScheduledPublicationInfo, type SchedulePublicationInput } from './scheduled-publication-control';
|
|
13
|
-
import type { UseNavigationGuard } from './navigation-guard';
|
|
11
|
+
import { type DocumentActionsLocaleOption } from './document-actions.js';
|
|
12
|
+
import { type ScheduledPublicationInfo, type SchedulePublicationInput } from './scheduled-publication-control.js';
|
|
13
|
+
import type { UseNavigationGuard } from './navigation-guard.js';
|
|
14
14
|
/** Metadata about a previously published version that is still live. */
|
|
15
15
|
export interface PublishedVersionInfo {
|
|
16
16
|
id: string;
|
|
@@ -37,6 +37,15 @@ export interface SystemFieldsSubmitPayload {
|
|
|
37
37
|
}
|
|
38
38
|
/** Props shared by both the public FormRenderer and its internal FormContent component. */
|
|
39
39
|
export interface FormRendererProps {
|
|
40
|
+
mutationIssue?: 'stale' | 'reload' | 'lock' | 'unavailable' | 'committed' | null;
|
|
41
|
+
mutationsBlocked?: boolean;
|
|
42
|
+
observedRevision?: number;
|
|
43
|
+
onMutationError?: (error: unknown) => 'blocked' | 'committed' | null | void;
|
|
44
|
+
onTreeMutationCommitted?: (receipt: import('@byline/core').StructuralMutationReceipt) => void;
|
|
45
|
+
scheduledPublicationsNeedReconfirmation?: boolean;
|
|
46
|
+
scheduledPublicationsHref?: string;
|
|
47
|
+
/** Explicit discard action; defaults to a complete server reload. */
|
|
48
|
+
onReloadDocument?: () => void | Promise<void>;
|
|
40
49
|
mode: 'create' | 'edit';
|
|
41
50
|
fields: Field[];
|
|
42
51
|
onSubmit: (data: any) => void | Promise<void>;
|
|
@@ -24,7 +24,7 @@ import { computeStatusTransitions } from "./status-transitions.js";
|
|
|
24
24
|
import { TreePlacementWidget } from "./tree-placement-widget.js";
|
|
25
25
|
import { executeUploadsWithProgress } from "./upload-executor.js";
|
|
26
26
|
import { useFormLayout } from "./use-form-layout.js";
|
|
27
|
-
const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
|
|
27
|
+
const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRevision, onMutationError, onTreeMutationCommitted, scheduledPublicationsNeedReconfirmation = false, scheduledPublicationsHref, onReloadDocument, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
|
|
28
28
|
const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getDirtyBreakdown, getSystemPath, getSystemAvailableLocales, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
|
|
29
29
|
const { t } = useTranslation('byline-admin');
|
|
30
30
|
const [errors, setErrors] = useState(initialErrors);
|
|
@@ -41,6 +41,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
41
41
|
const [pendingSystemFieldsSubmit, setPendingSystemFieldsSubmit] = useState(null);
|
|
42
42
|
const [contentLocale, setContentLocale] = useState(initialLocale ?? defaultLocale);
|
|
43
43
|
const scheduling = useScheduledPublication({
|
|
44
|
+
disabled: mutationsBlocked,
|
|
44
45
|
schedule: scheduledPublication ?? null,
|
|
45
46
|
onSchedule: onSchedulePublication,
|
|
46
47
|
onConfirm: onConfirmScheduledPublication,
|
|
@@ -96,7 +97,27 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
96
97
|
}) : 'create' === mode ? t('forms.heading.create') : t('forms.heading.edit'));
|
|
97
98
|
const guardFromContext = useNavigationGuardAdapter();
|
|
98
99
|
const useGuard = useNavigationGuardProp ?? guardFromContext;
|
|
99
|
-
const
|
|
100
|
+
const [discarding, setDiscarding] = useState(false);
|
|
101
|
+
const [reloadFailed, setReloadFailed] = useState(false);
|
|
102
|
+
const warningRef = useRef(null);
|
|
103
|
+
const mutationBlockedRef = useRef(mutationsBlocked);
|
|
104
|
+
mutationBlockedRef.current = mutationsBlocked || discarding;
|
|
105
|
+
const guard = useGuard(hasChanges && !discarding);
|
|
106
|
+
useEffect(()=>{
|
|
107
|
+
if (mutationIssue) warningRef.current?.focus();
|
|
108
|
+
}, [
|
|
109
|
+
mutationIssue
|
|
110
|
+
]);
|
|
111
|
+
useEffect(()=>{
|
|
112
|
+
if (!discarding) return;
|
|
113
|
+
Promise.resolve().then(()=>onReloadDocument ? onReloadDocument() : window.location.reload()).catch(()=>{
|
|
114
|
+
setDiscarding(false);
|
|
115
|
+
setReloadFailed(true);
|
|
116
|
+
});
|
|
117
|
+
}, [
|
|
118
|
+
discarding,
|
|
119
|
+
onReloadDocument
|
|
120
|
+
]);
|
|
100
121
|
const currentStatus = initialData?.status;
|
|
101
122
|
const { primaryStatus, secondaryStatuses, isTerminal } = computeStatusTransitions(currentStatus, workflowStatuses, nextStatus);
|
|
102
123
|
useEffect(()=>subscribeErrors((newErrors)=>setErrors(newErrors)), [
|
|
@@ -113,6 +134,11 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
113
134
|
useEffect(()=>{
|
|
114
135
|
if (isBusy || !restoreFocusAfterBusyRef.current) return;
|
|
115
136
|
restoreFocusAfterBusyRef.current = false;
|
|
137
|
+
if (mutationIssue) {
|
|
138
|
+
warningRef.current?.focus();
|
|
139
|
+
focusBeforeBusyRef.current = null;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
116
142
|
const original = focusBeforeBusyRef.current;
|
|
117
143
|
focusBeforeBusyRef.current = null;
|
|
118
144
|
const originalCanReceiveFocus = original?.isConnected === true && !original.matches(':disabled, [aria-disabled="true"]');
|
|
@@ -121,7 +147,8 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
121
147
|
preventScroll: true
|
|
122
148
|
});
|
|
123
149
|
}, [
|
|
124
|
-
isBusy
|
|
150
|
+
isBusy,
|
|
151
|
+
mutationIssue
|
|
125
152
|
]);
|
|
126
153
|
const captureFocusBeforeBusy = useCallback(()=>{
|
|
127
154
|
if (null != focusBeforeBusyRef.current) return;
|
|
@@ -134,7 +161,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
134
161
|
if (onCancel && 'function' == typeof onCancel) onCancel();
|
|
135
162
|
};
|
|
136
163
|
const submitPayload = useCallback(async (payload)=>{
|
|
137
|
-
if ('function' != typeof onSubmit) return;
|
|
164
|
+
if (mutationBlockedRef.current || 'function' != typeof onSubmit) return;
|
|
138
165
|
if (submittingRef.current) return;
|
|
139
166
|
submittingRef.current = true;
|
|
140
167
|
captureFocusBeforeBusy();
|
|
@@ -152,6 +179,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
152
179
|
resetHasChanges
|
|
153
180
|
]);
|
|
154
181
|
const handleSubmit = (e)=>{
|
|
182
|
+
if (mutationBlockedRef.current) return void e.preventDefault();
|
|
155
183
|
e.preventDefault();
|
|
156
184
|
(async ()=>{
|
|
157
185
|
const hookErrors = await runFieldHooks(fields);
|
|
@@ -161,6 +189,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
161
189
|
...formErrors
|
|
162
190
|
];
|
|
163
191
|
if (allErrors.length > 0) return void console.error('Form validation failed:', allErrors);
|
|
192
|
+
if (mutationBlockedRef.current) return;
|
|
164
193
|
const pendingUploads = getPendingUploads();
|
|
165
194
|
if (pendingUploads.size > 0) {
|
|
166
195
|
captureFocusBeforeBusy();
|
|
@@ -307,6 +336,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
307
336
|
/*#__PURE__*/ jsx("div", {
|
|
308
337
|
className: clsx('byline-form-status-details', form_renderer_module["status-details"]),
|
|
309
338
|
children: /*#__PURE__*/ jsx(FormStatusDisplay, {
|
|
339
|
+
disabled: mutationsBlocked || discarding,
|
|
310
340
|
initialData: initialData,
|
|
311
341
|
workflowStatuses: workflowStatuses,
|
|
312
342
|
publishedVersion: publishedVersion,
|
|
@@ -332,7 +362,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
332
362
|
className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
|
|
333
363
|
size: "sm",
|
|
334
364
|
type: "submit",
|
|
335
|
-
disabled: false === hasChanges || isUploading || isSubmitting,
|
|
365
|
+
disabled: mutationsBlocked || discarding || false === hasChanges || isUploading || isSubmitting,
|
|
336
366
|
"aria-label": isSubmitting ? t('common.actions.save') : void 0,
|
|
337
367
|
children: isUploading ? t('forms.actions.uploading') : /*#__PURE__*/ jsxs("span", {
|
|
338
368
|
className: clsx('byline-form-save-content', form_renderer_module["save-content"]),
|
|
@@ -366,21 +396,27 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
366
396
|
size: "sm",
|
|
367
397
|
type: "button",
|
|
368
398
|
intent: isTerminal ? 'info' : 'success',
|
|
369
|
-
disabled: statusBusy,
|
|
399
|
+
disabled: mutationsBlocked || discarding || statusBusy,
|
|
370
400
|
onOptionSelect: async (value)=>{
|
|
401
|
+
if (mutationBlockedRef.current) return;
|
|
371
402
|
if (hasChanges) return void setShowUnsavedModal(true);
|
|
372
403
|
setStatusBusy(true);
|
|
373
404
|
try {
|
|
374
405
|
await onStatusChange(value);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
onMutationError?.(error);
|
|
375
408
|
} finally{
|
|
376
409
|
setStatusBusy(false);
|
|
377
410
|
}
|
|
378
411
|
},
|
|
379
412
|
onButtonClick: isTerminal ? void 0 : async ()=>{
|
|
413
|
+
if (mutationBlockedRef.current) return;
|
|
380
414
|
if (hasChanges) return void setShowUnsavedModal(true);
|
|
381
415
|
setStatusBusy(true);
|
|
382
416
|
try {
|
|
383
417
|
await onStatusChange(primaryStatus.name);
|
|
418
|
+
} catch (error) {
|
|
419
|
+
onMutationError?.(error);
|
|
384
420
|
} finally{
|
|
385
421
|
setStatusBusy(false);
|
|
386
422
|
}
|
|
@@ -389,6 +425,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
389
425
|
})
|
|
390
426
|
}),
|
|
391
427
|
/*#__PURE__*/ jsx(DocumentActions, {
|
|
428
|
+
disabled: mutationsBlocked || discarding,
|
|
392
429
|
publishedVersion: publishedVersion,
|
|
393
430
|
onUnpublish: onUnpublish,
|
|
394
431
|
onDelete: onDelete,
|
|
@@ -411,10 +448,57 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
411
448
|
})
|
|
412
449
|
]
|
|
413
450
|
}),
|
|
451
|
+
(mutationIssue || scheduledPublicationsNeedReconfirmation) && /*#__PURE__*/ jsxs("div", {
|
|
452
|
+
ref: warningRef,
|
|
453
|
+
tabIndex: -1,
|
|
454
|
+
role: "alert",
|
|
455
|
+
"aria-live": "assertive",
|
|
456
|
+
className: clsx('byline-document-concurrency', form_renderer_module.concurrency),
|
|
457
|
+
children: [
|
|
458
|
+
mutationIssue && /*#__PURE__*/ jsxs(Alert, {
|
|
459
|
+
intent: "warning",
|
|
460
|
+
icon: true,
|
|
461
|
+
close: false,
|
|
462
|
+
title: t(`documentConcurrency.${mutationIssue}Title`),
|
|
463
|
+
children: [
|
|
464
|
+
/*#__PURE__*/ jsx("p", {
|
|
465
|
+
children: t(`documentConcurrency.${mutationIssue}`)
|
|
466
|
+
}),
|
|
467
|
+
'committed' !== mutationIssue && /*#__PURE__*/ jsx(Button, {
|
|
468
|
+
type: "button",
|
|
469
|
+
disabled: discarding,
|
|
470
|
+
onClick: ()=>{
|
|
471
|
+
setReloadFailed(false);
|
|
472
|
+
setDiscarding(true);
|
|
473
|
+
},
|
|
474
|
+
children: t('documentConcurrency.reloadAction')
|
|
475
|
+
}),
|
|
476
|
+
reloadFailed && /*#__PURE__*/ jsx("p", {
|
|
477
|
+
children: t('documentConcurrency.reloadFailed')
|
|
478
|
+
})
|
|
479
|
+
]
|
|
480
|
+
}),
|
|
481
|
+
scheduledPublicationsNeedReconfirmation && /*#__PURE__*/ jsxs(Alert, {
|
|
482
|
+
intent: "warning",
|
|
483
|
+
icon: true,
|
|
484
|
+
close: false,
|
|
485
|
+
title: t('documentConcurrency.schedulesTitle'),
|
|
486
|
+
children: [
|
|
487
|
+
/*#__PURE__*/ jsx("p", {
|
|
488
|
+
children: t('documentConcurrency.schedules')
|
|
489
|
+
}),
|
|
490
|
+
scheduledPublicationsHref && /*#__PURE__*/ jsx("a", {
|
|
491
|
+
href: scheduledPublicationsHref,
|
|
492
|
+
children: t('documentConcurrency.reviewSchedules')
|
|
493
|
+
})
|
|
494
|
+
]
|
|
495
|
+
})
|
|
496
|
+
]
|
|
497
|
+
}),
|
|
414
498
|
/*#__PURE__*/ jsx(ScheduledPublicationNotice, {
|
|
415
499
|
state: scheduling.state,
|
|
416
500
|
timeZone: scheduling.timeZone,
|
|
417
|
-
busy: scheduling.busy,
|
|
501
|
+
busy: mutationsBlocked || discarding || scheduling.busy,
|
|
418
502
|
onConfirm: scheduling.confirm,
|
|
419
503
|
onReschedule: scheduling.openSchedule,
|
|
420
504
|
onCancel: scheduling.cancel
|
|
@@ -450,6 +534,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
450
534
|
className: clsx('byline-form-sidebar', form_renderer_module.sidebar),
|
|
451
535
|
children: [
|
|
452
536
|
showPath && (useAsPath || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
|
|
537
|
+
disabled: mutationsBlocked || discarding,
|
|
453
538
|
useAsPath: useAsPath,
|
|
454
539
|
collectionPath: collectionPath ?? '',
|
|
455
540
|
defaultLocale: defaultLocale,
|
|
@@ -459,11 +544,16 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
459
544
|
sourceLocked: pathSourceLocked
|
|
460
545
|
}),
|
|
461
546
|
tree && 'edit' === mode && 'string' == typeof initialData?.id && /*#__PURE__*/ jsx(TreePlacementWidget, {
|
|
547
|
+
disabled: mutationsBlocked || discarding,
|
|
548
|
+
onMutationError: onMutationError,
|
|
549
|
+
onCommitted: onTreeMutationCommitted,
|
|
550
|
+
expectedRevision: observedRevision ?? initialData.revision,
|
|
462
551
|
collectionPath: collectionPath ?? '',
|
|
463
552
|
documentId: initialData.id,
|
|
464
553
|
useAsTitle: useAsTitle
|
|
465
554
|
}),
|
|
466
555
|
advertiseLocales && /*#__PURE__*/ jsx(AvailableLocalesWidget, {
|
|
556
|
+
disabled: mutationsBlocked || discarding,
|
|
467
557
|
contentLocales: contentLocales ?? [],
|
|
468
558
|
availableVersionLocales: initialData?._availableVersionLocales ?? []
|
|
469
559
|
}),
|
|
@@ -475,7 +565,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
|
|
|
475
565
|
showUnsavedModal && /*#__PURE__*/ jsx(UnsavedChangesModal, {
|
|
476
566
|
onClose: ()=>setShowUnsavedModal(false)
|
|
477
567
|
}),
|
|
478
|
-
null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(SystemFieldsConfirmModal, {
|
|
568
|
+
!mutationsBlocked && !discarding && null != pendingSystemFieldsSubmit && /*#__PURE__*/ jsx(SystemFieldsConfirmModal, {
|
|
479
569
|
contentDirty: pendingSystemFieldsSubmit.contentDirty,
|
|
480
570
|
pathDirty: pendingSystemFieldsSubmit.pathDirty,
|
|
481
571
|
availableLocalesDirty: pendingSystemFieldsSubmit.availableLocalesDirty,
|
|
@@ -40,6 +40,7 @@ const form_renderer_module = {
|
|
|
40
40
|
actionsComboButton: "actions-combo-button-XvqHCC",
|
|
41
41
|
"actions-combo-trigger": "actions-combo-trigger-bmXUzc",
|
|
42
42
|
actionsComboTrigger: "actions-combo-trigger-bmXUzc",
|
|
43
|
+
concurrency: "concurrency-UT6bX5",
|
|
43
44
|
layout: "layout-WTbLYr",
|
|
44
45
|
content: "content-_P5cdJ",
|
|
45
46
|
sidebar: "sidebar-WsxX88",
|
|
@@ -189,6 +189,18 @@
|
|
|
189
189
|
min-height: 28px;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
:is(.concurrency-UT6bX5, .byline-document-concurrency) {
|
|
193
|
+
margin-top: var(--spacing-16);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
:is(.concurrency-UT6bX5 button, .byline-document-concurrency button) {
|
|
197
|
+
margin-top: var(--spacing-12);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
:is(.concurrency-UT6bX5 ~ .layout-WTbLYr, .byline-document-concurrency ~ .byline-form-layout) {
|
|
201
|
+
padding-top: var(--spacing-16);
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
:is(.layout-WTbLYr, .byline-form-layout) {
|
|
193
205
|
gap: var(--spacing-12);
|
|
194
206
|
padding-top: var(--spacing-32);
|