@byline/admin 6.2.0 → 6.3.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/checkbox/checkbox-field.js +3 -1
- package/dist/fields/code/code-field.js +2 -1
- package/dist/fields/datetime/datetime-field.js +4 -2
- package/dist/fields/field-renderer.js +2 -1
- package/dist/fields/field-services-types.d.ts +23 -0
- package/dist/fields/file/file-field.js +3 -2
- package/dist/fields/group/group-field.js +3 -1
- package/dist/fields/image/image-field.js +3 -2
- package/dist/fields/numerical/numerical-field.js +2 -1
- package/dist/fields/relation/relation-field.js +3 -2
- package/dist/fields/relation/relation-many-field.js +3 -2
- package/dist/fields/relation/relation-picker.js +38 -7
- package/dist/fields/relation/relation-picker.module.js +3 -0
- package/dist/fields/relation/relation-picker_module.css +20 -0
- package/dist/fields/select/select-field.js +3 -2
- package/dist/fields/text/text-field.js +2 -1
- package/dist/fields/text-area/text-area-field.js +2 -1
- package/dist/forms/form-context.js +4 -1
- package/dist/forms/form-dom-scope.d.ts +34 -0
- package/dist/forms/form-dom-scope.js +20 -0
- package/dist/forms/form-layout-styles.test.node.d.ts +8 -0
- package/dist/forms/form-layout.d.ts +39 -0
- package/dist/forms/form-layout.js +102 -0
- package/dist/forms/form-page-chrome.d.ts +110 -0
- package/dist/forms/form-page-chrome.js +253 -0
- package/dist/forms/form-renderer.d.ts +2 -18
- package/dist/forms/form-renderer.js +105 -423
- package/dist/forms/form-renderer.module.js +2 -0
- package/dist/forms/form-renderer_module.css +13 -0
- package/dist/forms/use-form-submission.d.ts +74 -0
- package/dist/forms/use-form-submission.js +166 -0
- package/dist/forms/use-form-tabs.d.ts +29 -0
- package/dist/forms/use-form-tabs.js +45 -0
- package/dist/presentation/tabs.js +2 -1
- package/dist/react.d.ts +1 -1
- package/package.json +7 -7
- package/src/fields/checkbox/checkbox-field.tsx +3 -1
- package/src/fields/code/code-field.tsx +2 -1
- package/src/fields/datetime/datetime-field.tsx +4 -2
- package/src/fields/direct-widget-dom-ids.test.tsx +142 -0
- package/src/fields/field-dom-ids.test.tsx +293 -0
- package/src/fields/field-renderer.tsx +2 -1
- package/src/fields/field-services-types.ts +25 -0
- package/src/fields/file/file-field.tsx +3 -2
- package/src/fields/group/group-field.tsx +3 -1
- package/src/fields/image/image-field.tsx +3 -2
- package/src/fields/numerical/numerical-field.tsx +2 -1
- package/src/fields/relation/relation-field.tsx +3 -2
- package/src/fields/relation/relation-many-field.tsx +3 -2
- package/src/fields/relation/relation-picker-create.test.tsx +203 -0
- package/src/fields/relation/relation-picker-refresh.test.tsx +325 -0
- package/src/fields/relation/relation-picker.module.css +32 -0
- package/src/fields/relation/relation-picker.tsx +59 -1
- package/src/fields/select/select-field.tsx +3 -2
- package/src/fields/text/text-field.tsx +2 -1
- package/src/fields/text-area/text-area-field.tsx +2 -1
- package/src/forms/form-context.tsx +2 -1
- package/src/forms/form-dom-scope.test.tsx +213 -0
- package/src/forms/form-dom-scope.tsx +70 -0
- package/src/forms/form-layout-styles.test.node.ts +32 -0
- package/src/forms/form-layout.test.tsx +276 -0
- package/src/forms/form-layout.tsx +204 -0
- package/src/forms/form-page-chrome.tsx +460 -0
- package/src/forms/form-renderer-submit.test.tsx +40 -0
- package/src/forms/form-renderer-tab-persistence.test.tsx +145 -0
- package/src/forms/form-renderer.module.css +18 -0
- package/src/forms/form-renderer.tsx +126 -600
- package/src/forms/form-submit-isolation.test.tsx +247 -0
- package/src/forms/use-form-submission.test.tsx +564 -0
- package/src/forms/use-form-submission.ts +293 -0
- package/src/forms/use-form-tabs.ts +89 -0
- package/src/presentation/tab-dom-ids.test.tsx +153 -0
- package/src/presentation/tabs.test.tsx +49 -2
- package/src/presentation/tabs.tsx +11 -1
- package/src/react.ts +2 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment as external_react_Fragment } from "react";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { sliceFieldAdmin } from "../fields/field-admin.js";
|
|
6
|
+
import { FieldRenderer } from "../fields/field-renderer.js";
|
|
7
|
+
import { AdminGroup } from "../presentation/group.js";
|
|
8
|
+
import { AdminRow } from "../presentation/row.js";
|
|
9
|
+
import { AdminTabs, tabPanelId, tabTriggerId } from "../presentation/tabs.js";
|
|
10
|
+
import { useFormDomScope } from "./form-dom-scope.js";
|
|
11
|
+
import form_renderer_module from "./form-renderer.module.js";
|
|
12
|
+
import { useFormLayout } from "./use-form-layout.js";
|
|
13
|
+
import { useFormTabs } from "./use-form-tabs.js";
|
|
14
|
+
const BOTH_REGIONS = [
|
|
15
|
+
'main',
|
|
16
|
+
'sidebar'
|
|
17
|
+
];
|
|
18
|
+
const FormLayout = ({ fields, adminConfig, regions = BOTH_REGIONS, variant = 'split', activeLocale, initialData, activeTabBySet, onTabChange, sidebarSlot })=>{
|
|
19
|
+
const { fieldByName, tabSetByName, rowByName, groupByName, layout, fieldToTabPath } = useFormLayout(adminConfig, fields);
|
|
20
|
+
const tabs = useFormTabs(fieldToTabPath, activeTabBySet);
|
|
21
|
+
const domScope = useFormDomScope();
|
|
22
|
+
const renderField = (fieldName)=>{
|
|
23
|
+
const field = fieldByName.get(fieldName);
|
|
24
|
+
if (!field) return null;
|
|
25
|
+
return /*#__PURE__*/ jsx(FieldRenderer, {
|
|
26
|
+
field: field,
|
|
27
|
+
defaultValue: initialData?.fields?.[field.name],
|
|
28
|
+
contentLocale: activeLocale,
|
|
29
|
+
components: adminConfig?.fields?.[field.name]?.components,
|
|
30
|
+
editor: adminConfig?.fields?.[field.name]?.editor,
|
|
31
|
+
fieldAdmin: sliceFieldAdmin(adminConfig?.fields, field.name)
|
|
32
|
+
}, field.name);
|
|
33
|
+
};
|
|
34
|
+
const renderItem = (name)=>{
|
|
35
|
+
const tabSet = tabSetByName.get(name);
|
|
36
|
+
if (tabSet) return renderTabSet(tabSet);
|
|
37
|
+
const group = groupByName.get(name);
|
|
38
|
+
if (group) return renderGroup(group);
|
|
39
|
+
const row = rowByName.get(name);
|
|
40
|
+
if (row) return renderRow(row);
|
|
41
|
+
return renderField(name);
|
|
42
|
+
};
|
|
43
|
+
const renderRow = (row)=>/*#__PURE__*/ jsx(AdminRow, {
|
|
44
|
+
children: row.fields.map((name)=>renderField(name))
|
|
45
|
+
}, `row:${row.name}`);
|
|
46
|
+
const renderGroup = (group)=>/*#__PURE__*/ jsx(AdminGroup, {
|
|
47
|
+
label: group.label,
|
|
48
|
+
children: group.fields.map((name)=>renderItem(name))
|
|
49
|
+
}, `group:${group.name}`);
|
|
50
|
+
const renderTabSet = (set)=>{
|
|
51
|
+
const { visibleTabs, activeTabName, activeTab, errorCounts } = tabs.resolve(set);
|
|
52
|
+
const idBase = `${domScope}tabset:${set.name}`;
|
|
53
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
54
|
+
className: clsx('byline-form-tabset', form_renderer_module.tabset),
|
|
55
|
+
children: [
|
|
56
|
+
visibleTabs.length > 0 && /*#__PURE__*/ jsx(AdminTabs, {
|
|
57
|
+
idBase: idBase,
|
|
58
|
+
tabs: visibleTabs,
|
|
59
|
+
activeTab: activeTabName,
|
|
60
|
+
onChange: (tabName)=>onTabChange(set.name, tabName),
|
|
61
|
+
errorCounts: errorCounts,
|
|
62
|
+
className: clsx('byline-form-tabset-tabs', form_renderer_module["tabset-tabs"])
|
|
63
|
+
}),
|
|
64
|
+
activeTab && /*#__PURE__*/ jsx("div", {
|
|
65
|
+
role: "tabpanel",
|
|
66
|
+
id: tabPanelId(idBase, activeTab.name),
|
|
67
|
+
"aria-labelledby": tabTriggerId(idBase, activeTab.name),
|
|
68
|
+
className: clsx('byline-form-tabset-fields', form_renderer_module["tabset-fields"]),
|
|
69
|
+
children: activeTab.fields.map((name)=>renderItem(name))
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
}, idBase);
|
|
73
|
+
};
|
|
74
|
+
const sidebarNames = layout.sidebar ?? [];
|
|
75
|
+
const sidebarContents = /*#__PURE__*/ jsxs(Fragment, {
|
|
76
|
+
children: [
|
|
77
|
+
sidebarSlot,
|
|
78
|
+
sidebarNames.map((name)=>renderItem(name))
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
const renderRegion = (region)=>'main' === region ? /*#__PURE__*/ jsx("div", {
|
|
82
|
+
className: clsx('byline-form-content', form_renderer_module.content),
|
|
83
|
+
children: layout.main.map((name)=>renderItem(name))
|
|
84
|
+
}, "main") : /*#__PURE__*/ jsx("div", {
|
|
85
|
+
className: clsx('byline-form-sidebar', form_renderer_module.sidebar),
|
|
86
|
+
children: sidebarContents
|
|
87
|
+
}, "sidebar");
|
|
88
|
+
if ('stacked' === variant) return /*#__PURE__*/ jsx("div", {
|
|
89
|
+
className: clsx('byline-form-layout-stacked', form_renderer_module["layout-stacked"]),
|
|
90
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
91
|
+
className: clsx('byline-form-content', form_renderer_module.content),
|
|
92
|
+
children: regions.map((region)=>/*#__PURE__*/ jsx(external_react_Fragment, {
|
|
93
|
+
children: 'main' === region ? layout.main.map((name)=>renderItem(name)) : sidebarContents
|
|
94
|
+
}, region))
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
return /*#__PURE__*/ jsx("div", {
|
|
98
|
+
className: clsx('byline-form-layout', form_renderer_module.layout),
|
|
99
|
+
children: regions.map((region)=>renderRegion(region))
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
export { FormLayout };
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
* Page-level chrome for the full-page document editor: the parts that frame a
|
|
10
|
+
* form rather than render it. The embedded creation view renders none of this.
|
|
11
|
+
*
|
|
12
|
+
* Each component takes only the props it needs — deliberately not a
|
|
13
|
+
* `FormRendererProps` pass-through, so what a region actually depends on stays
|
|
14
|
+
* visible at its call site.
|
|
15
|
+
*/
|
|
16
|
+
import type { ReactNode, RefObject } from 'react';
|
|
17
|
+
import type { SlugifierFn, StructuralMutationReceipt, WorkflowStatus } from '@byline/core';
|
|
18
|
+
import { type DocumentActionsLocaleOption } from './document-actions.js';
|
|
19
|
+
import { type UseScheduledPublicationReturn } from './scheduled-publication-control.js';
|
|
20
|
+
import type { PublishedVersionInfo } from './form-renderer.js';
|
|
21
|
+
import type { StatusTransitions } from './status-transitions.js';
|
|
22
|
+
export interface FormHeadingRowProps {
|
|
23
|
+
heading: ReactNode;
|
|
24
|
+
/** Host-supplied controls rendered beside the heading (locale switcher, …). */
|
|
25
|
+
headerSlot?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
export declare const FormHeadingRow: ({ heading, headerSlot }: FormHeadingRowProps) => ReactNode;
|
|
28
|
+
export interface FormStatusBarProps {
|
|
29
|
+
/** Mutations blocked or a discard in flight. */
|
|
30
|
+
disabled: boolean;
|
|
31
|
+
initialData?: any;
|
|
32
|
+
workflowStatuses?: WorkflowStatus[];
|
|
33
|
+
publishedVersion?: PublishedVersionInfo | null;
|
|
34
|
+
scheduling: UseScheduledPublicationReturn;
|
|
35
|
+
hasChanges: boolean;
|
|
36
|
+
isUploading: boolean;
|
|
37
|
+
isSubmitting: boolean;
|
|
38
|
+
onCancel: () => void;
|
|
39
|
+
/** Status transitions, already computed for the current workflow position. */
|
|
40
|
+
transitions: StatusTransitions;
|
|
41
|
+
statusBusy: boolean;
|
|
42
|
+
onStatusBusyChange: (busy: boolean) => void;
|
|
43
|
+
onStatusChange?: (nextStatus: string) => Promise<void>;
|
|
44
|
+
onMutationError?: (error: unknown) => 'blocked' | 'committed' | null | void;
|
|
45
|
+
/** Read synchronously at click time, not at render time. */
|
|
46
|
+
isBlocked: () => boolean;
|
|
47
|
+
/** A guarded action was attempted while the form is dirty. */
|
|
48
|
+
onUnsavedChanges: () => void;
|
|
49
|
+
onUnpublish?: () => Promise<void>;
|
|
50
|
+
onDelete?: () => Promise<void>;
|
|
51
|
+
onDuplicate?: () => Promise<void>;
|
|
52
|
+
onCopyToLocale?: (args: {
|
|
53
|
+
targetLocale: string;
|
|
54
|
+
overwrite: boolean;
|
|
55
|
+
}) => Promise<void>;
|
|
56
|
+
onDeleteLocale?: (args: {
|
|
57
|
+
targetLocale: string;
|
|
58
|
+
}) => Promise<void>;
|
|
59
|
+
useAsTitle?: string;
|
|
60
|
+
contentLocale: string;
|
|
61
|
+
contentLocales?: ReadonlyArray<DocumentActionsLocaleOption>;
|
|
62
|
+
defaultLocale: string;
|
|
63
|
+
}
|
|
64
|
+
export declare const FormStatusBar: ({ disabled, initialData, workflowStatuses, publishedVersion, scheduling, hasChanges, isUploading, isSubmitting, onCancel, transitions, statusBusy, onStatusBusyChange, onStatusChange, onMutationError, isBlocked, onUnsavedChanges, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, useAsTitle, contentLocale, contentLocales, defaultLocale, }: FormStatusBarProps) => ReactNode;
|
|
65
|
+
export interface FormConcurrencyNoticesProps {
|
|
66
|
+
/** Mutations blocked or a discard in flight. */
|
|
67
|
+
disabled: boolean;
|
|
68
|
+
mutationIssue?: 'stale' | 'reload' | 'lock' | 'unavailable' | 'committed' | null;
|
|
69
|
+
scheduledPublicationsNeedReconfirmation?: boolean;
|
|
70
|
+
scheduledPublicationsHref?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Focus target when a mutation issue appears. The ref belongs to the form
|
|
73
|
+
* component, which restores focus after a save; this region only attaches it.
|
|
74
|
+
*/
|
|
75
|
+
warningRef: RefObject<HTMLDivElement | null>;
|
|
76
|
+
discarding: boolean;
|
|
77
|
+
reloadFailed: boolean;
|
|
78
|
+
onDiscardRequested: () => void;
|
|
79
|
+
scheduling: UseScheduledPublicationReturn;
|
|
80
|
+
restoreWarnings?: string[];
|
|
81
|
+
}
|
|
82
|
+
export declare const FormConcurrencyNotices: ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings, }: FormConcurrencyNoticesProps) => ReactNode;
|
|
83
|
+
export interface FormSidebarWidgetsProps {
|
|
84
|
+
/** Mutations blocked or a discard in flight. */
|
|
85
|
+
disabled: boolean;
|
|
86
|
+
mode: 'create' | 'edit';
|
|
87
|
+
initialData?: any;
|
|
88
|
+
collectionPath?: string;
|
|
89
|
+
defaultLocale: string;
|
|
90
|
+
contentLocale: string;
|
|
91
|
+
contentLocales?: ReadonlyArray<DocumentActionsLocaleOption>;
|
|
92
|
+
showPath: boolean;
|
|
93
|
+
useAsPath?: string;
|
|
94
|
+
lockPath?: boolean;
|
|
95
|
+
pathSlugifier?: SlugifierFn;
|
|
96
|
+
pathSourceLocked?: boolean;
|
|
97
|
+
tree?: boolean;
|
|
98
|
+
useAsTitle?: string;
|
|
99
|
+
advertiseLocales?: boolean;
|
|
100
|
+
observedRevision?: number;
|
|
101
|
+
onMutationError?: (error: unknown) => 'blocked' | 'committed' | null | void;
|
|
102
|
+
onTreeMutationCommitted?: (receipt: StructuralMutationReceipt) => void;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The page-level widgets in the editor's sidebar: path, tree placement and
|
|
106
|
+
* advertised locales. They are page concerns rather than schema fields, so they
|
|
107
|
+
* reach `FormLayout` through its `sidebarSlot` and the embedded creation view
|
|
108
|
+
* passes none of them.
|
|
109
|
+
*/
|
|
110
|
+
export declare const FormSidebarWidgets: ({ disabled, mode, initialData, collectionPath, defaultLocale, contentLocale, contentLocales, showPath, useAsPath, lockPath, pathSlugifier, pathSourceLocked, tree, useAsTitle, advertiseLocales, observedRevision, onMutationError, onTreeMutationCommitted, }: FormSidebarWidgetsProps) => ReactNode;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslation } from "@byline/i18n/react";
|
|
4
|
+
import { Alert, Button, ComboButton, LoaderEllipsis } from "@byline/ui/react";
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import { AvailableLocalesWidget } from "./available-locales-widget.js";
|
|
7
|
+
import { DocumentActions } from "./document-actions.js";
|
|
8
|
+
import form_renderer_module from "./form-renderer.module.js";
|
|
9
|
+
import { FormStatusDisplay } from "./form-status-display.js";
|
|
10
|
+
import { PathWidget } from "./path-widget.js";
|
|
11
|
+
import { ScheduledPublicationCell, ScheduledPublicationNotice } from "./scheduled-publication-control.js";
|
|
12
|
+
import { TreePlacementWidget } from "./tree-placement-widget.js";
|
|
13
|
+
const FormHeadingRow = ({ heading, headerSlot })=>/*#__PURE__*/ jsxs("div", {
|
|
14
|
+
className: clsx('byline-form-heading-row', form_renderer_module["heading-row"]),
|
|
15
|
+
children: [
|
|
16
|
+
/*#__PURE__*/ jsx("h1", {
|
|
17
|
+
className: clsx('byline-form-heading', form_renderer_module.heading),
|
|
18
|
+
children: heading
|
|
19
|
+
}),
|
|
20
|
+
headerSlot
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
const FormStatusBar = ({ disabled, initialData, workflowStatuses, publishedVersion, scheduling, hasChanges, isUploading, isSubmitting, onCancel, transitions, statusBusy, onStatusBusyChange, onStatusChange, onMutationError, isBlocked, onUnsavedChanges, onUnpublish, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, useAsTitle, contentLocale, contentLocales, defaultLocale })=>{
|
|
24
|
+
const { t } = useTranslation('byline-admin');
|
|
25
|
+
const { primaryStatus, secondaryStatuses, isTerminal } = transitions;
|
|
26
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
27
|
+
className: clsx('byline-form-status-bar', form_renderer_module["status-bar"]),
|
|
28
|
+
children: [
|
|
29
|
+
/*#__PURE__*/ jsx("div", {
|
|
30
|
+
className: clsx('byline-form-status-details', form_renderer_module["status-details"]),
|
|
31
|
+
children: /*#__PURE__*/ jsx(FormStatusDisplay, {
|
|
32
|
+
disabled: disabled,
|
|
33
|
+
initialData: initialData,
|
|
34
|
+
workflowStatuses: workflowStatuses,
|
|
35
|
+
publishedVersion: publishedVersion,
|
|
36
|
+
onUnpublish: onUnpublish,
|
|
37
|
+
afterStatusCells: /*#__PURE__*/ jsx(ScheduledPublicationCell, {
|
|
38
|
+
state: scheduling.state,
|
|
39
|
+
timeZone: scheduling.timeZone
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ jsxs("div", {
|
|
44
|
+
className: clsx('byline-form-actions', form_renderer_module.actions),
|
|
45
|
+
children: [
|
|
46
|
+
/*#__PURE__*/ jsx(Button, {
|
|
47
|
+
className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
|
|
48
|
+
size: "sm",
|
|
49
|
+
intent: "noeffect",
|
|
50
|
+
type: "button",
|
|
51
|
+
onClick: onCancel,
|
|
52
|
+
children: false === hasChanges ? t('common.actions.close') : t('common.actions.cancel')
|
|
53
|
+
}),
|
|
54
|
+
/*#__PURE__*/ jsx(Button, {
|
|
55
|
+
className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
|
|
56
|
+
size: "sm",
|
|
57
|
+
type: "submit",
|
|
58
|
+
disabled: disabled || false === hasChanges || isUploading || isSubmitting,
|
|
59
|
+
"aria-label": isSubmitting ? t('common.actions.save') : void 0,
|
|
60
|
+
children: isUploading ? t('forms.actions.uploading') : /*#__PURE__*/ jsxs("span", {
|
|
61
|
+
className: clsx('byline-form-save-content', form_renderer_module["save-content"]),
|
|
62
|
+
children: [
|
|
63
|
+
/*#__PURE__*/ jsx("span", {
|
|
64
|
+
className: clsx('byline-form-save-label', form_renderer_module["save-label"], isSubmitting && form_renderer_module["save-label-hidden"]),
|
|
65
|
+
children: t('common.actions.save')
|
|
66
|
+
}),
|
|
67
|
+
isSubmitting ? /*#__PURE__*/ jsx("span", {
|
|
68
|
+
className: clsx('byline-form-save-loader', form_renderer_module["save-loader"]),
|
|
69
|
+
children: /*#__PURE__*/ jsx(LoaderEllipsis, {
|
|
70
|
+
size: 28,
|
|
71
|
+
"aria-hidden": "true"
|
|
72
|
+
})
|
|
73
|
+
}) : null
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
}),
|
|
77
|
+
primaryStatus && onStatusChange && /*#__PURE__*/ jsx("div", {
|
|
78
|
+
className: clsx('byline-form-actions-status-wrap', form_renderer_module["actions-status-wrap"]),
|
|
79
|
+
children: /*#__PURE__*/ jsx(ComboButton, {
|
|
80
|
+
buttonClassName: clsx('byline-form-actions-combo-button', form_renderer_module["actions-combo-button"]),
|
|
81
|
+
triggerClassName: clsx('byline-form-actions-combo-trigger', form_renderer_module["actions-combo-trigger"]),
|
|
82
|
+
options: secondaryStatuses.map((s)=>({
|
|
83
|
+
label: isTerminal ? t('forms.actions.revertTo', {
|
|
84
|
+
label: s.label ?? s.name
|
|
85
|
+
}) : s.verb ?? s.label ?? s.name,
|
|
86
|
+
value: s.name
|
|
87
|
+
})),
|
|
88
|
+
sideOffset: 5,
|
|
89
|
+
size: "sm",
|
|
90
|
+
type: "button",
|
|
91
|
+
intent: isTerminal ? 'info' : 'success',
|
|
92
|
+
disabled: disabled || statusBusy,
|
|
93
|
+
onOptionSelect: async (value)=>{
|
|
94
|
+
if (isBlocked()) return;
|
|
95
|
+
if (hasChanges) return void onUnsavedChanges();
|
|
96
|
+
onStatusBusyChange(true);
|
|
97
|
+
try {
|
|
98
|
+
await onStatusChange(value);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
onMutationError?.(error);
|
|
101
|
+
} finally{
|
|
102
|
+
onStatusBusyChange(false);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
onButtonClick: isTerminal ? void 0 : async ()=>{
|
|
106
|
+
if (isBlocked()) return;
|
|
107
|
+
if (hasChanges) return void onUnsavedChanges();
|
|
108
|
+
onStatusBusyChange(true);
|
|
109
|
+
try {
|
|
110
|
+
await onStatusChange(primaryStatus.name);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
onMutationError?.(error);
|
|
113
|
+
} finally{
|
|
114
|
+
onStatusBusyChange(false);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
children: statusBusy ? '...' : isTerminal ? primaryStatus.label ?? primaryStatus.name : primaryStatus.verb ?? primaryStatus.label ?? primaryStatus.name
|
|
118
|
+
})
|
|
119
|
+
}),
|
|
120
|
+
/*#__PURE__*/ jsx(DocumentActions, {
|
|
121
|
+
disabled: disabled,
|
|
122
|
+
publishedVersion: publishedVersion,
|
|
123
|
+
onUnpublish: onUnpublish,
|
|
124
|
+
onDelete: onDelete,
|
|
125
|
+
onDuplicate: onDuplicate,
|
|
126
|
+
sourceTitle: null != useAsTitle && null != initialData ? initialData[useAsTitle] : null,
|
|
127
|
+
onCopyToLocale: onCopyToLocale,
|
|
128
|
+
sourceLocale: contentLocale,
|
|
129
|
+
contentLocales: contentLocales,
|
|
130
|
+
hasUnsavedChanges: hasChanges,
|
|
131
|
+
onUnsavedChanges: ()=>onUnsavedChanges(),
|
|
132
|
+
onDeleteLocale: onDeleteLocale,
|
|
133
|
+
defaultLocale: defaultLocale,
|
|
134
|
+
availableLocales: initialData?._availableVersionLocales,
|
|
135
|
+
scheduledPublicationState: scheduling.state,
|
|
136
|
+
onSchedulePublication: scheduling.openSchedule,
|
|
137
|
+
onConfirmScheduledPublication: scheduling.confirm,
|
|
138
|
+
onCancelScheduledPublication: scheduling.cancel
|
|
139
|
+
})
|
|
140
|
+
]
|
|
141
|
+
})
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
const FormConcurrencyNotices = ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings })=>{
|
|
146
|
+
const { t } = useTranslation('byline-admin');
|
|
147
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
148
|
+
children: [
|
|
149
|
+
(mutationIssue || scheduledPublicationsNeedReconfirmation) && /*#__PURE__*/ jsxs("div", {
|
|
150
|
+
ref: warningRef,
|
|
151
|
+
tabIndex: -1,
|
|
152
|
+
role: "alert",
|
|
153
|
+
"aria-live": "assertive",
|
|
154
|
+
className: clsx('byline-document-concurrency', form_renderer_module.concurrency),
|
|
155
|
+
children: [
|
|
156
|
+
mutationIssue && /*#__PURE__*/ jsxs(Alert, {
|
|
157
|
+
intent: "warning",
|
|
158
|
+
icon: true,
|
|
159
|
+
close: false,
|
|
160
|
+
title: t(`documentConcurrency.${mutationIssue}Title`),
|
|
161
|
+
children: [
|
|
162
|
+
/*#__PURE__*/ jsx("p", {
|
|
163
|
+
children: t(`documentConcurrency.${mutationIssue}`)
|
|
164
|
+
}),
|
|
165
|
+
'committed' !== mutationIssue && /*#__PURE__*/ jsx(Button, {
|
|
166
|
+
type: "button",
|
|
167
|
+
disabled: discarding,
|
|
168
|
+
onClick: onDiscardRequested,
|
|
169
|
+
children: t('documentConcurrency.reloadAction')
|
|
170
|
+
}),
|
|
171
|
+
reloadFailed && /*#__PURE__*/ jsx("p", {
|
|
172
|
+
children: t('documentConcurrency.reloadFailed')
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
}),
|
|
176
|
+
scheduledPublicationsNeedReconfirmation && /*#__PURE__*/ jsxs(Alert, {
|
|
177
|
+
intent: "warning",
|
|
178
|
+
icon: true,
|
|
179
|
+
close: false,
|
|
180
|
+
title: t('documentConcurrency.schedulesTitle'),
|
|
181
|
+
children: [
|
|
182
|
+
/*#__PURE__*/ jsx("p", {
|
|
183
|
+
children: t('documentConcurrency.schedules')
|
|
184
|
+
}),
|
|
185
|
+
scheduledPublicationsHref && /*#__PURE__*/ jsx("a", {
|
|
186
|
+
href: scheduledPublicationsHref,
|
|
187
|
+
children: t('documentConcurrency.reviewSchedules')
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ jsx(ScheduledPublicationNotice, {
|
|
194
|
+
state: scheduling.state,
|
|
195
|
+
timeZone: scheduling.timeZone,
|
|
196
|
+
busy: disabled || scheduling.busy,
|
|
197
|
+
onConfirm: scheduling.confirm,
|
|
198
|
+
onReschedule: scheduling.openSchedule,
|
|
199
|
+
onCancel: scheduling.cancel
|
|
200
|
+
}),
|
|
201
|
+
scheduling.modal,
|
|
202
|
+
restoreWarnings && restoreWarnings.length > 0 && /*#__PURE__*/ jsxs(Alert, {
|
|
203
|
+
className: "m-0 mt-4",
|
|
204
|
+
intent: "warning",
|
|
205
|
+
icon: true,
|
|
206
|
+
close: false,
|
|
207
|
+
title: t('forms.restoreWarnings.title'),
|
|
208
|
+
children: [
|
|
209
|
+
/*#__PURE__*/ jsx("p", {
|
|
210
|
+
children: t('forms.restoreWarnings.body', {
|
|
211
|
+
count: restoreWarnings.length
|
|
212
|
+
})
|
|
213
|
+
}),
|
|
214
|
+
/*#__PURE__*/ jsx("ul", {
|
|
215
|
+
children: restoreWarnings.map((w)=>/*#__PURE__*/ jsx("li", {
|
|
216
|
+
children: w
|
|
217
|
+
}, w))
|
|
218
|
+
})
|
|
219
|
+
]
|
|
220
|
+
})
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
const FormSidebarWidgets = ({ disabled, mode, initialData, collectionPath, defaultLocale, contentLocale, contentLocales, showPath, useAsPath, lockPath, pathSlugifier, pathSourceLocked, tree, useAsTitle, advertiseLocales, observedRevision, onMutationError, onTreeMutationCommitted })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
225
|
+
children: [
|
|
226
|
+
showPath && (useAsPath || true === lockPath || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
|
|
227
|
+
disabled: disabled,
|
|
228
|
+
useAsPath: useAsPath,
|
|
229
|
+
collectionPath: collectionPath ?? '',
|
|
230
|
+
defaultLocale: defaultLocale,
|
|
231
|
+
activeLocale: contentLocale,
|
|
232
|
+
mode: mode,
|
|
233
|
+
slugifier: pathSlugifier,
|
|
234
|
+
sourceLocked: pathSourceLocked,
|
|
235
|
+
lockPath: lockPath
|
|
236
|
+
}),
|
|
237
|
+
tree && 'edit' === mode && 'string' == typeof initialData?.id && /*#__PURE__*/ jsx(TreePlacementWidget, {
|
|
238
|
+
disabled: disabled,
|
|
239
|
+
onMutationError: onMutationError,
|
|
240
|
+
onCommitted: onTreeMutationCommitted,
|
|
241
|
+
expectedRevision: observedRevision ?? initialData.revision,
|
|
242
|
+
collectionPath: collectionPath ?? '',
|
|
243
|
+
documentId: initialData.id,
|
|
244
|
+
useAsTitle: useAsTitle
|
|
245
|
+
}),
|
|
246
|
+
advertiseLocales && /*#__PURE__*/ jsx(AvailableLocalesWidget, {
|
|
247
|
+
disabled: disabled,
|
|
248
|
+
contentLocales: contentLocales ?? [],
|
|
249
|
+
availableVersionLocales: initialData?._availableVersionLocales ?? []
|
|
250
|
+
})
|
|
251
|
+
]
|
|
252
|
+
});
|
|
253
|
+
export { FormConcurrencyNotices, FormHeadingRow, FormSidebarWidgets, FormStatusBar };
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type ReactNode } from 'react';
|
|
9
9
|
import type { AdminResourceConfig, Field, WorkflowStatus } from '@byline/core';
|
|
10
|
-
import type { DocumentPatch } from '@byline/core/patches';
|
|
11
|
-
import { type DocumentActionsLocaleOption } from './document-actions.js';
|
|
12
10
|
import { type ScheduledPublicationInfo, type SchedulePublicationInput } from './scheduled-publication-control.js';
|
|
11
|
+
import type { DocumentActionsLocaleOption } from './document-actions.js';
|
|
13
12
|
import type { UseNavigationGuard } from './navigation-guard.js';
|
|
13
|
+
export type { SystemFieldsSubmitPayload } from './use-form-submission.js';
|
|
14
14
|
/** Metadata about a previously published version that is still live. */
|
|
15
15
|
export interface PublishedVersionInfo {
|
|
16
16
|
id: string;
|
|
@@ -19,22 +19,6 @@ export interface PublishedVersionInfo {
|
|
|
19
19
|
createdAt: string | Date;
|
|
20
20
|
updatedAt: string | Date;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Payload emitted by the form on Save. Carries the content (field data +
|
|
24
|
-
* patches) alongside the document-grain system fields (path / advertised
|
|
25
|
-
* locales) and per-bucket dirty flags so the host can route each piece to the
|
|
26
|
-
* right write path — versioned for content, immediate/non-versioned for the
|
|
27
|
-
* system fields. See docs/08-internationalization/index.md.
|
|
28
|
-
*/
|
|
29
|
-
export interface SystemFieldsSubmitPayload {
|
|
30
|
-
data: any;
|
|
31
|
-
patches: DocumentPatch[];
|
|
32
|
-
contentDirty: boolean;
|
|
33
|
-
pathDirty: boolean;
|
|
34
|
-
systemPath?: string | null;
|
|
35
|
-
availableLocalesDirty: boolean;
|
|
36
|
-
systemAvailableLocales?: string[];
|
|
37
|
-
}
|
|
38
22
|
/** Props shared by both the public FormRenderer and its internal FormContent component. */
|
|
39
23
|
export interface FormRendererProps {
|
|
40
24
|
mutationIssue?: 'stale' | 'reload' | 'lock' | 'unavailable' | 'committed' | null;
|