@byline/host-tanstack-start 4.17.0 → 4.19.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/chrome/router-pager.d.ts +7 -0
- package/dist/admin-shell/chrome/router-pager.js +13 -0
- package/dist/admin-shell/chrome/scroll-to-top.d.ts +20 -0
- package/dist/admin-shell/chrome/scroll-to-top.js +9 -0
- package/dist/admin-shell/collections/create.js +22 -5
- package/dist/admin-shell/collections/edit.js +85 -17
- package/dist/admin-shell/collections/restore-version-modal.js +14 -2
- package/dist/admin-shell/collections/save-navigation.test.d.ts +8 -0
- package/dist/admin-shell/singletons/view.js +10 -4
- package/dist/server-fns/collections/copy-to-locale.d.ts +2 -1
- package/dist/server-fns/collections/copy-to-locale.js +13 -6
- package/dist/server-fns/collections/create.d.ts +1 -1
- package/dist/server-fns/collections/create.js +15 -7
- package/dist/server-fns/collections/delete-locale.d.ts +2 -1
- package/dist/server-fns/collections/delete-locale.js +11 -4
- package/dist/server-fns/collections/duplicate.d.ts +2 -1
- package/dist/server-fns/collections/duplicate.js +10 -3
- package/dist/server-fns/collections/index.d.ts +1 -0
- package/dist/server-fns/collections/index.js +1 -0
- package/dist/server-fns/collections/restore-version.d.ts +1 -1
- package/dist/server-fns/collections/restore-version.js +17 -10
- package/dist/server-fns/collections/save-outcome.d.ts +23 -0
- package/dist/server-fns/collections/save-outcome.js +18 -0
- package/dist/server-fns/collections/update.d.ts +3 -1
- package/dist/server-fns/collections/update.js +17 -7
- package/dist/server-fns/singletons/copy-to-locale.js +14 -5
- package/dist/server-fns/singletons/restore-version.js +10 -1
- package/dist/server-fns/singletons/update.js +12 -5
- package/package.json +10 -10
- package/src/admin-shell/chrome/router-pager.tsx +22 -0
- package/src/admin-shell/chrome/scroll-to-top.test.ts +56 -0
- package/src/admin-shell/chrome/scroll-to-top.ts +28 -0
- package/src/admin-shell/collections/create.tsx +22 -3
- package/src/admin-shell/collections/edit.tsx +63 -10
- package/src/admin-shell/collections/restore-version-modal.tsx +11 -2
- package/src/admin-shell/collections/save-navigation.test.tsx +384 -0
- package/src/admin-shell/singletons/history.test.tsx +43 -0
- package/src/admin-shell/singletons/view.test.tsx +27 -0
- package/src/admin-shell/singletons/view.tsx +35 -16
- package/src/server-fns/collections/copy-to-locale.ts +43 -29
- package/src/server-fns/collections/create.ts +16 -8
- package/src/server-fns/collections/delete-locale.ts +38 -24
- package/src/server-fns/collections/duplicate.ts +38 -24
- package/src/server-fns/collections/index.ts +1 -0
- package/src/server-fns/collections/restore-version.ts +16 -9
- package/src/server-fns/collections/save-outcome.test.node.ts +77 -0
- package/src/server-fns/collections/save-outcome.ts +55 -0
- package/src/server-fns/collections/update.ts +23 -8
- package/src/server-fns/singletons/copy-to-locale.ts +16 -9
- package/src/server-fns/singletons/restore-version.ts +12 -3
- package/src/server-fns/singletons/singletons.test.node.ts +34 -0
- package/src/server-fns/singletons/update.ts +12 -5
|
@@ -13,6 +13,13 @@ interface RouterPageProps extends PaginationProps {
|
|
|
13
13
|
* A convenience pager wrapped around uikit's Pagination with `asChild`
|
|
14
14
|
* targets that delegate to TanStack Router's `<Link>`. `asChild` merges
|
|
15
15
|
* the existing button styles into the rendered Link.
|
|
16
|
+
*
|
|
17
|
+
* `smoothScrollToTop` is for a pager sitting *below* a long list: turning a
|
|
18
|
+
* page there should carry the reader back to the top rather than leaving them
|
|
19
|
+
* at the foot of a fresh page. It suppresses the router's own instant
|
|
20
|
+
* `resetScroll` and animates instead — so the component owes an actual
|
|
21
|
+
* scroll, which was the missing half: the flag disabled the reset and put
|
|
22
|
+
* nothing in its place, so a bottom pager did not scroll at all.
|
|
16
23
|
*/
|
|
17
24
|
export declare function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ...rest }: RouterPageProps): React.JSX.Element;
|
|
18
25
|
export {};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback } from "react";
|
|
2
3
|
import { useRouterState } from "@tanstack/react-router";
|
|
3
4
|
import { ChevronLeftDoubleIcon, ChevronLeftIcon, ChevronRightDoubleIcon, ChevronRightIcon, Pagination } from "@byline/ui/react";
|
|
4
5
|
import { Link } from "./loose-router.js";
|
|
6
|
+
import { scrollWindowToTop } from "./scroll-to-top.js";
|
|
5
7
|
function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ...rest }) {
|
|
6
8
|
const location = useRouterState({
|
|
7
9
|
select: (s)=>s.location
|
|
8
10
|
});
|
|
11
|
+
const handleScrollToTop = useCallback(()=>{
|
|
12
|
+
if (true !== smoothScrollToTop) return;
|
|
13
|
+
scrollWindowToTop();
|
|
14
|
+
}, [
|
|
15
|
+
smoothScrollToTop
|
|
16
|
+
]);
|
|
9
17
|
return /*#__PURE__*/ jsx(Pagination, {
|
|
10
18
|
variant: "dashboard",
|
|
11
19
|
...rest,
|
|
@@ -19,6 +27,7 @@ function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ..
|
|
|
19
27
|
return /*#__PURE__*/ jsx(Pagination.First, {
|
|
20
28
|
render: item.disabled ? /*#__PURE__*/ jsx("div", {}) : /*#__PURE__*/ jsx(Link, {
|
|
21
29
|
resetScroll: true !== smoothScrollToTop,
|
|
30
|
+
onClick: handleScrollToTop,
|
|
22
31
|
to: location.pathname,
|
|
23
32
|
search: params
|
|
24
33
|
}),
|
|
@@ -32,6 +41,7 @@ function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ..
|
|
|
32
41
|
return /*#__PURE__*/ jsx(Pagination.Previous, {
|
|
33
42
|
render: item.disabled ? /*#__PURE__*/ jsx("div", {}) : /*#__PURE__*/ jsx(Link, {
|
|
34
43
|
resetScroll: true !== smoothScrollToTop,
|
|
44
|
+
onClick: handleScrollToTop,
|
|
35
45
|
to: location.pathname,
|
|
36
46
|
search: params
|
|
37
47
|
}),
|
|
@@ -46,6 +56,7 @@ function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ..
|
|
|
46
56
|
return /*#__PURE__*/ jsx(Pagination.Number, {
|
|
47
57
|
render: item.disabled ? /*#__PURE__*/ jsx("div", {}) : /*#__PURE__*/ jsx(Link, {
|
|
48
58
|
resetScroll: true !== smoothScrollToTop,
|
|
59
|
+
onClick: handleScrollToTop,
|
|
49
60
|
to: location.pathname,
|
|
50
61
|
search: params
|
|
51
62
|
}),
|
|
@@ -61,6 +72,7 @@ function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ..
|
|
|
61
72
|
return /*#__PURE__*/ jsx(Pagination.Next, {
|
|
62
73
|
render: item.disabled ? /*#__PURE__*/ jsx("div", {}) : /*#__PURE__*/ jsx(Link, {
|
|
63
74
|
resetScroll: true !== smoothScrollToTop,
|
|
75
|
+
onClick: handleScrollToTop,
|
|
64
76
|
to: location.pathname,
|
|
65
77
|
search: params
|
|
66
78
|
}),
|
|
@@ -75,6 +87,7 @@ function RouterPager({ className, smoothScrollToTop, 'aria-label': ariaLabel, ..
|
|
|
75
87
|
return /*#__PURE__*/ jsx(Pagination.Last, {
|
|
76
88
|
render: item.disabled ? /*#__PURE__*/ jsx("div", {}) : /*#__PURE__*/ jsx(Link, {
|
|
77
89
|
resetScroll: true !== smoothScrollToTop,
|
|
90
|
+
onClick: handleScrollToTop,
|
|
78
91
|
to: location.pathname,
|
|
79
92
|
search: params
|
|
80
93
|
}),
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
* Return the reader to the top of the page.
|
|
10
|
+
*
|
|
11
|
+
* Separate from `RouterPager` so the behaviour can be asserted without
|
|
12
|
+
* mounting a router. `smoothScrollToTop` suppresses TanStack's instant
|
|
13
|
+
* `resetScroll` in favour of an animated one, which makes a missing
|
|
14
|
+
* implementation silent — the page simply does not move. That is what
|
|
15
|
+
* happened to every pager carrying the flag.
|
|
16
|
+
*
|
|
17
|
+
* Honours `prefers-reduced-motion`: those readers still arrive at the top,
|
|
18
|
+
* without the travel.
|
|
19
|
+
*/
|
|
20
|
+
export declare function scrollWindowToTop(): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
function scrollWindowToTop() {
|
|
2
|
+
if ("u" < typeof window) return;
|
|
3
|
+
const prefersReducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
4
|
+
window.scrollTo({
|
|
5
|
+
top: 0,
|
|
6
|
+
behavior: true === prefersReducedMotion ? 'auto' : 'smooth'
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export { scrollWindowToTop };
|
|
@@ -5,7 +5,7 @@ import { useTranslation } from "@byline/i18n/react";
|
|
|
5
5
|
import { Container, Section, useToastManager } from "@byline/ui/react";
|
|
6
6
|
import { getAdminRoutePath } from "../../routes/admin-path.js";
|
|
7
7
|
import { decodeListReturnState } from "../../routes/list-return-state.js";
|
|
8
|
-
import { createCollectionDocument } from "../../server-fns/collections/index.js";
|
|
8
|
+
import { createCollectionDocument, hasCommittedDocumentHookFailure } from "../../server-fns/collections/index.js";
|
|
9
9
|
import { useNavigate } from "../chrome/loose-router.js";
|
|
10
10
|
import { useTanStackNavigationGuard } from "./tanstack-navigation-guard.js";
|
|
11
11
|
const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
|
|
@@ -31,6 +31,17 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
|
|
|
31
31
|
} : {}
|
|
32
32
|
}
|
|
33
33
|
});
|
|
34
|
+
const hookFailed = hasCommittedDocumentHookFailure(result);
|
|
35
|
+
if (hookFailed) toastManager.add({
|
|
36
|
+
title: t('collections.save.hookFailedToast'),
|
|
37
|
+
description: t("collections.save.hookFailedDescription"),
|
|
38
|
+
data: {
|
|
39
|
+
intent: 'warning',
|
|
40
|
+
iconType: 'warning',
|
|
41
|
+
icon: true,
|
|
42
|
+
close: true
|
|
43
|
+
}
|
|
44
|
+
});
|
|
34
45
|
result?.documentId ? navigate({
|
|
35
46
|
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
36
47
|
params: {
|
|
@@ -38,9 +49,12 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
|
|
|
38
49
|
id: result.documentId
|
|
39
50
|
},
|
|
40
51
|
search: {
|
|
41
|
-
|
|
52
|
+
...hookFailed ? {} : {
|
|
53
|
+
action: 'created'
|
|
54
|
+
},
|
|
42
55
|
from
|
|
43
|
-
}
|
|
56
|
+
},
|
|
57
|
+
ignoreBlocker: true
|
|
44
58
|
}) : navigate({
|
|
45
59
|
to: getAdminRoutePath('collections', '$collection'),
|
|
46
60
|
params: {
|
|
@@ -48,8 +62,11 @@ const CreateView = ({ collectionDefinition, adminConfig, initialData, from })=>{
|
|
|
48
62
|
},
|
|
49
63
|
search: {
|
|
50
64
|
...decodeListReturnState(from),
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
...hookFailed ? {} : {
|
|
66
|
+
action: 'created'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
ignoreBlocker: true
|
|
53
70
|
});
|
|
54
71
|
} catch (err) {
|
|
55
72
|
console.error(err);
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from "@byline/i18n/react";
|
|
|
6
6
|
import { Container, Section, useToastManager } from "@byline/ui/react";
|
|
7
7
|
import { getAdminRoutePath } from "../../routes/admin-path.js";
|
|
8
8
|
import { clearListReturnState } from "../../routes/list-return-storage.js";
|
|
9
|
-
import { cancelCollectionDocumentScheduledPublish, confirmCollectionDocumentScheduledPublish, copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, hasDeleteSideEffectFailures, scheduleCollectionDocumentPublish, unpublishDocument, updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
|
|
9
|
+
import { cancelCollectionDocumentScheduledPublish, confirmCollectionDocumentScheduledPublish, copyDocumentToLocale, deleteDocument, deleteDocumentLocale, duplicateCollectionDocument, hasCommittedDocumentHookFailure, hasDeleteSideEffectFailures, scheduleCollectionDocumentPublish, unpublishDocument, updateCollectionDocumentSystemFields, updateCollectionDocumentWithPatches, updateDocumentStatus } from "../../server-fns/collections/index.js";
|
|
10
10
|
import { useNavigate } from "../chrome/loose-router.js";
|
|
11
11
|
import { useTanStackNavigationGuard } from "./tanstack-navigation-guard.js";
|
|
12
12
|
import { ViewMenu } from "./view-menu.js";
|
|
@@ -43,6 +43,23 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
};
|
|
46
|
+
const notifyCommittedHookFailure = ()=>{
|
|
47
|
+
const description = t("collections.save.hookFailedDescription");
|
|
48
|
+
toastManager.add({
|
|
49
|
+
title: t('collections.save.hookFailedToast'),
|
|
50
|
+
description,
|
|
51
|
+
data: {
|
|
52
|
+
intent: 'warning',
|
|
53
|
+
iconType: 'warning',
|
|
54
|
+
icon: true,
|
|
55
|
+
close: true
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
setEditState({
|
|
59
|
+
status: 'warning',
|
|
60
|
+
message: description
|
|
61
|
+
});
|
|
62
|
+
};
|
|
46
63
|
const handleLocaleChange = (newLocale)=>{
|
|
47
64
|
navigate({
|
|
48
65
|
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
@@ -300,6 +317,20 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
300
317
|
id: String(initialData.id)
|
|
301
318
|
}
|
|
302
319
|
});
|
|
320
|
+
if (hasCommittedDocumentHookFailure(result)) {
|
|
321
|
+
notifyCommittedHookFailure();
|
|
322
|
+
navigate({
|
|
323
|
+
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
324
|
+
params: {
|
|
325
|
+
collection: path,
|
|
326
|
+
id: result.documentId
|
|
327
|
+
},
|
|
328
|
+
search: (prev)=>({
|
|
329
|
+
...prev
|
|
330
|
+
})
|
|
331
|
+
});
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
303
334
|
const description = result.pathRetried ? t("collections.edit.duplicatedAutoPathDescription", {
|
|
304
335
|
path: result.newPath
|
|
305
336
|
}) : t("collections.edit.duplicatedPathDescription", {
|
|
@@ -367,6 +398,22 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
367
398
|
overwrite
|
|
368
399
|
}
|
|
369
400
|
});
|
|
401
|
+
if (hasCommittedDocumentHookFailure(result)) {
|
|
402
|
+
notifyCommittedHookFailure();
|
|
403
|
+
notifyScheduleSuspended();
|
|
404
|
+
navigate({
|
|
405
|
+
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
406
|
+
params: {
|
|
407
|
+
collection: path,
|
|
408
|
+
id: String(initialData.id)
|
|
409
|
+
},
|
|
410
|
+
search: (prev)=>({
|
|
411
|
+
...prev,
|
|
412
|
+
locale: targetLocale
|
|
413
|
+
})
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
370
417
|
const sourceLabel = contentLocales.find((l)=>l.code === result.sourceLocale)?.label ?? result.sourceLocale;
|
|
371
418
|
const targetLabel = contentLocales.find((l)=>l.code === result.targetLocale)?.label ?? result.targetLocale;
|
|
372
419
|
const description = result.fieldsUpdated > 0 ? t("collections.edit.copiedFieldsDescription", {
|
|
@@ -440,6 +487,22 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
440
487
|
locale: targetLocale
|
|
441
488
|
}
|
|
442
489
|
});
|
|
490
|
+
if (hasCommittedDocumentHookFailure(result)) {
|
|
491
|
+
notifyCommittedHookFailure();
|
|
492
|
+
notifyScheduleSuspended();
|
|
493
|
+
navigate({
|
|
494
|
+
to: getAdminRoutePath('collections', '$collection', '$id'),
|
|
495
|
+
params: {
|
|
496
|
+
collection: path,
|
|
497
|
+
id: String(initialData.id)
|
|
498
|
+
},
|
|
499
|
+
search: (prev)=>({
|
|
500
|
+
...prev,
|
|
501
|
+
locale: defaultContentLocale
|
|
502
|
+
})
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
443
506
|
const targetLabel = contentLocales.find((l)=>l.code === result.locale)?.label ?? result.locale;
|
|
444
507
|
const description = t("collections.edit.deletedLocaleDescription", {
|
|
445
508
|
label: singular,
|
|
@@ -523,7 +586,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
523
586
|
}
|
|
524
587
|
});
|
|
525
588
|
setEditState({
|
|
526
|
-
status: 'success',
|
|
589
|
+
status: hasSideEffectFailures ? 'warning' : 'success',
|
|
527
590
|
message: description
|
|
528
591
|
});
|
|
529
592
|
clearListReturnState(path, String(initialData.id));
|
|
@@ -559,6 +622,7 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
559
622
|
};
|
|
560
623
|
const handleSubmit = async ({ data: _data, patches, contentDirty, pathDirty, systemPath, availableLocalesDirty, systemAvailableLocales })=>{
|
|
561
624
|
try {
|
|
625
|
+
let hookFailed = false;
|
|
562
626
|
if (pathDirty || availableLocalesDirty) await updateCollectionDocumentSystemFields({
|
|
563
627
|
data: {
|
|
564
628
|
collection: path,
|
|
@@ -572,33 +636,36 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
572
636
|
} : {}
|
|
573
637
|
}
|
|
574
638
|
});
|
|
575
|
-
if (contentDirty)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
639
|
+
if (contentDirty) {
|
|
640
|
+
const result = await updateCollectionDocumentWithPatches({
|
|
641
|
+
data: {
|
|
642
|
+
collection: path,
|
|
643
|
+
id: String(initialData.id),
|
|
644
|
+
patches,
|
|
645
|
+
versionId: initialData.versionId,
|
|
646
|
+
locale
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
hookFailed = hasCommittedDocumentHookFailure(result);
|
|
650
|
+
}
|
|
584
651
|
if (contentDirty && scheduledPublication?.state === 'armed') notifyScheduleSuspended();
|
|
585
|
-
const description = t("collections.edit.updatedDescription", {
|
|
652
|
+
const description = hookFailed ? t("collections.save.hookFailedDescription") : t("collections.edit.updatedDescription", {
|
|
586
653
|
label: singularLower
|
|
587
654
|
});
|
|
588
655
|
toastManager.add({
|
|
589
|
-
title: t('collections.edit.updateTitle', {
|
|
656
|
+
title: hookFailed ? t('collections.save.hookFailedToast') : t('collections.edit.updateTitle', {
|
|
590
657
|
label: singular
|
|
591
658
|
}),
|
|
592
659
|
description,
|
|
593
660
|
data: {
|
|
594
|
-
intent: 'success',
|
|
595
|
-
iconType: 'success',
|
|
661
|
+
intent: hookFailed ? 'warning' : 'success',
|
|
662
|
+
iconType: hookFailed ? 'warning' : 'success',
|
|
596
663
|
icon: true,
|
|
597
664
|
close: true
|
|
598
665
|
}
|
|
599
666
|
});
|
|
600
667
|
setEditState({
|
|
601
|
-
status: 'success',
|
|
668
|
+
status: hookFailed ? 'warning' : 'success',
|
|
602
669
|
message: description
|
|
603
670
|
});
|
|
604
671
|
navigate({
|
|
@@ -609,7 +676,8 @@ const EditView = ({ collectionDefinition, adminConfig, initialData, locale, cont
|
|
|
609
676
|
},
|
|
610
677
|
search: (prev)=>({
|
|
611
678
|
...prev
|
|
612
|
-
})
|
|
679
|
+
}),
|
|
680
|
+
ignoreBlocker: true
|
|
613
681
|
});
|
|
614
682
|
} catch (err) {
|
|
615
683
|
console.error('Network error:', err);
|
|
@@ -2,11 +2,13 @@
|
|
|
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
9
|
function RestoreVersionModal({ 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() {
|
|
@@ -14,7 +16,17 @@ function RestoreVersionModal({ versionLabel, versionNumber, restoreStatusLabel,
|
|
|
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) {
|
|
@@ -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 {};
|
|
@@ -6,6 +6,7 @@ import { ErrorCodes, getDefaultStatus, getWorkflow, getWorkflowStatuses, validat
|
|
|
6
6
|
import { useTranslation } from "@byline/i18n/react";
|
|
7
7
|
import { Container, Section, useToastManager } from "@byline/ui/react";
|
|
8
8
|
import { getAdminRoutePath } from "../../routes/admin-path.js";
|
|
9
|
+
import { hasCommittedDocumentHookFailure } from "../../server-fns/collections/index.js";
|
|
9
10
|
import { cancelSingletonScheduledPublish, changeSingletonStatus, confirmSingletonScheduledPublish, copySingletonToLocale, scheduleSingletonPublish, unpublishSingleton, updateSingleton } from "../../server-fns/singletons/index.js";
|
|
10
11
|
import { useNavigate } from "../chrome/loose-router.js";
|
|
11
12
|
import { useTanStackNavigationGuard } from "../collections/tanstack-navigation-guard.js";
|
|
@@ -49,9 +50,12 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
49
50
|
if (scheduledPublication?.state !== 'armed') return;
|
|
50
51
|
toast(t('scheduledPublication.toast.suspendedTitle'), t("scheduledPublication.toast.suspendedDescription"), 'warning');
|
|
51
52
|
};
|
|
53
|
+
const notifyCommittedHookFailure = ()=>{
|
|
54
|
+
toast(t('collections.save.hookFailedToast'), t("collections.save.hookFailedDescription"), 'warning');
|
|
55
|
+
};
|
|
52
56
|
const handleSubmit = async ({ data })=>{
|
|
53
57
|
try {
|
|
54
|
-
await updateSingleton({
|
|
58
|
+
const result = await updateSingleton({
|
|
55
59
|
data: {
|
|
56
60
|
singleton: path,
|
|
57
61
|
data,
|
|
@@ -60,7 +64,8 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
60
64
|
}
|
|
61
65
|
});
|
|
62
66
|
if (null != document) notifyScheduleSuspended();
|
|
63
|
-
|
|
67
|
+
if (hasCommittedDocumentHookFailure(result)) notifyCommittedHookFailure();
|
|
68
|
+
else toast(t('singletons.edit.updateTitle', {
|
|
64
69
|
label
|
|
65
70
|
}), t("singletons.edit.updatedDescription", {
|
|
66
71
|
label: label.toLowerCase()
|
|
@@ -122,7 +127,7 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
122
127
|
};
|
|
123
128
|
const handleCopyToLocale = async ({ targetLocale, overwrite })=>{
|
|
124
129
|
try {
|
|
125
|
-
await copySingletonToLocale({
|
|
130
|
+
const result = await copySingletonToLocale({
|
|
126
131
|
data: {
|
|
127
132
|
singleton: path,
|
|
128
133
|
sourceLocale: locale ?? defaultContentLocale,
|
|
@@ -133,7 +138,8 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
133
138
|
const sourceLocale = locale ?? defaultContentLocale;
|
|
134
139
|
const sourceLabel = contentLocales.find((entry)=>entry.code === sourceLocale)?.label ?? sourceLocale;
|
|
135
140
|
const targetLabel = contentLocales.find((entry)=>entry.code === targetLocale)?.label ?? targetLocale;
|
|
136
|
-
|
|
141
|
+
if (hasCommittedDocumentHookFailure(result)) notifyCommittedHookFailure();
|
|
142
|
+
else toast(t('collections.edit.copyToLocaleTitle', {
|
|
137
143
|
label
|
|
138
144
|
}), t('collections.edit.copiedSuccessMessage', {
|
|
139
145
|
source: sourceLabel,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { CopyToLocaleResult } from '@byline/core/services';
|
|
9
|
+
import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
|
|
9
10
|
/**
|
|
10
11
|
* Copy a document's content from `sourceLocale` into `targetLocale` on
|
|
11
12
|
* the same document. Mirrors the thin-wrapper pattern of the other
|
|
@@ -27,4 +28,4 @@ export declare const copyDocumentToLocale: import("@tanstack/react-start").Requi
|
|
|
27
28
|
sourceLocale: string;
|
|
28
29
|
targetLocale: string;
|
|
29
30
|
overwrite: boolean;
|
|
30
|
-
}, Promise<CopyToLocaleResult>>;
|
|
31
|
+
}, Promise<CollectionDocumentCommittedHookFailureResponse | CopyToLocaleResult>>;
|
|
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
|
|
|
3
3
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
4
4
|
import { copyToLocale } from "@byline/core/services";
|
|
5
5
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
|
+
import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
|
|
6
7
|
const copyDocumentToLocale = createServerFn({
|
|
7
8
|
method: 'POST'
|
|
8
9
|
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
@@ -27,11 +28,17 @@ const copyDocumentToLocale = createServerFn({
|
|
|
27
28
|
slugifier: serverConfig.slugifier,
|
|
28
29
|
requestContext: await getAdminRequestContext()
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
try {
|
|
32
|
+
return await copyToLocale(ctx, {
|
|
33
|
+
documentId: id,
|
|
34
|
+
sourceLocale,
|
|
35
|
+
targetLocale,
|
|
36
|
+
overwrite
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error);
|
|
40
|
+
if (null != committedFailure) return committedFailure;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
36
43
|
});
|
|
37
44
|
export { copyDocumentToLocale };
|
|
@@ -17,7 +17,7 @@ export declare const createCollectionDocument: import("@tanstack/react-start").R
|
|
|
17
17
|
locale?: string;
|
|
18
18
|
path?: string;
|
|
19
19
|
availableLocales?: string[];
|
|
20
|
-
}, Promise<{
|
|
20
|
+
}, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
|
|
21
21
|
status: 'ok';
|
|
22
22
|
documentId: string;
|
|
23
23
|
documentVersionId: string;
|
|
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
|
|
|
3
3
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
4
4
|
import { createDocument } from "@byline/core/services";
|
|
5
5
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
|
+
import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
|
|
6
7
|
const createCollectionDocument = createServerFn({
|
|
7
8
|
method: 'POST'
|
|
8
9
|
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
@@ -27,13 +28,20 @@ const createCollectionDocument = createServerFn({
|
|
|
27
28
|
slugifier: serverConfig.slugifier,
|
|
28
29
|
requestContext: await getAdminRequestContext()
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
let result;
|
|
32
|
+
try {
|
|
33
|
+
result = await createDocument(ctx, {
|
|
34
|
+
data: structuredClone(documentData),
|
|
35
|
+
status: documentData.status,
|
|
36
|
+
locale: locale ?? serverConfig.i18n.content.defaultLocale,
|
|
37
|
+
path: explicitPath,
|
|
38
|
+
availableLocales
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error);
|
|
42
|
+
if (null != committedFailure) return committedFailure;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
37
45
|
return {
|
|
38
46
|
status: 'ok',
|
|
39
47
|
documentId: result.documentId,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { DeleteLocaleResult } from '@byline/core/services';
|
|
9
|
+
import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
|
|
9
10
|
/**
|
|
10
11
|
* Remove a single (non-default) content locale from a document, leaving every
|
|
11
12
|
* other locale untouched. Mirrors the thin-wrapper pattern of the other
|
|
@@ -24,4 +25,4 @@ export declare const deleteDocumentLocale: import("@tanstack/react-start").Requi
|
|
|
24
25
|
collection: string;
|
|
25
26
|
id: string;
|
|
26
27
|
locale: string;
|
|
27
|
-
}, Promise<DeleteLocaleResult>>;
|
|
28
|
+
}, Promise<CollectionDocumentCommittedHookFailureResponse | DeleteLocaleResult>>;
|
|
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
|
|
|
3
3
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
4
4
|
import { deleteLocale } from "@byline/core/services";
|
|
5
5
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
|
+
import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
|
|
6
7
|
const deleteDocumentLocale = createServerFn({
|
|
7
8
|
method: 'POST'
|
|
8
9
|
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
@@ -27,9 +28,15 @@ const deleteDocumentLocale = createServerFn({
|
|
|
27
28
|
slugifier: serverConfig.slugifier,
|
|
28
29
|
requestContext: await getAdminRequestContext()
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
try {
|
|
32
|
+
return await deleteLocale(ctx, {
|
|
33
|
+
documentId: id,
|
|
34
|
+
locale
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error);
|
|
38
|
+
if (null != committedFailure) return committedFailure;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
34
41
|
});
|
|
35
42
|
export { deleteDocumentLocale };
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { DuplicateDocumentResult } from '@byline/core/services';
|
|
9
|
+
import { type CollectionDocumentCommittedHookFailureResponse } from './save-outcome.js';
|
|
9
10
|
/**
|
|
10
11
|
* Duplicate an existing document — clones all locales into a brand-new
|
|
11
12
|
* document in one atomic write. Returns the new document's id so the UI
|
|
@@ -23,4 +24,4 @@ export declare const duplicateCollectionDocument: import("@tanstack/react-start"
|
|
|
23
24
|
}) => {
|
|
24
25
|
collection: string;
|
|
25
26
|
id: string;
|
|
26
|
-
}, Promise<DuplicateDocumentResult>>;
|
|
27
|
+
}, Promise<CollectionDocumentCommittedHookFailureResponse | DuplicateDocumentResult>>;
|
|
@@ -3,6 +3,7 @@ import { getAdminRequestContext } from "@byline/client/server";
|
|
|
3
3
|
import { ERR_NOT_FOUND, getLogger, getServerConfig } from "@byline/core";
|
|
4
4
|
import { duplicateDocument } from "@byline/core/services";
|
|
5
5
|
import { ensureCollection } from "../../integrations/api-utils.js";
|
|
6
|
+
import { toCommittedDocumentHookFailureResponse } from "./save-outcome.js";
|
|
6
7
|
const duplicateCollectionDocument = createServerFn({
|
|
7
8
|
method: 'POST'
|
|
8
9
|
}).validator((input)=>input).handler(async ({ data: input })=>{
|
|
@@ -27,8 +28,14 @@ const duplicateCollectionDocument = createServerFn({
|
|
|
27
28
|
slugifier: serverConfig.slugifier,
|
|
28
29
|
requestContext: await getAdminRequestContext()
|
|
29
30
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
try {
|
|
32
|
+
return await duplicateDocument(ctx, {
|
|
33
|
+
sourceDocumentId
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const committedFailure = toCommittedDocumentHookFailureResponse(error);
|
|
37
|
+
if (null != committedFailure) return committedFailure;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
33
40
|
});
|
|
34
41
|
export { duplicateCollectionDocument };
|
|
@@ -17,6 +17,7 @@ export * from './list';
|
|
|
17
17
|
export * from './reindex';
|
|
18
18
|
export * from './reorder';
|
|
19
19
|
export * from './restore-version';
|
|
20
|
+
export * from './save-outcome';
|
|
20
21
|
export * from './scheduled-publication';
|
|
21
22
|
export * from './set-list-view-preference';
|
|
22
23
|
export * from './stats';
|
|
@@ -11,6 +11,7 @@ export * from "./list.js";
|
|
|
11
11
|
export * from "./reindex.js";
|
|
12
12
|
export * from "./reorder.js";
|
|
13
13
|
export * from "./restore-version.js";
|
|
14
|
+
export * from "./save-outcome.js";
|
|
14
15
|
export * from "./scheduled-publication.js";
|
|
15
16
|
export * from "./set-list-view-preference.js";
|
|
16
17
|
export * from "./stats.js";
|
|
@@ -13,7 +13,7 @@ export declare const restoreDocumentVersion: import("@tanstack/react-start").Req
|
|
|
13
13
|
collection: string;
|
|
14
14
|
id: string;
|
|
15
15
|
versionId: string;
|
|
16
|
-
}, Promise<{
|
|
16
|
+
}, Promise<import("./save-outcome.js").CollectionDocumentCommittedHookFailureResponse | {
|
|
17
17
|
status: 'ok';
|
|
18
18
|
documentId: string;
|
|
19
19
|
documentVersionId: string;
|