@byline/host-tanstack-start 4.16.0 → 4.17.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.
Files changed (146) hide show
  1. package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -14
  2. package/dist/admin-shell/chrome/dashboard.js +41 -5
  3. package/dist/admin-shell/chrome/dashboard.test.d.ts +8 -0
  4. package/dist/admin-shell/collections/api.d.ts +2 -2
  5. package/dist/admin-shell/collections/create.d.ts +2 -2
  6. package/dist/admin-shell/collections/create.js +1 -0
  7. package/dist/admin-shell/collections/edit.d.ts +2 -2
  8. package/dist/admin-shell/collections/edit.js +1 -0
  9. package/dist/admin-shell/collections/history.d.ts +4 -12
  10. package/dist/admin-shell/collections/history.js +112 -303
  11. package/dist/admin-shell/collections/reindex-button.d.ts +1 -1
  12. package/dist/admin-shell/collections/reindex-button.js +5 -1
  13. package/dist/admin-shell/collections/restore-version-modal.d.ts +4 -4
  14. package/dist/admin-shell/collections/restore-version-modal.js +8 -24
  15. package/dist/admin-shell/collections/version-history.d.ts +72 -0
  16. package/dist/admin-shell/collections/version-history.js +259 -0
  17. package/dist/admin-shell/singletons/history.d.ts +19 -0
  18. package/dist/admin-shell/singletons/history.js +170 -0
  19. package/dist/admin-shell/singletons/history.test.d.ts +8 -0
  20. package/dist/admin-shell/singletons/preview-link.d.ts +12 -0
  21. package/dist/admin-shell/singletons/preview-link.js +54 -0
  22. package/dist/admin-shell/singletons/view-menu.d.ts +13 -0
  23. package/dist/admin-shell/singletons/view-menu.js +116 -0
  24. package/dist/admin-shell/singletons/view.d.ts +18 -0
  25. package/dist/admin-shell/singletons/view.js +259 -0
  26. package/dist/admin-shell/singletons/view.test.d.ts +8 -0
  27. package/dist/integrations/api-utils.d.ts +18 -4
  28. package/dist/integrations/api-utils.js +10 -3
  29. package/dist/routes/create-admin-dashboard-route.js +2 -2
  30. package/dist/routes/create-collection-api-route.js +3 -3
  31. package/dist/routes/create-collection-create-route.js +4 -3
  32. package/dist/routes/create-collection-edit-route.js +4 -3
  33. package/dist/routes/create-collection-history-route.js +4 -3
  34. package/dist/routes/create-collection-list-route.js +4 -3
  35. package/dist/routes/create-singleton-history-route.d.ts +8 -0
  36. package/dist/routes/create-singleton-history-route.js +99 -0
  37. package/dist/routes/create-singleton-route.d.ts +35 -0
  38. package/dist/routes/create-singleton-route.js +130 -0
  39. package/dist/routes/get-multi-collection-definition.d.ts +10 -0
  40. package/dist/routes/get-multi-collection-definition.js +6 -0
  41. package/dist/routes/get-singleton-definition.d.ts +10 -0
  42. package/dist/routes/get-singleton-definition.js +6 -0
  43. package/dist/routes/index.d.ts +2 -0
  44. package/dist/routes/index.js +2 -0
  45. package/dist/server-fns/actors.d.ts +16 -0
  46. package/dist/server-fns/actors.js +25 -0
  47. package/dist/server-fns/admin-document-presentation.d.ts +30 -0
  48. package/dist/server-fns/admin-document-presentation.js +32 -0
  49. package/dist/server-fns/collections/actors.d.ts +1 -10
  50. package/dist/server-fns/collections/actors.js +1 -25
  51. package/dist/server-fns/collections/get.js +9 -26
  52. package/dist/server-fns/collections/reorder.js +1 -1
  53. package/dist/server-fns/collections/scheduled-publication.js +2 -2
  54. package/dist/server-fns/collections/upload.js +3 -3
  55. package/dist/server-fns/collections/utils.d.ts +1 -7
  56. package/dist/server-fns/collections/utils.js +1 -4
  57. package/dist/server-fns/serialise.d.ts +14 -0
  58. package/dist/server-fns/serialise.js +4 -0
  59. package/dist/server-fns/singleton-document-read.d.ts +20 -0
  60. package/dist/server-fns/singleton-document-read.js +10 -0
  61. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +12 -0
  62. package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -0
  63. package/dist/server-fns/singletons/change-status.d.ts +14 -0
  64. package/dist/server-fns/singletons/change-status.js +7 -0
  65. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +14 -0
  66. package/dist/server-fns/singletons/confirm-scheduled-publish.js +9 -0
  67. package/dist/server-fns/singletons/copy-to-locale.d.ts +18 -0
  68. package/dist/server-fns/singletons/copy-to-locale.js +11 -0
  69. package/dist/server-fns/singletons/find-by-version.d.ts +16 -0
  70. package/dist/server-fns/singletons/find-by-version.js +9 -0
  71. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +12 -0
  72. package/dist/server-fns/singletons/get-scheduled-publish.js +7 -0
  73. package/dist/server-fns/singletons/get.d.ts +24 -0
  74. package/dist/server-fns/singletons/get.js +6 -0
  75. package/dist/server-fns/singletons/history.d.ts +31 -0
  76. package/dist/server-fns/singletons/history.js +22 -0
  77. package/dist/server-fns/singletons/index.d.ts +19 -0
  78. package/dist/server-fns/singletons/index.js +12 -0
  79. package/dist/server-fns/singletons/restore-version.d.ts +14 -0
  80. package/dist/server-fns/singletons/restore-version.js +7 -0
  81. package/dist/server-fns/singletons/schedule-publish.d.ts +16 -0
  82. package/dist/server-fns/singletons/schedule-publish.js +10 -0
  83. package/dist/server-fns/singletons/unpublish.d.ts +12 -0
  84. package/dist/server-fns/singletons/unpublish.js +7 -0
  85. package/dist/server-fns/singletons/update.d.ts +14 -0
  86. package/dist/server-fns/singletons/update.js +14 -0
  87. package/dist/vitest-jsdom.test.d.ts +8 -0
  88. package/package.json +24 -18
  89. package/src/admin-shell/chrome/admin-menu-drawer.tsx +13 -13
  90. package/src/admin-shell/chrome/dashboard.test.tsx +199 -0
  91. package/src/admin-shell/chrome/dashboard.tsx +43 -9
  92. package/src/admin-shell/collections/api.tsx +2 -2
  93. package/src/admin-shell/collections/create.tsx +6 -2
  94. package/src/admin-shell/collections/edit.tsx +6 -2
  95. package/src/admin-shell/collections/history.module.css +1 -1
  96. package/src/admin-shell/collections/history.tsx +162 -442
  97. package/src/admin-shell/collections/reindex-button.tsx +4 -2
  98. package/src/admin-shell/collections/restore-version-modal.tsx +19 -25
  99. package/src/admin-shell/collections/version-history.tsx +349 -0
  100. package/src/admin-shell/singletons/history.test.tsx +407 -0
  101. package/src/admin-shell/singletons/history.tsx +185 -0
  102. package/src/admin-shell/singletons/preview-link.tsx +75 -0
  103. package/src/admin-shell/singletons/view-menu.tsx +136 -0
  104. package/src/admin-shell/singletons/view.test.tsx +400 -0
  105. package/src/admin-shell/singletons/view.tsx +351 -0
  106. package/src/integrations/api-utils.ts +34 -9
  107. package/src/routes/create-admin-dashboard-route.test.node.ts +70 -0
  108. package/src/routes/create-admin-dashboard-route.tsx +10 -2
  109. package/src/routes/create-collection-api-route.tsx +4 -4
  110. package/src/routes/create-collection-create-route.tsx +5 -8
  111. package/src/routes/create-collection-edit-route.tsx +5 -4
  112. package/src/routes/create-collection-history-route.tsx +5 -8
  113. package/src/routes/create-collection-list-route.tsx +5 -8
  114. package/src/routes/create-singleton-history-route.test.node.ts +148 -0
  115. package/src/routes/create-singleton-history-route.tsx +116 -0
  116. package/src/routes/create-singleton-route.test.node.ts +172 -0
  117. package/src/routes/create-singleton-route.tsx +176 -0
  118. package/src/routes/get-multi-collection-definition.ts +16 -0
  119. package/src/routes/get-singleton-definition.ts +16 -0
  120. package/src/routes/index.ts +2 -0
  121. package/src/server-fns/actors.ts +43 -0
  122. package/src/server-fns/admin-document-presentation.ts +78 -0
  123. package/src/server-fns/collections/actors.ts +1 -52
  124. package/src/server-fns/collections/get.ts +10 -49
  125. package/src/server-fns/collections/reorder.ts +1 -1
  126. package/src/server-fns/collections/scheduled-publication.ts +7 -1
  127. package/src/server-fns/collections/upload-singleton.test.node.ts +164 -0
  128. package/src/server-fns/collections/upload.ts +7 -3
  129. package/src/server-fns/collections/utils.ts +1 -9
  130. package/src/server-fns/serialise.ts +17 -0
  131. package/src/server-fns/singleton-document-read.ts +32 -0
  132. package/src/server-fns/singletons/cancel-scheduled-publish.ts +19 -0
  133. package/src/server-fns/singletons/change-status.ts +19 -0
  134. package/src/server-fns/singletons/confirm-scheduled-publish.ts +23 -0
  135. package/src/server-fns/singletons/copy-to-locale.ts +32 -0
  136. package/src/server-fns/singletons/find-by-version.ts +23 -0
  137. package/src/server-fns/singletons/get-scheduled-publish.ts +19 -0
  138. package/src/server-fns/singletons/get.ts +32 -0
  139. package/src/server-fns/singletons/history.ts +41 -0
  140. package/src/server-fns/singletons/index.ts +20 -0
  141. package/src/server-fns/singletons/restore-version.ts +19 -0
  142. package/src/server-fns/singletons/schedule-publish.ts +24 -0
  143. package/src/server-fns/singletons/singletons.test.node.ts +298 -0
  144. package/src/server-fns/singletons/unpublish.ts +19 -0
  145. package/src/server-fns/singletons/update.ts +31 -0
  146. package/src/vitest-jsdom.test.tsx +27 -0
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
4
+ import { useTranslation } from "@byline/i18n/react";
5
+ import { Button, HistoryIcon, IconButton, Label, Select } from "@byline/ui/react";
6
+ import clsx from "clsx";
7
+ import { getAdminRoutePath } from "../../routes/admin-path.js";
8
+ import { useNavigate } from "../chrome/loose-router.js";
9
+ import view_menu_module from "../collections/view-menu.module.js";
10
+ import { SingletonPreviewLink } from "./preview-link.js";
11
+ function SingletonViewMenu({ singleton, activeView, locale, contentLocales, defaultContentLocale, adminConfig, doc }) {
12
+ const navigate = useNavigate();
13
+ const { t } = useTranslation('byline-admin');
14
+ useEffect(()=>{
15
+ if ('edit' === activeView && 'all' === locale) navigate({
16
+ to: getAdminRoutePath('singletons', '$singleton'),
17
+ params: {
18
+ singleton
19
+ },
20
+ search: {
21
+ locale: defaultContentLocale
22
+ },
23
+ replace: true
24
+ });
25
+ }, [
26
+ activeView,
27
+ locale,
28
+ singleton,
29
+ defaultContentLocale,
30
+ navigate
31
+ ]);
32
+ const handleLocaleChange = (value)=>{
33
+ if (null == value) return;
34
+ navigate({
35
+ to: 'history' === activeView ? getAdminRoutePath('singletons', '$singleton', 'history') : getAdminRoutePath('singletons', '$singleton'),
36
+ params: {
37
+ singleton
38
+ },
39
+ search: {
40
+ locale: value
41
+ }
42
+ });
43
+ };
44
+ return /*#__PURE__*/ jsxs("div", {
45
+ className: clsx('byline-view-menu', view_menu_module.root),
46
+ children: [
47
+ /*#__PURE__*/ jsx(Label, {
48
+ className: clsx('muted byline-view-menu-label', view_menu_module.label),
49
+ id: "contentLocaleLabel",
50
+ htmlFor: "contentLocale",
51
+ label: t('collections.viewMenu.contentLocaleLabel')
52
+ }),
53
+ /*#__PURE__*/ jsx(Select, {
54
+ name: "contentLocale",
55
+ id: "contentLocale",
56
+ className: clsx('byline-view-menu-locale-select', view_menu_module.localeSelect),
57
+ size: "xs",
58
+ variant: "outlined",
59
+ value: locale ?? defaultContentLocale,
60
+ items: [
61
+ ...'history' === activeView ? [
62
+ {
63
+ value: 'all',
64
+ label: t('collections.viewMenu.localeAll')
65
+ }
66
+ ] : [],
67
+ ...contentLocales.map((entry)=>({
68
+ value: entry.code,
69
+ label: entry.label
70
+ }))
71
+ ],
72
+ onValueChange: handleLocaleChange
73
+ }),
74
+ adminConfig?.preview != null && null != doc && /*#__PURE__*/ jsx(SingletonPreviewLink, {
75
+ doc: doc,
76
+ adminConfig: adminConfig,
77
+ locale: locale,
78
+ className: clsx('byline-view-menu-icon-button', view_menu_module.iconButton)
79
+ }),
80
+ /*#__PURE__*/ jsx(IconButton, {
81
+ className: clsx('byline-view-menu-icon-button', view_menu_module.iconButton),
82
+ size: "xs",
83
+ variant: 'history' === activeView ? 'filled' : 'text',
84
+ "aria-label": t('collections.breadcrumbs.history'),
85
+ onClick: ()=>navigate({
86
+ to: getAdminRoutePath('singletons', '$singleton', 'history'),
87
+ params: {
88
+ singleton
89
+ },
90
+ search: locale ? {
91
+ locale
92
+ } : {}
93
+ }),
94
+ children: /*#__PURE__*/ jsx(HistoryIcon, {
95
+ className: clsx('byline-view-menu-icon-button-icon', view_menu_module.iconButtonIcon)
96
+ })
97
+ }),
98
+ /*#__PURE__*/ jsx(Button, {
99
+ size: "xs",
100
+ variant: 'edit' === activeView ? 'filled' : 'outlined',
101
+ className: clsx('byline-view-menu-button', view_menu_module.button),
102
+ onClick: ()=>navigate({
103
+ to: getAdminRoutePath('singletons', '$singleton'),
104
+ params: {
105
+ singleton
106
+ },
107
+ search: locale ? {
108
+ locale
109
+ } : {}
110
+ }),
111
+ children: t('common.actions.edit')
112
+ })
113
+ ]
114
+ });
115
+ }
116
+ export { SingletonViewMenu };
@@ -0,0 +1,18 @@
1
+ import type { SingletonAdminConfig, SingletonDefinition } from '@byline/core';
2
+ import type { ContentLocaleOption } from '../collections/view-menu.js';
3
+ type SingletonDocument = Record<string, any> & {
4
+ id: string;
5
+ status: string;
6
+ fields: Record<string, any>;
7
+ };
8
+ export declare function SingletonView({ singletonDefinition, adminConfig, document, initialData, locale, contentLocales, defaultContentLocale, }: {
9
+ singletonDefinition: SingletonDefinition;
10
+ adminConfig?: SingletonAdminConfig;
11
+ document: SingletonDocument | null;
12
+ /** Schema defaults resolved by the server-side route loader for an empty slot. */
13
+ initialData?: Record<string, any>;
14
+ locale?: string;
15
+ contentLocales: ReadonlyArray<ContentLocaleOption>;
16
+ defaultContentLocale: string;
17
+ }): import("react").JSX.Element;
18
+ export {};
@@ -0,0 +1,259 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useRouter } from "@tanstack/react-router";
4
+ import { FormRenderer } from "@byline/admin/react";
5
+ import { ErrorCodes, getDefaultStatus, getWorkflow, getWorkflowStatuses, validateStatusTransition } from "@byline/core";
6
+ import { useTranslation } from "@byline/i18n/react";
7
+ import { Container, Section, useToastManager } from "@byline/ui/react";
8
+ import { getAdminRoutePath } from "../../routes/admin-path.js";
9
+ import { cancelSingletonScheduledPublish, changeSingletonStatus, confirmSingletonScheduledPublish, copySingletonToLocale, scheduleSingletonPublish, unpublishSingleton, updateSingleton } from "../../server-fns/singletons/index.js";
10
+ import { useNavigate } from "../chrome/loose-router.js";
11
+ import { useTanStackNavigationGuard } from "../collections/tanstack-navigation-guard.js";
12
+ import { SingletonViewMenu } from "./view-menu.js";
13
+ function errorCode(error) {
14
+ return 'string' == typeof error?.code ? error.code : null;
15
+ }
16
+ function SingletonView({ singletonDefinition, adminConfig, document, initialData, locale, contentLocales, defaultContentLocale }) {
17
+ const router = useRouter();
18
+ const navigate = useNavigate();
19
+ const toastManager = useToastManager();
20
+ const { t } = useTranslation('byline-admin');
21
+ const { path, label, fields } = singletonDefinition;
22
+ const workflowStatuses = getWorkflowStatuses(singletonDefinition);
23
+ const currentStatus = document?.status ?? getDefaultStatus(singletonDefinition);
24
+ const currentIndex = workflowStatuses.findIndex((status)=>status.name === currentStatus);
25
+ const nextStatus = -1 !== currentIndex && currentIndex < workflowStatuses.length - 1 ? workflowStatuses[currentIndex + 1] : void 0;
26
+ const publishedVersion = document?._publishedVersion ?? null;
27
+ const restoreWarnings = document?._restoreWarnings;
28
+ const scheduledPublicationEnabled = document?._scheduledPublicationEnabled === true;
29
+ const canSchedulePublication = document?._canSchedulePublication === true;
30
+ const scheduledPublication = document?._scheduledPublish ?? null;
31
+ const publishTransitionValid = null != document && 'published' !== currentStatus && workflowStatuses.length > 1 && validateStatusTransition(getWorkflow(singletonDefinition), currentStatus, 'published').valid;
32
+ const canArmSchedule = scheduledPublicationEnabled && canSchedulePublication && publishTransitionValid;
33
+ const reload = async ()=>{
34
+ await router.invalidate();
35
+ };
36
+ const toast = (title, description, intent)=>{
37
+ toastManager.add({
38
+ title,
39
+ description,
40
+ data: {
41
+ intent,
42
+ iconType: intent,
43
+ icon: true,
44
+ close: true
45
+ }
46
+ });
47
+ };
48
+ const notifyScheduleSuspended = ()=>{
49
+ if (scheduledPublication?.state !== 'armed') return;
50
+ toast(t('scheduledPublication.toast.suspendedTitle'), t("scheduledPublication.toast.suspendedDescription"), 'warning');
51
+ };
52
+ const handleSubmit = async ({ data })=>{
53
+ try {
54
+ await updateSingleton({
55
+ data: {
56
+ singleton: path,
57
+ data,
58
+ locale: locale ?? defaultContentLocale,
59
+ expectedVersionId: document?.versionId
60
+ }
61
+ });
62
+ if (null != document) notifyScheduleSuspended();
63
+ toast(t('singletons.edit.updateTitle', {
64
+ label
65
+ }), t("singletons.edit.updatedDescription", {
66
+ label: label.toLowerCase()
67
+ }), 'success');
68
+ await reload();
69
+ } catch (err) {
70
+ const code = errorCode(err);
71
+ const description = code === ErrorCodes.CONFLICT ? t("singletons.edit.conflictDescription") : code === ErrorCodes.NOT_FOUND ? t("singletons.edit.notConfiguredDescription") : t("singletons.edit.updateFailedDescription", {
72
+ label: label.toLowerCase()
73
+ });
74
+ toast(t('singletons.edit.updateTitle', {
75
+ label
76
+ }), description, 'danger');
77
+ throw err;
78
+ }
79
+ };
80
+ const handleStatusChange = async (status)=>{
81
+ try {
82
+ await changeSingletonStatus({
83
+ data: {
84
+ singleton: path,
85
+ status
86
+ }
87
+ });
88
+ toast(t('collections.edit.statusUpdateTitle', {
89
+ label
90
+ }), t("collections.edit.statusChangedDescription", {
91
+ status
92
+ }), 'success');
93
+ await reload();
94
+ } catch (err) {
95
+ toast(t('collections.edit.statusUpdateTitle', {
96
+ label
97
+ }), t("collections.edit.statusChangeFailedDescription", {
98
+ message: err.message
99
+ }), 'danger');
100
+ throw err;
101
+ }
102
+ };
103
+ const handleUnpublish = async ()=>{
104
+ try {
105
+ await unpublishSingleton({
106
+ data: {
107
+ singleton: path
108
+ }
109
+ });
110
+ toast(t('collections.edit.unpublishTitle', {
111
+ label
112
+ }), t("collections.edit.unpublishedDescription"), 'success');
113
+ await reload();
114
+ } catch (err) {
115
+ toast(t('collections.edit.unpublishTitle', {
116
+ label
117
+ }), t("collections.edit.unpublishFailedDescription", {
118
+ message: err.message
119
+ }), 'danger');
120
+ throw err;
121
+ }
122
+ };
123
+ const handleCopyToLocale = async ({ targetLocale, overwrite })=>{
124
+ try {
125
+ await copySingletonToLocale({
126
+ data: {
127
+ singleton: path,
128
+ sourceLocale: locale ?? defaultContentLocale,
129
+ targetLocale,
130
+ overwrite
131
+ }
132
+ });
133
+ const sourceLocale = locale ?? defaultContentLocale;
134
+ const sourceLabel = contentLocales.find((entry)=>entry.code === sourceLocale)?.label ?? sourceLocale;
135
+ const targetLabel = contentLocales.find((entry)=>entry.code === targetLocale)?.label ?? targetLocale;
136
+ toast(t('collections.edit.copyToLocaleTitle', {
137
+ label
138
+ }), t('collections.edit.copiedSuccessMessage', {
139
+ source: sourceLabel,
140
+ target: targetLabel
141
+ }), 'success');
142
+ notifyScheduleSuspended();
143
+ navigate({
144
+ to: getAdminRoutePath('singletons', '$singleton'),
145
+ params: {
146
+ singleton: path
147
+ },
148
+ search: {
149
+ locale: targetLocale
150
+ }
151
+ });
152
+ } catch (err) {
153
+ toast(t('collections.edit.copyToLocaleTitle', {
154
+ label
155
+ }), t("collections.edit.copyFailedDescription", {
156
+ message: err.message
157
+ }), 'danger');
158
+ throw err;
159
+ }
160
+ };
161
+ const handleSchedulePublication = async ({ publishAt })=>{
162
+ try {
163
+ await scheduleSingletonPublish({
164
+ data: {
165
+ singleton: path,
166
+ publishAt,
167
+ expectedVersionId: String(document?.versionId)
168
+ }
169
+ });
170
+ toast(t('scheduledPublication.toast.scheduledTitle'), t("scheduledPublication.toast.scheduledDescription"), 'success');
171
+ await reload();
172
+ } catch (err) {
173
+ toast(t('scheduledPublication.toast.failedTitle'), err.message, 'danger');
174
+ throw err;
175
+ }
176
+ };
177
+ const handleConfirmScheduledPublication = async ()=>{
178
+ try {
179
+ await confirmSingletonScheduledPublish({
180
+ data: {
181
+ singleton: path,
182
+ expectedVersionId: String(document?.versionId)
183
+ }
184
+ });
185
+ toast(t('scheduledPublication.toast.confirmedTitle'), t("scheduledPublication.toast.confirmedDescription"), 'success');
186
+ await reload();
187
+ } catch (err) {
188
+ toast(t('scheduledPublication.toast.failedTitle'), err.message, 'danger');
189
+ throw err;
190
+ }
191
+ };
192
+ const handleCancelScheduledPublication = async ()=>{
193
+ try {
194
+ await cancelSingletonScheduledPublish({
195
+ data: {
196
+ singleton: path
197
+ }
198
+ });
199
+ toast(t('scheduledPublication.toast.cancelledTitle'), t("scheduledPublication.toast.cancelledDescription"), 'success');
200
+ await reload();
201
+ } catch (err) {
202
+ toast(t('scheduledPublication.toast.failedTitle'), err.message, 'danger');
203
+ throw err;
204
+ }
205
+ };
206
+ return /*#__PURE__*/ jsx(Section, {
207
+ children: /*#__PURE__*/ jsx(Container, {
208
+ children: /*#__PURE__*/ jsx(FormRenderer, {
209
+ mode: null == document ? 'create' : 'edit',
210
+ fields: fields,
211
+ onSubmit: handleSubmit,
212
+ initialData: document ?? (initialData ? {
213
+ fields: initialData
214
+ } : void 0),
215
+ adminConfig: adminConfig,
216
+ heading: label,
217
+ showPath: false,
218
+ initialLocale: locale,
219
+ defaultLocale: defaultContentLocale,
220
+ onLocaleChange: null == document ? void 0 : (nextLocale)=>navigate({
221
+ to: getAdminRoutePath('singletons', '$singleton'),
222
+ params: {
223
+ singleton: path
224
+ },
225
+ search: {
226
+ locale: nextLocale
227
+ }
228
+ }),
229
+ useNavigationGuard: useTanStackNavigationGuard,
230
+ headerSlot: null == document ? void 0 : /*#__PURE__*/ jsx(SingletonViewMenu, {
231
+ singleton: path,
232
+ activeView: "edit",
233
+ locale: locale,
234
+ contentLocales: contentLocales,
235
+ defaultContentLocale: defaultContentLocale,
236
+ adminConfig: adminConfig,
237
+ doc: document
238
+ }),
239
+ onStatusChange: null == document ? void 0 : handleStatusChange,
240
+ onUnpublish: null != document && publishedVersion ? handleUnpublish : void 0,
241
+ scheduledPublication: null == document ? null : scheduledPublication,
242
+ onSchedulePublication: canArmSchedule ? handleSchedulePublication : void 0,
243
+ onConfirmScheduledPublication: canArmSchedule && scheduledPublication?.state === 'needs_reconfirm' ? handleConfirmScheduledPublication : void 0,
244
+ onCancelScheduledPublication: null != document && null != scheduledPublication ? handleCancelScheduledPublication : void 0,
245
+ onCopyToLocale: null == document ? void 0 : handleCopyToLocale,
246
+ contentLocales: null == document ? void 0 : contentLocales,
247
+ publishedVersion: null == document ? null : publishedVersion,
248
+ restoreWarnings: restoreWarnings,
249
+ nextStatus: null == document ? void 0 : nextStatus,
250
+ workflowStatuses: null == document ? void 0 : workflowStatuses,
251
+ onCancel: ()=>navigate({
252
+ to: getAdminRoutePath()
253
+ }),
254
+ collectionPath: path
255
+ })
256
+ })
257
+ });
258
+ }
259
+ export { SingletonView };
@@ -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 {};
@@ -10,8 +10,8 @@
10
10
  * of our API and used only for development.
