@byline/admin 4.18.0 → 5.0.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-types.d.ts +5 -2
- package/dist/forms/available-locales-widget.d.ts +2 -1
- package/dist/forms/available-locales-widget.js +4 -2
- package/dist/forms/document-actions.d.ts +2 -1
- package/dist/forms/document-actions.js +32 -14
- package/dist/forms/form-renderer.d.ts +9 -0
- package/dist/forms/form-renderer.js +338 -191
- package/dist/forms/form-renderer.module.js +11 -0
- package/dist/forms/form-renderer_module.css +46 -0
- package/dist/forms/form-status-display.d.ts +2 -1
- 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/package.json +7 -7
- 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 +231 -8
- package/src/forms/form-renderer.module.css +62 -0
- package/src/forms/form-renderer.tsx +393 -200
- 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
|
@@ -23,12 +23,14 @@ import type { PublishedVersionInfo } from './form-renderer'
|
|
|
23
23
|
* Unpublish action when a previously-published version is still live.
|
|
24
24
|
*/
|
|
25
25
|
export const FormStatusDisplay = ({
|
|
26
|
+
disabled = false,
|
|
26
27
|
initialData,
|
|
27
28
|
workflowStatuses,
|
|
28
29
|
publishedVersion,
|
|
29
30
|
onUnpublish,
|
|
30
31
|
afterStatusCells,
|
|
31
32
|
}: {
|
|
33
|
+
disabled?: boolean
|
|
32
34
|
initialData?: Record<string, any>
|
|
33
35
|
workflowStatuses?: WorkflowStatus[]
|
|
34
36
|
publishedVersion?: PublishedVersionInfo | null
|
|
@@ -106,7 +108,10 @@ export const FormStatusDisplay = ({
|
|
|
106
108
|
{' '}
|
|
107
109
|
<button
|
|
108
110
|
type="button"
|
|
109
|
-
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
onClick={() => {
|
|
113
|
+
if (!disabled) void onUnpublish().catch(() => {})
|
|
114
|
+
}}
|
|
110
115
|
className={cx('byline-form-status-unpublish', styles['status-unpublish'])}
|
|
111
116
|
>
|
|
112
117
|
{t('common.actions.unpublish')}
|
|
@@ -31,6 +31,7 @@ function coerceToString(value: unknown): string {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface PathWidgetProps {
|
|
34
|
+
disabled?: boolean
|
|
34
35
|
/** The collection's `useAsPath` source field name, when configured. */
|
|
35
36
|
useAsPath: string | undefined
|
|
36
37
|
/** Collection path, forwarded to the slugifier as context. */
|
|
@@ -91,6 +92,7 @@ export const PathWidget = ({
|
|
|
91
92
|
mode,
|
|
92
93
|
slugifier,
|
|
93
94
|
sourceLocked = false,
|
|
95
|
+
disabled = false,
|
|
94
96
|
}: PathWidgetProps) => {
|
|
95
97
|
const { setSystemPath } = useFormContext()
|
|
96
98
|
const { t } = useTranslation('byline-admin')
|
|
@@ -125,18 +127,19 @@ export const PathWidget = ({
|
|
|
125
127
|
|
|
126
128
|
const handleChange = useCallback(
|
|
127
129
|
(next: string) => {
|
|
130
|
+
if (disabled) return
|
|
128
131
|
// Empty string clears the override — server falls back to derive
|
|
129
132
|
// (create) or sticky (update).
|
|
130
133
|
setSystemPath(next.length === 0 ? null : next)
|
|
131
134
|
},
|
|
132
|
-
[setSystemPath]
|
|
135
|
+
[disabled, setSystemPath]
|
|
133
136
|
)
|
|
134
137
|
|
|
135
138
|
const handleRegenerate = useCallback(() => {
|
|
136
|
-
if (livePreview.length > 0) {
|
|
139
|
+
if (!disabled && livePreview.length > 0) {
|
|
137
140
|
setSystemPath(livePreview)
|
|
138
141
|
}
|
|
139
|
-
}, [livePreview, setSystemPath])
|
|
142
|
+
}, [disabled, livePreview, setSystemPath])
|
|
140
143
|
|
|
141
144
|
// Validate live: if the typed value differs from its slugified form,
|
|
142
145
|
// surface an inline hint without blocking input (mirrors the previous
|
|
@@ -184,6 +187,7 @@ export const PathWidget = ({
|
|
|
184
187
|
type="button"
|
|
185
188
|
onClick={handleRegenerate}
|
|
186
189
|
className={cx('byline-form-path-regenerate', styles.regenerate)}
|
|
190
|
+
disabled={disabled}
|
|
187
191
|
aria-label={t('pathWidget.regenerateAriaLabel', { field: useAsPath })}
|
|
188
192
|
>
|
|
189
193
|
{t('pathWidget.regenerateButton', { field: useAsPath })}
|
|
@@ -191,6 +195,7 @@ export const PathWidget = ({
|
|
|
191
195
|
)}
|
|
192
196
|
</div>
|
|
193
197
|
<Input
|
|
198
|
+
disabled={disabled}
|
|
194
199
|
id="system-path"
|
|
195
200
|
name="__systemPath__"
|
|
196
201
|
value={inputValue}
|
|
@@ -110,6 +110,7 @@ function seedScheduleInstant(schedule: ScheduledPublicationInfo | null): Date {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export interface UseScheduledPublicationArgs {
|
|
113
|
+
disabled?: boolean
|
|
113
114
|
schedule: ScheduledPublicationInfo | null
|
|
114
115
|
onSchedule?: (input: SchedulePublicationInput) => Promise<void>
|
|
115
116
|
onConfirm?: () => Promise<void>
|
|
@@ -132,6 +133,7 @@ export interface UseScheduledPublicationReturn {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
export function useScheduledPublication({
|
|
136
|
+
disabled = false,
|
|
135
137
|
schedule,
|
|
136
138
|
onSchedule,
|
|
137
139
|
onConfirm,
|
|
@@ -171,15 +173,16 @@ export function useScheduledPublication({
|
|
|
171
173
|
// to be resolved before any of these operations can name a version. Cancel
|
|
172
174
|
// is exempt: withdrawing a schedule says nothing about content.
|
|
173
175
|
const openSchedule = useCallback(() => {
|
|
176
|
+
if (disabled) return
|
|
174
177
|
if (hasUnsavedChanges) {
|
|
175
178
|
onUnsavedChanges()
|
|
176
179
|
return
|
|
177
180
|
}
|
|
178
181
|
setShowSchedule(true)
|
|
179
|
-
}, [hasUnsavedChanges, onUnsavedChanges])
|
|
182
|
+
}, [disabled, hasUnsavedChanges, onUnsavedChanges])
|
|
180
183
|
|
|
181
184
|
const confirm = useCallback(async () => {
|
|
182
|
-
if (onConfirm == null) return
|
|
185
|
+
if (disabled || onConfirm == null) return
|
|
183
186
|
if (hasUnsavedChanges) {
|
|
184
187
|
onUnsavedChanges()
|
|
185
188
|
return
|
|
@@ -187,37 +190,43 @@ export function useScheduledPublication({
|
|
|
187
190
|
setBusy(true)
|
|
188
191
|
try {
|
|
189
192
|
await onConfirm()
|
|
193
|
+
} catch {
|
|
194
|
+
// The host reports the failure; keep this view open.
|
|
190
195
|
} finally {
|
|
191
196
|
setBusy(false)
|
|
192
197
|
}
|
|
193
|
-
}, [onConfirm, hasUnsavedChanges, onUnsavedChanges])
|
|
198
|
+
}, [disabled, onConfirm, hasUnsavedChanges, onUnsavedChanges])
|
|
194
199
|
|
|
195
200
|
const cancel = useCallback(async () => {
|
|
196
|
-
if (onCancel == null) return
|
|
201
|
+
if (disabled || onCancel == null) return
|
|
197
202
|
setBusy(true)
|
|
198
203
|
try {
|
|
199
204
|
await onCancel()
|
|
205
|
+
} catch {
|
|
206
|
+
// The host reports the failure; keep this view open.
|
|
200
207
|
} finally {
|
|
201
208
|
setBusy(false)
|
|
202
209
|
}
|
|
203
|
-
}, [onCancel])
|
|
210
|
+
}, [disabled, onCancel])
|
|
204
211
|
|
|
205
212
|
const modal = showSchedule ? (
|
|
206
213
|
<ScheduleModal
|
|
207
214
|
schedule={schedule}
|
|
208
215
|
timeZone={timeZone}
|
|
209
216
|
onSubmit={async (input) => {
|
|
210
|
-
if (onSchedule == null) return
|
|
217
|
+
if (disabled || onSchedule == null) return
|
|
211
218
|
setBusy(true)
|
|
212
219
|
try {
|
|
213
220
|
await onSchedule(input)
|
|
214
221
|
setShowSchedule(false)
|
|
222
|
+
} catch {
|
|
223
|
+
// The host reports the failure; keep this view open.
|
|
215
224
|
} finally {
|
|
216
225
|
setBusy(false)
|
|
217
226
|
}
|
|
218
227
|
}}
|
|
219
228
|
onDismiss={() => setShowSchedule(false)}
|
|
220
|
-
busy={busy}
|
|
229
|
+
busy={disabled || busy}
|
|
221
230
|
/>
|
|
222
231
|
) : null
|
|
223
232
|
|
|
@@ -20,6 +20,10 @@ import { RelationPicker } from '../fields/relation/relation-picker.js'
|
|
|
20
20
|
import styles from './tree-placement-widget.module.css'
|
|
21
21
|
|
|
22
22
|
export interface TreePlacementWidgetProps {
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
onMutationError?: (error: unknown) => 'blocked' | 'committed' | null | void
|
|
25
|
+
onCommitted?: (receipt: import('@byline/core').StructuralMutationReceipt) => void
|
|
26
|
+
expectedRevision: number
|
|
23
27
|
/** The collection path (`tree: true`). */
|
|
24
28
|
collectionPath: string
|
|
25
29
|
/** The logical id of the document being edited. */
|
|
@@ -44,6 +48,10 @@ export interface TreePlacementWidgetProps {
|
|
|
44
48
|
*/
|
|
45
49
|
export const TreePlacementWidget = ({
|
|
46
50
|
collectionPath,
|
|
51
|
+
disabled = false,
|
|
52
|
+
onMutationError,
|
|
53
|
+
onCommitted,
|
|
54
|
+
expectedRevision,
|
|
47
55
|
documentId,
|
|
48
56
|
useAsTitle,
|
|
49
57
|
}: TreePlacementWidgetProps) => {
|
|
@@ -99,7 +107,7 @@ export const TreePlacementWidget = ({
|
|
|
99
107
|
// placement (including making the node a root) leaves it placed.
|
|
100
108
|
const place = useCallback(
|
|
101
109
|
async (parentDocumentId: string | null, optimistic: { id: string; title: string } | null) => {
|
|
102
|
-
if (placeTreeNode == null || busy) return
|
|
110
|
+
if (disabled || placeTreeNode == null || busy) return
|
|
103
111
|
const previousParent = parent
|
|
104
112
|
const previousPlaced = placed
|
|
105
113
|
setError(null)
|
|
@@ -107,8 +115,15 @@ export const TreePlacementWidget = ({
|
|
|
107
115
|
setParent(optimistic)
|
|
108
116
|
setPlaced(true)
|
|
109
117
|
try {
|
|
110
|
-
await placeTreeNode({
|
|
111
|
-
|
|
118
|
+
const result = await placeTreeNode({
|
|
119
|
+
expectedRevision,
|
|
120
|
+
collection: collectionPath,
|
|
121
|
+
documentId,
|
|
122
|
+
parentDocumentId,
|
|
123
|
+
})
|
|
124
|
+
onCommitted?.(result)
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (onMutationError?.(error) === 'committed') return
|
|
112
127
|
setParent(previousParent)
|
|
113
128
|
setPlaced(previousPlaced)
|
|
114
129
|
setError(t('treeWidget.error'))
|
|
@@ -116,7 +131,19 @@ export const TreePlacementWidget = ({
|
|
|
116
131
|
setBusy(false)
|
|
117
132
|
}
|
|
118
133
|
},
|
|
119
|
-
[
|
|
134
|
+
[
|
|
135
|
+
disabled,
|
|
136
|
+
onMutationError,
|
|
137
|
+
onCommitted,
|
|
138
|
+
expectedRevision,
|
|
139
|
+
placeTreeNode,
|
|
140
|
+
busy,
|
|
141
|
+
parent,
|
|
142
|
+
placed,
|
|
143
|
+
collectionPath,
|
|
144
|
+
documentId,
|
|
145
|
+
t,
|
|
146
|
+
]
|
|
120
147
|
)
|
|
121
148
|
|
|
122
149
|
const handlePick = useCallback(
|
|
@@ -156,7 +183,7 @@ export const TreePlacementWidget = ({
|
|
|
156
183
|
size="xs"
|
|
157
184
|
variant="outlined"
|
|
158
185
|
intent="noeffect"
|
|
159
|
-
disabled={loading || busy}
|
|
186
|
+
disabled={disabled || loading || busy}
|
|
160
187
|
onClick={() => setPickerOpen(true)}
|
|
161
188
|
>
|
|
162
189
|
{t('treeWidget.choose')}
|
|
@@ -165,7 +192,7 @@ export const TreePlacementWidget = ({
|
|
|
165
192
|
<button
|
|
166
193
|
type="button"
|
|
167
194
|
className={cx('byline-form-tree-link', styles.link)}
|
|
168
|
-
disabled={loading || busy}
|
|
195
|
+
disabled={disabled || loading || busy}
|
|
169
196
|
onClick={() => place(null, null)}
|
|
170
197
|
>
|
|
171
198
|
{t('treeWidget.addToTree')}
|
|
@@ -175,7 +202,7 @@ export const TreePlacementWidget = ({
|
|
|
175
202
|
<button
|
|
176
203
|
type="button"
|
|
177
204
|
className={cx('byline-form-tree-link', styles.link)}
|
|
178
|
-
disabled={busy}
|
|
205
|
+
disabled={disabled || busy}
|
|
179
206
|
onClick={() => place(null, null)}
|
|
180
207
|
>
|
|
181
208
|
{t('treeWidget.makeRoot')}
|