@byline/host-tanstack-start 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/admin-shell/collections/create.js +22 -5
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +222 -34
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +23 -6
- package/dist/admin-shell/collections/save-navigation.test.d.ts +8 -0
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +108 -23
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/collections/copy-to-locale.d.ts +4 -8
- package/dist/server-fns/collections/copy-to-locale.js +17 -8
- package/dist/server-fns/collections/create.d.ts +2 -8
- package/dist/server-fns/collections/create.js +20 -10
- package/dist/server-fns/collections/delete-locale.d.ts +4 -8
- package/dist/server-fns/collections/delete-locale.js +15 -6
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +2 -7
- package/dist/server-fns/collections/delete.js +5 -3
- package/dist/server-fns/collections/duplicate.d.ts +4 -8
- package/dist/server-fns/collections/duplicate.js +14 -5
- package/dist/server-fns/collections/get.d.ts +1 -7
- package/dist/server-fns/collections/get.js +5 -4
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/list.d.ts +0 -7
- package/dist/server-fns/collections/list.js +11 -3
- package/dist/server-fns/collections/reorder.d.ts +7 -8
- package/dist/server-fns/collections/reorder.js +20 -76
- package/dist/server-fns/collections/restore-version.d.ts +4 -8
- package/dist/server-fns/collections/restore-version.js +22 -12
- package/dist/server-fns/collections/save-outcome.d.ts +24 -0
- package/dist/server-fns/collections/save-outcome.js +19 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
- package/dist/server-fns/collections/scheduled-publication.js +31 -21
- package/dist/server-fns/collections/status.d.ts +8 -7
- package/dist/server-fns/collections/status.js +13 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +14 -8
- package/dist/server-fns/collections/tree.js +21 -17
- package/dist/server-fns/collections/update.d.ts +14 -11
- package/dist/server-fns/collections/update.js +48 -24
- package/dist/server-fns/collections/upload.d.ts +1 -0
- package/dist/server-fns/collections/upload.js +3 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/change-status.d.ts +2 -7
- package/dist/server-fns/singletons/change-status.js +4 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
- package/dist/server-fns/singletons/copy-to-locale.js +16 -5
- package/dist/server-fns/singletons/get.d.ts +4 -4
- package/dist/server-fns/singletons/restore-version.d.ts +2 -7
- package/dist/server-fns/singletons/restore-version.js +13 -1
- package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
- package/dist/server-fns/singletons/schedule-publish.js +4 -2
- package/dist/server-fns/singletons/unpublish.d.ts +2 -7
- package/dist/server-fns/singletons/unpublish.js +4 -1
- package/dist/server-fns/singletons/update.d.ts +4 -11
- package/dist/server-fns/singletons/update.js +16 -8
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +11 -11
- package/src/admin-shell/collections/create.tsx +22 -3
- package/src/admin-shell/collections/edit.tsx +233 -61
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +23 -5
- package/src/admin-shell/collections/save-navigation.test.tsx +532 -0
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +46 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +86 -18
- package/src/admin-shell/singletons/view.tsx +145 -45
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/collections/copy-to-locale.ts +48 -29
- package/src/server-fns/collections/create.ts +56 -44
- package/src/server-fns/collections/delete-locale.ts +48 -25
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +44 -38
- package/src/server-fns/collections/duplicate.ts +45 -25
- package/src/server-fns/collections/get.ts +8 -4
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/list.ts +8 -3
- package/src/server-fns/collections/reorder.ts +32 -124
- package/src/server-fns/collections/restore-version.ts +51 -36
- package/src/server-fns/collections/revision-input.test.node.ts +108 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +80 -0
- package/src/server-fns/collections/save-outcome.ts +57 -0
- package/src/server-fns/collections/scheduled-publication.ts +138 -111
- package/src/server-fns/collections/status.ts +86 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +150 -122
- package/src/server-fns/collections/update.ts +110 -71
- package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
- package/src/server-fns/collections/upload.ts +81 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
- package/src/server-fns/singletons/change-status.ts +10 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
- package/src/server-fns/singletons/copy-to-locale.ts +20 -8
- package/src/server-fns/singletons/get.ts +3 -3
- package/src/server-fns/singletons/restore-version.ts +17 -3
- package/src/server-fns/singletons/schedule-publish.ts +18 -7
- package/src/server-fns/singletons/singletons.test.node.ts +96 -24
- package/src/server-fns/singletons/unpublish.ts +10 -3
- package/src/server-fns/singletons/update.ts +21 -9
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DocumentMutationNotice } from "../document-mutation-notice.js";
|
|
3
|
+
import { useDocumentMutationState } from "../document-mutation-state.js";
|
|
2
4
|
import { useEffect, useMemo, useState } from "react";
|
|
3
5
|
import { useRouter, useRouterState } from "@tanstack/react-router";
|
|
4
6
|
import { StatusBadge, renderFormatted } from "@byline/admin/react";
|
|
@@ -77,6 +79,7 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
|
|
|
77
79
|
const navigate = useNavigate();
|
|
78
80
|
const router = useRouter();
|
|
79
81
|
const toastManager = useToastManager();
|
|
82
|
+
const mutation = useDocumentMutationState(void 0);
|
|
80
83
|
const { t } = useTranslation('byline-admin');
|
|
81
84
|
const location = useRouterState({
|
|
82
85
|
select: (s)=>s.location
|
|
@@ -84,14 +87,15 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
|
|
|
84
87
|
const [localDocs, setLocalDocs] = useState(data.docs);
|
|
85
88
|
const [isReordering, setIsReordering] = useState(false);
|
|
86
89
|
useEffect(()=>{
|
|
87
|
-
if (!isReordering) setLocalDocs(data.docs);
|
|
90
|
+
if (!isReordering && !mutation.blocked) setLocalDocs(data.docs);
|
|
88
91
|
}, [
|
|
89
92
|
data.docs,
|
|
90
|
-
isReordering
|
|
93
|
+
isReordering,
|
|
94
|
+
mutation.blocked
|
|
91
95
|
]);
|
|
92
96
|
const searchParams = location.search;
|
|
93
97
|
const isCanonicalView = !searchParams.order && !searchParams.desc && !searchParams.query && !searchParams.status;
|
|
94
|
-
const dragEnabled = orderable && isCanonicalView && !!onReorder;
|
|
98
|
+
const dragEnabled = orderable && isCanonicalView && !!onReorder && !mutation.blocked;
|
|
95
99
|
const activeOrder = searchParams.order ?? data?.meta.order;
|
|
96
100
|
const activeDesc = null != searchParams.order ? searchParams.desc : data?.meta.desc;
|
|
97
101
|
const sensors = useSensors(useSensor(PointerSensor, {
|
|
@@ -103,7 +107,7 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
|
|
|
103
107
|
}));
|
|
104
108
|
const handleDragEnd = async (event)=>{
|
|
105
109
|
const { active, over } = event;
|
|
106
|
-
if (!over || active.id === over.id || !onReorder) return;
|
|
110
|
+
if (mutation.blocked || isReordering || !over || active.id === over.id || !onReorder) return;
|
|
107
111
|
const oldIndex = localDocs.findIndex((d)=>d.id === active.id);
|
|
108
112
|
const newIndex = localDocs.findIndex((d)=>d.id === over.id);
|
|
109
113
|
if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) return;
|
|
@@ -119,14 +123,30 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
|
|
|
119
123
|
const before = next[newIndex - 1]?.id ?? null;
|
|
120
124
|
const after = next[newIndex + 1]?.id ?? null;
|
|
121
125
|
try {
|
|
122
|
-
|
|
126
|
+
mutation.assertWritable();
|
|
127
|
+
const receipt = await onReorder({
|
|
123
128
|
documentId: String(active.id),
|
|
124
129
|
beforeDocumentId: before,
|
|
125
130
|
afterDocumentId: after
|
|
126
131
|
});
|
|
127
|
-
|
|
132
|
+
mutation.reportStructure(receipt);
|
|
133
|
+
try {
|
|
134
|
+
await router.invalidate();
|
|
135
|
+
} catch {
|
|
136
|
+
toastManager.add({
|
|
137
|
+
title: t('collections.list.treeRefreshFailedToast'),
|
|
138
|
+
description: t("collections.list.treeRefreshFailedDescription"),
|
|
139
|
+
data: {
|
|
140
|
+
intent: 'warning',
|
|
141
|
+
iconType: 'warning',
|
|
142
|
+
icon: true,
|
|
143
|
+
close: true
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
128
147
|
} catch (_err) {
|
|
129
148
|
setLocalDocs(previousDocs);
|
|
149
|
+
if (mutation.report(_err)) return;
|
|
130
150
|
toastManager.add({
|
|
131
151
|
title: t('collections.list.reorderFailedToast'),
|
|
132
152
|
description: t("collections.list.reorderFailedDescription"),
|
|
@@ -241,6 +261,10 @@ const ListView = ({ data, columns, workflowStatuses, useAsTitle, orderable = fal
|
|
|
241
261
|
return /*#__PURE__*/ jsx(Section, {
|
|
242
262
|
children: /*#__PURE__*/ jsxs(Container, {
|
|
243
263
|
children: [
|
|
264
|
+
/*#__PURE__*/ jsx(DocumentMutationNotice, {
|
|
265
|
+
issue: mutation.issue,
|
|
266
|
+
scheduleNotice: mutation.scheduleNotice
|
|
267
|
+
}),
|
|
244
268
|
/*#__PURE__*/ jsxs("div", {
|
|
245
269
|
className: clsx('byline-coll-list-head', list_module.head),
|
|
246
270
|
children: [
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
interface RestoreVersionModalProps {
|
|
2
|
+
disabled?: boolean;
|
|
3
|
+
onMutationError?: (error: unknown) => unknown;
|
|
2
4
|
versionLabel: string;
|
|
3
5
|
versionNumber: number;
|
|
4
6
|
restoreStatusLabel: string;
|
|
@@ -6,5 +8,5 @@ interface RestoreVersionModalProps {
|
|
|
6
8
|
restoreVersion: () => Promise<unknown>;
|
|
7
9
|
onRestoreComplete: () => void | Promise<void>;
|
|
8
10
|
}
|
|
9
|
-
export declare function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel, onClose, restoreVersion, onRestoreComplete, }: RestoreVersionModalProps): import("react").JSX.Element;
|
|
11
|
+
export declare function RestoreVersionModal({ disabled, onMutationError, versionLabel, versionNumber, restoreStatusLabel, onClose, restoreVersion, onRestoreComplete, }: RestoreVersionModalProps): import("react").JSX.Element;
|
|
10
12
|
export {};
|
|
@@ -2,22 +2,39 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { useTranslation } from "@byline/i18n/react";
|
|
5
|
-
import { Alert, Button, LoaderEllipsis, Modal } from "@byline/ui/react";
|
|
5
|
+
import { Alert, Button, LoaderEllipsis, Modal, useToastManager } from "@byline/ui/react";
|
|
6
6
|
import clsx from "clsx";
|
|
7
|
+
import { hasCommittedDocumentHookFailure } from "../../server-fns/collections/save-outcome.js";
|
|
7
8
|
import restore_version_modal_module from "./restore-version-modal.module.js";
|
|
8
|
-
function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel, onClose, restoreVersion, onRestoreComplete }) {
|
|
9
|
+
function RestoreVersionModal({ disabled = false, onMutationError, versionLabel, versionNumber, restoreStatusLabel, onClose, restoreVersion, onRestoreComplete }) {
|
|
9
10
|
const { t } = useTranslation('byline-admin');
|
|
11
|
+
const toastManager = useToastManager();
|
|
10
12
|
const [error, setError] = useState(null);
|
|
11
13
|
const [pending, setPending] = useState(false);
|
|
12
14
|
async function handleRestore() {
|
|
13
|
-
if (pending) return;
|
|
15
|
+
if (pending || disabled) return;
|
|
14
16
|
setPending(true);
|
|
15
17
|
setError(null);
|
|
16
18
|
try {
|
|
17
|
-
await restoreVersion();
|
|
19
|
+
const result = await restoreVersion();
|
|
20
|
+
if (hasCommittedDocumentHookFailure(result)) toastManager.add({
|
|
21
|
+
title: t('collections.save.hookFailedToast'),
|
|
22
|
+
description: t("collections.save.hookFailedDescription"),
|
|
23
|
+
data: {
|
|
24
|
+
intent: 'warning',
|
|
25
|
+
iconType: 'warning',
|
|
26
|
+
icon: true,
|
|
27
|
+
close: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
18
30
|
onClose();
|
|
19
31
|
await onRestoreComplete();
|
|
20
32
|
} catch (err) {
|
|
33
|
+
if (onMutationError?.(err)) {
|
|
34
|
+
setPending(false);
|
|
35
|
+
onClose();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
21
38
|
const code = getErrorCode(err);
|
|
22
39
|
if ('ERR_INVALID_TRANSITION' === code) setError(t('collections.restore.errors.alreadyCurrent'));
|
|
23
40
|
else if ('ERR_NOT_FOUND' === code) setError(t('collections.restore.errors.notFound'));
|
|
@@ -85,7 +102,7 @@ function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel,
|
|
|
85
102
|
intent: "noeffect",
|
|
86
103
|
size: "sm",
|
|
87
104
|
onClick: onClose,
|
|
88
|
-
disabled: pending,
|
|
105
|
+
disabled: pending || disabled,
|
|
89
106
|
className: clsx('byline-coll-restore-button', restore_version_modal_module.button),
|
|
90
107
|
children: t('common.actions.cancel')
|
|
91
108
|
}),
|
|
@@ -96,7 +113,7 @@ function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel,
|
|
|
96
113
|
style: {
|
|
97
114
|
minWidth: '80px'
|
|
98
115
|
},
|
|
99
|
-
disabled: pending,
|
|
116
|
+
disabled: pending || disabled,
|
|
100
117
|
className: clsx('byline-coll-restore-button', restore_version_modal_module.button),
|
|
101
118
|
children: true === pending ? /*#__PURE__*/ jsx(LoaderEllipsis, {
|
|
102
119
|
size: 42
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DocumentMutationNotice } from "../document-mutation-notice.js";
|
|
3
|
+
import { useDocumentMutationState } from "../document-mutation-state.js";
|
|
2
4
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
5
|
import { useRouter } from "@tanstack/react-router";
|
|
4
6
|
import { StatusBadge, renderFormatted } from "@byline/admin/react";
|
|
@@ -55,6 +57,7 @@ function SortableTreeRow({ id, dragging, disabled, dragHandleLabel, children })
|
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection, collectionLabels, onMove, listActions })=>{
|
|
60
|
+
const mutation = useDocumentMutationState(void 0);
|
|
58
61
|
const { t } = useTranslation('byline-admin');
|
|
59
62
|
const router = useRouter();
|
|
60
63
|
const toastManager = useToastManager();
|
|
@@ -66,12 +69,13 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
|
|
|
66
69
|
const [moveController] = useState(()=>createAdminTreeMoveController());
|
|
67
70
|
const preserveOptimisticForRows = useRef(null);
|
|
68
71
|
useEffect(()=>{
|
|
69
|
-
if (!shouldSyncAdminTreeRows(rows, preserveOptimisticForRows.current, isMoving)) return;
|
|
72
|
+
if (mutation.blocked || !shouldSyncAdminTreeRows(rows, preserveOptimisticForRows.current, isMoving)) return;
|
|
70
73
|
preserveOptimisticForRows.current = null;
|
|
71
74
|
setLocalPlaced(rows.filter((r)=>!r.unplaced));
|
|
72
75
|
}, [
|
|
73
76
|
rows,
|
|
74
|
-
isMoving
|
|
77
|
+
isMoving,
|
|
78
|
+
mutation.blocked
|
|
75
79
|
]);
|
|
76
80
|
const [activeId, setActiveId] = useState(null);
|
|
77
81
|
const [overId, setOverId] = useState(null);
|
|
@@ -98,12 +102,12 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
|
|
|
98
102
|
setOffsetLeft(0);
|
|
99
103
|
};
|
|
100
104
|
const handleDragStart = ({ active })=>{
|
|
101
|
-
if (!moveController.isMoving()) setActiveId(String(active.id));
|
|
105
|
+
if (!mutation.blocked && !moveController.isMoving()) setActiveId(String(active.id));
|
|
102
106
|
};
|
|
103
107
|
const handleDragMove = ({ delta })=>setOffsetLeft(delta.x);
|
|
104
108
|
const handleDragOver = ({ over })=>setOverId(over ? String(over.id) : null);
|
|
105
109
|
const handleDragEnd = async ({ active, over })=>{
|
|
106
|
-
if (moveController.isMoving()) return void resetDnd();
|
|
110
|
+
if (mutation.blocked || moveController.isMoving()) return void resetDnd();
|
|
107
111
|
const proj = null != over ? getTreeProjection(localPlaced, String(active.id), String(over.id), offsetLeft, INDENT) : null;
|
|
108
112
|
resetDnd();
|
|
109
113
|
if (!onMove || null == proj) return;
|
|
@@ -125,9 +129,11 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
|
|
|
125
129
|
setIsMoving(moveController.isMoving());
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
132
|
+
if ('receipt' in outcome) mutation.reportStructure(outcome.receipt);
|
|
133
|
+
const handled = 'error' in outcome ? mutation.report(outcome.error) : null;
|
|
128
134
|
const effects = getAdminTreeMoveUiEffects(outcome);
|
|
129
135
|
if (effects.rollback) setLocalPlaced(current);
|
|
130
|
-
if (null != effects.structuralFailure) {
|
|
136
|
+
if (!handled && null != effects.structuralFailure) {
|
|
131
137
|
const conflict = 'conflict' === effects.structuralFailure;
|
|
132
138
|
toastManager.add({
|
|
133
139
|
title: t(conflict ? 'collections.list.treeConflictToast' : 'collections.list.reorderFailedToast'),
|
|
@@ -202,6 +208,10 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
|
|
|
202
208
|
return /*#__PURE__*/ jsx(Section, {
|
|
203
209
|
children: /*#__PURE__*/ jsxs(Container, {
|
|
204
210
|
children: [
|
|
211
|
+
/*#__PURE__*/ jsx(DocumentMutationNotice, {
|
|
212
|
+
issue: mutation.issue,
|
|
213
|
+
scheduleNotice: mutation.scheduleNotice
|
|
214
|
+
}),
|
|
205
215
|
/*#__PURE__*/ jsxs("div", {
|
|
206
216
|
className: clsx('byline-coll-list-head', tree_list_module.head),
|
|
207
217
|
children: [
|
|
@@ -271,7 +281,7 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
|
|
|
271
281
|
return dndEnabled ? /*#__PURE__*/ jsx(SortableTreeRow, {
|
|
272
282
|
id: row.id,
|
|
273
283
|
dragging: row.id === activeId,
|
|
274
|
-
disabled: isMoving,
|
|
284
|
+
disabled: mutation.blocked || isMoving,
|
|
275
285
|
dragHandleLabel: t('collections.list.dragHandleAriaLabel'),
|
|
276
286
|
children: renderCells(row, depth)
|
|
277
287
|
}, row.id) : /*#__PURE__*/ jsx(Table.Row, {
|
|
@@ -40,6 +40,7 @@ interface VersionHistoryRowContext {
|
|
|
40
40
|
actionLabel: string;
|
|
41
41
|
restoreStatusLabel: string;
|
|
42
42
|
openCompare: () => void;
|
|
43
|
+
mutationsBlocked: boolean;
|
|
43
44
|
openRestore: () => void;
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
@@ -66,7 +67,7 @@ export declare function VersionHistoryCore({ data, resourceDefinition, resourceP
|
|
|
66
67
|
rowPresentation: VersionHistoryRowPresentation;
|
|
67
68
|
loadHistoricalVersion: HistoricalVersionLoader;
|
|
68
69
|
onPageSizeChange: (pageSize: number) => void;
|
|
69
|
-
restoreVersion: (versionId: string) => Promise<unknown>;
|
|
70
|
+
restoreVersion: (versionId: string, expectedRevision: number) => Promise<unknown>;
|
|
70
71
|
onRestoreComplete: () => void | Promise<void>;
|
|
71
72
|
}): import("react").JSX.Element;
|
|
72
73
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DocumentMutationNotice } from "../document-mutation-notice.js";
|
|
3
|
+
import { useDocumentMutationState } from "../document-mutation-state.js";
|
|
2
4
|
import { Fragment as external_react_Fragment, Suspense, lazy, useState } from "react";
|
|
3
5
|
import { getDefaultStatus, getWorkflowStatuses } from "@byline/core";
|
|
4
6
|
import { useTranslation } from "@byline/i18n/react";
|
|
@@ -33,6 +35,7 @@ function padRows(value) {
|
|
|
33
35
|
}, `empty-row-${index}`));
|
|
34
36
|
}
|
|
35
37
|
function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId, currentDocument, locale, rowPresentation, loadHistoricalVersion, onPageSizeChange, restoreVersion, onRestoreComplete }) {
|
|
38
|
+
const mutation = useDocumentMutationState(currentDocument);
|
|
36
39
|
const { t } = useTranslation('byline-admin');
|
|
37
40
|
const [selectedVersion, setSelectedVersion] = useState(null);
|
|
38
41
|
const [restoreTarget, setRestoreTarget] = useState(null);
|
|
@@ -41,6 +44,9 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
|
|
|
41
44
|
const restoreStatusLabel = getWorkflowStatuses(resourceDefinition).find((status)=>status.name === restoreStatus)?.label ?? restoreStatus;
|
|
42
45
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
43
46
|
children: [
|
|
47
|
+
/*#__PURE__*/ jsx(DocumentMutationNotice, {
|
|
48
|
+
issue: mutation.issue
|
|
49
|
+
}),
|
|
44
50
|
/*#__PURE__*/ jsx("div", {
|
|
45
51
|
className: clsx('byline-coll-history-options', history_module.options),
|
|
46
52
|
children: /*#__PURE__*/ jsx(RouterPager, {
|
|
@@ -85,7 +91,7 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
|
|
|
85
91
|
});
|
|
86
92
|
};
|
|
87
93
|
const openRestore = ()=>{
|
|
88
|
-
if (null == versionId || versionId === currentVersionId) return;
|
|
94
|
+
if (mutation.blocked || null == versionId || versionId === currentVersionId) return;
|
|
89
95
|
setRestoreTarget({
|
|
90
96
|
versionId,
|
|
91
97
|
label: versionLabel,
|
|
@@ -119,6 +125,7 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
|
|
|
119
125
|
actionLabel,
|
|
120
126
|
restoreStatusLabel,
|
|
121
127
|
openCompare,
|
|
128
|
+
mutationsBlocked: mutation.blocked,
|
|
122
129
|
openRestore
|
|
123
130
|
})
|
|
124
131
|
]
|
|
@@ -243,11 +250,13 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
|
|
|
243
250
|
]
|
|
244
251
|
}),
|
|
245
252
|
restoreTarget ? /*#__PURE__*/ jsx(RestoreVersionModal, {
|
|
253
|
+
disabled: mutation.blocked,
|
|
254
|
+
onMutationError: mutation.report,
|
|
246
255
|
versionLabel: restoreTarget.label,
|
|
247
256
|
versionNumber: restoreTarget.versionNumber,
|
|
248
257
|
restoreStatusLabel: restoreStatusLabel,
|
|
249
258
|
onClose: ()=>setRestoreTarget(null),
|
|
250
|
-
restoreVersion: ()=>restoreVersion(restoreTarget.versionId),
|
|
259
|
+
restoreVersion: ()=>restoreVersion(restoreTarget.versionId, mutation.expectedRevision()),
|
|
251
260
|
onRestoreComplete: onRestoreComplete
|
|
252
261
|
}) : null
|
|
253
262
|
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DocumentMutationIssue } from './document-mutation-state.js';
|
|
2
|
+
/** Persistent recovery for list, history and schedule views without local form drafts. */
|
|
3
|
+
export declare function DocumentMutationNotice({ issue, scheduleNotice, }: {
|
|
4
|
+
issue: DocumentMutationIssue | null;
|
|
5
|
+
scheduleNotice?: boolean;
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { useTranslation } from "@byline/i18n/react";
|
|
4
|
+
import { Alert, Button } from "@byline/ui/react";
|
|
5
|
+
import { getAdminRoutePath } from "../routes/admin-path.js";
|
|
6
|
+
function DocumentMutationNotice({ issue, scheduleNotice = false }) {
|
|
7
|
+
const { t } = useTranslation('byline-admin');
|
|
8
|
+
const warning = useRef(null);
|
|
9
|
+
useEffect(()=>{
|
|
10
|
+
if (issue) warning.current?.focus();
|
|
11
|
+
}, [
|
|
12
|
+
issue
|
|
13
|
+
]);
|
|
14
|
+
if (!issue && !scheduleNotice) return null;
|
|
15
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
16
|
+
ref: warning,
|
|
17
|
+
tabIndex: -1,
|
|
18
|
+
role: "alert",
|
|
19
|
+
"aria-live": "assertive",
|
|
20
|
+
children: [
|
|
21
|
+
issue && /*#__PURE__*/ jsxs(Alert, {
|
|
22
|
+
close: false,
|
|
23
|
+
intent: "warning",
|
|
24
|
+
title: t(`documentConcurrency.${issue}Title`),
|
|
25
|
+
children: [
|
|
26
|
+
/*#__PURE__*/ jsx("p", {
|
|
27
|
+
children: t(`documentConcurrency.${issue}`)
|
|
28
|
+
}),
|
|
29
|
+
'committed' !== issue && /*#__PURE__*/ jsx(Button, {
|
|
30
|
+
type: "button",
|
|
31
|
+
onClick: ()=>window.location.reload(),
|
|
32
|
+
children: t('documentConcurrency.reloadAction')
|
|
33
|
+
})
|
|
34
|
+
]
|
|
35
|
+
}),
|
|
36
|
+
scheduleNotice && /*#__PURE__*/ jsxs(Alert, {
|
|
37
|
+
close: false,
|
|
38
|
+
intent: "warning",
|
|
39
|
+
title: t('documentConcurrency.schedulesTitle'),
|
|
40
|
+
children: [
|
|
41
|
+
/*#__PURE__*/ jsx("p", {
|
|
42
|
+
children: t('documentConcurrency.schedules')
|
|
43
|
+
}),
|
|
44
|
+
/*#__PURE__*/ jsx("a", {
|
|
45
|
+
href: getAdminRoutePath('scheduled-publications'),
|
|
46
|
+
children: t('documentConcurrency.reviewSchedules')
|
|
47
|
+
})
|
|
48
|
+
]
|
|
49
|
+
})
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export { DocumentMutationNotice };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type DocumentMutationIssue = 'stale' | 'reload' | 'lock' | 'unavailable' | 'committed';
|
|
2
|
+
/** A rejected observation stays frozen until an explicit editor reload. */
|
|
3
|
+
export declare function useDocumentMutationState<T extends {
|
|
4
|
+
id?: unknown;
|
|
5
|
+
versionId?: unknown;
|
|
6
|
+
revision?: unknown;
|
|
7
|
+
}>(document: T | null | undefined): {
|
|
8
|
+
issue: DocumentMutationIssue | null;
|
|
9
|
+
blocked: boolean;
|
|
10
|
+
scheduleNotice: boolean;
|
|
11
|
+
document: T | null | undefined;
|
|
12
|
+
revision: number | undefined;
|
|
13
|
+
expectedRevision: () => number;
|
|
14
|
+
assertWritable: () => void;
|
|
15
|
+
adopt: (receipt: {
|
|
16
|
+
documentId: string;
|
|
17
|
+
revision: number;
|
|
18
|
+
scheduledPublicationsNeedReconfirmation?: boolean;
|
|
19
|
+
}) => void;
|
|
20
|
+
report: (error: unknown) => 'blocked' | 'committed' | null;
|
|
21
|
+
reportStructure: (result: unknown) => void;
|
|
22
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useRef, useState } from "react";
|
|
2
|
+
import { ErrorCodes, getDocumentRevisionValidationDetails, getDocumentStaleDetails, getLockConflictDetails, parseDocumentRevision } from "@byline/core";
|
|
3
|
+
import { getDocumentHookCommittedDetails } from "@byline/core/services";
|
|
4
|
+
import { committedTreeReceipt, structuralReceipt } from "../server-fns/structural-receipt.js";
|
|
5
|
+
function documentMutationIssue(error) {
|
|
6
|
+
if (getDocumentStaleDetails(error)) return 'stale';
|
|
7
|
+
if (getDocumentRevisionValidationDetails(error)) return 'reload';
|
|
8
|
+
if (getLockConflictDetails(error)) return 'lock';
|
|
9
|
+
if (getDocumentHookCommittedDetails(error) || committedTreeReceipt(error)) return 'committed';
|
|
10
|
+
if ('object' == typeof error && null !== error && Reflect.get(error, 'code') === ErrorCodes.NOT_FOUND) return 'unavailable';
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function useDocumentMutationState(document) {
|
|
14
|
+
const [issue, setIssue] = useState(null);
|
|
15
|
+
const [scheduleNotice, setScheduleNotice] = useState(false);
|
|
16
|
+
const [, setReceiptRevision] = useState();
|
|
17
|
+
const blockedError = useRef(void 0);
|
|
18
|
+
const observation = useRef({
|
|
19
|
+
identity: document,
|
|
20
|
+
revision: document?.revision
|
|
21
|
+
});
|
|
22
|
+
if (observation.current.identity !== document && void 0 === blockedError.current && document?.revision === observation.current.revision) observation.current = {
|
|
23
|
+
identity: document,
|
|
24
|
+
revision: document?.revision
|
|
25
|
+
};
|
|
26
|
+
const assertWritable = ()=>{
|
|
27
|
+
if (void 0 !== blockedError.current) throw blockedError.current;
|
|
28
|
+
};
|
|
29
|
+
const expectedRevision = ()=>{
|
|
30
|
+
assertWritable();
|
|
31
|
+
return parseDocumentRevision(observation.current.revision);
|
|
32
|
+
};
|
|
33
|
+
const adopt = (receipt)=>{
|
|
34
|
+
if (void 0 !== blockedError.current) return;
|
|
35
|
+
if (document?.id != null && receipt.documentId !== document.id) return;
|
|
36
|
+
observation.current.revision = parseDocumentRevision(receipt.revision);
|
|
37
|
+
setReceiptRevision(receipt.revision);
|
|
38
|
+
if (receipt.scheduledPublicationsNeedReconfirmation) setScheduleNotice(true);
|
|
39
|
+
};
|
|
40
|
+
const reportStructure = (result)=>{
|
|
41
|
+
const receipt = structuralReceipt(result);
|
|
42
|
+
if (receipt) adopt(receipt);
|
|
43
|
+
};
|
|
44
|
+
const report = (error)=>{
|
|
45
|
+
const kind = documentMutationIssue(error);
|
|
46
|
+
if (null === kind) return null;
|
|
47
|
+
if ('committed' === kind) {
|
|
48
|
+
if (void 0 !== blockedError.current) return 'blocked';
|
|
49
|
+
const details = getDocumentHookCommittedDetails(error) ?? committedTreeReceipt(error);
|
|
50
|
+
if (details) adopt(details);
|
|
51
|
+
setIssue(kind);
|
|
52
|
+
return 'committed';
|
|
53
|
+
}
|
|
54
|
+
blockedError.current = error;
|
|
55
|
+
setIssue(kind);
|
|
56
|
+
return 'blocked';
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
issue,
|
|
60
|
+
blocked: null !== issue && 'committed' !== issue,
|
|
61
|
+
scheduleNotice,
|
|
62
|
+
document: observation.current.identity,
|
|
63
|
+
revision: 'number' == typeof observation.current.revision ? observation.current.revision : void 0,
|
|
64
|
+
expectedRevision,
|
|
65
|
+
assertWritable,
|
|
66
|
+
adopt,
|
|
67
|
+
report,
|
|
68
|
+
reportStructure
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export { useDocumentMutationState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { DocumentMutationNotice } from "../document-mutation-notice.js";
|
|
4
|
+
import { useDocumentMutationState } from "../document-mutation-state.js";
|
|
3
5
|
import { useMemo, useState } from "react";
|
|
4
6
|
import { useRouterState } from "@tanstack/react-router";
|
|
5
7
|
import { deriveScheduledPublicationState } from "@byline/admin/react";
|
|
@@ -35,6 +37,7 @@ function ScheduledPublicationsView({ data }) {
|
|
|
35
37
|
select: (state)=>state.location
|
|
36
38
|
});
|
|
37
39
|
const search = location.search;
|
|
40
|
+
const mutation = useDocumentMutationState(void 0);
|
|
38
41
|
const [cancelling, setCancelling] = useState(null);
|
|
39
42
|
const applySearch = (patch)=>{
|
|
40
43
|
navigate({
|
|
@@ -46,13 +49,16 @@ function ScheduledPublicationsView({ data }) {
|
|
|
46
49
|
}
|
|
47
50
|
});
|
|
48
51
|
};
|
|
49
|
-
const cancel = async (collection, documentId)=>{
|
|
52
|
+
const cancel = async (collection, documentId, expectedRevision)=>{
|
|
53
|
+
if (mutation.blocked || cancelling) return;
|
|
50
54
|
setCancelling(documentId);
|
|
51
55
|
try {
|
|
56
|
+
mutation.assertWritable();
|
|
52
57
|
const result = await cancelCollectionDocumentScheduledPublish({
|
|
53
58
|
data: {
|
|
54
59
|
collection,
|
|
55
|
-
id: documentId
|
|
60
|
+
id: documentId,
|
|
61
|
+
expectedRevision
|
|
56
62
|
}
|
|
57
63
|
});
|
|
58
64
|
const cancelled = 'cancelled' === result.status;
|
|
@@ -71,9 +77,10 @@ function ScheduledPublicationsView({ data }) {
|
|
|
71
77
|
search: location.search
|
|
72
78
|
});
|
|
73
79
|
} catch (error) {
|
|
80
|
+
if (mutation.report(error)) return;
|
|
74
81
|
toastManager.add({
|
|
75
82
|
title: t('scheduledPublication.toast.failedTitle'),
|
|
76
|
-
description:
|
|
83
|
+
description: t('documentConcurrency.failed'),
|
|
77
84
|
data: {
|
|
78
85
|
intent: 'danger',
|
|
79
86
|
iconType: 'danger',
|
|
@@ -89,6 +96,9 @@ function ScheduledPublicationsView({ data }) {
|
|
|
89
96
|
return /*#__PURE__*/ jsx(Section, {
|
|
90
97
|
children: /*#__PURE__*/ jsxs(Container, {
|
|
91
98
|
children: [
|
|
99
|
+
/*#__PURE__*/ jsx(DocumentMutationNotice, {
|
|
100
|
+
issue: mutation.issue
|
|
101
|
+
}),
|
|
92
102
|
/*#__PURE__*/ jsxs("div", {
|
|
93
103
|
className: clsx('byline-scheduled-list-head', list_module.head),
|
|
94
104
|
children: [
|
|
@@ -264,9 +274,9 @@ function ScheduledPublicationsView({ data }) {
|
|
|
264
274
|
size: "xs",
|
|
265
275
|
type: "button",
|
|
266
276
|
intent: "danger",
|
|
267
|
-
disabled:
|
|
277
|
+
disabled: mutation.blocked || null !== cancelling,
|
|
268
278
|
"aria-label": t('scheduledPublication.actions.cancel'),
|
|
269
|
-
onClick: ()=>cancel(schedule.collectionPath, schedule.documentId),
|
|
279
|
+
onClick: ()=>cancel(schedule.collectionPath, schedule.documentId, schedule.revision),
|
|
270
280
|
children: t('common.actions.cancel')
|
|
271
281
|
})
|
|
272
282
|
})
|
|
@@ -53,7 +53,7 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
|
|
|
53
53
|
]
|
|
54
54
|
}),
|
|
55
55
|
auditColSpan: 4,
|
|
56
|
-
renderCells: ({ document, versionId, currentVersionId, actorLabel, restoreStatusLabel, openRestore })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
56
|
+
renderCells: ({ document, versionId, currentVersionId, actorLabel, restoreStatusLabel, mutationsBlocked, openRestore })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
57
57
|
children: [
|
|
58
58
|
/*#__PURE__*/ jsx(Table.Cell, {
|
|
59
59
|
className: "byline-singleton-history-status-cell",
|
|
@@ -77,6 +77,7 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
|
|
|
77
77
|
variant: "outlined",
|
|
78
78
|
size: "xs",
|
|
79
79
|
intent: "noeffect",
|
|
80
|
+
disabled: mutationsBlocked,
|
|
80
81
|
onClick: openRestore,
|
|
81
82
|
className: clsx('byline-coll-history-restore-button', history_module.restoreButton),
|
|
82
83
|
title: t('collections.history.restoreButtonTitle', {
|
|
@@ -143,8 +144,9 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
|
|
|
143
144
|
return historical;
|
|
144
145
|
},
|
|
145
146
|
onPageSizeChange: handlePageSizeChange,
|
|
146
|
-
restoreVersion: (versionId)=>restoreSingletonVersion({
|
|
147
|
+
restoreVersion: (versionId, expectedRevision)=>restoreSingletonVersion({
|
|
147
148
|
data: {
|
|
149
|
+
expectedRevision,
|
|
148
150
|
singleton,
|
|
149
151
|
versionId
|
|
150
152
|
}
|
|
@@ -5,7 +5,7 @@ type SingletonDocument = Record<string, any> & {
|
|
|
5
5
|
status: string;
|
|
6
6
|
fields: Record<string, any>;
|
|
7
7
|
};
|
|
8
|
-
export declare function SingletonView({ singletonDefinition, adminConfig, document, initialData, locale, contentLocales, defaultContentLocale, }: {
|
|
8
|
+
export declare function SingletonView({ singletonDefinition, adminConfig, document: loadedDocument, initialData, locale, contentLocales, defaultContentLocale, }: {
|
|
9
9
|
singletonDefinition: SingletonDefinition;
|
|
10
10
|
adminConfig?: SingletonAdminConfig;
|
|
11
11
|
document: SingletonDocument | null;
|