@btst/stack 1.8.0 → 1.9.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 (44) hide show
  1. package/dist/packages/better-stack/src/plugins/cms/api/plugin.cjs +445 -16
  2. package/dist/packages/better-stack/src/plugins/cms/api/plugin.mjs +445 -16
  3. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -7
  4. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.mjs +25 -8
  5. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/relation-field.cjs +224 -0
  6. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/relation-field.mjs +222 -0
  7. package/dist/packages/better-stack/src/plugins/cms/client/components/inverse-relations-panel.cjs +243 -0
  8. package/dist/packages/better-stack/src/plugins/cms/client/components/inverse-relations-panel.mjs +241 -0
  9. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +56 -2
  10. package/dist/packages/better-stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +56 -2
  11. package/dist/packages/better-stack/src/plugins/cms/client/hooks/cms-hooks.cjs +190 -0
  12. package/dist/packages/better-stack/src/plugins/cms/client/hooks/cms-hooks.mjs +187 -1
  13. package/dist/packages/better-stack/src/plugins/cms/db.cjs +38 -0
  14. package/dist/packages/better-stack/src/plugins/cms/db.mjs +38 -0
  15. package/dist/packages/ui/src/components/auto-form/fields/object.cjs +81 -1
  16. package/dist/packages/ui/src/components/auto-form/fields/object.mjs +81 -1
  17. package/dist/packages/ui/src/components/dialog.cjs +6 -0
  18. package/dist/packages/ui/src/components/dialog.mjs +6 -1
  19. package/dist/plugins/cms/api/index.d.cts +67 -3
  20. package/dist/plugins/cms/api/index.d.mts +67 -3
  21. package/dist/plugins/cms/api/index.d.ts +67 -3
  22. package/dist/plugins/cms/client/hooks/index.cjs +4 -0
  23. package/dist/plugins/cms/client/hooks/index.d.cts +82 -3
  24. package/dist/plugins/cms/client/hooks/index.d.mts +82 -3
  25. package/dist/plugins/cms/client/hooks/index.d.ts +82 -3
  26. package/dist/plugins/cms/client/hooks/index.mjs +1 -1
  27. package/dist/plugins/cms/query-keys.d.cts +1 -1
  28. package/dist/plugins/cms/query-keys.d.mts +1 -1
  29. package/dist/plugins/cms/query-keys.d.ts +1 -1
  30. package/dist/plugins/form-builder/api/index.d.cts +1 -1
  31. package/dist/plugins/form-builder/api/index.d.mts +1 -1
  32. package/dist/plugins/form-builder/api/index.d.ts +1 -1
  33. package/dist/shared/{stack.L-UFwz2G.d.cts → stack.oGOteE6g.d.cts} +27 -5
  34. package/dist/shared/{stack.L-UFwz2G.d.mts → stack.oGOteE6g.d.mts} +27 -5
  35. package/dist/shared/{stack.L-UFwz2G.d.ts → stack.oGOteE6g.d.ts} +27 -5
  36. package/package.json +1 -1
  37. package/src/plugins/cms/api/plugin.ts +667 -21
  38. package/src/plugins/cms/client/components/forms/content-form.tsx +60 -18
  39. package/src/plugins/cms/client/components/forms/relation-field.tsx +299 -0
  40. package/src/plugins/cms/client/components/inverse-relations-panel.tsx +329 -0
  41. package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +127 -1
  42. package/src/plugins/cms/client/hooks/cms-hooks.tsx +344 -0
  43. package/src/plugins/cms/db.ts +38 -0
  44. package/src/plugins/cms/types.ts +99 -10