11
11
  * We'll extract a properly configured API server soon.
12
12
  */
13
- import type { CollectionDefinition } from '@byline/core';
14
- export interface EnsuredCollection {
13
+ import type { CollectionDefinition, MultiCollectionDefinition } from '@byline/core';
14
+ export interface EnsuredDocumentResource {
15
15
  definition: CollectionDefinition;
16
16
  collection: {
17
17
  id: string;
@@ -20,14 +20,28 @@ export interface EnsuredCollection {
20
20
  schemaHash: string;
21
21
  };
22
22
  }
23
+ export interface EnsuredCollection extends EnsuredDocumentResource {
24
+ definition: MultiCollectionDefinition;
25
+ }
23
26
  /**
24
- * Resolve a collection for an admin API request.
27
+ * Resolve either registered document-resource kind against the reconciled
28
+ * collection record.
25
29
  *
26
30
  * Collections are reconciled with the database once at startup by
27
31
  * `initBylineCore()` (see `packages/core/src/services/collection-bootstrap.ts`).
28
32
  * This helper is a per-request cache lookup against the resulting in-memory
29
33
  * registry — no DB I/O, no hash work.
30
34
  *
31
- * Returns `null` when the path is not registered in the client/server config.
35
+ * This broader seam is reserved for operations such as field upload whose core
36
+ * service is explicitly kind-aware; collection route families continue to use
37
+ * `ensureCollection` below.
38
+ */
39
+ export declare function ensureDocumentResource(path: string): Promise<EnsuredDocumentResource | null>;
40
+ /**
41
+ * Narrow the shared document-resource resolution to a multi-document
42
+ * collection for a collection-scoped admin API request.
43
+ *
44
+ * Returns `null` when the path is unregistered or belongs to a singleton, so
45
+ * collection route families cannot accidentally admit the other resource kind.
32
46
  */
33
47
  export declare function ensureCollection(path: string): Promise<EnsuredCollection | null>;
@@ -1,6 +1,6 @@
1
- import { getCollectionDefinition } from "@byline/core";
1
+ import { getCollectionDefinition, isSingleton } from "@byline/core";
2
2
  import { bylineCore } from "./byline-core.js";
3
- async function ensureCollection(path) {
3
+ async function ensureDocumentResource(path) {
4
4
  const definition = getCollectionDefinition(path);
5
5
  if (null == definition) return null;
6
6
  let record;
@@ -19,4 +19,11 @@ async function ensureCollection(path) {
19
19
  }
20
20
  };
21
21
  }
22
- export { ensureCollection };
22
+ async function ensureCollection(path) {
23
+ const resource = await ensureDocumentResource(path);
24
+ return null == resource || isSingleton(resource.definition) ? null : {
25
+ ...resource,
26
+ definition: resource.definition
27
+ };
28
+ }
29
+ export { ensureCollection, ensureDocumentResource };
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute } from "@tanstack/react-router";
3
- import { filterReadableCollections, getAdminConfig } from "@byline/core";
3
+ import { filterReadableCollections, getAdminConfig, isSingleton } from "@byline/core";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
5
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
6
6
  import { AdminDashboard } from "../admin-shell/chrome/dashboard.js";
@@ -15,7 +15,7 @@ function createAdminDashboardRoute(path) {
15
15
  abilities: context.user.abilities
16
16
  });
