@btst/plugin-cms 1.0.0 → 1.2.0-beta.1

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 (76) hide show
  1. package/dist/api/getters.cjs +37 -25
  2. package/dist/api/getters.mjs +37 -25
  3. package/dist/api/index.cjs +1 -0
  4. package/dist/api/index.d.cts +17 -8
  5. package/dist/api/index.d.mts +17 -8
  6. package/dist/api/index.d.ts +17 -8
  7. package/dist/api/index.mjs +1 -1
  8. package/dist/api/mutations.cjs +46 -19
  9. package/dist/api/mutations.mjs +46 -20
  10. package/dist/api/operations.cjs +586 -177
  11. package/dist/api/operations.mjs +585 -178
  12. package/dist/api/plugin.cjs +56 -7
  13. package/dist/api/plugin.mjs +57 -8
  14. package/dist/api/publication.cjs +8 -0
  15. package/dist/api/publication.mjs +6 -0
  16. package/dist/client/components/forms/content-form.cjs +107 -1
  17. package/dist/client/components/forms/content-form.mjs +108 -2
  18. package/dist/client/components/pages/content-editor-page.internal.cjs +23 -5
  19. package/dist/client/components/pages/content-editor-page.internal.mjs +23 -5
  20. package/dist/client/components/pages/content-list-page.internal.cjs +3 -0
  21. package/dist/client/components/pages/content-list-page.internal.mjs +3 -0
  22. package/dist/client/components/pages/dashboard-page.internal.cjs +10 -30
  23. package/dist/client/components/pages/dashboard-page.internal.mjs +10 -30
  24. package/dist/client/components/revision-history.cjs +161 -0
  25. package/dist/client/components/revision-history.mjs +159 -0
  26. package/dist/client/components/shared/content-type-card.cjs +56 -0
  27. package/dist/client/components/shared/content-type-card.mjs +54 -0
  28. package/dist/client/components/shared/empty-state.cjs +18 -1
  29. package/dist/client/components/shared/empty-state.mjs +19 -2
  30. package/dist/client/hooks/cms-hooks.cjs +18 -2
  31. package/dist/client/hooks/cms-hooks.mjs +17 -3
  32. package/dist/client/hooks/index.cjs +2 -0
  33. package/dist/client/hooks/index.d.cts +306 -2
  34. package/dist/client/hooks/index.d.mts +306 -2
  35. package/dist/client/hooks/index.d.ts +306 -2
  36. package/dist/client/hooks/index.mjs +1 -1
  37. package/dist/client/index.d.cts +55 -2
  38. package/dist/client/index.d.mts +55 -2
  39. package/dist/client/index.d.ts +55 -2
  40. package/dist/db.cjs +25 -0
  41. package/dist/db.mjs +25 -0
  42. package/dist/permissions.cjs +2 -0
  43. package/dist/permissions.d.cts +1 -0
  44. package/dist/permissions.d.mts +1 -0
  45. package/dist/permissions.d.ts +1 -0
  46. package/dist/permissions.mjs +2 -0
  47. package/dist/query-keys.cjs +31 -1
  48. package/dist/query-keys.d.cts +1 -1
  49. package/dist/query-keys.d.mts +1 -1
  50. package/dist/query-keys.d.ts +1 -1
  51. package/dist/query-keys.mjs +31 -1
  52. package/dist/shared/{plugin-cms.W4Le8D91.d.mts → plugin-cms.DAAUZ41w.d.mts} +377 -144
  53. package/dist/shared/{plugin-cms.c-GeWvTf.d.cts → plugin-cms.DJ_293QU.d.cts} +377 -144
  54. package/dist/shared/{plugin-cms.DQLAoogy.d.ts → plugin-cms.DlUSv7wz.d.ts} +377 -144
  55. package/dist/types.d.cts +38 -2
  56. package/dist/types.d.mts +38 -2
  57. package/dist/types.d.ts +38 -2
  58. package/dist/ui/src/components/auto-form/config.cjs +2 -0
  59. package/dist/ui/src/components/auto-form/config.mjs +2 -0
  60. package/dist/ui/src/components/auto-form/fields/file.cjs +68 -0
  61. package/dist/ui/src/components/auto-form/fields/file.mjs +63 -0
  62. package/dist/ui/src/components/auto-form/fields/object.cjs +9 -2
  63. package/dist/ui/src/components/auto-form/fields/object.mjs +9 -2
  64. package/dist/ui/src/components/auto-form/helpers.cjs +9 -0
  65. package/dist/ui/src/components/auto-form/helpers.mjs +9 -0
  66. package/dist/ui/src/components/auto-form/index.cjs +29 -4
  67. package/dist/ui/src/components/auto-form/index.mjs +31 -6
  68. package/dist/ui/src/components/auto-form/stepped-auto-form.cjs +34 -29
  69. package/dist/ui/src/components/auto-form/stepped-auto-form.mjs +35 -30
  70. package/dist/ui/src/components/sheet.cjs +126 -0
  71. package/dist/ui/src/components/sheet.mjs +105 -0
  72. package/dist/ui/src/lib/form-visibility.cjs +20 -0
  73. package/dist/ui/src/lib/form-visibility.mjs +18 -0
  74. package/dist/ui/src/lib/schema-converter.cjs +101 -0
  75. package/dist/ui/src/lib/schema-converter.mjs +100 -1
  76. package/package.json +5 -4