@@ -0,0 +1,243 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const reactQuery = require('@tanstack/react-query');
7
+ const lucideReact = require('lucide-react');
8
+ const button = require('../../../../../../ui/src/components/button.cjs');
9
+ const card = require('../../../../../../ui/src/components/card.cjs');
10
+ const client = require('@btst/stack/plugins/client');
11
+ const context = require('@btst/stack/context');
12
+ const alertDialog = require('../../../../../../ui/src/components/alert-dialog.cjs');
13
+ const cmsHooks = require('../hooks/cms-hooks.cjs');
14
+
15
+ function InverseRelationsPanel({
16
+ contentTypeSlug,
17
+ itemId
18
+ }) {
19
+ const { apiBaseURL, apiBasePath, headers, navigate, Link } = context.usePluginOverrides("cms");
20
+ const basePath = context.useBasePath();
21
+ const client$1 = client.createApiClient({
22
+ baseURL: apiBaseURL,
23
+ basePath: apiBasePath
24
+ });
25
+ const { data: inverseRelationsData, isLoading } = reactQuery.useQuery({
26
+ queryKey: ["cmsInverseRelations", contentTypeSlug, itemId],
27
+ queryFn: async () => {
28
+ const response = await client$1("/content-types/:slug/inverse-relations", {
29
+ method: "GET",
30
+ params: { slug: contentTypeSlug },
31
+ query: { itemId },
32
+ headers
33
+ });
34
+ return response.data?.inverseRelations ?? [];
35
+ },
36
+ staleTime: 1e3 * 60 * 5
37
+ });
38
+ if (isLoading) {
39
+ return /* @__PURE__ */ jsxRuntime.jsx(card.Card, { className: "animate-pulse", children: /* @__PURE__ */ jsxRuntime.jsx(card.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 w-32 bg-muted rounded" }) }) });
40
+ }
41
+ const inverseRelations = inverseRelationsData ?? [];
42
+ if (inverseRelations.length === 0) {
43
+ return null;
44
+ }
45
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
46
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold", children: "Related Items" }),
47
+ inverseRelations.map((relation) => /* @__PURE__ */ jsxRuntime.jsx(
48
+ InverseRelationSection,
49
+ {
50
+ relation,
51
+ contentTypeSlug,
52
+ itemId,
53
+ basePath,
54
+ navigate,
55
+ Link,
56
+ client: client$1,
57
+ headers
58
+ },
59
+ `${relation.sourceType}-${relation.fieldName}`
60
+ ))
61
+ ] });
62
+ }
63
+ function InverseRelationSection({
64
+ relation,
65
+ contentTypeSlug,
66
+ itemId,
67
+ basePath,
68
+ navigate,
69
+ Link,
70
+ client,
71
+ headers
72
+ }) {
73
+ const [isExpanded, setIsExpanded] = React.useState(true);
74
+ const [deleteItemId, setDeleteItemId] = React.useState(null);
75
+ const [deleteError, setDeleteError] = React.useState(null);
76
+ const deleteContent = cmsHooks.useDeleteContent(relation.sourceType);
77
+ const { data: itemsData, refetch } = reactQuery.useQuery({
78
+ queryKey: [
79
+ "cmsInverseRelationItems",
80
+ contentTypeSlug,
81
+ relation.sourceType,
82
+ itemId,
83
+ relation.fieldName
84
+ ],
85
+ queryFn: async () => {
86
+ const response = await client(
87
+ "/content-types/:slug/inverse-relations/:sourceType",
88
+ {
89
+ method: "GET",
90
+ params: { slug: contentTypeSlug, sourceType: relation.sourceType },
91
+ query: { itemId, fieldName: relation.fieldName },
92
+ headers
93
+ }
94
+ );
95
+ return response.data ?? { items: [], total: 0 };
96
+ },
97
+ staleTime: 1e3 * 60 * 5,
98
+ enabled: isExpanded
99
+ });
100
+ const items = itemsData?.items ?? [];
101
+ const total = itemsData?.total ?? relation.count;
102
+ const handleDelete = async () => {
103
+ if (deleteItemId) {
104
+ setDeleteError(null);
105
+ try {
106
+ await deleteContent.mutateAsync(deleteItemId);
107
+ setDeleteItemId(null);
108
+ refetch();
109
+ } catch (error) {
110
+ const message = error instanceof Error ? error.message : "Failed to delete item. Please try again.";
111
+ setDeleteError(message);
112
+ }
113
+ }
114
+ };
115
+ const handleAddNew = () => {
116
+ const createUrl = `${basePath}/cms/${relation.sourceType}/new?prefill_${relation.fieldName}=${itemId}`;
117
+ navigate(createUrl);
118
+ };
119
+ const LinkComponent = Link ?? "a";
120
+ return /* @__PURE__ */ jsxRuntime.jsxs(card.Card, { children: [
121
+ /* @__PURE__ */ jsxRuntime.jsx(card.CardHeader, { className: "py-3", children: /* @__PURE__ */ jsxRuntime.jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: () => setIsExpanded(!isExpanded),
126
+ className: "flex items-center justify-between w-full text-left",
127
+ children: /* @__PURE__ */ jsxRuntime.jsxs(card.CardTitle, { className: "text-base flex items-center gap-2", children: [
128
+ isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" }),
129
+ relation.sourceTypeName,
130
+ " (",
131
+ total,
132
+ ")"
133
+ ] })
134
+ }
135
+ ) }),
136
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsxs(card.CardContent, { className: "pt-0", children: [
137
+ items.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground py-2", children: [
138
+ "No ",
139
+ relation.sourceTypeName.toLowerCase(),
140
+ " items yet."
141
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-2", children: items.map((item) => {
142
+ const displayValue = getDisplayValue(item);
143
+ const editUrl = `${basePath}/cms/${relation.sourceType}/${item.id}`;
144
+ return /* @__PURE__ */ jsxRuntime.jsxs(
145
+ "li",
146
+ {
147
+ className: "flex items-center justify-between py-2 px-3 rounded-md bg-muted/50 hover:bg-muted transition-colors",
148
+ children: [
149
+ /* @__PURE__ */ jsxRuntime.jsxs(
150
+ LinkComponent,
151
+ {
152
+ href: editUrl,
153
+ className: "flex-1 text-sm hover:underline flex items-center gap-2",
154
+ children: [
155
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: displayValue }),
156
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 opacity-50" })
157
+ ]
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsxRuntime.jsx(
161
+ button.Button,
162
+ {
163
+ variant: "ghost",
164
+ size: "icon",
165
+ className: "h-7 w-7 text-muted-foreground hover:text-destructive",
166
+ onClick: () => setDeleteItemId(item.id),
167
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
168
+ }
169
+ )
170
+ ]
171
+ },
172
+ item.id
173
+ );
174
+ }) }),
175
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 pt-3 border-t", children: /* @__PURE__ */ jsxRuntime.jsxs(
176
+ button.Button,
177
+ {
178
+ variant: "outline",
179
+ size: "sm",
180
+ onClick: handleAddNew,
181
+ className: "w-full",
182
+ children: [
183
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 mr-2" }),
184
+ "Add ",
185
+ relation.sourceTypeName
186
+ ]
187
+ }
188
+ ) })
189
+ ] }),
190
+ /* @__PURE__ */ jsxRuntime.jsx(
191
+ alertDialog.AlertDialog,
192
+ {
193
+ open: !!deleteItemId,
194
+ onOpenChange: (open) => {
195
+ if (!open) {
196
+ setDeleteItemId(null);
197
+ setDeleteError(null);
198
+ }
199
+ },
200
+ children: /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogContent, { children: [
201
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogHeader, { children: [
202
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogTitle, { children: [
203
+ "Delete ",
204
+ relation.sourceTypeName,
205
+ "?"
206
+ ] }),
207
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogDescription, { children: [
208
+ "This action cannot be undone. This will permanently delete this",
209
+ " ",
210
+ relation.sourceTypeName.toLowerCase(),
211
+ "."
212
+ ] })
213
+ ] }),
214
+ deleteError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: deleteError }),
215
+ /* @__PURE__ */ jsxRuntime.jsxs(alertDialog.AlertDialogFooter, { children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx(alertDialog.AlertDialogCancel, { children: "Cancel" }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(
218
+ alertDialog.AlertDialogAction,
219
+ {
220
+ onClick: handleDelete,
221
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
222
+ children: "Delete"
223
+ }
224
+ )
225
+ ] })
226
+ ] })
227
+ }
228
+ )
229
+ ] });
230
+ }
231
+ function getDisplayValue(item) {
232
+ const data = item.parsedData;
233
+ const displayFields = ["name", "title", "label", "content", "author", "slug"];
234
+ for (const field of displayFields) {
235
+ if (typeof data[field] === "string" && data[field]) {
236
+ const value = data[field];
237
+ return value.length > 50 ? `${value.slice(0, 50)}...` : value;
238
+ }
239
+ }
240
+ return item.slug;
241
+ }
242
+
243
+ exports.InverseRelationsPanel = InverseRelationsPanel;
@@ -0,0 +1,241 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { useQuery } from '@tanstack/react-query';
5
+ import { ChevronDown, ChevronRight, ExternalLink, Trash2, Plus } from 'lucide-react';
6
+ import { Button } from '../../../../../../ui/src/components/button.mjs';
7
+ import { Card, CardHeader, CardTitle, CardContent } from '../../../../../../ui/src/components/card.mjs';
8
+ import { createApiClient } from '@btst/stack/plugins/client';
9
+ import { usePluginOverrides, useBasePath } from '@btst/stack/context';
10
+ import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../../../../../ui/src/components/alert-dialog.mjs';
11
+ import { useDeleteContent } from '../hooks/cms-hooks.mjs';
12
+
13
+ function InverseRelationsPanel({
14
+ contentTypeSlug,
15
+ itemId
16
+ }) {
17
+ const { apiBaseURL, apiBasePath, headers, navigate, Link } = usePluginOverrides("cms");
18
+ const basePath = useBasePath();
19
+ const client = createApiClient({
20
+ baseURL: apiBaseURL,
21
+ basePath: apiBasePath
22
+ });
23
+ const { data: inverseRelationsData, isLoading } = useQuery({
24
+ queryKey: ["cmsInverseRelations", contentTypeSlug, itemId],
25
+ queryFn: async () => {
26
+ const response = await client("/content-types/:slug/inverse-relations", {
27
+ method: "GET",
28
+ params: { slug: contentTypeSlug },
29
+ query: { itemId },
30
+ headers
31
+ });
32
+ return response.data?.inverseRelations ?? [];
33
+ },
34
+ staleTime: 1e3 * 60 * 5
35
+ });
36
+ if (isLoading) {
37
+ return /* @__PURE__ */ jsx(Card, { className: "animate-pulse", children: /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx("div", { className: "h-5 w-32 bg-muted rounded" }) }) });
38
+ }
39
+ const inverseRelations = inverseRelationsData ?? [];
40
+ if (inverseRelations.length === 0) {
41
+ return null;
42
+ }
43
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
44
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: "Related Items" }),
45
+ inverseRelations.map((relation) => /* @__PURE__ */ jsx(
46
+ InverseRelationSection,
47
+ {
48
+ relation,
49
+ contentTypeSlug,
50
+ itemId,
51
+ basePath,
52
+ navigate,
53
+ Link,
54
+ client,
55
+ headers
56
+ },
57
+ `${relation.sourceType}-${relation.fieldName}`
58
+ ))
59
+ ] });
60
+ }
61
+ function InverseRelationSection({
62
+ relation,
63
+ contentTypeSlug,
64
+ itemId,
65
+ basePath,
66
+ navigate,
67
+ Link,
68
+ client,
69
+ headers
70
+ }) {
71
+ const [isExpanded, setIsExpanded] = useState(true);
72
+ const [deleteItemId, setDeleteItemId] = useState(null);
73
+ const [deleteError, setDeleteError] = useState(null);
74
+ const deleteContent = useDeleteContent(relation.sourceType);
75
+ const { data: itemsData, refetch } = useQuery({
76
+ queryKey: [
77
+ "cmsInverseRelationItems",
78
+ contentTypeSlug,
79
+ relation.sourceType,
80
+ itemId,
81
+ relation.fieldName
82
+ ],
83
+ queryFn: async () => {
84
+ const response = await client(
85
+ "/content-types/:slug/inverse-relations/:sourceType",
86
+ {
87
+ method: "GET",
88
+ params: { slug: contentTypeSlug, sourceType: relation.sourceType },
89
+ query: { itemId, fieldName: relation.fieldName },
90
+ headers
91
+ }
92
+ );
93
+ return response.data ?? { items: [], total: 0 };
94
+ },
95
+ staleTime: 1e3 * 60 * 5,
96
+ enabled: isExpanded
97
+ });
98
+ const items = itemsData?.items ?? [];
99
+ const total = itemsData?.total ?? relation.count;
100
+ const handleDelete = async () => {
101
+ if (deleteItemId) {
102
+ setDeleteError(null);
103
+ try {
104
+ await deleteContent.mutateAsync(deleteItemId);
105
+ setDeleteItemId(null);
106
+ refetch();
107
+ } catch (error) {
108
+ const message = error instanceof Error ? error.message : "Failed to delete item. Please try again.";
109
+ setDeleteError(message);
110
+ }
111
+ }
112
+ };
113
+ const handleAddNew = () => {
114
+ const createUrl = `${basePath}/cms/${relation.sourceType}/new?prefill_${relation.fieldName}=${itemId}`;
115
+ navigate(createUrl);
116
+ };
117
+ const LinkComponent = Link ?? "a";
118
+ return /* @__PURE__ */ jsxs(Card, { children: [
119
+ /* @__PURE__ */ jsx(CardHeader, { className: "py-3", children: /* @__PURE__ */ jsx(
120
+ "button",
121
+ {
122
+ type: "button",
123
+ onClick: () => setIsExpanded(!isExpanded),
124
+ className: "flex items-center justify-between w-full text-left",
125
+ children: /* @__PURE__ */ jsxs(CardTitle, { className: "text-base flex items-center gap-2", children: [
126
+ isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }),
127
+ relation.sourceTypeName,
128
+ " (",
129
+ total,
130
+ ")"
131
+ ] })
132
+ }
133
+ ) }),
134
+ isExpanded && /* @__PURE__ */ jsxs(CardContent, { className: "pt-0", children: [
135
+ items.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground py-2", children: [
136
+ "No ",
137
+ relation.sourceTypeName.toLowerCase(),
138
+ " items yet."
139
+ ] }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: items.map((item) => {
140
+ const displayValue = getDisplayValue(item);
141
+ const editUrl = `${basePath}/cms/${relation.sourceType}/${item.id}`;
142
+ return /* @__PURE__ */ jsxs(
143
+ "li",
144
+ {
145
+ className: "flex items-center justify-between py-2 px-3 rounded-md bg-muted/50 hover:bg-muted transition-colors",
146
+ children: [
147
+ /* @__PURE__ */ jsxs(
148
+ LinkComponent,
149
+ {
150
+ href: editUrl,
151
+ className: "flex-1 text-sm hover:underline flex items-center gap-2",
152
+ children: [
153
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: displayValue }),
154
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 opacity-50" })
155
+ ]
156
+ }
157
+ ),
158
+ /* @__PURE__ */ jsx(
159
+ Button,
160
+ {
161
+ variant: "ghost",
162
+ size: "icon",
163
+ className: "h-7 w-7 text-muted-foreground hover:text-destructive",
164
+ onClick: () => setDeleteItemId(item.id),
165
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
166
+ }
167
+ )
168
+ ]
169
+ },
170
+ item.id
171
+ );
172
+ }) }),
173
+ /* @__PURE__ */ jsx("div", { className: "mt-3 pt-3 border-t", children: /* @__PURE__ */ jsxs(
174
+ Button,
175
+ {
176
+ variant: "outline",
177
+ size: "sm",
178
+ onClick: handleAddNew,
179
+ className: "w-full",
180
+ children: [
181
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
182
+ "Add ",
183
+ relation.sourceTypeName
184
+ ]
185
+ }
186
+ ) })
187
+ ] }),
188
+ /* @__PURE__ */ jsx(
189
+ AlertDialog,
190
+ {
191
+ open: !!deleteItemId,
192
+ onOpenChange: (open) => {
193
+ if (!open) {
194
+ setDeleteItemId(null);
195
+ setDeleteError(null);
196
+ }
197
+ },
198
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
199
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
200
+ /* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
201
+ "Delete ",
202
+ relation.sourceTypeName,
203
+ "?"
204
+ ] }),
205
+ /* @__PURE__ */ jsxs(AlertDialogDescription, { children: [
206
+ "This action cannot be undone. This will permanently delete this",
207
+ " ",
208
+ relation.sourceTypeName.toLowerCase(),
209
+ "."
210
+ ] })
211
+ ] }),
212
+ deleteError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: deleteError }),
213
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
214
+ /* @__PURE__ */ jsx(AlertDialogCancel, { children: "Cancel" }),
215
+ /* @__PURE__ */ jsx(
216
+ AlertDialogAction,
217
+ {
218
+ onClick: handleDelete,
219
+ className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
220
+ children: "Delete"
221
+ }
222
+ )
223
+ ] })
224
+ ] })
225
+ }
226
+ )
227
+ ] });
228
+ }
229
+ function getDisplayValue(item) {
230
+ const data = item.parsedData;
231
+ const displayFields = ["name", "title", "label", "content", "author", "slug"];
232
+ for (const field of displayFields) {
233
+ if (typeof data[field] === "string" && data[field]) {
234
+ const value = data[field];
235
+ return value.length > 50 ? `${value.slice(0, 50)}...` : value;
236
+ }
237
+ }
238
+ return item.slug;
239
+ }
240
+
241
+ export { InverseRelationsPanel };
@@ -2,10 +2,12 @@
2
2
  'use strict';