17
17
  const statsMap = {};
18
- await Promise.all(visible.filter((c)=>true === c.showStats).map(async (c)=>{
18
+ await Promise.all(visible.filter((resource)=>!isSingleton(resource) && true === resource.showStats).map(async (c)=>{
19
19
  try {
20
20
  statsMap[c.path] = await getCollectionStats(c.path);
21
21
  } catch {
@@ -1,6 +1,5 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute, notFound } from "@tanstack/react-router";
3
- import { getCollectionDefinition } from "@byline/core";
4
3
  import { useTranslation } from "@byline/i18n/react";
5
4
  import { z } from "zod";
6
5
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
@@ -8,6 +7,7 @@ import { ApiView } from "../admin-shell/collections/api.js";
8
7
  import { getCollectionDocument } from "../server-fns/collections/index.js";
9
8
  import { getAdminRoutePath } from "./admin-path.js";
10
9
  import { getContentLocaleRouteConfig } from "./get-content-locale-route-config.js";
10
+ import { getMultiCollectionDefinition } from "./get-multi-collection-definition.js";
11
11
  const searchSchema = z.object({
12
12
  locale: z.string().optional(),
13
13
  depth: z.coerce.number().int().min(0).max(3).optional()
@@ -20,7 +20,7 @@ function createCollectionApiRoute(path) {
20
20
  depth: search.depth
21
21
  }),
22
22
  loader: async ({ params, deps })=>{
23
- const collectionDef = getCollectionDefinition(params.collection);
23
+ const collectionDef = getMultiCollectionDefinition(params.collection);
24
24
  if (!collectionDef) throw notFound();
25
25
  const { defaultContentLocale } = getContentLocaleRouteConfig();
26
26
  const resolvedLocale = deps.locale ?? defaultContentLocale;
@@ -35,7 +35,7 @@ function createCollectionApiRoute(path) {
35
35
  const data = Route.useLoaderData();
36
36
  const { collection, id } = Route.useParams();
37
37
  const { locale, depth } = Route.useSearch();
38
- const collectionDef = getCollectionDefinition(collection);
38
+ const collectionDef = getMultiCollectionDefinition(collection);
39
39
  const { t } = useTranslation('byline-admin');
40
40
  const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig();
41
41
  return /*#__PURE__*/ jsxs(Fragment, {
@@ -1,11 +1,12 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute, notFound } from "@tanstack/react-router";
3
- import { buildInitialDataFromFields, getCollectionAdminConfig, getCollectionDefinition } from "@byline/core";
3
+ import { buildInitialDataFromFields, getCollectionAdminConfig } from "@byline/core";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
5
  import { z } from "zod";
6
6
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
7
7
  import { CreateView } from "../admin-shell/collections/create.js";
8
8
  import { getAdminRoutePath } from "./admin-path.js";
9
+ import { getMultiCollectionDefinition } from "./get-multi-collection-definition.js";
9
10
  const searchSchema = z.object({
10
11
  from: z.string().optional()
11
12
  });
@@ -13,7 +14,7 @@ function createCollectionCreateRoute(path) {
13
14
  const Route = createFileRoute(path)({
14
15
  validateSearch: searchSchema,
15
16
  loader: async ({ params })=>{
16
- const collectionDef = getCollectionDefinition(params.collection);
17
+ const collectionDef = getMultiCollectionDefinition(params.collection);
17
18
  if (!collectionDef) throw notFound();
18
19
  const initialData = await buildInitialDataFromFields(collectionDef.fields, {
19
20
  data: {},
@@ -26,7 +27,7 @@ function createCollectionCreateRoute(path) {
26
27
  component: function() {
27
28
  const { collection } = Route.useParams();
28
29
  const { initialData } = Route.useLoaderData();
29
- const collectionDef = getCollectionDefinition(collection);
30
+ const collectionDef = getMultiCollectionDefinition(collection);
30
31
  const adminConfig = getCollectionAdminConfig(collection);
31
32
  const { t } = useTranslation('byline-admin');
32
33
  const search = Route.useSearch();
@@ -1,7 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { createFileRoute, notFound } from "@tanstack/react-router";
4
- import { getCollectionAdminConfig, getCollectionDefinition } from "@byline/core";
4
+ import { getCollectionAdminConfig } from "@byline/core";
5
5
  import { useTranslation } from "@byline/i18n/react";
6
6
  import { useToastManager } from "@byline/ui/react";
7
7
  import { z } from "zod";
@@ -11,6 +11,7 @@ import { EditView } from "../admin-shell/collections/edit.js";
11
11
  import { getCollectionDocument } from "../server-fns/collections/index.js";
12
12
  import { getAdminRoutePath } from "./admin-path.js";
13
13
  import { getContentLocaleRouteConfig } from "./get-content-locale-route-config.js";
14
+ import { getMultiCollectionDefinition } from "./get-multi-collection-definition.js";
14
15
  import { decodeListReturnState } from "./list-return-state.js";
15
16
  import { persistListReturnState, readListReturnState } from "./list-return-storage.js";
16
17
  const searchSchema = z.object({
@@ -27,7 +28,7 @@ function createCollectionEditRoute(path) {
27
28
  locale: search.locale
28
29
  }),
29
30
  loader: async ({ params, deps })=>{
30
- const collectionDef = getCollectionDefinition(params.collection);
31
+ const collectionDef = getMultiCollectionDefinition(params.collection);
31
32
  if (!collectionDef) throw notFound();
32
33
  const data = await getCollectionDocument(params.collection, params.id, deps.locale, void 0, true);
33
34
  if (!data) throw notFound();
@@ -41,7 +42,7 @@ function createCollectionEditRoute(path) {
41
42
  const { collection, id } = Route.useParams();
42
43
  const search = Route.useSearch();
43
44
  const { locale } = search;
44
- const collectionDef = getCollectionDefinition(collection);
45
+ const collectionDef = getMultiCollectionDefinition(collection);
45
46
  const adminConfig = getCollectionAdminConfig(collection);
46
47
  const { t } = useTranslation('byline-admin');
47
48
  const toastManager = useToastManager();
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createFileRoute, notFound } from "@tanstack/react-router";
3
- import { getCollectionAdminConfig, getCollectionDefinition, getWorkflowStatuses } from "@byline/core";
3
+ import { getCollectionAdminConfig, getWorkflowStatuses } from "@byline/core";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
5
  import { z } from "zod";
6
6
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
@@ -8,6 +8,7 @@ import { HistoryView } from "../admin-shell/collections/history.js";
8
8
  import { getCollectionDocument, getCollectionDocumentAuditLog, getCollectionDocumentHistory } from "../server-fns/collections/index.js";
9
9
  import { getAdminRoutePath } from "./admin-path.js";
10
10
  import { getContentLocaleRouteConfig } from "./get-content-locale-route-config.js";
11
+ import { getMultiCollectionDefinition } from "./get-multi-collection-definition.js";
11
12
  const searchSchema = z.object({
12
13
  page: z.coerce.number().min(1).optional(),
13
14
  page_size: z.coerce.number().max(100).optional(),
@@ -31,7 +32,7 @@ function createCollectionHistoryRoute(path) {
31
32
  locale: search.locale
32
33
  }),
33
34
  loader: async ({ params, deps })=>{
34
- const collectionDef = getCollectionDefinition(params.collection);
35
+ const collectionDef = getMultiCollectionDefinition(params.collection);
35
36
  if (!collectionDef) throw notFound();
36
37
  const [history, currentDocument, auditLog] = await Promise.all([
37
38
  getCollectionDocumentHistory({
@@ -71,7 +72,7 @@ function createCollectionHistoryRoute(path) {
71
72
  component: function() {
72
73
  const { history, currentDocument, auditLog } = Route.useLoaderData();
73
74
  const { collection } = Route.useParams();
74
- const collectionDef = getCollectionDefinition(collection);
75
+ const collectionDef = getMultiCollectionDefinition(collection);
75
76
  const adminConfig = getCollectionAdminConfig(collection);
76
77
  const { t } = useTranslation('byline-admin');
77
78
  const { contentLocales, defaultContentLocale } = getContentLocaleRouteConfig();
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from "react";
3
3
  import { createFileRoute, notFound } from "@tanstack/react-router";
4
4
  import { applyRelationColumnFormatters } from "@byline/admin/react";
5
- import { getCollectionAdminConfig, getCollectionDefinition, getWorkflowStatuses } from "@byline/core";
5
+ import { getCollectionAdminConfig, getWorkflowStatuses } from "@byline/core";
6
6
  import { useTranslation } from "@byline/i18n/react";
7
7
  import { useToastManager } from "@byline/ui/react";
8
8
  import { BreadcrumbsClient } from "../admin-shell/chrome/breadcrumbs/breadcrumbs-client.js";
@@ -11,6 +11,7 @@ import { ListView } from "../admin-shell/collections/list.js";
11
11
  import { TreeListView } from "../admin-shell/collections/tree-list.js";
12
12
  import { getCollectionDocuments, getCollectionTree, placeTreeNode, reorderCollectionDocument } from "../server-fns/collections/index.js";
13
13
  import { getAdminRoutePath } from "./admin-path.js";
14
+ import { getMultiCollectionDefinition } from "./get-multi-collection-definition.js";
14
15
  import { collectionListSearchSchema } from "./list-return-state.js";
15
16
  function createCollectionListRoute(path) {
16
17
  const Route = createFileRoute(path)({
@@ -25,7 +26,7 @@ function createCollectionListRoute(path) {
25
26
  status: search.status
26
27
  }),
27
28
  loader: async ({ params, deps })=>{
28
- const collectionDef = getCollectionDefinition(params.collection);
29
+ const collectionDef = getMultiCollectionDefinition(params.collection);
29
30
  if (!collectionDef) throw notFound();
30
31
  if (true === collectionDef.tree) return await getCollectionTree({
31
32
  data: {
@@ -59,7 +60,7 @@ function createCollectionListRoute(path) {
59
60
  const { collection } = Route.useParams();
60
61
  const search = Route.useSearch();
61
62
  const navigate = useNavigate();
62
- const collectionDef = getCollectionDefinition(collection);
63
+ const collectionDef = getMultiCollectionDefinition(collection);
63
64
  const adminConfig = getCollectionAdminConfig(collection);
64
65
  const columns = applyRelationColumnFormatters(adminConfig?.columns || [], collectionDef);
65
66
  const workflowStatuses = getWorkflowStatuses(collectionDef);