@@ -8,6 +8,7 @@ import { orderCMSContentTypes } from '../../overrides.mjs';
8
8
  import { CMS_PLUGIN_ID } from '../../constants.mjs';
9
9
  import { ContentForm } from '../forms/content-form.mjs';
10
10
  import { InverseRelationsPanel } from '../inverse-relations-panel.mjs';
11
+ import { RevisionHistory } from '../revision-history.mjs';
11
12
  import { EmptyState } from '../shared/empty-state.mjs';
12
13
  import { PageWrapper } from '../shared/page-wrapper.mjs';
13
14
  import { EditorSkeleton } from '../loading/editor-skeleton.mjs';
@@ -85,6 +86,7 @@ function ContentEditorPage({ typeSlug, id }) {
85
86
  );
86
87
  const contentType = contentTypes.find((ct) => ct.slug === typeSlug);
87
88
  const isEditing = !!id;
89
+ const [restoreCount, setRestoreCount] = useState(0);
88
90
  const { item, isLoading: isLoadingItem } = useContentItem(typeSlug, id ?? "");
89
91
  const resourceForm = useContentItemForm({
90
92
  action: isEditing ? "edit" : "create",
@@ -93,12 +95,17 @@ function ContentEditorPage({ typeSlug, id }) {
93
95
  toCreateVars: (values) => ({
94
96
  typeSlug,
95
97
  slug: values.slug,
96
- data: values.data
98
+ data: values.data,
99
+ publishAt: values.publishAt
97
100
  }),
98
101
  toUpdateVars: (values) => ({
99
102
  typeSlug,
100
103
  id: id ?? "",
101
- data: { slug: values.slug, data: values.data }
104
+ data: {
105
+ slug: values.slug,
106
+ data: values.data,
107
+ publishAt: values.publishAt
108
+ }
102
109
  }),
103
110
  onSuccess: () => {
104
111
  void navigate?.(joinBasePath(basePath, `/cms/${typeSlug}`));
@@ -108,6 +115,7 @@ function ContentEditorPage({ typeSlug, id }) {
108
115
  return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-editor-page", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-2xl", children: /* @__PURE__ */ jsx(
109
116
  EmptyState,
110
117
  {
118
+ page: id ? "editContent" : "newContent",
111
119
  title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
112
120
  description: localization?.CMS_ERROR_TYPE_NOT_FOUND_DESCRIPTION ?? t("cms.common.typeNotFoundDescription", "Content type not found")
113
121
  }
@@ -120,6 +128,7 @@ function ContentEditorPage({ typeSlug, id }) {
120
128
  return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-editor-page", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-2xl", children: /* @__PURE__ */ jsx(
121
129
  EmptyState,
122
130
  {
131
+ page: id ? "editContent" : "newContent",
123
132
  title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
124
133
  description: localization?.CMS_ERROR_ITEM_NOT_FOUND_DESCRIPTION ?? t("cms.common.itemNotFoundDescription", "Content item not found")
125
134
  }
@@ -131,7 +140,7 @@ function ContentEditorPage({ typeSlug, id }) {
131
140
  const hasFieldErrors = Object.keys(resourceForm.fieldErrors).length > 0;
132
141
  const title = isEditing ? (localization?.CMS_EDITOR_TITLE_EDIT ?? t("cms.editor.titleEdit", "Edit {typeName}")).replace("{typeName}", contentType.name) : (localization?.CMS_EDITOR_TITLE_NEW ?? t("cms.editor.titleNew", "New {typeName}")).replace("{typeName}", contentType.name);
133
142
  return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-editor-page", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl space-y-6", children: [
134
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
143
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [
135
144
  /* @__PURE__ */ jsx(
136
145
  Button,
137
146
  {
@@ -141,7 +150,15 @@ function ContentEditorPage({ typeSlug, id }) {
141
150
  children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
142
151
  }
143
152
  ),
144
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title })
153
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
154
+ isEditing && id && /* @__PURE__ */ jsx(
155
+ RevisionHistory,
156
+ {
157
+ typeSlug,
158
+ itemId: id,
159
+ onRestored: () => setRestoreCount((count) => count + 1)
160
+ }
161
+ )
145
162
  ] }),
146
163
  /* @__PURE__ */ jsx(
147
164
  ContentForm,
@@ -152,6 +169,7 @@ function ContentEditorPage({ typeSlug, id }) {
152
169
  JSON.parse(contentType.jsonSchema)
153
170
  ) : void 0,
154
171
  initialSlug: item?.slug,
172
+ initialPublishAt: item?.publishAt,
155
173
  isEditing,
156
174
  onSubmit: handleSubmit,
157
175
  onCancel: () => void navigate?.(joinBasePath(basePath, `/cms/${typeSlug}`)),
@@ -159,7 +177,7 @@ function ContentEditorPage({ typeSlug, id }) {
159
177
  errorMessage: hasFieldErrors ? void 0 : resourceForm.error?.message,
160
178
  isSubmitting: resourceForm.isSubmitting
161
179
  },
162
- isEditing ? `edit-${id}` : "create"
180
+ isEditing ? `edit-${id}-${restoreCount}` : "create"
163
181
  ),
164
182
  isEditing && id && /* @__PURE__ */ jsx(InverseRelationsPanel, { contentTypeSlug: typeSlug, itemId: id })
165
183
  ] }) });
@@ -99,6 +99,7 @@ function ContentListPage({ typeSlug }) {
99
99
  return /* @__PURE__ */ jsxRuntime.jsx(pageWrapper.PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full max-w-5xl", children: /* @__PURE__ */ jsxRuntime.jsx(
100
100
  emptyState.EmptyState,
101
101
  {
102
+ page: "contentList",
102
103
  title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
103
104
  description: localization?.CMS_ERROR_TYPE_NOT_FOUND_DESCRIPTION ?? t("cms.common.typeNotFoundDescription", "Content type not found")
104
105
  }
@@ -157,6 +158,7 @@ function ContentListPage({ typeSlug }) {
157
158
  items.length === 0 ? isSearchLoading ? null : hasSearch ? /* @__PURE__ */ jsxRuntime.jsx(
158
159
  emptyState.EmptyState,
159
160
  {
161
+ page: "contentList",
160
162
  title: localization?.CMS_LIST_SEARCH_EMPTY ?? t("cms.list.searchEmpty", "No items match your search"),
161
163
  description: localization?.CMS_LIST_SEARCH_EMPTY_DESCRIPTION ?? t(
162
164
  "cms.list.searchEmptyDescription",
@@ -166,6 +168,7 @@ function ContentListPage({ typeSlug }) {
166
168
  ) : /* @__PURE__ */ jsxRuntime.jsx(
167
169
  emptyState.EmptyState,
168
170
  {
171
+ page: "contentList",
169
172
  title: localization?.CMS_LIST_EMPTY ?? t("cms.list.empty", "No items yet"),
170
173
  description: localization?.CMS_LIST_EMPTY_DESCRIPTION ?? t(
171
174
  "cms.list.emptyDescription",
@@ -97,6 +97,7 @@ function ContentListPage({ typeSlug }) {
97
97
  return /* @__PURE__ */ jsx(PageWrapper, { testId: "cms-list-page", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-5xl", children: /* @__PURE__ */ jsx(
98
98
  EmptyState,
99
99
  {
100
+ page: "contentList",
100
101
  title: localization?.CMS_ERROR_NOT_FOUND ?? t("cms.common.notFound", "Not found"),
101
102
  description: localization?.CMS_ERROR_TYPE_NOT_FOUND_DESCRIPTION ?? t("cms.common.typeNotFoundDescription", "Content type not found")
102
103
  }
@@ -155,6 +156,7 @@ function ContentListPage({ typeSlug }) {
155
156
  items.length === 0 ? isSearchLoading ? null : hasSearch ? /* @__PURE__ */ jsx(
156
157
  EmptyState,
157
158
  {
159
+ page: "contentList",
158
160
  title: localization?.CMS_LIST_SEARCH_EMPTY ?? t("cms.list.searchEmpty", "No items match your search"),
159
161
  description: localization?.CMS_LIST_SEARCH_EMPTY_DESCRIPTION ?? t(
160
162
  "cms.list.searchEmptyDescription",
@@ -164,6 +166,7 @@ function ContentListPage({ typeSlug }) {
164
166
  ) : /* @__PURE__ */ jsx(
165
167
  EmptyState,
166
168
  {
169
+ page: "contentList",
167
170
  title: localization?.CMS_LIST_EMPTY ?? t("cms.list.empty", "No items yet"),
168
171
  description: localization?.CMS_LIST_EMPTY_DESCRIPTION ?? t(
169
172
  "cms.list.emptyDescription",
@@ -2,15 +2,13 @@
2
2
  'use strict';
3
3
 
4
4
  const jsxRuntime = require('react/jsx-runtime');
5
- const lucideReact = require('lucide-react');
6
- const card = require('../../../ui/src/components/card.cjs');
7
5
  const context = require('@btst/stack/context');
8
6
  const overrides = require('../../overrides.cjs');
9
7
  const constants = require('../../constants.cjs');
10
8
  const emptyState = require('../shared/empty-state.cjs');
11
9
  const pageWrapper = require('../shared/page-wrapper.cjs');
12
10
  const useRouteLifecycle = require('../../../ui/src/hooks/use-route-lifecycle.cjs');
13
- const utils = require('../../../utils.cjs');
11
+ const contentTypeCard = require('../shared/content-type-card.cjs');
14
12
  const cmsHooks = require('../../hooks/cms-hooks.cjs');
15
13
 
16
14
  function DashboardPage() {
@@ -44,6 +42,7 @@ function DashboardPage() {
44
42
  /* @__PURE__ */ jsxRuntime.jsx(
45
43
  emptyState.EmptyState,
46
44
  {
45
+ page: "dashboard",
47
46
  title: localization?.CMS_DASHBOARD_NO_TYPES ?? t("cms.dashboard.noTypes", "No content types configured"),
48
47
  description: localization?.CMS_DASHBOARD_NO_TYPES_DESCRIPTION ?? t(
49
48
  "cms.dashboard.noTypesDescription",
@@ -65,34 +64,15 @@ function DashboardPage() {
65
64
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
66
65
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: subtitle })
67
66
  ] }),
68
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contentTypes.map((ct) => /* @__PURE__ */ jsxRuntime.jsxs(
69
- card.Card,
67
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contentTypes.map((ct) => /* @__PURE__ */ jsxRuntime.jsx(
68
+ contentTypeCard.ContentTypeCard,
70
69
  {
71
- className: utils.cn(
72
- "hover:border-primary/50 transition-colors cursor-pointer",
73
- classNames?.contentTypeCard
74
- ),
75
- onClick: () => void navigate?.(context.joinBasePath(basePath, `/cms/${ct.slug}`)),
76
- children: [
77
- /* @__PURE__ */ jsxRuntime.jsxs(card.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
78
- /* @__PURE__ */ jsxRuntime.jsx(
79
- card.CardTitle,
80
- {
81
- className: utils.cn(
82
- "text-lg font-medium",
83
- classNames?.contentTypeTitle
84
- ),
85
- children: ct.name
86
- }
87
- ),
88
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-5 w-5 text-muted-foreground" })
89
- ] }),
90
- /* @__PURE__ */ jsxRuntime.jsxs(card.CardContent, { children: [
91
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold", children: ct.itemCount }),
92
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatItemCount(ct.itemCount) }),
93
- ct.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2 line-clamp-2", children: ct.description })
94
- ] })
95
- ]
70
+ page: "dashboard",
71
+ contentType: ct,
72
+ itemCountLabel: formatItemCount(ct.itemCount),
73
+ className: classNames?.contentTypeCard,
74
+ titleClassName: classNames?.contentTypeTitle,
75
+ onOpen: () => void navigate?.(context.joinBasePath(basePath, `/cms/${ct.slug}`))
96
76
  },
97
77
  ct.id
98
78
  )) })
@@ -1,14 +1,12 @@
1
1
  'use client';
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
- import { FileText } from 'lucide-react';
4
- import { Card, CardHeader, CardTitle, CardContent } from '../../../ui/src/components/card.mjs';
5
3
  import { useTranslate, usePluginOverrides, useStack, joinBasePath } from '@btst/stack/context';
6
4
  import { orderCMSContentTypes } from '../../overrides.mjs';
7
5
  import { CMS_PLUGIN_ID } from '../../constants.mjs';
8
6
  import { EmptyState } from '../shared/empty-state.mjs';
9
7
  import { PageWrapper } from '../shared/page-wrapper.mjs';
10
8
  import { useRouteLifecycle } from '../../../ui/src/hooks/use-route-lifecycle.mjs';
11
- import { cn } from '../../../utils.mjs';
9
+ import { ContentTypeCard } from '../shared/content-type-card.mjs';
12
10
  import { useSuspenseContentTypes } from '../../hooks/cms-hooks.mjs';
13
11
 
14
12
  function DashboardPage() {
@@ -42,6 +40,7 @@ function DashboardPage() {
42
40
  /* @__PURE__ */ jsx(
43
41
  EmptyState,
44
42
  {
43
+ page: "dashboard",
45
44
  title: localization?.CMS_DASHBOARD_NO_TYPES ?? t("cms.dashboard.noTypes", "No content types configured"),
46
45
  description: localization?.CMS_DASHBOARD_NO_TYPES_DESCRIPTION ?? t(
47
46
  "cms.dashboard.noTypesDescription",
@@ -63,34 +62,15 @@ function DashboardPage() {
63
62
  /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }),
64
63
  /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: subtitle })
65
64
  ] }),
66
- /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contentTypes.map((ct) => /* @__PURE__ */ jsxs(
67
- Card,
65
+ /* @__PURE__ */ jsx("div", { className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3", children: contentTypes.map((ct) => /* @__PURE__ */ jsx(
66
+ ContentTypeCard,
68
67
  {
69
- className: cn(
70
- "hover:border-primary/50 transition-colors cursor-pointer",
71
- classNames?.contentTypeCard
72
- ),
73
- onClick: () => void navigate?.(joinBasePath(basePath, `/cms/${ct.slug}`)),
74
- children: [
75
- /* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
76
- /* @__PURE__ */ jsx(
77
- CardTitle,
78
- {
79
- className: cn(
80
- "text-lg font-medium",
81
- classNames?.contentTypeTitle
82
- ),
83
- children: ct.name
84
- }
85
- ),
86
- /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-muted-foreground" })
87
- ] }),
88
- /* @__PURE__ */ jsxs(CardContent, { children: [
89
- /* @__PURE__ */ jsx("div", { className: "text-2xl font-bold", children: ct.itemCount }),
90
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatItemCount(ct.itemCount) }),
91
- ct.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2 line-clamp-2", children: ct.description })
92
- ] })
93
- ]
68
+ page: "dashboard",
69
+ contentType: ct,
70
+ itemCountLabel: formatItemCount(ct.itemCount),
71
+ className: classNames?.contentTypeCard,
72
+ titleClassName: classNames?.contentTypeTitle,
73
+ onOpen: () => void navigate?.(joinBasePath(basePath, `/cms/${ct.slug}`))
94
74
  },
95
75
  ct.id
96
76
  )) })
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const button = require('../../ui/src/components/button.cjs');
7
+ const sheet = require('../../ui/src/components/sheet.cjs');
8
+ const alertDialog = require('../../ui/src/components/alert-dialog.cjs');
9
+ const context = require('@btst/stack/context');
10
+ const cmsHooks = require('../hooks/cms-hooks.cjs');
11
+
12
+ function RevisionHistory({
13
+ typeSlug,
14
+ itemId,
15
+ onRestored
16
+ }) {
17
+ const t = context.useTranslate();
18
+ const [open, setOpen] = React.useState(false);
19
+ const [selected, setSelected] = React.useState(
20
+ null
21
+ );
22
+ const [restoreError, setRestoreError] = React.useState(null);
23
+ const history = cmsHooks.useContentRevisions(typeSlug, itemId, open);
24
+ const restore = cmsHooks.useRestoreContentRevision();
25
+ const restoreSelected = async () => {
26
+ if (!selected) return;
27
+ setRestoreError(null);
28
+ try {
29
+ await restore.mutateAsync({
30
+ typeSlug,
31
+ id: itemId,
32
+ revisionId: selected.id
33
+ });
34
+ onRestored();
35
+ setSelected(null);
36
+ setOpen(false);
37
+ } catch (error) {
38
+ setRestoreError(
39
+ error instanceof Error ? error.message : t("cms.revisions.restoreError", "Could not restore revision")
40
+ );
41
+ }
42
+ };
43
+ return /* @__PURE__ */ jsxRuntime.jsxs(sheet.Sheet, { open, onOpenChange: setOpen, children: [
44
+ /* @__PURE__ */ jsxRuntime.jsx(sheet.SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "outline", children: t("cms.revisions.title", "Revision history") }) }),
45
+ /* @__PURE__ */ jsxRuntime.jsxs(sheet.SheetContent, { className: "w-full overflow-y-auto sm:max-w-lg", children: [
46
+ /* @__PURE__ */ jsxRuntime.jsxs(sheet.SheetHeader, { children: [
47
+ /* @__PURE__ */ jsxRuntime.jsx(sheet.SheetTitle, { children: t("cms.revisions.title", "Revision history") }),
48
+ /* @__PURE__ */ jsxRuntime.jsx(sheet.SheetDescription, { children: t(
49
+ "cms.revisions.description",
50
+ "Inspect saved versions. Restoring a version replaces the editor state and creates a new revision."
51
+ ) })
52
+ ] }),
53
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 px-4 pb-6", children: [
54
+ history.isLoading && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t("cms.revisions.loading", "Loading revisions...") }),
55
+ history.error && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "space-y-2", children: [
56
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive", children: history.error.message }),
57
+ /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "outline", onClick: () => void history.refetch(), children: t("cms.revisions.retry", "Retry") })
58
+ ] }),
59
+ !history.isLoading && !history.error && history.revisions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: t(
60
+ "cms.revisions.empty",
61
+ "No revisions yet. Saving this entry will create its first revision."
62
+ ) }),
63
+ history.revisions.map((revision) => /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "rounded-md border p-3", children: [
64
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "cursor-pointer space-y-1 break-words", children: [
65
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: t("cms.revisions.version", "Version {version}").replace(
66
+ "{version}",
67
+ String(revision.version)
68
+ ) }),
69
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: revision.createdAt, children: new Date(revision.createdAt).toLocaleString() }) }),
70
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "block text-sm text-muted-foreground", children: [
71
+ t("cms.revisions.author", "Author"),
72
+ ":",
73
+ " ",
74
+ revision.authorId ?? t("cms.revisions.system", "System")
75
+ ] })
76
+ ] }),
77
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-3 text-sm", children: [
78
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "break-words", children: [
79
+ t("cms.common.slugLabel", "Slug"),
80
+ ": ",
81
+ revision.slug
82
+ ] }),
83
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
84
+ t("cms.editor.publishAt", "Publish at"),
85
+ ":",
86
+ " ",
87
+ revision.publishAt ? new Date(revision.publishAt).toLocaleString() : t("cms.revisions.unscheduled", "Not scheduled")
88
+ ] }),
89
+ /* @__PURE__ */ jsxRuntime.jsx(
90
+ "pre",
91
+ {
92
+ className: "max-h-80 overflow-auto rounded-md bg-muted p-3 text-xs",
93
+ "aria-label": t("cms.revisions.snapshot", "Field snapshot"),
94
+ children: JSON.stringify(JSON.parse(revision.data), null, 2)
95
+ }
96
+ ),
97
+ /* @__PURE__ */ jsxRuntime.jsx(
98
+ button.Button,
99
+ {
100
+ variant: "outline",
101
+ disabled: restore.isPending,
102
+ onClick: () => {
103
+ setRestoreError(null);
104
+ setSelected(revision);
105
+ },
106
+ children: t("cms.revisions.restore", "Restore this version")
107
+ }
108
+ )
109
+ ] })
110
+ ] }, revision.id)),
111
+ history.hasNextPage && /* @__PURE__ */ jsxRuntime.jsx(
112
+ button.Button,
113
+ {
114
+ variant: "outline",
115
+ disabled: history.isFetchingNextPage,
116
+ onClick: () => void history.fetchNextPage(),
117
+ children: t("cms.revisions.loadMore", "Load older revisions")
118
+ }
119
+ )
120
+ ] })
121
+ ] }),
122
+ /* @__PURE__ */ jsxRuntime.jsx(
123
+ alertDialog.AlertDialog,
124
+ {
125
+ open: !!selected,
126
+ onOpenChange: (value) => {
127
+ if (!value && !restore.isPending) setSelected(null);
128
+ },
129
+ children: /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogContent, { children: [
130
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogHeader, { children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx(alertDialog.AlertDialogTitle, { children: t(
132
+ "cms.revisions.confirmTitle",
133
+ "Restore version {version}?"
134
+ ).replace("{version}", String(selected?.version ?? "")) }),
135
+ /* @__PURE__ */ jsxRuntime.jsx(alertDialog.AlertDialogDescription, { children: t(
136
+ "cms.revisions.confirmDescription",
137
+ "This replaces the active entry, including its slug and schedule. Unsaved editor changes will be discarded. Existing revisions are kept."
138
+ ) })
139
+ ] }),
140
+ restoreError && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "text-sm text-destructive", children: restoreError }),
141
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogFooter, { children: [
142
+ /* @__PURE__ */ jsxRuntime.jsx(alertDialog.AlertDialogCancel, { disabled: restore.isPending, children: t("cms.common.cancel", "Cancel") }),
143
+ /* @__PURE__ */ jsxRuntime.jsx(
144
+ alertDialog.AlertDialogAction,
145
+ {
146
+ disabled: restore.isPending,
147
+ onClick: (event) => {
148
+ event.preventDefault();
149
+ void restoreSelected();
150
+ },
151
+ children: restore.isPending ? t("cms.revisions.restoring", "Restoring...") : t("cms.revisions.confirm", "Restore version")
152
+ }
153
+ )
154
+ ] })
155
+ ] })
156
+ }
157
+ )
158
+ ] });
159
+ }
160
+
161
+ exports.RevisionHistory = RevisionHistory;
@@ -0,0 +1,159 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { Button } from '../../ui/src/components/button.mjs';
5
+ import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../ui/src/components/sheet.mjs';
6
+ import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../ui/src/components/alert-dialog.mjs';
7
+ import { useTranslate } from '@btst/stack/context';
8
+ import { useContentRevisions, useRestoreContentRevision } from '../hooks/cms-hooks.mjs';
9
+
10
+ function RevisionHistory({
11
+ typeSlug,
12
+ itemId,
13
+ onRestored
14
+ }) {
15
+ const t = useTranslate();
16
+ const [open, setOpen] = useState(false);
17
+ const [selected, setSelected] = useState(
18
+ null
19
+ );
20
+ const [restoreError, setRestoreError] = useState(null);
21
+ const history = useContentRevisions(typeSlug, itemId, open);
22
+ const restore = useRestoreContentRevision();
23
+ const restoreSelected = async () => {
24
+ if (!selected) return;
25
+ setRestoreError(null);
26
+ try {
27
+ await restore.mutateAsync({
28
+ typeSlug,
29
+ id: itemId,
30
+ revisionId: selected.id
31
+ });
32
+ onRestored();
33
+ setSelected(null);
34
+ setOpen(false);
35
+ } catch (error) {
36
+ setRestoreError(
37
+ error instanceof Error ? error.message : t("cms.revisions.restoreError", "Could not restore revision")
38
+ );
39
+ }
40
+ };
41
+ return /* @__PURE__ */ jsxs(Sheet, { open, onOpenChange: setOpen, children: [
42
+ /* @__PURE__ */ jsx(SheetTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", children: t("cms.revisions.title", "Revision history") }) }),
43
+ /* @__PURE__ */ jsxs(SheetContent, { className: "w-full overflow-y-auto sm:max-w-lg", children: [
44
+ /* @__PURE__ */ jsxs(SheetHeader, { children: [
45
+ /* @__PURE__ */ jsx(SheetTitle, { children: t("cms.revisions.title", "Revision history") }),
46
+ /* @__PURE__ */ jsx(SheetDescription, { children: t(
47
+ "cms.revisions.description",
48
+ "Inspect saved versions. Restoring a version replaces the editor state and creates a new revision."
49
+ ) })
50
+ ] }),
51
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4 px-4 pb-6", children: [
52
+ history.isLoading && /* @__PURE__ */ jsx("p", { role: "status", children: t("cms.revisions.loading", "Loading revisions...") }),
53
+ history.error && /* @__PURE__ */ jsxs("div", { role: "alert", className: "space-y-2", children: [
54
+ /* @__PURE__ */ jsx("p", { className: "text-destructive", children: history.error.message }),
55
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void history.refetch(), children: t("cms.revisions.retry", "Retry") })
56
+ ] }),
57
+ !history.isLoading && !history.error && history.revisions.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: t(
58
+ "cms.revisions.empty",
59
+ "No revisions yet. Saving this entry will create its first revision."
60
+ ) }),
61
+ history.revisions.map((revision) => /* @__PURE__ */ jsxs("details", { className: "rounded-md border p-3", children: [
62
+ /* @__PURE__ */ jsxs("summary", { className: "cursor-pointer space-y-1 break-words", children: [
63
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("cms.revisions.version", "Version {version}").replace(
64
+ "{version}",
65
+ String(revision.version)
66
+ ) }),
67
+ /* @__PURE__ */ jsx("span", { className: "block text-sm text-muted-foreground", children: /* @__PURE__ */ jsx("time", { dateTime: revision.createdAt, children: new Date(revision.createdAt).toLocaleString() }) }),
68
+ /* @__PURE__ */ jsxs("span", { className: "block text-sm text-muted-foreground", children: [
69
+ t("cms.revisions.author", "Author"),
70
+ ":",
71
+ " ",
72
+ revision.authorId ?? t("cms.revisions.system", "System")
73
+ ] })
74
+ ] }),
75
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3 text-sm", children: [
76
+ /* @__PURE__ */ jsxs("p", { className: "break-words", children: [
77
+ t("cms.common.slugLabel", "Slug"),
78
+ ": ",
79
+ revision.slug
80
+ ] }),
81
+ /* @__PURE__ */ jsxs("p", { children: [
82
+ t("cms.editor.publishAt", "Publish at"),
83
+ ":",
84
+ " ",
85
+ revision.publishAt ? new Date(revision.publishAt).toLocaleString() : t("cms.revisions.unscheduled", "Not scheduled")
86
+ ] }),
87
+ /* @__PURE__ */ jsx(
88
+ "pre",
89
+ {
90
+ className: "max-h-80 overflow-auto rounded-md bg-muted p-3 text-xs",
91
+ "aria-label": t("cms.revisions.snapshot", "Field snapshot"),
92
+ children: JSON.stringify(JSON.parse(revision.data), null, 2)
93
+ }
94
+ ),
95
+ /* @__PURE__ */ jsx(
96
+ Button,
97
+ {
98
+ variant: "outline",
99
+ disabled: restore.isPending,
100
+ onClick: () => {
101
+ setRestoreError(null);
102
+ setSelected(revision);
103
+ },
104
+ children: t("cms.revisions.restore", "Restore this version")
105
+ }
106
+ )
107
+ ] })
108
+ ] }, revision.id)),
109
+ history.hasNextPage && /* @__PURE__ */ jsx(
110
+ Button,
111
+ {
112
+ variant: "outline",
113
+ disabled: history.isFetchingNextPage,
114
+ onClick: () => void history.fetchNextPage(),
115
+ children: t("cms.revisions.loadMore", "Load older revisions")
116
+ }
117
+ )
118
+ ] })
119
+ ] }),
120
+ /* @__PURE__ */ jsx(
121
+ AlertDialog,
122
+ {
123
+ open: !!selected,
124
+ onOpenChange: (value) => {
125
+ if (!value && !restore.isPending) setSelected(null);
126
+ },
127
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
128
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
129
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: t(
130
+ "cms.revisions.confirmTitle",
131
+ "Restore version {version}?"
132
+ ).replace("{version}", String(selected?.version ?? "")) }),
133
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: t(
134
+ "cms.revisions.confirmDescription",
135
+ "This replaces the active entry, including its slug and schedule. Unsaved editor changes will be discarded. Existing revisions are kept."
136
+ ) })
137
+ ] }),
138
+ restoreError && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: restoreError }),
139
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
140
+ /* @__PURE__ */ jsx(AlertDialogCancel, { disabled: restore.isPending, children: t("cms.common.cancel", "Cancel") }),
141
+ /* @__PURE__ */ jsx(
142
+ AlertDialogAction,
143
+ {
144
+ disabled: restore.isPending,
145
+ onClick: (event) => {
146
+ event.preventDefault();
147
+ void restoreSelected();
148
+ },
149
+ children: restore.isPending ? t("cms.revisions.restoring", "Restoring...") : t("cms.revisions.confirm", "Restore version")
150
+ }
151
+ )
152
+ ] })
153
+ ] })
154
+ }
155
+ )
156
+ ] });
157
+ }
158
+
159
+ export { RevisionHistory };
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const lucideReact = require('lucide-react');
6
+ const card = require('../../../ui/src/components/card.cjs');
7
+ const context = require('@btst/stack/context');
8
+ const constants = require('../../constants.cjs');
9
+ const utils = require('../../../utils.cjs');
10
+
11
+ function ContentTypeCard({
12
+ page,
13
+ ...props
14
+ }) {
15
+ if (!page) return /* @__PURE__ */ jsxRuntime.jsx(DefaultContentTypeCard, { ...props });
16
+ return /* @__PURE__ */ jsxRuntime.jsx(ScopedContentTypeCard, { page, ...props });
17
+ }
18
+ function ScopedContentTypeCard({
19
+ page,
20
+ ...props
21
+ }) {
22
+ const { pageOverrides } = context.usePluginOverrides(constants.CMS_PLUGIN_ID);
23
+ const TypeCard = (page ? pageOverrides?.[page]?.ContentTypeCard : void 0) ?? DefaultContentTypeCard;
24
+ return /* @__PURE__ */ jsxRuntime.jsx(TypeCard, { ...props });
25
+ }
26
+ function DefaultContentTypeCard({
27
+ contentType,
28
+ itemCountLabel,
29
+ onOpen,
30
+ className,
31
+ titleClassName
32
+ }) {
33
+ return /* @__PURE__ */ jsxRuntime.jsxs(
34
+ card.Card,
35
+ {
36
+ className: utils.cn(
37
+ "hover:border-primary/50 transition-colors cursor-pointer",
38
+ className
39
+ ),
40
+ onClick: onOpen,
41
+ children: [
42
+ /* @__PURE__ */ jsxRuntime.jsxs(card.CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
43
+ /* @__PURE__ */ jsxRuntime.jsx(card.CardTitle, { className: utils.cn("text-lg font-medium", titleClassName), children: contentType.name }),
44
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-5 w-5 text-muted-foreground" })
45
+ ] }),
46
+ /* @__PURE__ */ jsxRuntime.jsxs(card.CardContent, { children: [
47
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold", children: contentType.itemCount }),
48
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: itemCountLabel }),
49
+ contentType.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground mt-2 line-clamp-2", children: contentType.description })
50
+ ] })
51
+ ]
52
+ }
53
+ );
54
+ }
55
+
56
+ exports.ContentTypeCard = ContentTypeCard;