3
3
 
4
4
  const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
5
6
  const lucideReact = require('lucide-react');
6
7
  const button = require('../../../../../../../ui/src/components/button.cjs');
7
8
  const context = require('@btst/stack/context');
8
9
  const contentForm = require('../forms/content-form.cjs');
10
+ const inverseRelationsPanel = require('../inverse-relations-panel.cjs');
9
11
  const emptyState = require('../shared/empty-state.cjs');
10
12
  const pageWrapper = require('../shared/page-wrapper.cjs');
11
13
  const editorSkeleton = require('../loading/editor-skeleton.cjs');
@@ -13,11 +15,59 @@ const index = require('../../localization/index.cjs');
13
15
  const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
14
16
  const cmsHooks = require('../../hooks/cms-hooks.cjs');
15
17
 
18
+ function usePrefillParams() {
19
+ const [prefillData, setPrefillData] = React.useState({});
20
+ React.useEffect(() => {
21
+ if (typeof window === "undefined") {
22
+ return;
23
+ }
24
+ const parseAndSetPrefillData = () => {
25
+ const params = new URLSearchParams(window.location.search);
26
+ const data = {};
27
+ for (const [key, value] of params.entries()) {
28
+ if (key.startsWith("prefill_")) {
29
+ const fieldName = key.slice("prefill_".length);
30
+ if (fieldName) {
31
+ data[fieldName] = value;
32
+ }
33
+ }
34
+ }
35
+ setPrefillData(data);
36
+ };
37
+ parseAndSetPrefillData();
38
+ window.addEventListener("popstate", parseAndSetPrefillData);
39
+ return () => {
40
+ window.removeEventListener("popstate", parseAndSetPrefillData);
41
+ };
42
+ }, []);
43
+ return prefillData;
44
+ }
45
+ function convertPrefillToFormData(prefillParams, jsonSchema) {
46
+ const properties = jsonSchema.properties;
47
+ if (!properties) {
48
+ return prefillParams;
49
+ }
50
+ const result = {};
51
+ for (const [fieldName, value] of Object.entries(prefillParams)) {
52
+ const fieldSchema = properties[fieldName];
53
+ if (fieldSchema?.fieldType === "relation" && fieldSchema.relation) {
54
+ if (fieldSchema.relation.type === "belongsTo") {
55
+ result[fieldName] = { id: value };
56
+ } else {
57
+ result[fieldName] = [{ id: value }];
58
+ }
59
+ } else {
60
+ result[fieldName] = value;
61
+ }
62
+ }
63
+ return result;
64
+ }
16
65
  function ContentEditorPage({ typeSlug, id }) {
17
66
  const overrides = context.usePluginOverrides("cms");
18
67
  const { navigate } = overrides;
19
68
  const localization = { ...index.CMS_LOCALIZATION, ...overrides.localization };
20
69
  const basePath = context.useBasePath();
70
+ const prefillParams = usePrefillParams();
21
71
  useRouteLifecycle.useRouteLifecycle({
22
72
  routeName: "contentEditor",
23
73
  context: {
@@ -86,14 +136,18 @@ function ContentEditorPage({ typeSlug, id }) {
86
136
  contentForm.ContentForm,
87
137
  {
88
138
  contentType,
89
- initialData: item?.parsedData,
139
+ initialData: isEditing ? item?.parsedData : Object.keys(prefillParams).length > 0 ? convertPrefillToFormData(
140
+ prefillParams,
141
+ JSON.parse(contentType.jsonSchema)
142
+ ) : void 0,
90
143
  initialSlug: item?.slug,
91
144
  isEditing,
92
145
  onSubmit: handleSubmit,
93
146
  onCancel: () => navigate(`${basePath}/cms/${typeSlug}`)
94
147
  },
95
148
  isEditing ? `edit-${id}` : "create"
96
- )
149
+ ),
150
+ isEditing && id && /* @__PURE__ */ jsxRuntime.jsx(inverseRelationsPanel.InverseRelationsPanel, { contentTypeSlug: typeSlug, itemId: id })
97
151
  ] }) });
98
152
  }
99
153
 
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useState, useEffect } from 'react';
3
4
  import { ArrowLeft } from 'lucide-react';
4
5
  import { Button } from '../../../../../../../ui/src/components/button.mjs';
5
6
  import { usePluginOverrides, useBasePath } from '@btst/stack/context';
6
7
  import { ContentForm } from '../forms/content-form.mjs';
8
+ import { InverseRelationsPanel } from '../inverse-relations-panel.mjs';
7
9
  import { EmptyState } from '../shared/empty-state.mjs';
8
10
  import { PageWrapper } from '../shared/page-wrapper.mjs';
9
11
  import { EditorSkeleton } from '../loading/editor-skeleton.mjs';
@@ -11,11 +13,59 @@ import { CMS_LOCALIZATION } from '../../localization/index.mjs';
11
13
  import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
12
14
  import { useSuspenseContentTypes, useContentItem, useCreateContent, useUpdateContent } from '../../hooks/cms-hooks.mjs';
13
15
 
16
+ function usePrefillParams() {
17
+ const [prefillData, setPrefillData] = useState({});
18
+ useEffect(() => {
19
+ if (typeof window === "undefined") {
20
+ return;
21
+ }
22
+ const parseAndSetPrefillData = () => {
23
+ const params = new URLSearchParams(window.location.search);
24
+ const data = {};
25
+ for (const [key, value] of params.entries()) {
26
+ if (key.startsWith("prefill_")) {
27
+ const fieldName = key.slice("prefill_".length);
28
+ if (fieldName) {
29
+ data[fieldName] = value;
30
+ }
31
+ }
32
+ }
33
+ setPrefillData(data);
34
+ };
35
+ parseAndSetPrefillData();
36
+ window.addEventListener("popstate", parseAndSetPrefillData);
37
+ return () => {
38
+ window.removeEventListener("popstate", parseAndSetPrefillData);
39
+ };
40
+ }, []);
41
+ return prefillData;
42
+ }
43
+ function convertPrefillToFormData(prefillParams, jsonSchema) {
44
+ const properties = jsonSchema.properties;
45
+ if (!properties) {
46
+ return prefillParams;
47
+ }
48
+ const result = {};
49
+ for (const [fieldName, value] of Object.entries(prefillParams)) {
50
+ const fieldSchema = properties[fieldName];
51
+ if (fieldSchema?.fieldType === "relation" && fieldSchema.relation) {
52
+ if (fieldSchema.relation.type === "belongsTo") {
53
+ result[fieldName] = { id: value };
54
+ } else {
55
+ result[fieldName] = [{ id: value }];
56
+ }
57
+ } else {
58
+ result[fieldName] = value;
59
+ }
60
+ }
61
+ return result;
62
+ }
14
63
  function ContentEditorPage({ typeSlug, id }) {
15
64
  const overrides = usePluginOverrides("cms");
16
65
  const { navigate } = overrides;
17
66
  const localization = { ...CMS_LOCALIZATION, ...overrides.localization };
18
67
  const basePath = useBasePath();
68
+ const prefillParams = usePrefillParams();
19
69
  useRouteLifecycle({
20
70
  routeName: "contentEditor",
21
71
  context: {
@@ -84,14 +134,18 @@ function ContentEditorPage({ typeSlug, id }) {
84
134
  ContentForm,
85
135
  {
86
136
  contentType,
87
- initialData: item?.parsedData,
137
+ initialData: isEditing ? item?.parsedData : Object.keys(prefillParams).length > 0 ? convertPrefillToFormData(
138
+ prefillParams,
139
+ JSON.parse(contentType.jsonSchema)
140
+ ) : void 0,
88
141
  initialSlug: item?.slug,
89
142
  isEditing,
90
143
  onSubmit: handleSubmit,
91
144
  onCancel: () => navigate(`${basePath}/cms/${typeSlug}`)
92
145
  },
93
146
  isEditing ? `edit-${id}` : "create"
94
- )
147
+ ),
148
+ isEditing && id && /* @__PURE__ */ jsx(InverseRelationsPanel, { contentTypeSlug: typeSlug, itemId: id })
95
149
  ] }) });
96
150
  }
97
151