@dyrected/admin 2.5.65 → 2.6.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.
- package/dist/admin.css +101 -4
- package/dist/components/forms/fields/join-field.test.d.ts +1 -0
- package/dist/components/forms/form-engine.d.ts +13 -2
- package/dist/components/ui/data-table.d.ts +4 -2
- package/dist/components/ui/render-cell.test.d.ts +1 -0
- package/dist/components/workflow/WorkflowPanel.d.ts +8 -1
- package/dist/components/workflow/draft-live-compare-sheet.d.ts +6 -0
- package/dist/components/workflow/workflow-transition-controls.d.ts +60 -0
- package/dist/index.mjs +1857 -506
- package/dist/lib/document-title.d.ts +16 -0
- package/dist/lib/document-title.test.d.ts +1 -0
- package/dist/lib/draft-live-compare.d.ts +38 -0
- package/dist/lib/draft-live-compare.test.d.ts +1 -0
- package/dist/lib/workflow-autosave.d.ts +18 -0
- package/dist/lib/workflow-autosave.test.d.ts +1 -0
- package/dist/lib/workflow-ui.d.ts +21 -0
- package/dist/lib/workflow-ui.test.d.ts +1 -0
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { HashRouter, Link, MemoryRouter, Route, Routes, useLocation, useNavigate
|
|
|
5
5
|
import { QueryClient, QueryClientProvider, keepPreviousData, useInfiniteQuery, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { DyrectedError, PREVIEW_TOKEN_PARAM, createClient } from "@dyrected/sdk";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
-
import { AlertCircle, AlertTriangle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpDown, ArrowUpRight, Bold, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Copy, Database, Download, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, FileUp, Filter, Globe, GripVertical, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, History, Home, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, LayoutGrid, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, Monitor, Moon, MoreHorizontal, MousePointer2, PanelLeftClose, PanelLeftOpen, Pencil, Play, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, Smartphone, Sparkles, Star, Strikethrough, Sun, Table, Trash2, Underline, Undo2, Upload, UploadCloud, Users, Video, Volume2, Workflow, X, XCircle, icons } from "lucide-react";
|
|
8
|
+
import { AlertCircle, AlertTriangle, AlignCenter, AlignLeft, AlignRight, Archive, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, ArrowUpDown, ArrowUpRight, Bold, Braces, Calendar, Check, CheckCircle, CheckCircle2, ChevronDown, ChevronLeft, ChevronLeftIcon, ChevronRight, ChevronRightIcon, ChevronUp, ChevronsUpDown, Circle, Clock, Clock3, Code, Copy, Database, Download, ExternalLink, Eye, EyeOff, FileDown, FileIcon, FileText, FileUp, Filter, Globe, GripVertical, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, History, Home, Image as Image$1, Info, Italic, KeyRound, Layers, LayoutDashboard, LayoutGrid, Library, Link as Link$1, List, ListOrdered, Loader2, Lock, LogOut, Mail, Menu, MessageSquarePlus, Monitor, Moon, MoreHorizontal, MousePointer2, PanelLeftClose, PanelLeftOpen, Pencil, Play, Plus, Quote, RotateCcw, Save, Scissors, Search, Settings, Settings2, Share2, Shield, Smartphone, Sparkles, Star, Strikethrough, Sun, Table, Trash2, Underline, Undo2, Upload, UploadCloud, Users, Video, Volume2, Workflow, X, XCircle, icons } from "lucide-react";
|
|
9
9
|
import { clsx } from "clsx";
|
|
10
10
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
11
11
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
@@ -15,7 +15,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
|
15
15
|
import { Toaster, toast } from "sonner";
|
|
16
16
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
17
17
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
18
|
-
import { flexRender, getCoreRowModel,
|
|
18
|
+
import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
19
19
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
20
20
|
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
21
21
|
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
@@ -812,10 +812,65 @@ function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collap
|
|
|
812
812
|
const collections = (schemas?.collections)?.filter((c) => !c?.admin?.hidden && !c?.slug.startsWith("platform_")) ?? [];
|
|
813
813
|
const globals = (schemas?.globals)?.filter((g) => !g?.admin?.hidden && !g?.slug.startsWith("platform_")) ?? [];
|
|
814
814
|
const uploadCollections = collections.filter((c) => c.upload);
|
|
815
|
+
const standardCollections = collections.filter((c) => !c.upload && !c.auth);
|
|
816
|
+
const authCollections = collections.filter((c) => !c.upload && c.auth);
|
|
815
817
|
const groupLabel = (text) => !collapsed ? /* @__PURE__ */ jsx("p", {
|
|
816
818
|
className: "dy-px-3 dy-mb-1.5 dy-text-[10px] dy-font-semibold dy-uppercase dy-tracking-widest dy-text-muted-foreground/50",
|
|
817
819
|
children: text
|
|
818
820
|
}) : /* @__PURE__ */ jsx("div", { className: "dy-my-2 dy-mx-3 dy-h-px dy-bg-border" });
|
|
821
|
+
const renderCollectionItem = (col) => {
|
|
822
|
+
const isReadOnly = col.access?.read && !col.access?.create && !col.access?.update && !col.access?.delete;
|
|
823
|
+
const navLabel = /* @__PURE__ */ jsxs("div", {
|
|
824
|
+
className: "dy-flex dy-items-center dy-gap-1.5 dy-min-w-0",
|
|
825
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
826
|
+
className: "dy-truncate",
|
|
827
|
+
children: col.labels?.plural ?? col.label ?? col.slug
|
|
828
|
+
}), !collapsed && /* @__PURE__ */ jsxs("div", {
|
|
829
|
+
className: "dy-flex dy-gap-1 dy-shrink-0",
|
|
830
|
+
children: [
|
|
831
|
+
col.auth && /* @__PURE__ */ jsx(Shield, { className: "dy-h-4 dy-w-4 dy-text-primary/70" }),
|
|
832
|
+
col.shared && /* @__PURE__ */ jsx(Share2, { className: "dy-h-4 dy-w-4 dy-text-purple-500/70" }),
|
|
833
|
+
isReadOnly && /* @__PURE__ */ jsx(Lock, { className: "dy-h-4 dy-w-4 dy-text-muted-foreground/40" })
|
|
834
|
+
]
|
|
835
|
+
})]
|
|
836
|
+
});
|
|
837
|
+
return /* @__PURE__ */ jsx(NavItem, {
|
|
838
|
+
to: `/collections/${col.slug}`,
|
|
839
|
+
icon: resolveAdminIcon(col.admin?.icon, col.auth ? Users : Database),
|
|
840
|
+
label: navLabel,
|
|
841
|
+
active: location.pathname.startsWith(`/collections/${col.slug}`),
|
|
842
|
+
collapsed,
|
|
843
|
+
onClick: onNavigate
|
|
844
|
+
}, col.slug);
|
|
845
|
+
};
|
|
846
|
+
const renderCollectionSection = (sectionCollections, ungroupedLabel) => {
|
|
847
|
+
if (sectionCollections.length === 0) return null;
|
|
848
|
+
const groups = /* @__PURE__ */ new Map();
|
|
849
|
+
const ungrouped = [];
|
|
850
|
+
sectionCollections.forEach((col) => {
|
|
851
|
+
const groupName = col.admin?.group;
|
|
852
|
+
if (groupName) {
|
|
853
|
+
if (!groups.has(groupName)) groups.set(groupName, []);
|
|
854
|
+
groups.get(groupName).push(col);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
ungrouped.push(col);
|
|
858
|
+
});
|
|
859
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
860
|
+
className: "dy-space-y-1",
|
|
861
|
+
children: [Array.from(groups.entries()).map(([groupName, cols]) => /* @__PURE__ */ jsx(NavGroup, {
|
|
862
|
+
label: groupName,
|
|
863
|
+
collapsed,
|
|
864
|
+
defaultExpanded: true,
|
|
865
|
+
children: cols.map((col) => renderCollectionItem(col))
|
|
866
|
+
}, groupName)), ungrouped.length > 0 && /* @__PURE__ */ jsx(NavGroup, {
|
|
867
|
+
label: ungroupedLabel,
|
|
868
|
+
collapsed,
|
|
869
|
+
defaultExpanded: true,
|
|
870
|
+
children: ungrouped.map((col) => renderCollectionItem(col))
|
|
871
|
+
})]
|
|
872
|
+
});
|
|
873
|
+
};
|
|
819
874
|
const branding = schemas?.admin?.branding;
|
|
820
875
|
const meta = schemas?.admin?.meta;
|
|
821
876
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -870,65 +925,17 @@ function SidebarInner({ schemas, isLoading, location, logout, isEmbedded, collap
|
|
|
870
925
|
collapsed,
|
|
871
926
|
onClick: onNavigate
|
|
872
927
|
}, col.slug))] }),
|
|
873
|
-
(isLoading ||
|
|
928
|
+
(isLoading || standardCollections.length > 0 || authCollections.length > 0) && /* @__PURE__ */ jsx("div", { children: isLoading ? /* @__PURE__ */ jsx("div", {
|
|
874
929
|
className: "dy-space-y-1 dy-px-1",
|
|
875
930
|
children: [
|
|
876
931
|
1,
|
|
877
932
|
2,
|
|
878
933
|
3
|
|
879
934
|
].map((i) => /* @__PURE__ */ jsx("div", { className: cn("dy-h-8 dy-rounded-md dy-bg-muted/60 dy-animate-pulse", collapsed ? "dy-mx-1" : "dy-mx-2") }, i))
|
|
880
|
-
}) : (
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
nonUpload.forEach((col) => {
|
|
885
|
-
let g = col.admin?.group;
|
|
886
|
-
if (!g && col.auth) g = "System";
|
|
887
|
-
if (g) {
|
|
888
|
-
if (!groups.has(g)) groups.set(g, []);
|
|
889
|
-
groups.get(g).push(col);
|
|
890
|
-
} else ungrouped.push(col);
|
|
891
|
-
});
|
|
892
|
-
const renderCollectionItem = (col) => {
|
|
893
|
-
const isReadOnly = col.access?.read && !col.access?.create && !col.access?.update && !col.access?.delete;
|
|
894
|
-
const navLabel = /* @__PURE__ */ jsxs("div", {
|
|
895
|
-
className: "dy-flex dy-items-center dy-gap-1.5 dy-min-w-0",
|
|
896
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
897
|
-
className: "dy-truncate",
|
|
898
|
-
children: col.labels?.plural ?? col.label ?? col.slug
|
|
899
|
-
}), !collapsed && /* @__PURE__ */ jsxs("div", {
|
|
900
|
-
className: "dy-flex dy-gap-1 dy-shrink-0",
|
|
901
|
-
children: [
|
|
902
|
-
col.auth && /* @__PURE__ */ jsx(Shield, { className: "dy-h-4 dy-w-4 dy-text-primary/70" }),
|
|
903
|
-
col.shared && /* @__PURE__ */ jsx(Share2, { className: "dy-h-4 dy-w-4 dy-text-purple-500/70" }),
|
|
904
|
-
isReadOnly && /* @__PURE__ */ jsx(Lock, { className: "dy-h-4 dy-w-4 dy-text-muted-foreground/40" })
|
|
905
|
-
]
|
|
906
|
-
})]
|
|
907
|
-
});
|
|
908
|
-
return /* @__PURE__ */ jsx(NavItem, {
|
|
909
|
-
to: `/collections/${col.slug}`,
|
|
910
|
-
icon: resolveAdminIcon(col.admin?.icon, col.auth ? Users : Database),
|
|
911
|
-
label: navLabel,
|
|
912
|
-
active: location.pathname.startsWith(`/collections/${col.slug}`),
|
|
913
|
-
collapsed,
|
|
914
|
-
onClick: onNavigate
|
|
915
|
-
}, col.slug);
|
|
916
|
-
};
|
|
917
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
918
|
-
className: "dy-space-y-1",
|
|
919
|
-
children: [Array.from(groups.entries()).map(([groupName, cols]) => /* @__PURE__ */ jsx(NavGroup, {
|
|
920
|
-
label: groupName,
|
|
921
|
-
collapsed,
|
|
922
|
-
defaultExpanded: true,
|
|
923
|
-
children: cols.map((col) => renderCollectionItem(col))
|
|
924
|
-
}, groupName)), ungrouped.length > 0 && /* @__PURE__ */ jsx(NavGroup, {
|
|
925
|
-
label: "Collections",
|
|
926
|
-
collapsed,
|
|
927
|
-
defaultExpanded: true,
|
|
928
|
-
children: ungrouped.map((col) => renderCollectionItem(col))
|
|
929
|
-
})]
|
|
930
|
-
});
|
|
931
|
-
})() }),
|
|
935
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
936
|
+
className: "dy-space-y-1",
|
|
937
|
+
children: [renderCollectionSection(standardCollections, "Collections"), renderCollectionSection(authCollections, "Auth")]
|
|
938
|
+
}) }),
|
|
932
939
|
globals.length > 0 && /* @__PURE__ */ jsxs("div", { children: [groupLabel("Configuration"), /* @__PURE__ */ jsx("div", {
|
|
933
940
|
className: "dy-space-y-0.5",
|
|
934
941
|
children: globals.map((glob) => /* @__PURE__ */ jsx(NavItem, {
|
|
@@ -1038,7 +1045,7 @@ function isNewerVersion(latest, current) {
|
|
|
1038
1045
|
return false;
|
|
1039
1046
|
}
|
|
1040
1047
|
function useUpdateCheck() {
|
|
1041
|
-
const currentVersion = "2.
|
|
1048
|
+
const currentVersion = "2.6.0";
|
|
1042
1049
|
const [updateInfo, setUpdateInfo] = useState(() => {
|
|
1043
1050
|
if (typeof window === "undefined") return null;
|
|
1044
1051
|
const cacheKey = "dyrected_latest_release";
|
|
@@ -1264,7 +1271,7 @@ function getStatusLabel(doc) {
|
|
|
1264
1271
|
}
|
|
1265
1272
|
function Dashboard() {
|
|
1266
1273
|
const { client, components, user } = useDyrected();
|
|
1267
|
-
const currentVersion = "2.
|
|
1274
|
+
const currentVersion = "2.6.0";
|
|
1268
1275
|
const [latestVersion, setLatestVersion] = useState(() => {
|
|
1269
1276
|
if (typeof window === "undefined") return null;
|
|
1270
1277
|
return localStorage.getItem("dyrected_latest_release");
|
|
@@ -2238,9 +2245,8 @@ function usePreferences(key, defaultValue) {
|
|
|
2238
2245
|
//#endregion
|
|
2239
2246
|
//#region src/components/ui/data-table.tsx
|
|
2240
2247
|
var EMPTY_VISIBILITY = {};
|
|
2241
|
-
function DataTable({ columns, data,
|
|
2248
|
+
function DataTable({ columns, data, searchPlaceholder, searchValue, onSearchChange, rowSelection: externalRowSelection, onRowSelectionChange, bulkActions, toolbarActions, persistenceKey, initialColumnVisibility, hideViewButton = false }) {
|
|
2242
2249
|
const [sorting, setSorting] = React$1.useState([]);
|
|
2243
|
-
const [columnFilters, setColumnFilters] = React$1.useState([]);
|
|
2244
2250
|
const [columnVisibility, setColumnVisibility] = usePreferences(persistenceKey ? `visibility_${persistenceKey}` : "temp_visibility", initialColumnVisibility ?? EMPTY_VISIBILITY);
|
|
2245
2251
|
const [internalRowSelection, setInternalRowSelection] = React$1.useState({});
|
|
2246
2252
|
const rowSelection = externalRowSelection || internalRowSelection;
|
|
@@ -2249,15 +2255,12 @@ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelectio
|
|
|
2249
2255
|
data,
|
|
2250
2256
|
columns,
|
|
2251
2257
|
onSortingChange: setSorting,
|
|
2252
|
-
onColumnFiltersChange: setColumnFilters,
|
|
2253
2258
|
getCoreRowModel: getCoreRowModel(),
|
|
2254
2259
|
getSortedRowModel: getSortedRowModel(),
|
|
2255
|
-
getFilteredRowModel: getFilteredRowModel(),
|
|
2256
2260
|
onColumnVisibilityChange: setColumnVisibility,
|
|
2257
2261
|
onRowSelectionChange: setRowSelection,
|
|
2258
2262
|
state: {
|
|
2259
2263
|
sorting,
|
|
2260
|
-
columnFilters,
|
|
2261
2264
|
columnVisibility,
|
|
2262
2265
|
rowSelection
|
|
2263
2266
|
}
|
|
@@ -2289,15 +2292,12 @@ function DataTable({ columns, data, searchKey, rowSelection: externalRowSelectio
|
|
|
2289
2292
|
})
|
|
2290
2293
|
})] })]
|
|
2291
2294
|
}),
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
className: "dy-order-2 dy-h-9 dy-w-full sm:dy-order-1 sm:dy-max-w-sm"
|
|
2299
|
-
});
|
|
2300
|
-
})(),
|
|
2295
|
+
onSearchChange ? /* @__PURE__ */ jsx(Input, {
|
|
2296
|
+
placeholder: searchPlaceholder || "Search...",
|
|
2297
|
+
value: searchValue ?? "",
|
|
2298
|
+
onChange: (event) => onSearchChange(event.target.value),
|
|
2299
|
+
className: "dy-order-2 dy-h-9 dy-w-full sm:dy-order-1 sm:dy-max-w-sm"
|
|
2300
|
+
}) : null,
|
|
2301
2301
|
bulkActions && table.getFilteredSelectedRowModel().rows.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
2302
2302
|
className: "dy-order-3 dy-flex dy-w-full dy-items-center dy-gap-2 dy-animate-in dy-slide-in-from-left-2 sm:dy-order-2 sm:dy-w-auto",
|
|
2303
2303
|
children: bulkActions(table.getFilteredSelectedRowModel().rows.map((r) => r.original.id))
|
|
@@ -3058,6 +3058,135 @@ function CsvImporter({ slug, schema, onClose }) {
|
|
|
3058
3058
|
});
|
|
3059
3059
|
}
|
|
3060
3060
|
//#endregion
|
|
3061
|
+
//#region src/lib/document-title.ts
|
|
3062
|
+
var FALLBACK_FIELD_NAMES = [
|
|
3063
|
+
"title",
|
|
3064
|
+
"name",
|
|
3065
|
+
"label",
|
|
3066
|
+
"heading",
|
|
3067
|
+
"email",
|
|
3068
|
+
"subject"
|
|
3069
|
+
];
|
|
3070
|
+
var MAX_TITLE_DEPTH = 8;
|
|
3071
|
+
function findField(collection, fieldName) {
|
|
3072
|
+
if (!collection?.fields || !fieldName) return void 0;
|
|
3073
|
+
return collection.fields.find((field) => field.name === fieldName);
|
|
3074
|
+
}
|
|
3075
|
+
function findNestedField(fields, fieldName) {
|
|
3076
|
+
if (!fields || !fieldName) return void 0;
|
|
3077
|
+
return fields.find((field) => field.name === fieldName);
|
|
3078
|
+
}
|
|
3079
|
+
function resolveCollectionTitleFieldName(collection) {
|
|
3080
|
+
const configured = findField(collection, collection?.admin?.useAsTitle);
|
|
3081
|
+
if (configured?.name) return configured.name;
|
|
3082
|
+
for (const fieldName of FALLBACK_FIELD_NAMES) {
|
|
3083
|
+
const field = findField(collection, fieldName);
|
|
3084
|
+
if (field?.name) return field.name;
|
|
3085
|
+
}
|
|
3086
|
+
return collection?.fields?.find((field) => !!field.name && field.type !== "join" && field.type !== "row")?.name;
|
|
3087
|
+
}
|
|
3088
|
+
function getObjectFallbackTitle(value) {
|
|
3089
|
+
for (const key of FALLBACK_FIELD_NAMES) {
|
|
3090
|
+
const candidate = value[key];
|
|
3091
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
3092
|
+
}
|
|
3093
|
+
const idValue = value.id;
|
|
3094
|
+
if (typeof idValue === "string" && idValue.trim()) return idValue;
|
|
3095
|
+
if (typeof idValue === "number") return String(idValue);
|
|
3096
|
+
return null;
|
|
3097
|
+
}
|
|
3098
|
+
function resolveStructuredTitleFieldName(field) {
|
|
3099
|
+
if (!field?.fields?.length) return void 0;
|
|
3100
|
+
const configured = findNestedField(field.fields, field.admin?.useAsTitle);
|
|
3101
|
+
if (configured?.name) return configured.name;
|
|
3102
|
+
for (const fieldName of FALLBACK_FIELD_NAMES) {
|
|
3103
|
+
const candidate = findNestedField(field.fields, fieldName);
|
|
3104
|
+
if (candidate?.name) return candidate.name;
|
|
3105
|
+
}
|
|
3106
|
+
return field.fields.find((childField) => !!childField.name && childField.type !== "join" && childField.type !== "row")?.name;
|
|
3107
|
+
}
|
|
3108
|
+
function summarizeTitles(values, limit = 3) {
|
|
3109
|
+
const unique = values.filter(Boolean);
|
|
3110
|
+
if (unique.length === 0) return null;
|
|
3111
|
+
if (unique.length <= limit) return unique.join(", ");
|
|
3112
|
+
return `${unique.slice(0, limit).join(", ")} +${unique.length - limit} more`;
|
|
3113
|
+
}
|
|
3114
|
+
function resolveRelationshipValueTitle(value, field, collections, depth) {
|
|
3115
|
+
if (!field || field.type !== "relationship" || depth >= MAX_TITLE_DEPTH) return null;
|
|
3116
|
+
const relatedCollection = collections?.find((collection) => collection.slug === field.relationTo || collection.slug === field.collection);
|
|
3117
|
+
const relatedTitleFieldName = resolveCollectionTitleFieldName(relatedCollection);
|
|
3118
|
+
const relatedTitleField = findField(relatedCollection, relatedTitleFieldName);
|
|
3119
|
+
const resolveOne = (item) => {
|
|
3120
|
+
if (item == null) return null;
|
|
3121
|
+
if (typeof item === "string" || typeof item === "number") return String(item);
|
|
3122
|
+
if (typeof item !== "object" || Array.isArray(item)) return null;
|
|
3123
|
+
const record = item;
|
|
3124
|
+
if (relatedTitleFieldName && relatedTitleField) {
|
|
3125
|
+
const relatedValue = record[relatedTitleFieldName];
|
|
3126
|
+
const resolved = resolveValueTitleInternal(relatedValue, relatedTitleField, collections, depth + 1);
|
|
3127
|
+
if (resolved) return resolved;
|
|
3128
|
+
}
|
|
3129
|
+
return getObjectFallbackTitle(record);
|
|
3130
|
+
};
|
|
3131
|
+
if (Array.isArray(value)) return summarizeTitles(value.map((item) => resolveOne(item)).filter((item) => !!item));
|
|
3132
|
+
return resolveOne(value);
|
|
3133
|
+
}
|
|
3134
|
+
function resolveStructuredObjectTitle(value, field, collections, depth) {
|
|
3135
|
+
if (!field?.fields?.length || depth >= MAX_TITLE_DEPTH) return getObjectFallbackTitle(value);
|
|
3136
|
+
const titleFieldName = resolveStructuredTitleFieldName(field);
|
|
3137
|
+
const titleField = findNestedField(field.fields, titleFieldName);
|
|
3138
|
+
if (titleFieldName && titleField) {
|
|
3139
|
+
const resolved = resolveValueTitleInternal(value[titleFieldName], titleField, collections, depth + 1);
|
|
3140
|
+
if (resolved) return resolved;
|
|
3141
|
+
}
|
|
3142
|
+
return getObjectFallbackTitle(value);
|
|
3143
|
+
}
|
|
3144
|
+
function resolveStructuredArrayTitle(value, field, collections, depth) {
|
|
3145
|
+
if (depth >= MAX_TITLE_DEPTH) return null;
|
|
3146
|
+
return summarizeTitles(value.map((item) => {
|
|
3147
|
+
if (item == null) return null;
|
|
3148
|
+
if (typeof item !== "object" || Array.isArray(item)) return resolveValueTitleInternal(item, void 0, collections, depth + 1);
|
|
3149
|
+
return resolveStructuredObjectTitle(item, field, collections, depth + 1);
|
|
3150
|
+
}).filter((item) => !!item));
|
|
3151
|
+
}
|
|
3152
|
+
function resolveValueTitle(value, field, collections) {
|
|
3153
|
+
return resolveValueTitleInternal(value, field, collections, 0);
|
|
3154
|
+
}
|
|
3155
|
+
function resolveValueTitleInternal(value, field, collections, depth) {
|
|
3156
|
+
if (value == null) return null;
|
|
3157
|
+
if (field?.type === "relationship") {
|
|
3158
|
+
const relationTitle = resolveRelationshipValueTitle(value, field, collections, depth);
|
|
3159
|
+
if (relationTitle) return relationTitle;
|
|
3160
|
+
}
|
|
3161
|
+
if (field?.type === "array" && Array.isArray(value)) {
|
|
3162
|
+
const arrayTitle = resolveStructuredArrayTitle(value, field, collections, depth);
|
|
3163
|
+
if (arrayTitle) return arrayTitle;
|
|
3164
|
+
}
|
|
3165
|
+
if (field?.type === "object" && typeof value === "object" && !Array.isArray(value)) {
|
|
3166
|
+
const objectTitle = resolveStructuredObjectTitle(value, field, collections, depth);
|
|
3167
|
+
if (objectTitle) return objectTitle;
|
|
3168
|
+
}
|
|
3169
|
+
if (Array.isArray(value)) return summarizeTitles(value.map((item) => resolveValueTitleInternal(item, void 0, collections, depth + 1)).filter((item) => !!item));
|
|
3170
|
+
if (typeof value === "string") return value;
|
|
3171
|
+
if (typeof value === "number") return String(value);
|
|
3172
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
3173
|
+
if (typeof value === "object") return getObjectFallbackTitle(value);
|
|
3174
|
+
return null;
|
|
3175
|
+
}
|
|
3176
|
+
function resolveDocumentTitle(args) {
|
|
3177
|
+
const { entry, collection, collections } = args;
|
|
3178
|
+
if (!entry) return "";
|
|
3179
|
+
const titleFieldName = resolveCollectionTitleFieldName(collection);
|
|
3180
|
+
const titleField = findField(collection, titleFieldName);
|
|
3181
|
+
if (titleFieldName && titleField) {
|
|
3182
|
+
const title = resolveValueTitle(entry[titleFieldName], titleField, collections);
|
|
3183
|
+
if (title) return title;
|
|
3184
|
+
}
|
|
3185
|
+
const fallback = getObjectFallbackTitle(entry);
|
|
3186
|
+
if (fallback) return fallback;
|
|
3187
|
+
return String(entry.id ?? "");
|
|
3188
|
+
}
|
|
3189
|
+
//#endregion
|
|
3061
3190
|
//#region src/lib/format.ts
|
|
3062
3191
|
/** Normalize the shorthand string form into the object form. */
|
|
3063
3192
|
function resolveNumberFormat(format) {
|
|
@@ -3409,6 +3538,23 @@ function RenderCell({ value, field, client, schemas }) {
|
|
|
3409
3538
|
children: formatDate(value, field.admin?.format, field.type)
|
|
3410
3539
|
})]
|
|
3411
3540
|
});
|
|
3541
|
+
if (field.type === "icon") {
|
|
3542
|
+
if (!isAdminIconName(value)) return /* @__PURE__ */ jsx("span", {
|
|
3543
|
+
className: "dy-text-muted-foreground",
|
|
3544
|
+
children: "-"
|
|
3545
|
+
});
|
|
3546
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3547
|
+
className: "dy-inline-flex dy-items-center dy-gap-2 dy-text-foreground",
|
|
3548
|
+
children: [/* @__PURE__ */ jsx(resolveAdminIcon(value, Calendar), {
|
|
3549
|
+
className: "dy-h-4 dy-w-4 dy-shrink-0",
|
|
3550
|
+
"aria-hidden": "true"
|
|
3551
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3552
|
+
className: "dy-text-sm dy-font-medium",
|
|
3553
|
+
title: String(value),
|
|
3554
|
+
children: String(value)
|
|
3555
|
+
})]
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3412
3558
|
const relationTo = field.relationTo || field.collection;
|
|
3413
3559
|
if (field.type === "image" || field.type === "relationship" && isUploadCollection(relationTo, schemas)) {
|
|
3414
3560
|
if (!value) return /* @__PURE__ */ jsx("span", {
|
|
@@ -3430,8 +3576,7 @@ function RenderCell({ value, field, client, schemas }) {
|
|
|
3430
3576
|
});
|
|
3431
3577
|
}
|
|
3432
3578
|
if (field.type === "relationship" && typeof value === "object") {
|
|
3433
|
-
const
|
|
3434
|
-
const displayValue = value[(schemas?.collections?.find((c) => c?.slug === relTo))?.admin?.useAsTitle || "title"] || value.name || value.id || "Unknown";
|
|
3579
|
+
const displayValue = resolveValueTitle(value, field, schemas?.collections) || "Unknown";
|
|
3435
3580
|
return /* @__PURE__ */ jsx("div", {
|
|
3436
3581
|
className: "dy-flex dy-items-center dy-gap-2",
|
|
3437
3582
|
children: /* @__PURE__ */ jsx(Badge, {
|
|
@@ -3445,22 +3590,34 @@ function RenderCell({ value, field, client, schemas }) {
|
|
|
3445
3590
|
className: "dy-text-[11px] dy-text-muted-foreground dy-font-mono dy-bg-muted/30 dy-px-1.5 dy-py-0.5 dy-rounded",
|
|
3446
3591
|
children: formatJson(value, field.admin?.format)
|
|
3447
3592
|
});
|
|
3448
|
-
if (Array.isArray(value))
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
className: "dy-
|
|
3456
|
-
children: [
|
|
3457
|
-
"
|
|
3458
|
-
|
|
3459
|
-
"
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3593
|
+
if (Array.isArray(value)) {
|
|
3594
|
+
const structuredTitle = resolveValueTitle(value, field, schemas?.collections);
|
|
3595
|
+
if (structuredTitle) return /* @__PURE__ */ jsx("span", {
|
|
3596
|
+
className: "dy-text-[11px] dy-text-muted-foreground dy-font-medium dy-leading-tight",
|
|
3597
|
+
children: structuredTitle
|
|
3598
|
+
});
|
|
3599
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3600
|
+
className: "dy-flex dy-flex-wrap dy-gap-1",
|
|
3601
|
+
children: [value.slice(0, 2).map((item, i) => /* @__PURE__ */ jsx(Badge, {
|
|
3602
|
+
variant: "outline",
|
|
3603
|
+
className: "dy-text-[10px] dy-px-1.5 dy-h-5",
|
|
3604
|
+
children: typeof item === "object" ? item.title || item.name || item.id : String(item)
|
|
3605
|
+
}, i)), value.length > 2 && /* @__PURE__ */ jsxs("span", {
|
|
3606
|
+
className: "dy-text-[10px] dy-text-muted-foreground",
|
|
3607
|
+
children: [
|
|
3608
|
+
"+",
|
|
3609
|
+
value.length - 2,
|
|
3610
|
+
" more"
|
|
3611
|
+
]
|
|
3612
|
+
})]
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3463
3615
|
if (typeof value === "object" && !Array.isArray(value)) {
|
|
3616
|
+
const structuredTitle = resolveValueTitle(value, field, schemas?.collections);
|
|
3617
|
+
if (structuredTitle) return /* @__PURE__ */ jsx("span", {
|
|
3618
|
+
className: "dy-text-[11px] dy-text-muted-foreground dy-font-medium dy-leading-tight",
|
|
3619
|
+
children: structuredTitle
|
|
3620
|
+
});
|
|
3464
3621
|
const entries = Object.entries(value).filter(([, v]) => typeof v !== "object" && v !== null && v !== void 0).slice(0, 3);
|
|
3465
3622
|
if (entries.length > 0) return /* @__PURE__ */ jsxs("span", {
|
|
3466
3623
|
className: "dy-text-[11px] dy-text-muted-foreground dy-font-medium dy-leading-tight",
|
|
@@ -6389,6 +6546,16 @@ function JoinField({ schema, control }) {
|
|
|
6389
6546
|
name: schema.name
|
|
6390
6547
|
})?.docs;
|
|
6391
6548
|
const getLabel = (item) => item[displayField] || item.name || item.slug || item.id;
|
|
6549
|
+
const showCreateButton = schema.admin?.showCreateButton !== false;
|
|
6550
|
+
const showViewButton = schema.admin?.showViewButton !== false;
|
|
6551
|
+
const handleCreateNew = () => {
|
|
6552
|
+
const params = new URLSearchParams({ [onField]: docId });
|
|
6553
|
+
navigate(`/collections/${targetCollection}/new?${params.toString()}`);
|
|
6554
|
+
};
|
|
6555
|
+
const handleViewAll = () => {
|
|
6556
|
+
const params = new URLSearchParams({ where: JSON.stringify({ [onField]: { equals: docId } }) });
|
|
6557
|
+
navigate(`/collections/${targetCollection}?${params.toString()}`);
|
|
6558
|
+
};
|
|
6392
6559
|
if (!docId) return /* @__PURE__ */ jsxs("p", {
|
|
6393
6560
|
className: "dy-text-xs dy-text-muted-foreground/60 dy-italic dy-py-2",
|
|
6394
6561
|
children: [
|
|
@@ -6423,17 +6590,27 @@ function JoinField({ schema, control }) {
|
|
|
6423
6590
|
" found."
|
|
6424
6591
|
]
|
|
6425
6592
|
})
|
|
6426
|
-
}), /* @__PURE__ */ jsxs(
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
"
|
|
6435
|
-
|
|
6436
|
-
|
|
6593
|
+
}), (showCreateButton || showViewButton) && /* @__PURE__ */ jsxs("div", {
|
|
6594
|
+
className: cn("dy-flex dy-items-center dy-gap-3", showCreateButton && showViewButton ? "dy-justify-between" : showCreateButton ? "dy-justify-end" : "dy-justify-start"),
|
|
6595
|
+
children: [showViewButton && /* @__PURE__ */ jsxs(Button, {
|
|
6596
|
+
type: "button",
|
|
6597
|
+
variant: "ghost",
|
|
6598
|
+
size: "sm",
|
|
6599
|
+
className: "dy-h-8 dy-text-[11px] dy-font-semibold dy-rounded-lg dy-text-muted-foreground hover:dy-text-foreground hover:dy-bg-muted/60 dy-transition-all",
|
|
6600
|
+
onClick: handleViewAll,
|
|
6601
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "dy-w-3 dy-h-3 dy-mr-1.5" }), "View all"]
|
|
6602
|
+
}), showCreateButton && /* @__PURE__ */ jsxs(Button, {
|
|
6603
|
+
type: "button",
|
|
6604
|
+
variant: "outline",
|
|
6605
|
+
size: "sm",
|
|
6606
|
+
className: "dy-h-8 dy-text-[11px] dy-font-bold dy-rounded-lg dy-border-primary/20 hover:dy-bg-primary/5 hover:dy-text-primary dy-transition-all dy-shadow-sm",
|
|
6607
|
+
onClick: handleCreateNew,
|
|
6608
|
+
children: [
|
|
6609
|
+
/* @__PURE__ */ jsx(Plus, { className: "dy-w-3 dy-h-3 dy-mr-1.5" }),
|
|
6610
|
+
"Create new ",
|
|
6611
|
+
targetSchema?.labels?.singular || targetCollection
|
|
6612
|
+
]
|
|
6613
|
+
})]
|
|
6437
6614
|
})]
|
|
6438
6615
|
});
|
|
6439
6616
|
}
|
|
@@ -8970,7 +9147,7 @@ function ArrayFieldRenderer({ schema, basePath, control, renderField }) {
|
|
|
8970
9147
|
const { schemas } = useDyrected();
|
|
8971
9148
|
const { drillInEnabled, drillInto, reconcileAfterMutation, activePath, registerFieldArray, unregisterFieldArray } = useNestedEditor();
|
|
8972
9149
|
const [isBulkOpen, setIsBulkOpen] = React$1.useState(false);
|
|
8973
|
-
const [expandedIds, setExpandedIds] = React$1.useState(
|
|
9150
|
+
const [expandedIds, setExpandedIds] = React$1.useState(() => Object.fromEntries(fields.map((field) => [field.id, true])));
|
|
8974
9151
|
const watchedItems = useWatch({
|
|
8975
9152
|
control,
|
|
8976
9153
|
name: basePath
|
|
@@ -9057,7 +9234,7 @@ function ArrayFieldRenderer({ schema, basePath, control, renderField }) {
|
|
|
9057
9234
|
});
|
|
9058
9235
|
nextIds.forEach((id) => {
|
|
9059
9236
|
if (next[id] === void 0) {
|
|
9060
|
-
next[id] =
|
|
9237
|
+
next[id] = true;
|
|
9061
9238
|
changed = true;
|
|
9062
9239
|
}
|
|
9063
9240
|
});
|
|
@@ -9552,10 +9729,29 @@ function FormFieldRendererInner({ schema, basePath, control, collection, canUpda
|
|
|
9552
9729
|
});
|
|
9553
9730
|
}
|
|
9554
9731
|
//#endregion
|
|
9732
|
+
//#region src/lib/workflow-autosave.ts
|
|
9733
|
+
var DEFAULT_WORKFLOW_AUTOSAVE_DELAY_MS = 1500;
|
|
9734
|
+
function isWorkflowEnabledCollection(collection) {
|
|
9735
|
+
return Boolean(collection?.workflow || collection?.drafts);
|
|
9736
|
+
}
|
|
9737
|
+
function resolveWorkflowAutosaveSettings(collection) {
|
|
9738
|
+
if (!isWorkflowEnabledCollection(collection)) return {
|
|
9739
|
+
enabled: false,
|
|
9740
|
+
delayMs: DEFAULT_WORKFLOW_AUTOSAVE_DELAY_MS
|
|
9741
|
+
};
|
|
9742
|
+
return {
|
|
9743
|
+
enabled: collection?.admin?.autosave !== false,
|
|
9744
|
+
delayMs: collection?.admin?.autosaveDelayMs ?? 1500
|
|
9745
|
+
};
|
|
9746
|
+
}
|
|
9747
|
+
function classifyWorkflowAutosaveError(error) {
|
|
9748
|
+
return (typeof error === "object" && error !== null && "statusCode" in error ? error.statusCode : void 0) === 409 ? "conflict" : "error";
|
|
9749
|
+
}
|
|
9750
|
+
//#endregion
|
|
9555
9751
|
//#region src/components/forms/form-engine.tsx
|
|
9556
9752
|
/** Query-string key holding the active form tab (replace-navigated). */
|
|
9557
9753
|
var TAB_PARAM = "tab";
|
|
9558
|
-
function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onChange, isLoading, submitLabel = "Save", hideSubmit = false, readOnly, onDataChange, passwordChangeMode = null, documentId, defaultTabLabel }) {
|
|
9754
|
+
function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onChange, isLoading, submitLabel = "Save", hideSubmit = false, readOnly, onDataChange, passwordChangeMode = null, documentId, defaultTabLabel, autosave }, ref) {
|
|
9559
9755
|
const { activePath, navigateToPath, getStableId } = useNestedEditor();
|
|
9560
9756
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
9561
9757
|
const isDrilledIn = activePath.length > 0;
|
|
@@ -9603,6 +9799,12 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9603
9799
|
});
|
|
9604
9800
|
const { isDirty } = form.formState;
|
|
9605
9801
|
const flatErrors = getFlatErrors(form.formState.errors);
|
|
9802
|
+
const autosaveEnabled = Boolean(autosave?.enabled && autosave.onSave && !readOnly);
|
|
9803
|
+
const autosaveStateRef = React.useRef("idle");
|
|
9804
|
+
const emitAutosaveState = useCallback((state) => {
|
|
9805
|
+
autosaveStateRef.current = state;
|
|
9806
|
+
autosave?.onStatusChange?.(state);
|
|
9807
|
+
}, [autosave]);
|
|
9606
9808
|
useEffect(() => {
|
|
9607
9809
|
if (flatErrors.length > 0 && form.formState.submitCount > 0) console.warn("[Validation] Submission failed with errors:", form.formState.errors);
|
|
9608
9810
|
}, [
|
|
@@ -9613,16 +9815,71 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9613
9815
|
useEffect(() => {
|
|
9614
9816
|
onChange?.(isDirty);
|
|
9615
9817
|
}, [isDirty, onChange]);
|
|
9818
|
+
useEffect(() => {
|
|
9819
|
+
if (!autosaveEnabled) {
|
|
9820
|
+
emitAutosaveState("idle");
|
|
9821
|
+
return;
|
|
9822
|
+
}
|
|
9823
|
+
if (!isDirty) {
|
|
9824
|
+
if (autosaveStateRef.current !== "saving") emitAutosaveState("saved");
|
|
9825
|
+
return;
|
|
9826
|
+
}
|
|
9827
|
+
if (autosaveStateRef.current !== "saving") emitAutosaveState("dirty");
|
|
9828
|
+
}, [
|
|
9829
|
+
autosaveEnabled,
|
|
9830
|
+
emitAutosaveState,
|
|
9831
|
+
isDirty
|
|
9832
|
+
]);
|
|
9616
9833
|
const watchedValues = useWatch({ control: form.control });
|
|
9617
|
-
const
|
|
9834
|
+
const persistFormData = useCallback(async (data, persist, options) => {
|
|
9618
9835
|
const draftKey = `dyrected_draft:${collection}:${defaultValues?.id || "global"}`;
|
|
9619
|
-
|
|
9620
|
-
|
|
9836
|
+
if (options?.reportAutosaveStatus) emitAutosaveState("saving");
|
|
9837
|
+
try {
|
|
9838
|
+
localStorage.removeItem(draftKey);
|
|
9839
|
+
const result = await persist(data);
|
|
9840
|
+
form.reset(data);
|
|
9841
|
+
if (options?.reportAutosaveStatus) emitAutosaveState("saved");
|
|
9842
|
+
return result;
|
|
9843
|
+
} catch (error) {
|
|
9844
|
+
if (options?.reportAutosaveStatus) emitAutosaveState(classifyWorkflowAutosaveError(error));
|
|
9845
|
+
throw error;
|
|
9846
|
+
}
|
|
9621
9847
|
}, [
|
|
9622
9848
|
collection,
|
|
9623
9849
|
defaultValues,
|
|
9624
|
-
|
|
9850
|
+
emitAutosaveState,
|
|
9851
|
+
form
|
|
9852
|
+
]);
|
|
9853
|
+
const handleFormSubmit = useCallback(async (data) => {
|
|
9854
|
+
return persistFormData(data, onSubmit, { reportAutosaveStatus: autosaveEnabled });
|
|
9855
|
+
}, [
|
|
9856
|
+
autosaveEnabled,
|
|
9857
|
+
onSubmit,
|
|
9858
|
+
persistFormData
|
|
9859
|
+
]);
|
|
9860
|
+
const submitCurrentDraft = useCallback(() => {
|
|
9861
|
+
return new Promise((resolve, reject) => {
|
|
9862
|
+
form.handleSubmit(async (data) => {
|
|
9863
|
+
try {
|
|
9864
|
+
resolve(await handleFormSubmit(data));
|
|
9865
|
+
} catch (error) {
|
|
9866
|
+
reject(error);
|
|
9867
|
+
}
|
|
9868
|
+
}, () => {
|
|
9869
|
+
if (autosaveEnabled) emitAutosaveState("dirty");
|
|
9870
|
+
reject(/* @__PURE__ */ new Error("Please resolve validation errors before continuing."));
|
|
9871
|
+
})();
|
|
9872
|
+
});
|
|
9873
|
+
}, [
|
|
9874
|
+
autosaveEnabled,
|
|
9875
|
+
emitAutosaveState,
|
|
9876
|
+
form,
|
|
9877
|
+
handleFormSubmit
|
|
9625
9878
|
]);
|
|
9879
|
+
React.useImperativeHandle(ref, () => ({
|
|
9880
|
+
submitCurrentDraft,
|
|
9881
|
+
isDirty: () => form.formState.isDirty
|
|
9882
|
+
}), [form.formState.isDirty, submitCurrentDraft]);
|
|
9626
9883
|
useEffect(() => {
|
|
9627
9884
|
if (readOnly || isLoading) return;
|
|
9628
9885
|
const handleKeyDown = (e) => {
|
|
@@ -9691,6 +9948,24 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9691
9948
|
collection,
|
|
9692
9949
|
defaultValues
|
|
9693
9950
|
]);
|
|
9951
|
+
useEffect(() => {
|
|
9952
|
+
if (!autosaveEnabled || !isDirty || isLoading) return;
|
|
9953
|
+
const handler = setTimeout(() => {
|
|
9954
|
+
form.handleSubmit(async (data) => {
|
|
9955
|
+
await persistFormData(data, autosave.onSave, { reportAutosaveStatus: true }).catch(() => {});
|
|
9956
|
+
}, () => emitAutosaveState("dirty"))();
|
|
9957
|
+
}, autosave?.delayMs ?? 1500);
|
|
9958
|
+
return () => clearTimeout(handler);
|
|
9959
|
+
}, [
|
|
9960
|
+
autosave,
|
|
9961
|
+
autosaveEnabled,
|
|
9962
|
+
emitAutosaveState,
|
|
9963
|
+
form,
|
|
9964
|
+
isDirty,
|
|
9965
|
+
isLoading,
|
|
9966
|
+
persistFormData,
|
|
9967
|
+
watchedValues
|
|
9968
|
+
]);
|
|
9694
9969
|
useEffect(() => {
|
|
9695
9970
|
if (onDataChange) {
|
|
9696
9971
|
const handler = setTimeout(() => {
|
|
@@ -10036,10 +10311,17 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
10036
10311
|
})
|
|
10037
10312
|
})] });
|
|
10038
10313
|
}
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10314
|
+
var ForwardedFormEngineInner = React.forwardRef(FormEngineInner);
|
|
10315
|
+
var FormEngine = React.forwardRef(function FormEngine(props, ref) {
|
|
10316
|
+
if (React.useContext(NestedEditorContext)) return /* @__PURE__ */ jsx(ForwardedFormEngineInner, {
|
|
10317
|
+
...props,
|
|
10318
|
+
ref
|
|
10319
|
+
});
|
|
10320
|
+
return /* @__PURE__ */ jsx(NestedEditorProvider, { children: /* @__PURE__ */ jsx(ForwardedFormEngineInner, {
|
|
10321
|
+
...props,
|
|
10322
|
+
ref
|
|
10323
|
+
}) });
|
|
10324
|
+
});
|
|
10043
10325
|
//#endregion
|
|
10044
10326
|
//#region src/components/forms/fields/relationship-picker.tsx
|
|
10045
10327
|
var PAGE_SIZE = 50;
|
|
@@ -10913,69 +11195,564 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
|
|
|
10913
11195
|
});
|
|
10914
11196
|
}
|
|
10915
11197
|
//#endregion
|
|
10916
|
-
//#region src/
|
|
10917
|
-
function
|
|
10918
|
-
|
|
10919
|
-
return
|
|
10920
|
-
ref: setNodeRef,
|
|
10921
|
-
style: {
|
|
10922
|
-
transform: CSS.Transform.toString(transform),
|
|
10923
|
-
transition,
|
|
10924
|
-
zIndex: isDragging ? 10 : 1
|
|
10925
|
-
},
|
|
10926
|
-
className: cn("dy-flex dy-items-center dy-gap-2.5 dy-p-2 dy-bg-background dy-border dy-border-border/60 dy-rounded-lg dy-shadow-sm dy-transition-all", isDragging && "dy-opacity-50 dy-border-primary"),
|
|
10927
|
-
children: [
|
|
10928
|
-
/* @__PURE__ */ jsx("div", {
|
|
10929
|
-
...attributes,
|
|
10930
|
-
...listeners,
|
|
10931
|
-
className: "dy-cursor-grab active:dy-cursor-grabbing dy-text-muted-foreground/60 hover:dy-text-foreground dy-p-0.5",
|
|
10932
|
-
children: /* @__PURE__ */ jsx(GripVertical, { className: "dy-h-3.5 dy-w-3.5" })
|
|
10933
|
-
}),
|
|
10934
|
-
/* @__PURE__ */ jsx(Checkbox, {
|
|
10935
|
-
checked: visible,
|
|
10936
|
-
onCheckedChange: (val) => onToggleVisible(!!val)
|
|
10937
|
-
}),
|
|
10938
|
-
/* @__PURE__ */ jsx("div", {
|
|
10939
|
-
className: "dy-flex-1 dy-text-xs dy-font-medium dy-text-foreground",
|
|
10940
|
-
children: label
|
|
10941
|
-
})
|
|
10942
|
-
]
|
|
10943
|
-
});
|
|
11198
|
+
//#region src/lib/workflow-ui.ts
|
|
11199
|
+
function resolveWorkflowState(workflowConfig, workflowMeta) {
|
|
11200
|
+
if (!workflowConfig || !workflowMeta?.state) return null;
|
|
11201
|
+
return workflowConfig.states.find((state) => state.name === workflowMeta.state) ?? null;
|
|
10944
11202
|
}
|
|
10945
|
-
function
|
|
11203
|
+
function resolveWorkflowStateFromDocument(workflowConfig, item) {
|
|
11204
|
+
return resolveWorkflowState(workflowConfig, item._workflow ?? null);
|
|
11205
|
+
}
|
|
11206
|
+
function resolvePublishingStatus(schema, item) {
|
|
10946
11207
|
const hasPublishingState = !!(schema?.workflow || schema?.drafts);
|
|
10947
|
-
const
|
|
10948
|
-
const workflowMeta = item
|
|
10949
|
-
|
|
10950
|
-
|
|
11208
|
+
const workflowState = resolveWorkflowStateFromDocument(schema?.workflow ?? null, item);
|
|
11209
|
+
const workflowMeta = item._workflow;
|
|
11210
|
+
if (workflowState) {
|
|
11211
|
+
const rawWorkflowLabel = (workflowState.label || workflowState.name || "").trim();
|
|
11212
|
+
const workflowStateLabel = rawWorkflowLabel && !["published", "draft"].includes(rawWorkflowLabel.toLowerCase()) ? rawWorkflowLabel : null;
|
|
11213
|
+
if (workflowState.published) return {
|
|
11214
|
+
label: "Published",
|
|
11215
|
+
color: "success",
|
|
11216
|
+
workflowStateLabel
|
|
11217
|
+
};
|
|
11218
|
+
if (workflowMeta?.publishedRevision) return {
|
|
11219
|
+
label: "Changed",
|
|
11220
|
+
color: "info",
|
|
11221
|
+
workflowStateLabel
|
|
11222
|
+
};
|
|
11223
|
+
return {
|
|
11224
|
+
label: "Draft",
|
|
11225
|
+
color: "warning",
|
|
11226
|
+
workflowStateLabel
|
|
11227
|
+
};
|
|
11228
|
+
}
|
|
10951
11229
|
if (!hasPublishingState) return null;
|
|
10952
11230
|
const plainStatus = item.status;
|
|
10953
|
-
if (plainStatus === "published") return
|
|
10954
|
-
|
|
11231
|
+
if (plainStatus === "published") return {
|
|
11232
|
+
label: "Published",
|
|
11233
|
+
color: "success",
|
|
11234
|
+
workflowStateLabel: null
|
|
11235
|
+
};
|
|
11236
|
+
if (plainStatus === "draft") return {
|
|
11237
|
+
label: "Draft",
|
|
11238
|
+
color: "warning",
|
|
11239
|
+
workflowStateLabel: null
|
|
11240
|
+
};
|
|
10955
11241
|
return null;
|
|
10956
11242
|
}
|
|
10957
|
-
function
|
|
10958
|
-
|
|
10959
|
-
const
|
|
10960
|
-
return workflowConfig
|
|
11243
|
+
function getAvailableWorkflowTransitions(workflowConfig, workflowMeta) {
|
|
11244
|
+
if (!workflowConfig || !workflowMeta) return [];
|
|
11245
|
+
const available = new Set(workflowMeta.availableTransitions ?? []);
|
|
11246
|
+
return workflowConfig.transitions.filter((transition) => available.has(transition.name));
|
|
10961
11247
|
}
|
|
10962
|
-
function
|
|
10963
|
-
|
|
11248
|
+
function getPrimaryWorkflowTransition(transitions) {
|
|
11249
|
+
if (transitions.length === 0) return null;
|
|
11250
|
+
if (transitions.length === 1) return transitions[0] ?? null;
|
|
11251
|
+
return transitions.find((transition) => !transition.unpublish) ?? transitions[0] ?? null;
|
|
11252
|
+
}
|
|
11253
|
+
function groupWorkflowTransitions(transitions) {
|
|
11254
|
+
return transitions.reduce((groups, transition) => {
|
|
11255
|
+
if (transition.unpublish) groups.unpublish.push(transition);
|
|
11256
|
+
else groups.normal.push(transition);
|
|
11257
|
+
return groups;
|
|
11258
|
+
}, {
|
|
11259
|
+
normal: [],
|
|
11260
|
+
unpublish: []
|
|
11261
|
+
});
|
|
11262
|
+
}
|
|
11263
|
+
function getCommonWorkflowTransitions(workflowConfig, docs) {
|
|
11264
|
+
if (!workflowConfig || docs.length === 0) return [];
|
|
11265
|
+
const availableSets = docs.map((doc) => doc._workflow?.availableTransitions).filter((value) => Array.isArray(value) && value.length > 0).map((value) => new Set(value));
|
|
11266
|
+
if (availableSets.length !== docs.length) return [];
|
|
11267
|
+
return workflowConfig.transitions.filter((transition) => availableSets.every((set) => set.has(transition.name)));
|
|
11268
|
+
}
|
|
11269
|
+
//#endregion
|
|
11270
|
+
//#region src/components/workflow/workflow-transition-controls.tsx
|
|
11271
|
+
function formatSingleTransitionSuccessMessage(transitionLabel, documentLabel) {
|
|
11272
|
+
return documentLabel?.trim() ? `${documentLabel}: ${transitionLabel}` : `${transitionLabel} completed`;
|
|
11273
|
+
}
|
|
11274
|
+
function StateIcon$1({ color }) {
|
|
11275
|
+
if (color === "success") return /* @__PURE__ */ jsx(CheckCircle, { className: "dy-h-3.5 dy-w-3.5" });
|
|
11276
|
+
if (color === "warning") return /* @__PURE__ */ jsx(Clock, { className: "dy-h-3.5 dy-w-3.5" });
|
|
11277
|
+
if (color === "danger") return /* @__PURE__ */ jsx(XCircle, { className: "dy-h-3.5 dy-w-3.5" });
|
|
11278
|
+
if (color === "info") return /* @__PURE__ */ jsx(AlertCircle, { className: "dy-h-3.5 dy-w-3.5" });
|
|
11279
|
+
return /* @__PURE__ */ jsx(Clock, { className: "dy-h-3.5 dy-w-3.5" });
|
|
11280
|
+
}
|
|
11281
|
+
function WorkflowCommentDialog({ transition, pending, onOpenChange, onConfirm }) {
|
|
11282
|
+
const [comment, setComment] = React$1.useState("");
|
|
11283
|
+
React$1.useEffect(() => {
|
|
11284
|
+
if (!transition) setComment("");
|
|
11285
|
+
}, [transition]);
|
|
11286
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
11287
|
+
open: !!transition,
|
|
11288
|
+
onOpenChange,
|
|
11289
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
11290
|
+
className: "sm:dy-max-w-md",
|
|
11291
|
+
children: [
|
|
11292
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: transition?.label }), /* @__PURE__ */ jsx(DialogDescription, { children: "Add the required comment before applying this workflow transition." })] }),
|
|
11293
|
+
/* @__PURE__ */ jsxs("div", {
|
|
11294
|
+
className: "dy-space-y-2",
|
|
11295
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
11296
|
+
className: "dy-text-xs dy-font-semibold dy-text-muted-foreground",
|
|
11297
|
+
children: "Comment"
|
|
11298
|
+
}), /* @__PURE__ */ jsx("textarea", {
|
|
11299
|
+
className: "dy-min-h-28 dy-w-full dy-rounded-lg dy-border dy-border-border/60 dy-bg-background dy-px-3 dy-py-2 dy-text-sm dy-text-foreground placeholder:dy-text-muted-foreground focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-primary/30",
|
|
11300
|
+
value: comment,
|
|
11301
|
+
onChange: (event) => setComment(event.target.value),
|
|
11302
|
+
placeholder: `Required for "${transition?.label ?? "this transition"}"`,
|
|
11303
|
+
autoFocus: true
|
|
11304
|
+
})]
|
|
11305
|
+
}),
|
|
11306
|
+
/* @__PURE__ */ jsxs(DialogFooter, {
|
|
11307
|
+
className: "dy-flex dy-justify-end dy-gap-2",
|
|
11308
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
11309
|
+
variant: "outline",
|
|
11310
|
+
onClick: () => onOpenChange(false),
|
|
11311
|
+
disabled: pending,
|
|
11312
|
+
children: "Cancel"
|
|
11313
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
11314
|
+
onClick: () => onConfirm(comment.trim()),
|
|
11315
|
+
disabled: pending || !comment.trim(),
|
|
11316
|
+
children: pending ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-4 dy-w-4 dy-animate-spin" }) : transition?.label
|
|
11317
|
+
})]
|
|
11318
|
+
})
|
|
11319
|
+
]
|
|
11320
|
+
})
|
|
11321
|
+
});
|
|
11322
|
+
}
|
|
11323
|
+
function WorkflowTransitionActionItems({ transitions, workflowConfig, onSelect, disabled }) {
|
|
11324
|
+
return transitions.map((transition) => {
|
|
11325
|
+
return /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
11326
|
+
onClick: () => onSelect(transition),
|
|
11327
|
+
disabled,
|
|
11328
|
+
className: "dy-flex dy-items-center dy-gap-2",
|
|
11329
|
+
children: [
|
|
11330
|
+
/* @__PURE__ */ jsx(StateIcon$1, { color: workflowConfig.states.find((state) => state.name === transition.to)?.color }),
|
|
11331
|
+
/* @__PURE__ */ jsx("span", {
|
|
11332
|
+
className: "dy-flex-1",
|
|
11333
|
+
children: transition.label
|
|
11334
|
+
}),
|
|
11335
|
+
transition.requireComment && /* @__PURE__ */ jsx(MessageSquarePlus, { className: "dy-h-3.5 dy-w-3.5 dy-text-muted-foreground" })
|
|
11336
|
+
]
|
|
11337
|
+
}, transition.name);
|
|
11338
|
+
});
|
|
11339
|
+
}
|
|
11340
|
+
function useWorkflowTransitionExecutor({ collection, documentIds, documentLabels, expectedRevisions, invalidateQueryKeys, onComplete, prepareTransition }) {
|
|
11341
|
+
const { client } = useDyrected();
|
|
10964
11342
|
const queryClient = useQueryClient();
|
|
10965
|
-
const
|
|
10966
|
-
const [
|
|
10967
|
-
const [
|
|
10968
|
-
const
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
11343
|
+
const wfClient = client;
|
|
11344
|
+
const [commentTransition, setCommentTransition] = React$1.useState(null);
|
|
11345
|
+
const [isPreparing, setIsPreparing] = React$1.useState(false);
|
|
11346
|
+
const getDefaultContext = React$1.useCallback(() => ({
|
|
11347
|
+
documentIds,
|
|
11348
|
+
documentLabels,
|
|
11349
|
+
expectedRevisions,
|
|
11350
|
+
invalidateQueryKeys
|
|
11351
|
+
}), [
|
|
11352
|
+
documentIds,
|
|
11353
|
+
documentLabels,
|
|
11354
|
+
expectedRevisions,
|
|
11355
|
+
invalidateQueryKeys
|
|
11356
|
+
]);
|
|
11357
|
+
const mutation = useMutation({
|
|
11358
|
+
mutationFn: async ({ transition, comment, context }) => {
|
|
11359
|
+
let successCount = 0;
|
|
11360
|
+
let failureCount = 0;
|
|
11361
|
+
let firstError;
|
|
11362
|
+
for (const id of context.documentIds) try {
|
|
11363
|
+
await wfClient.transition(collection, id, transition.name, {
|
|
11364
|
+
expectedRevision: context.expectedRevisions?.[id],
|
|
11365
|
+
comment
|
|
11366
|
+
});
|
|
11367
|
+
successCount += 1;
|
|
11368
|
+
} catch (error) {
|
|
11369
|
+
failureCount += 1;
|
|
11370
|
+
if (!firstError) firstError = error instanceof Error ? error : new Error(String(error));
|
|
11371
|
+
}
|
|
11372
|
+
return {
|
|
11373
|
+
transition,
|
|
11374
|
+
successCount,
|
|
11375
|
+
failureCount,
|
|
11376
|
+
firstError,
|
|
11377
|
+
context
|
|
11378
|
+
};
|
|
11379
|
+
},
|
|
11380
|
+
onSuccess: async (result) => {
|
|
11381
|
+
setCommentTransition(null);
|
|
11382
|
+
const queryKeys = [...invalidateQueryKeys ?? [], ...result.context.invalidateQueryKeys ?? []];
|
|
11383
|
+
if (queryKeys.length > 0) await Promise.all(queryKeys.map((queryKey) => queryClient.invalidateQueries({ queryKey: [...queryKey] })));
|
|
11384
|
+
if (result.failureCount === 0) if (result.context.documentIds.length === 1) {
|
|
11385
|
+
const singleDocumentId = result.context.documentIds[0] ?? "";
|
|
11386
|
+
toast.success(formatSingleTransitionSuccessMessage(result.transition.label, result.context.documentLabels?.[singleDocumentId]));
|
|
11387
|
+
} else toast.success(`Applied "${result.transition.label}" to ${result.successCount} ${result.successCount === 1 ? "entry" : "entries"}`);
|
|
11388
|
+
else if (result.successCount === 0) toast.error("Transition failed", { description: result.firstError?.message ?? "The workflow transition could not be completed." });
|
|
11389
|
+
else toast(`Applied "${result.transition.label}" to ${result.successCount} entries`, { description: `${result.failureCount} failed. ${result.firstError?.message ?? ""}`.trim() });
|
|
11390
|
+
onComplete?.(result);
|
|
11391
|
+
},
|
|
11392
|
+
onError: (error) => {
|
|
11393
|
+
setCommentTransition(null);
|
|
11394
|
+
toast.error("Transition failed", { description: error.message });
|
|
10976
11395
|
}
|
|
10977
|
-
}
|
|
10978
|
-
|
|
11396
|
+
});
|
|
11397
|
+
return {
|
|
11398
|
+
requestTransition: React$1.useCallback(async (transition) => {
|
|
11399
|
+
try {
|
|
11400
|
+
setIsPreparing(true);
|
|
11401
|
+
const context = await (prepareTransition?.(transition) ?? Promise.resolve(getDefaultContext()));
|
|
11402
|
+
if (!context || context.documentIds.length === 0) return;
|
|
11403
|
+
if (transition.requireComment) {
|
|
11404
|
+
setCommentTransition({
|
|
11405
|
+
transition,
|
|
11406
|
+
context
|
|
11407
|
+
});
|
|
11408
|
+
return;
|
|
11409
|
+
}
|
|
11410
|
+
mutation.mutate({
|
|
11411
|
+
transition,
|
|
11412
|
+
context
|
|
11413
|
+
});
|
|
11414
|
+
} catch (error) {
|
|
11415
|
+
const message = error instanceof Error ? error.message : "The workflow action could not be prepared.";
|
|
11416
|
+
toast.error("Could not continue", { description: message });
|
|
11417
|
+
} finally {
|
|
11418
|
+
setIsPreparing(false);
|
|
11419
|
+
}
|
|
11420
|
+
}, [
|
|
11421
|
+
getDefaultContext,
|
|
11422
|
+
mutation,
|
|
11423
|
+
prepareTransition
|
|
11424
|
+
]),
|
|
11425
|
+
commentTransition,
|
|
11426
|
+
setCommentTransition,
|
|
11427
|
+
confirmComment: React$1.useCallback((comment) => {
|
|
11428
|
+
if (!commentTransition) return;
|
|
11429
|
+
mutation.mutate({
|
|
11430
|
+
transition: commentTransition.transition,
|
|
11431
|
+
comment,
|
|
11432
|
+
context: commentTransition.context
|
|
11433
|
+
});
|
|
11434
|
+
}, [commentTransition, mutation]),
|
|
11435
|
+
mutation,
|
|
11436
|
+
isPreparing
|
|
11437
|
+
};
|
|
11438
|
+
}
|
|
11439
|
+
function WorkflowTransitionSplitButton({ collection, documentId, workflowConfig, workflowMeta, invalidateQueryKeys, onComplete, onPendingChange, onSaveDraft, saveDraftPending, documentLabel, className, prepareTransition }) {
|
|
11440
|
+
const transitions = React$1.useMemo(() => getAvailableWorkflowTransitions(workflowConfig, workflowMeta), [workflowConfig, workflowMeta]);
|
|
11441
|
+
const groupedTransitions = React$1.useMemo(() => groupWorkflowTransitions(transitions), [transitions]);
|
|
11442
|
+
const primaryTransition = React$1.useMemo(() => getPrimaryWorkflowTransition(transitions), [transitions]);
|
|
11443
|
+
const { requestTransition, commentTransition, setCommentTransition, confirmComment, mutation, isPreparing } = useWorkflowTransitionExecutor({
|
|
11444
|
+
collection,
|
|
11445
|
+
documentIds: [documentId],
|
|
11446
|
+
documentLabels: { [documentId]: documentLabel },
|
|
11447
|
+
expectedRevisions: { [documentId]: workflowMeta.revision },
|
|
11448
|
+
invalidateQueryKeys,
|
|
11449
|
+
onComplete,
|
|
11450
|
+
prepareTransition
|
|
11451
|
+
});
|
|
11452
|
+
const activeTransitionName = mutation.variables?.transition?.name;
|
|
11453
|
+
const isPrimaryLoading = mutation.isPending && activeTransitionName === primaryTransition?.name;
|
|
11454
|
+
const hasTransitions = transitions.length > 0;
|
|
11455
|
+
const hasMenuItems = hasTransitions || !!onSaveDraft;
|
|
11456
|
+
const isBusy = mutation.isPending || isPreparing || !!saveDraftPending;
|
|
11457
|
+
React$1.useEffect(() => {
|
|
11458
|
+
onPendingChange?.(mutation.isPending || isPreparing);
|
|
11459
|
+
}, [
|
|
11460
|
+
isPreparing,
|
|
11461
|
+
mutation.isPending,
|
|
11462
|
+
onPendingChange
|
|
11463
|
+
]);
|
|
11464
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
11465
|
+
className: cn("dy-inline-flex dy-items-center", className),
|
|
11466
|
+
children: [/* @__PURE__ */ jsx("div", { className: "dy-mx-1 dy-h-6 dy-w-px dy-bg-border/60" }), /* @__PURE__ */ jsxs("div", {
|
|
11467
|
+
className: "dy-inline-flex dy-overflow-hidden dy-rounded-lg dy-border dy-border-primary/20 dy-shadow-sm",
|
|
11468
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
11469
|
+
size: "sm",
|
|
11470
|
+
className: "dy-h-9 dy-rounded-none dy-border-0 dy-px-4 dy-font-bold dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90",
|
|
11471
|
+
disabled: !primaryTransition || isBusy,
|
|
11472
|
+
onClick: () => {
|
|
11473
|
+
if (primaryTransition) requestTransition(primaryTransition);
|
|
11474
|
+
},
|
|
11475
|
+
title: primaryTransition?.label ?? "No workflow actions available",
|
|
11476
|
+
children: isPrimaryLoading || isPreparing ? /* @__PURE__ */ jsxs("span", {
|
|
11477
|
+
className: "dy-flex dy-items-center dy-gap-2",
|
|
11478
|
+
children: [/* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }), isPreparing ? "Saving…" : "Applying…"]
|
|
11479
|
+
}) : primaryTransition?.label ?? "No actions"
|
|
11480
|
+
}), /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
11481
|
+
asChild: true,
|
|
11482
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
11483
|
+
size: "sm",
|
|
11484
|
+
variant: "ghost",
|
|
11485
|
+
className: "dy-h-9 dy-rounded-none dy-border-0 dy-px-2 dy-text-primary-foreground hover:dy-bg-primary/90 hover:dy-text-primary-foreground",
|
|
11486
|
+
disabled: !hasMenuItems || isBusy,
|
|
11487
|
+
"aria-label": "More workflow actions",
|
|
11488
|
+
children: /* @__PURE__ */ jsx(ChevronDown, { className: "dy-h-4 dy-w-4" })
|
|
11489
|
+
})
|
|
11490
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
11491
|
+
align: "end",
|
|
11492
|
+
className: "dy-min-w-56",
|
|
11493
|
+
children: [onSaveDraft && /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
11494
|
+
onClick: () => void onSaveDraft(),
|
|
11495
|
+
disabled: isBusy,
|
|
11496
|
+
className: "dy-flex dy-items-center dy-gap-2",
|
|
11497
|
+
children: [saveDraftPending ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "dy-h-3.5 dy-w-3.5" }), /* @__PURE__ */ jsx("span", {
|
|
11498
|
+
className: "dy-flex-1",
|
|
11499
|
+
children: saveDraftPending ? "Saving draft..." : "Save draft"
|
|
11500
|
+
})]
|
|
11501
|
+
}), hasTransitions && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11502
|
+
onSaveDraft && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
11503
|
+
/* @__PURE__ */ jsx(WorkflowTransitionActionItems, {
|
|
11504
|
+
transitions: groupedTransitions.normal,
|
|
11505
|
+
workflowConfig,
|
|
11506
|
+
onSelect: (transition) => {
|
|
11507
|
+
requestTransition(transition);
|
|
11508
|
+
},
|
|
11509
|
+
disabled: isBusy
|
|
11510
|
+
}),
|
|
11511
|
+
groupedTransitions.unpublish.length > 0 && groupedTransitions.normal.length > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
11512
|
+
/* @__PURE__ */ jsx(WorkflowTransitionActionItems, {
|
|
11513
|
+
transitions: groupedTransitions.unpublish,
|
|
11514
|
+
workflowConfig,
|
|
11515
|
+
onSelect: (transition) => {
|
|
11516
|
+
requestTransition(transition);
|
|
11517
|
+
},
|
|
11518
|
+
disabled: isBusy
|
|
11519
|
+
})
|
|
11520
|
+
] })]
|
|
11521
|
+
})] })]
|
|
11522
|
+
})]
|
|
11523
|
+
}), /* @__PURE__ */ jsx(WorkflowCommentDialog, {
|
|
11524
|
+
transition: commentTransition?.transition ?? null,
|
|
11525
|
+
pending: mutation.isPending,
|
|
11526
|
+
onOpenChange: (open) => !open && setCommentTransition(null),
|
|
11527
|
+
onConfirm: confirmComment
|
|
11528
|
+
})] });
|
|
11529
|
+
}
|
|
11530
|
+
function WorkflowTransitionMenu({ collection, documentIds, workflowConfig, transitions, expectedRevisions, invalidateQueryKeys, onComplete, trigger, documentLabels, align = "end", sideOffset = 4, disabled }) {
|
|
11531
|
+
const { requestTransition, commentTransition, setCommentTransition, confirmComment, mutation, isPreparing } = useWorkflowTransitionExecutor({
|
|
11532
|
+
collection,
|
|
11533
|
+
documentIds,
|
|
11534
|
+
documentLabels,
|
|
11535
|
+
expectedRevisions,
|
|
11536
|
+
invalidateQueryKeys,
|
|
11537
|
+
onComplete
|
|
11538
|
+
});
|
|
11539
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
11540
|
+
asChild: true,
|
|
11541
|
+
disabled: disabled || transitions.length === 0,
|
|
11542
|
+
children: trigger
|
|
11543
|
+
}), /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
11544
|
+
align,
|
|
11545
|
+
sideOffset,
|
|
11546
|
+
className: "dy-min-w-56",
|
|
11547
|
+
children: /* @__PURE__ */ jsx(WorkflowTransitionActionItems, {
|
|
11548
|
+
transitions,
|
|
11549
|
+
workflowConfig,
|
|
11550
|
+
onSelect: (transition) => {
|
|
11551
|
+
requestTransition(transition);
|
|
11552
|
+
},
|
|
11553
|
+
disabled: mutation.isPending || isPreparing
|
|
11554
|
+
})
|
|
11555
|
+
})] }), /* @__PURE__ */ jsx(WorkflowCommentDialog, {
|
|
11556
|
+
transition: commentTransition?.transition ?? null,
|
|
11557
|
+
pending: mutation.isPending,
|
|
11558
|
+
onOpenChange: (open) => !open && setCommentTransition(null),
|
|
11559
|
+
onConfirm: confirmComment
|
|
11560
|
+
})] });
|
|
11561
|
+
}
|
|
11562
|
+
function WorkflowTransitionPanelActions({ collection, documentId, workflowConfig, workflowMeta, onSaveDraft, saveDraftPending, prepareTransition }) {
|
|
11563
|
+
const transitions = React$1.useMemo(() => getAvailableWorkflowTransitions(workflowConfig, workflowMeta), [workflowConfig, workflowMeta]);
|
|
11564
|
+
const groupedTransitions = React$1.useMemo(() => groupWorkflowTransitions(transitions), [transitions]);
|
|
11565
|
+
const { requestTransition, commentTransition, setCommentTransition, confirmComment, mutation, isPreparing } = useWorkflowTransitionExecutor({
|
|
11566
|
+
collection,
|
|
11567
|
+
documentIds: [documentId],
|
|
11568
|
+
expectedRevisions: { [documentId]: workflowMeta.revision },
|
|
11569
|
+
invalidateQueryKeys: [
|
|
11570
|
+
[
|
|
11571
|
+
"entry",
|
|
11572
|
+
collection,
|
|
11573
|
+
documentId
|
|
11574
|
+
],
|
|
11575
|
+
["collection", collection],
|
|
11576
|
+
[
|
|
11577
|
+
"workflow-history",
|
|
11578
|
+
collection,
|
|
11579
|
+
documentId
|
|
11580
|
+
]
|
|
11581
|
+
],
|
|
11582
|
+
prepareTransition
|
|
11583
|
+
});
|
|
11584
|
+
const activeTransitionName = mutation.variables?.transition?.name;
|
|
11585
|
+
if (transitions.length === 0 && !onSaveDraft) return /* @__PURE__ */ jsx("p", {
|
|
11586
|
+
className: "dy-text-xs dy-text-muted-foreground/50 dy-italic",
|
|
11587
|
+
children: "No transitions available from this state."
|
|
11588
|
+
});
|
|
11589
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
|
|
11590
|
+
className: "dy-space-y-2",
|
|
11591
|
+
children: [
|
|
11592
|
+
onSaveDraft && /* @__PURE__ */ jsxs(Button, {
|
|
11593
|
+
size: "sm",
|
|
11594
|
+
variant: "secondary",
|
|
11595
|
+
className: "dy-w-full dy-h-9 dy-rounded-lg dy-text-xs dy-font-semibold dy-justify-start dy-gap-2",
|
|
11596
|
+
disabled: mutation.isPending || isPreparing || saveDraftPending,
|
|
11597
|
+
onClick: () => void onSaveDraft(),
|
|
11598
|
+
children: [saveDraftPending ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "dy-h-3.5 dy-w-3.5" }), saveDraftPending ? "Saving draft..." : "Save draft"]
|
|
11599
|
+
}),
|
|
11600
|
+
groupedTransitions.normal.map((transition) => {
|
|
11601
|
+
const isLoading = mutation.isPending && activeTransitionName === transition.name;
|
|
11602
|
+
const targetState = workflowConfig.states.find((state) => state.name === transition.to);
|
|
11603
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
11604
|
+
size: "sm",
|
|
11605
|
+
variant: "default",
|
|
11606
|
+
className: "dy-w-full dy-h-9 dy-rounded-lg dy-text-xs dy-font-semibold dy-justify-start dy-gap-2",
|
|
11607
|
+
disabled: mutation.isPending || isPreparing,
|
|
11608
|
+
onClick: () => {
|
|
11609
|
+
requestTransition(transition);
|
|
11610
|
+
},
|
|
11611
|
+
children: [isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }) : /* @__PURE__ */ jsx(StateIcon$1, { color: targetState?.color }), transition.label]
|
|
11612
|
+
}, transition.name);
|
|
11613
|
+
}),
|
|
11614
|
+
groupedTransitions.unpublish.length > 0 && groupedTransitions.normal.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
11615
|
+
className: "dy-px-1 dy-pt-2",
|
|
11616
|
+
children: /* @__PURE__ */ jsx("div", { className: "dy-h-px dy-bg-border/60" })
|
|
11617
|
+
}),
|
|
11618
|
+
groupedTransitions.unpublish.map((transition) => {
|
|
11619
|
+
const isLoading = mutation.isPending && activeTransitionName === transition.name;
|
|
11620
|
+
const targetState = workflowConfig.states.find((state) => state.name === transition.to);
|
|
11621
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
11622
|
+
size: "sm",
|
|
11623
|
+
variant: "outline",
|
|
11624
|
+
className: "dy-w-full dy-h-9 dy-rounded-lg dy-text-xs dy-font-semibold dy-justify-start dy-gap-2",
|
|
11625
|
+
disabled: mutation.isPending || isPreparing,
|
|
11626
|
+
onClick: () => {
|
|
11627
|
+
requestTransition(transition);
|
|
11628
|
+
},
|
|
11629
|
+
children: [isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }) : /* @__PURE__ */ jsx(StateIcon$1, { color: targetState?.color }), transition.label]
|
|
11630
|
+
}, transition.name);
|
|
11631
|
+
})
|
|
11632
|
+
]
|
|
11633
|
+
}), /* @__PURE__ */ jsx(WorkflowCommentDialog, {
|
|
11634
|
+
transition: commentTransition?.transition ?? null,
|
|
11635
|
+
pending: mutation.isPending,
|
|
11636
|
+
onOpenChange: (open) => !open && setCommentTransition(null),
|
|
11637
|
+
onConfirm: confirmComment
|
|
11638
|
+
})] });
|
|
11639
|
+
}
|
|
11640
|
+
//#endregion
|
|
11641
|
+
//#region src/pages/collections/list-page.tsx
|
|
11642
|
+
function SortableColumnItem({ id, label, visible, onToggleVisible }) {
|
|
11643
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
11644
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
11645
|
+
ref: setNodeRef,
|
|
11646
|
+
style: {
|
|
11647
|
+
transform: CSS.Transform.toString(transform),
|
|
11648
|
+
transition,
|
|
11649
|
+
zIndex: isDragging ? 10 : 1
|
|
11650
|
+
},
|
|
11651
|
+
className: cn("dy-flex dy-items-center dy-gap-2.5 dy-p-2 dy-bg-background dy-border dy-border-border/60 dy-rounded-lg dy-shadow-sm dy-transition-all", isDragging && "dy-opacity-50 dy-border-primary"),
|
|
11652
|
+
children: [
|
|
11653
|
+
/* @__PURE__ */ jsx("div", {
|
|
11654
|
+
...attributes,
|
|
11655
|
+
...listeners,
|
|
11656
|
+
className: "dy-cursor-grab active:dy-cursor-grabbing dy-text-muted-foreground/60 hover:dy-text-foreground dy-p-0.5",
|
|
11657
|
+
children: /* @__PURE__ */ jsx(GripVertical, { className: "dy-h-3.5 dy-w-3.5" })
|
|
11658
|
+
}),
|
|
11659
|
+
/* @__PURE__ */ jsx(Checkbox, {
|
|
11660
|
+
checked: visible,
|
|
11661
|
+
onCheckedChange: (val) => onToggleVisible(!!val)
|
|
11662
|
+
}),
|
|
11663
|
+
/* @__PURE__ */ jsx("div", {
|
|
11664
|
+
className: "dy-flex-1 dy-text-xs dy-font-medium dy-text-foreground",
|
|
11665
|
+
children: label
|
|
11666
|
+
})
|
|
11667
|
+
]
|
|
11668
|
+
});
|
|
11669
|
+
}
|
|
11670
|
+
function WorkflowStatusCell({ schema, slug, item }) {
|
|
11671
|
+
const workflowConfig = schema.workflow ?? null;
|
|
11672
|
+
const workflowMeta = item._workflow ?? null;
|
|
11673
|
+
const publishingStatus = resolvePublishingStatus(schema, item);
|
|
11674
|
+
const documentLabel = resolveDocumentTitle({
|
|
11675
|
+
entry: item,
|
|
11676
|
+
collection: schema,
|
|
11677
|
+
collections: [schema]
|
|
11678
|
+
});
|
|
11679
|
+
if (!publishingStatus) return /* @__PURE__ */ jsx("span", {
|
|
11680
|
+
className: "dy-text-xs dy-text-muted-foreground",
|
|
11681
|
+
children: "-"
|
|
11682
|
+
});
|
|
11683
|
+
const workflowState = resolveWorkflowStateFromDocument(workflowConfig, item);
|
|
11684
|
+
const badgePresentation = {
|
|
11685
|
+
className: WORKFLOW_BADGE_COLORS[publishingStatus.color],
|
|
11686
|
+
style: void 0
|
|
11687
|
+
};
|
|
11688
|
+
const workflowStagePresentation = workflowState && publishingStatus.workflowStateLabel ? getWorkflowBadgePresentation(workflowState.color) : null;
|
|
11689
|
+
const transitions = getAvailableWorkflowTransitions(workflowConfig, workflowMeta);
|
|
11690
|
+
const badge = /* @__PURE__ */ jsxs("div", {
|
|
11691
|
+
className: "dy-inline-flex dy-items-center dy-gap-1.5",
|
|
11692
|
+
children: [/* @__PURE__ */ jsx(Badge, {
|
|
11693
|
+
variant: "outline",
|
|
11694
|
+
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-shrink-0", transitions.length > 0 && "hover:dy-opacity-85", badgePresentation.className),
|
|
11695
|
+
style: badgePresentation.style,
|
|
11696
|
+
children: publishingStatus.label
|
|
11697
|
+
}), publishingStatus.workflowStateLabel && workflowStagePresentation && /* @__PURE__ */ jsx(Badge, {
|
|
11698
|
+
variant: "outline",
|
|
11699
|
+
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-semibold dy-tracking-wide dy-shrink-0", transitions.length > 0 && "hover:dy-opacity-85", workflowStagePresentation.className),
|
|
11700
|
+
style: workflowStagePresentation.style,
|
|
11701
|
+
children: publishingStatus.workflowStateLabel
|
|
11702
|
+
})]
|
|
11703
|
+
});
|
|
11704
|
+
if (!workflowConfig || !workflowMeta || transitions.length === 0 || typeof item.id !== "string") return badge;
|
|
11705
|
+
return /* @__PURE__ */ jsx(WorkflowTransitionMenu, {
|
|
11706
|
+
collection: slug,
|
|
11707
|
+
documentIds: [item.id],
|
|
11708
|
+
documentLabels: { [item.id]: documentLabel },
|
|
11709
|
+
workflowConfig,
|
|
11710
|
+
transitions,
|
|
11711
|
+
expectedRevisions: { [item.id]: workflowMeta.revision },
|
|
11712
|
+
invalidateQueryKeys: [
|
|
11713
|
+
["collection", slug],
|
|
11714
|
+
[
|
|
11715
|
+
"entry",
|
|
11716
|
+
slug,
|
|
11717
|
+
item.id
|
|
11718
|
+
],
|
|
11719
|
+
[
|
|
11720
|
+
"workflow-history",
|
|
11721
|
+
slug,
|
|
11722
|
+
item.id
|
|
11723
|
+
]
|
|
11724
|
+
],
|
|
11725
|
+
trigger: /* @__PURE__ */ jsx("button", {
|
|
11726
|
+
type: "button",
|
|
11727
|
+
className: "dy-inline-flex",
|
|
11728
|
+
title: "Change workflow state",
|
|
11729
|
+
"aria-label": "Change workflow state",
|
|
11730
|
+
children: badge
|
|
11731
|
+
})
|
|
11732
|
+
});
|
|
11733
|
+
}
|
|
11734
|
+
function CollectionListPage({ slug }) {
|
|
11735
|
+
return /* @__PURE__ */ jsx(CollectionListPageContent, { slug }, slug);
|
|
11736
|
+
}
|
|
11737
|
+
function CollectionListPageContent({ slug }) {
|
|
11738
|
+
const { client, components, user } = useDyrected();
|
|
11739
|
+
const queryClient = useQueryClient();
|
|
11740
|
+
const [page, setPage] = React$1.useState(1);
|
|
11741
|
+
const [rowSelection, setRowSelection] = React$1.useState({});
|
|
11742
|
+
const [isImportOpen, setIsImportOpen] = React$1.useState(false);
|
|
11743
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
11744
|
+
const whereParam = searchParams.get("where");
|
|
11745
|
+
const searchParam = searchParams.get("search") || "";
|
|
11746
|
+
const debouncedSearch = useDebouncedValue(searchParam.trim(), 250);
|
|
11747
|
+
const rules = React$1.useMemo(() => {
|
|
11748
|
+
if (!whereParam) return [];
|
|
11749
|
+
try {
|
|
11750
|
+
return whereToRules(JSON.parse(whereParam));
|
|
11751
|
+
} catch {
|
|
11752
|
+
return [];
|
|
11753
|
+
}
|
|
11754
|
+
}, [whereParam]);
|
|
11755
|
+
const handleRulesChange = React$1.useCallback((newRules) => {
|
|
10979
11756
|
setSearchParams((prev) => {
|
|
10980
11757
|
if (newRules.length === 0) prev.delete("where");
|
|
10981
11758
|
else prev.set("where", JSON.stringify(rulesToWhere(newRules)));
|
|
@@ -10983,12 +11760,6 @@ function CollectionListPage({ slug }) {
|
|
|
10983
11760
|
}, { replace: true });
|
|
10984
11761
|
setPage(1);
|
|
10985
11762
|
}, [setSearchParams]);
|
|
10986
|
-
const [prevSlug, setPrevSlug] = React$1.useState(slug);
|
|
10987
|
-
if (prevSlug !== slug) {
|
|
10988
|
-
setPrevSlug(slug);
|
|
10989
|
-
setPage(1);
|
|
10990
|
-
setRowSelection({});
|
|
10991
|
-
}
|
|
10992
11763
|
const { data: schemas } = useQuery({
|
|
10993
11764
|
queryKey: ["schemas"],
|
|
10994
11765
|
queryFn: () => client.getSchemas(),
|
|
@@ -11139,7 +11910,8 @@ function CollectionListPage({ slug }) {
|
|
|
11139
11910
|
"collection",
|
|
11140
11911
|
slug,
|
|
11141
11912
|
page,
|
|
11142
|
-
whereParam
|
|
11913
|
+
whereParam,
|
|
11914
|
+
debouncedSearch
|
|
11143
11915
|
],
|
|
11144
11916
|
queryFn: () => {
|
|
11145
11917
|
const queryParams = {
|
|
@@ -11150,6 +11922,7 @@ function CollectionListPage({ slug }) {
|
|
|
11150
11922
|
if (whereParam) try {
|
|
11151
11923
|
queryParams.where = JSON.parse(whereParam);
|
|
11152
11924
|
} catch {}
|
|
11925
|
+
if (debouncedSearch) queryParams.search = debouncedSearch;
|
|
11153
11926
|
return client.collection(slug).find(queryParams).exec();
|
|
11154
11927
|
},
|
|
11155
11928
|
enabled: !!client
|
|
@@ -11294,6 +12067,10 @@ function CollectionListPage({ slug }) {
|
|
|
11294
12067
|
}
|
|
11295
12068
|
if (window.confirm(`Delete ${cleanIds.length} entries? This cannot be undone.`)) bulkDeleteMutation.mutate(cleanIds);
|
|
11296
12069
|
}
|
|
12070
|
+
const workflowConfig = schema?.workflow ?? null;
|
|
12071
|
+
const selectedWorkflowDocs = React$1.useMemo(() => Object.keys(rowSelection).filter((id) => rowSelection[id]).map((id) => response?.docs?.find((doc) => String(doc.id) === id)).filter((doc) => !!doc && typeof doc.id === "string" && !!doc._workflow), [rowSelection, response]);
|
|
12072
|
+
const sharedWorkflowTransitions = React$1.useMemo(() => getCommonWorkflowTransitions(workflowConfig, selectedWorkflowDocs), [workflowConfig, selectedWorkflowDocs]);
|
|
12073
|
+
const selectedWorkflowRevisions = React$1.useMemo(() => Object.fromEntries(selectedWorkflowDocs.map((doc) => [doc.id, doc._workflow?.revision])), [selectedWorkflowDocs]);
|
|
11297
12074
|
const columns = React$1.useMemo(() => {
|
|
11298
12075
|
if (!schema) return [];
|
|
11299
12076
|
const allDisplayFields = schema.fields.filter((f) => f.name && f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join");
|
|
@@ -11451,21 +12228,10 @@ function CollectionListPage({ slug }) {
|
|
|
11451
12228
|
header: "Status",
|
|
11452
12229
|
enableHiding: false,
|
|
11453
12230
|
cell: ({ row }) => {
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
});
|
|
11459
|
-
const workflowState = resolveWorkflowState(schema, row.original);
|
|
11460
|
-
const badgePresentation = workflowState ? getWorkflowBadgePresentation(workflowState.color) : {
|
|
11461
|
-
className: status === "Published" ? WORKFLOW_BADGE_COLORS.success : WORKFLOW_BADGE_COLORS.warning,
|
|
11462
|
-
style: void 0
|
|
11463
|
-
};
|
|
11464
|
-
return /* @__PURE__ */ jsx(Badge, {
|
|
11465
|
-
variant: "outline",
|
|
11466
|
-
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-wider dy-shrink-0", badgePresentation.className),
|
|
11467
|
-
style: badgePresentation.style,
|
|
11468
|
-
children: status
|
|
12231
|
+
return /* @__PURE__ */ jsx(WorkflowStatusCell, {
|
|
12232
|
+
schema,
|
|
12233
|
+
slug,
|
|
12234
|
+
item: row.original
|
|
11469
12235
|
});
|
|
11470
12236
|
}
|
|
11471
12237
|
});
|
|
@@ -11644,7 +12410,7 @@ function CollectionListPage({ slug }) {
|
|
|
11644
12410
|
}),
|
|
11645
12411
|
/* @__PURE__ */ jsx(PageHeader, {
|
|
11646
12412
|
title: schema.labels?.plural || schema.slug,
|
|
11647
|
-
description: `Manage your ${schema.labels?.plural || schema.slug} entries and update content.`,
|
|
12413
|
+
description: schema.admin?.description || `Manage your ${schema.labels?.plural || schema.slug} entries and update content.`,
|
|
11648
12414
|
icon: resolveAdminIcon(schema.admin?.icon, schema.auth ? Users : Database),
|
|
11649
12415
|
children: /* @__PURE__ */ jsxs("div", {
|
|
11650
12416
|
className: "dy-flex dy-items-center dy-gap-2 dy-w-full sm:dy-w-auto",
|
|
@@ -11826,7 +12592,16 @@ function CollectionListPage({ slug }) {
|
|
|
11826
12592
|
}) : /* @__PURE__ */ jsx(DataTable, {
|
|
11827
12593
|
columns,
|
|
11828
12594
|
data: response?.docs || [],
|
|
11829
|
-
|
|
12595
|
+
searchPlaceholder: `Search ${schema.labels?.plural || schema.slug}...`,
|
|
12596
|
+
searchValue: searchParam,
|
|
12597
|
+
onSearchChange: (value) => {
|
|
12598
|
+
setSearchParams((prev) => {
|
|
12599
|
+
if (value.trim()) prev.set("search", value);
|
|
12600
|
+
else prev.delete("search");
|
|
12601
|
+
return prev;
|
|
12602
|
+
}, { replace: true });
|
|
12603
|
+
setPage(1);
|
|
12604
|
+
},
|
|
11830
12605
|
onRowSelectionChange: setRowSelection,
|
|
11831
12606
|
rowSelection,
|
|
11832
12607
|
hideViewButton: true,
|
|
@@ -11870,31 +12645,63 @@ function CollectionListPage({ slug }) {
|
|
|
11870
12645
|
}
|
|
11871
12646
|
return canDelete;
|
|
11872
12647
|
});
|
|
11873
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
11897
|
-
|
|
12648
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12649
|
+
workflowConfig && selectedWorkflowDocs.length > 0 && sharedWorkflowTransitions.length > 0 && /* @__PURE__ */ jsx(WorkflowTransitionMenu, {
|
|
12650
|
+
collection: slug,
|
|
12651
|
+
documentIds: selectedWorkflowDocs.map((doc) => doc.id),
|
|
12652
|
+
documentLabels: Object.fromEntries(selectedWorkflowDocs.map((doc) => [doc.id, resolveDocumentTitle({
|
|
12653
|
+
entry: doc,
|
|
12654
|
+
collection: schema,
|
|
12655
|
+
collections: [schema]
|
|
12656
|
+
})])),
|
|
12657
|
+
workflowConfig,
|
|
12658
|
+
transitions: sharedWorkflowTransitions,
|
|
12659
|
+
expectedRevisions: selectedWorkflowRevisions,
|
|
12660
|
+
invalidateQueryKeys: [["collection", slug]],
|
|
12661
|
+
onComplete: () => setRowSelection({}),
|
|
12662
|
+
trigger: /* @__PURE__ */ jsxs(Button, {
|
|
12663
|
+
variant: "outline",
|
|
12664
|
+
size: "sm",
|
|
12665
|
+
className: "dy-h-8",
|
|
12666
|
+
disabled: selectedWorkflowDocs.length === 0,
|
|
12667
|
+
children: [
|
|
12668
|
+
"Change state (",
|
|
12669
|
+
selectedWorkflowDocs.length,
|
|
12670
|
+
")"
|
|
12671
|
+
]
|
|
12672
|
+
})
|
|
12673
|
+
}),
|
|
12674
|
+
workflowConfig && selectedWorkflowDocs.length > 0 && sharedWorkflowTransitions.length === 0 && /* @__PURE__ */ jsx("span", {
|
|
12675
|
+
className: "dy-text-xs dy-text-muted-foreground",
|
|
12676
|
+
children: "No shared workflow action for this selection."
|
|
12677
|
+
}),
|
|
12678
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
12679
|
+
variant: "outline",
|
|
12680
|
+
size: "sm",
|
|
12681
|
+
className: "dy-h-8",
|
|
12682
|
+
onClick: () => handleExportSelected(selectedIds),
|
|
12683
|
+
disabled: selectedIds.length === 0,
|
|
12684
|
+
children: [
|
|
12685
|
+
/* @__PURE__ */ jsx(FileDown, { className: "dy-h-4 dy-w-4 dy-mr-2" }),
|
|
12686
|
+
"Export Selected (",
|
|
12687
|
+
selectedIds.length,
|
|
12688
|
+
")"
|
|
12689
|
+
]
|
|
12690
|
+
}),
|
|
12691
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
12692
|
+
variant: "destructive",
|
|
12693
|
+
size: "sm",
|
|
12694
|
+
className: "dy-h-8",
|
|
12695
|
+
onClick: () => handleBulkDelete(deletableIds),
|
|
12696
|
+
disabled: bulkDeleteMutation.isPending || deletableIds.length === 0,
|
|
12697
|
+
children: [
|
|
12698
|
+
/* @__PURE__ */ jsx(Trash2, { className: "dy-h-4 dy-w-4 dy-mr-2" }),
|
|
12699
|
+
"Delete Selected (",
|
|
12700
|
+
deletableIds.length,
|
|
12701
|
+
")"
|
|
12702
|
+
]
|
|
12703
|
+
})
|
|
12704
|
+
] });
|
|
11898
12705
|
}
|
|
11899
12706
|
}, slug),
|
|
11900
12707
|
/* @__PURE__ */ jsx(AdminComponentSlot, {
|
|
@@ -12168,55 +12975,11 @@ function StateIcon({ color }) {
|
|
|
12168
12975
|
if (color === "info") return /* @__PURE__ */ jsx(AlertCircle, { className: "dy-h-3.5 dy-w-3.5" });
|
|
12169
12976
|
return /* @__PURE__ */ jsx(Clock, { className: "dy-h-3.5 dy-w-3.5" });
|
|
12170
12977
|
}
|
|
12171
|
-
function
|
|
12172
|
-
const [comment, setComment] = useState("");
|
|
12173
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
12174
|
-
className: "dy-mt-3 dy-space-y-2",
|
|
12175
|
-
children: [
|
|
12176
|
-
/* @__PURE__ */ jsxs("label", {
|
|
12177
|
-
className: "dy-text-xs dy-font-medium dy-text-muted-foreground",
|
|
12178
|
-
children: ["Comment ", /* @__PURE__ */ jsx("span", {
|
|
12179
|
-
className: "dy-text-destructive",
|
|
12180
|
-
children: "*"
|
|
12181
|
-
})]
|
|
12182
|
-
}),
|
|
12183
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
12184
|
-
className: "dy-w-full dy-rounded-lg dy-border dy-border-border/60 dy-bg-background dy-px-3 dy-py-2 dy-text-sm dy-text-foreground placeholder:dy-text-muted-foreground focus:dy-outline-none focus:dy-ring-2 focus:dy-ring-primary/30 dy-resize-none",
|
|
12185
|
-
rows: 3,
|
|
12186
|
-
placeholder: `Required for "${transitionLabel}"…`,
|
|
12187
|
-
value: comment,
|
|
12188
|
-
onChange: (e) => setComment(e.target.value),
|
|
12189
|
-
autoFocus: true
|
|
12190
|
-
}),
|
|
12191
|
-
/* @__PURE__ */ jsxs("div", {
|
|
12192
|
-
className: "dy-flex dy-gap-2",
|
|
12193
|
-
children: [/* @__PURE__ */ jsx(Button, {
|
|
12194
|
-
size: "sm",
|
|
12195
|
-
className: "dy-h-8 dy-px-3 dy-rounded-lg dy-text-xs dy-font-semibold",
|
|
12196
|
-
disabled: !comment.trim() || isPending,
|
|
12197
|
-
onClick: () => onConfirm(comment.trim()),
|
|
12198
|
-
children: isPending ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3 dy-w-3 dy-animate-spin" }) : transitionLabel
|
|
12199
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
12200
|
-
size: "sm",
|
|
12201
|
-
variant: "ghost",
|
|
12202
|
-
className: "dy-h-8 dy-px-3 dy-rounded-lg dy-text-xs",
|
|
12203
|
-
onClick: onCancel,
|
|
12204
|
-
disabled: isPending,
|
|
12205
|
-
children: "Cancel"
|
|
12206
|
-
})]
|
|
12207
|
-
})
|
|
12208
|
-
]
|
|
12209
|
-
});
|
|
12210
|
-
}
|
|
12211
|
-
function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig }) {
|
|
12978
|
+
function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig, onCompareToLive, compareToLiveEnabled = false, compareToLiveReason = null, onSaveDraft, saveDraftPending, prepareTransition }) {
|
|
12212
12979
|
const { client } = useDyrected();
|
|
12213
|
-
const queryClient = useQueryClient();
|
|
12214
12980
|
const [showHistory, setShowHistory] = useState(false);
|
|
12215
|
-
const [pendingComment, setPendingComment] = useState(null);
|
|
12216
12981
|
const currentState = workflowConfig.states.find((s) => s.name === workflowMeta.state);
|
|
12217
12982
|
const colors = stateColors(currentState?.color);
|
|
12218
|
-
const available = workflowMeta.availableTransitions ?? [];
|
|
12219
|
-
const availableTransitions = workflowConfig.transitions.filter((t) => available.includes(t.name));
|
|
12220
12983
|
const wfClient = client;
|
|
12221
12984
|
const { data: historyResult, isLoading: historyLoading } = useQuery({
|
|
12222
12985
|
queryKey: [
|
|
@@ -12228,34 +12991,6 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12228
12991
|
enabled: showHistory && !!client
|
|
12229
12992
|
});
|
|
12230
12993
|
const history = historyResult?.docs ?? [];
|
|
12231
|
-
const transitionMutation = useMutation({
|
|
12232
|
-
mutationFn: ({ name, comment }) => wfClient.transition(collection, documentId, name, {
|
|
12233
|
-
expectedRevision: workflowMeta.revision,
|
|
12234
|
-
comment
|
|
12235
|
-
}),
|
|
12236
|
-
onSuccess: () => {
|
|
12237
|
-
setPendingComment(null);
|
|
12238
|
-
queryClient.invalidateQueries({ queryKey: [
|
|
12239
|
-
"entry",
|
|
12240
|
-
collection,
|
|
12241
|
-
documentId
|
|
12242
|
-
] });
|
|
12243
|
-
queryClient.invalidateQueries({ queryKey: [
|
|
12244
|
-
"workflow-history",
|
|
12245
|
-
collection,
|
|
12246
|
-
documentId
|
|
12247
|
-
] });
|
|
12248
|
-
toast.success("Transition applied");
|
|
12249
|
-
},
|
|
12250
|
-
onError: (err) => {
|
|
12251
|
-
setPendingComment(null);
|
|
12252
|
-
toast.error("Transition failed", { description: err.message });
|
|
12253
|
-
}
|
|
12254
|
-
});
|
|
12255
|
-
function handleTransitionClick(t) {
|
|
12256
|
-
if (t.requireComment) setPendingComment(t.name);
|
|
12257
|
-
else transitionMutation.mutate({ name: t.name });
|
|
12258
|
-
}
|
|
12259
12994
|
return /* @__PURE__ */ jsxs("div", {
|
|
12260
12995
|
className: "dy-rounded-2xl dy-border dy-border-border/50 dy-bg-muted/10 dy-p-4 dy-space-y-4",
|
|
12261
12996
|
children: [
|
|
@@ -12273,32 +13008,32 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12273
13008
|
className: "dy-flex dy-gap-4 dy-text-[11px] dy-text-muted-foreground/60",
|
|
12274
13009
|
children: [/* @__PURE__ */ jsxs("span", { children: ["Revision ", workflowMeta.revision] }), workflowMeta.publishedAt && /* @__PURE__ */ jsxs("span", { children: ["Published ", new Date(workflowMeta.publishedAt).toLocaleDateString()] })]
|
|
12275
13010
|
}),
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
className: cn("dy-w-full dy-h-9 dy-rounded-lg dy-text-xs dy-font-semibold dy-justify-start dy-gap-2", isWaiting && "dy-ring-2 dy-ring-primary/30"),
|
|
12285
|
-
disabled: transitionMutation.isPending,
|
|
12286
|
-
onClick: () => handleTransitionClick(t),
|
|
12287
|
-
children: [isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3.5 dy-w-3.5 dy-animate-spin" }) : /* @__PURE__ */ jsx(StateIcon, { color: workflowConfig.states.find((s) => s.name === t.to)?.color }), t.label]
|
|
12288
|
-
}), isWaiting && /* @__PURE__ */ jsx(CommentDialog, {
|
|
12289
|
-
transitionLabel: t.label,
|
|
12290
|
-
isPending: transitionMutation.isPending,
|
|
12291
|
-
onConfirm: (comment) => transitionMutation.mutate({
|
|
12292
|
-
name: t.name,
|
|
12293
|
-
comment
|
|
12294
|
-
}),
|
|
12295
|
-
onCancel: () => setPendingComment(null)
|
|
12296
|
-
})] }, t.name);
|
|
12297
|
-
})
|
|
13011
|
+
/* @__PURE__ */ jsx(WorkflowTransitionPanelActions, {
|
|
13012
|
+
collection,
|
|
13013
|
+
documentId,
|
|
13014
|
+
workflowConfig,
|
|
13015
|
+
workflowMeta,
|
|
13016
|
+
onSaveDraft,
|
|
13017
|
+
saveDraftPending,
|
|
13018
|
+
prepareTransition
|
|
12298
13019
|
}),
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
13020
|
+
onCompareToLive && /* @__PURE__ */ jsx(Button, {
|
|
13021
|
+
type: "button",
|
|
13022
|
+
variant: "outline",
|
|
13023
|
+
className: "dy-w-full dy-justify-between dy-rounded-xl dy-border-border/60 dy-bg-background/70 dy-text-left",
|
|
13024
|
+
onClick: onCompareToLive,
|
|
13025
|
+
disabled: !compareToLiveEnabled,
|
|
13026
|
+
title: !compareToLiveEnabled && compareToLiveReason ? compareToLiveReason : "Compare draft to live",
|
|
13027
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
13028
|
+
className: "dy-flex dy-flex-col dy-items-start",
|
|
13029
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
13030
|
+
className: "dy-text-sm dy-font-semibold dy-text-foreground",
|
|
13031
|
+
children: "Compare to live"
|
|
13032
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
13033
|
+
className: "dy-text-xs dy-font-normal dy-text-muted-foreground",
|
|
13034
|
+
children: compareToLiveEnabled ? "See exactly what will change on publish." : compareToLiveReason ?? "Publish this entry once to unlock live comparisons."
|
|
13035
|
+
})]
|
|
13036
|
+
})
|
|
12302
13037
|
}),
|
|
12303
13038
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("button", {
|
|
12304
13039
|
className: "dy-flex dy-items-center dy-gap-1 dy-text-[11px] dy-font-medium dy-text-muted-foreground/60 hover:dy-text-muted-foreground dy-transition-colors",
|
|
@@ -12355,38 +13090,248 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12355
13090
|
});
|
|
12356
13091
|
}
|
|
12357
13092
|
//#endregion
|
|
12358
|
-
//#region src/
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
13093
|
+
//#region src/components/ui/card.tsx
|
|
13094
|
+
var Card = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13095
|
+
ref,
|
|
13096
|
+
className: cn("dy-rounded-lg dy-border dy-bg-card dy-text-card-foreground dy-shadow-sm", className),
|
|
13097
|
+
...props
|
|
13098
|
+
}));
|
|
13099
|
+
Card.displayName = "Card";
|
|
13100
|
+
var CardHeader = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13101
|
+
ref,
|
|
13102
|
+
className: cn("dy-flex dy-flex-col dy-space-y-1.5 dy-p-6", className),
|
|
13103
|
+
...props
|
|
13104
|
+
}));
|
|
13105
|
+
CardHeader.displayName = "CardHeader";
|
|
13106
|
+
var CardTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13107
|
+
ref,
|
|
13108
|
+
className: cn("dy-text-2xl dy-font-semibold dy-leading-none dy-tracking-tight", className),
|
|
13109
|
+
...props
|
|
13110
|
+
}));
|
|
13111
|
+
CardTitle.displayName = "CardTitle";
|
|
13112
|
+
var CardDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13113
|
+
ref,
|
|
13114
|
+
className: cn("dy-text-sm dy-text-muted-foreground", className),
|
|
13115
|
+
...props
|
|
13116
|
+
}));
|
|
13117
|
+
CardDescription.displayName = "CardDescription";
|
|
13118
|
+
var CardContent = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13119
|
+
ref,
|
|
13120
|
+
className: cn("dy-p-6 dy-pt-0", className),
|
|
13121
|
+
...props
|
|
13122
|
+
}));
|
|
13123
|
+
CardContent.displayName = "CardContent";
|
|
13124
|
+
var CardFooter = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13125
|
+
ref,
|
|
13126
|
+
className: cn("dy-flex dy-items-center dy-p-6 dy-pt-0", className),
|
|
13127
|
+
...props
|
|
13128
|
+
}));
|
|
13129
|
+
CardFooter.displayName = "CardFooter";
|
|
13130
|
+
//#endregion
|
|
13131
|
+
//#region src/components/workflow/draft-live-compare-sheet.tsx
|
|
13132
|
+
function statusBadgeClass(status) {
|
|
13133
|
+
if (status === "Added") return "dy-bg-emerald-50 dy-text-emerald-700 dy-border-emerald-200";
|
|
13134
|
+
if (status === "Removed") return "dy-bg-rose-50 dy-text-rose-700 dy-border-rose-200";
|
|
13135
|
+
return "dy-bg-amber-50 dy-text-amber-700 dy-border-amber-200";
|
|
13136
|
+
}
|
|
13137
|
+
function DraftLiveCompareSheet({ open, onOpenChange, comparison }) {
|
|
13138
|
+
return /* @__PURE__ */ jsx(Sheet, {
|
|
13139
|
+
open,
|
|
13140
|
+
onOpenChange,
|
|
13141
|
+
children: /* @__PURE__ */ jsxs(SheetContent, {
|
|
13142
|
+
side: "right",
|
|
13143
|
+
className: "dy-w-[96vw] sm:dy-max-w-4xl lg:dy-max-w-6xl dy-border-l dy-border-border/60",
|
|
13144
|
+
children: [/* @__PURE__ */ jsx(SheetHeader, {
|
|
13145
|
+
className: "dy-border-b dy-border-border/50 dy-bg-background/95 dy-px-6 dy-py-5 dy-backdrop-blur",
|
|
13146
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
13147
|
+
className: "dy-flex dy-flex-wrap dy-items-start dy-justify-between dy-gap-4",
|
|
13148
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
13149
|
+
className: "dy-space-y-1",
|
|
13150
|
+
children: [/* @__PURE__ */ jsx(SheetTitle, {
|
|
13151
|
+
className: "dy-text-2xl dy-font-serif dy-font-bold dy-tracking-tight",
|
|
13152
|
+
children: "Compare draft to live"
|
|
13153
|
+
}), /* @__PURE__ */ jsx(SheetDescription, { children: "These changes are in your draft and are not live yet." })]
|
|
13154
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
13155
|
+
className: "dy-flex dy-flex-wrap dy-gap-2",
|
|
13156
|
+
children: [
|
|
13157
|
+
/* @__PURE__ */ jsx(SummaryChip, { label: `${comparison.fieldChangeCount} fields changed` }),
|
|
13158
|
+
/* @__PURE__ */ jsx(SummaryChip, { label: `${comparison.sectionsAdded} sections added` }),
|
|
13159
|
+
/* @__PURE__ */ jsx(SummaryChip, { label: `${comparison.sectionsRemoved} sections removed` })
|
|
13160
|
+
]
|
|
13161
|
+
})]
|
|
13162
|
+
})
|
|
13163
|
+
}), /* @__PURE__ */ jsx(ScrollArea, {
|
|
13164
|
+
className: "dy-h-[calc(100vh-132px)]",
|
|
13165
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
13166
|
+
className: "dy-space-y-8 dy-p-6",
|
|
13167
|
+
children: [!comparison.hasChanges && /* @__PURE__ */ jsx(Card, {
|
|
13168
|
+
className: "dy-rounded-2xl dy-border-border/60",
|
|
13169
|
+
children: /* @__PURE__ */ jsxs(CardContent, {
|
|
13170
|
+
className: "dy-p-8 dy-text-center",
|
|
13171
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
13172
|
+
className: "dy-text-base dy-font-semibold dy-text-foreground",
|
|
13173
|
+
children: "Draft matches the live site."
|
|
13174
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
13175
|
+
className: "dy-mt-2 dy-text-sm dy-text-muted-foreground",
|
|
13176
|
+
children: "There are no unpublished changes in this document right now."
|
|
13177
|
+
})]
|
|
13178
|
+
})
|
|
13179
|
+
}), comparison.groups.map((group) => /* @__PURE__ */ jsxs("section", {
|
|
13180
|
+
className: "dy-space-y-4",
|
|
13181
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
13182
|
+
className: "dy-flex dy-items-center dy-justify-between",
|
|
13183
|
+
children: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
|
|
13184
|
+
className: "dy-text-sm dy-font-bold dy-uppercase dy-tracking-[0.18em] dy-text-muted-foreground/70",
|
|
13185
|
+
children: group.title
|
|
13186
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
13187
|
+
className: "dy-mt-1 dy-text-sm dy-text-muted-foreground",
|
|
13188
|
+
children: [
|
|
13189
|
+
group.cards.length,
|
|
13190
|
+
" ",
|
|
13191
|
+
group.cards.length === 1 ? "change" : "changes"
|
|
13192
|
+
]
|
|
13193
|
+
})] })
|
|
13194
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
13195
|
+
className: "dy-space-y-4",
|
|
13196
|
+
children: group.cards.map((card) => /* @__PURE__ */ jsx(ChangeCard, { card }, card.id))
|
|
13197
|
+
})]
|
|
13198
|
+
}, group.title))]
|
|
13199
|
+
})
|
|
13200
|
+
})]
|
|
13201
|
+
})
|
|
13202
|
+
});
|
|
13203
|
+
}
|
|
13204
|
+
function SummaryChip({ label }) {
|
|
13205
|
+
return /* @__PURE__ */ jsx("div", {
|
|
13206
|
+
className: "dy-rounded-full dy-border dy-border-border/60 dy-bg-muted/40 dy-px-3 dy-py-1.5 dy-text-xs dy-font-semibold dy-text-foreground/80",
|
|
13207
|
+
children: label
|
|
13208
|
+
});
|
|
13209
|
+
}
|
|
13210
|
+
function ChangeCard({ card }) {
|
|
13211
|
+
return /* @__PURE__ */ jsxs(Card, {
|
|
13212
|
+
className: "dy-overflow-hidden dy-rounded-2xl dy-border-border/60",
|
|
13213
|
+
children: [/* @__PURE__ */ jsx(CardHeader, {
|
|
13214
|
+
className: "dy-gap-3 dy-border-b dy-border-border/50 dy-bg-muted/20 dy-px-5 dy-py-4",
|
|
13215
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
13216
|
+
className: "dy-flex dy-flex-wrap dy-items-center dy-justify-between dy-gap-3",
|
|
13217
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(CardTitle, {
|
|
13218
|
+
className: "dy-text-base dy-font-semibold",
|
|
13219
|
+
children: card.label
|
|
13220
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
13221
|
+
className: "dy-mt-1 dy-text-xs dy-text-muted-foreground",
|
|
13222
|
+
children: card.path
|
|
13223
|
+
})] }), /* @__PURE__ */ jsx(Badge, {
|
|
13224
|
+
variant: "outline",
|
|
13225
|
+
className: cn("dy-rounded-full dy-border dy-px-2.5 dy-py-1 dy-text-[10px] dy-font-bold dy-uppercase dy-tracking-[0.18em]", statusBadgeClass(card.status)),
|
|
13226
|
+
children: card.status
|
|
13227
|
+
})]
|
|
13228
|
+
})
|
|
13229
|
+
}), /* @__PURE__ */ jsxs(CardContent, {
|
|
13230
|
+
className: "dy-grid dy-gap-4 dy-p-5 lg:dy-grid-cols-2",
|
|
13231
|
+
children: [/* @__PURE__ */ jsx(CompareSide, {
|
|
13232
|
+
title: "Live",
|
|
13233
|
+
text: card.liveText,
|
|
13234
|
+
media: card.liveMedia
|
|
13235
|
+
}), /* @__PURE__ */ jsx(CompareSide, {
|
|
13236
|
+
title: "Draft",
|
|
13237
|
+
text: card.draftText,
|
|
13238
|
+
media: card.draftMedia,
|
|
13239
|
+
emphasize: true
|
|
13240
|
+
})]
|
|
13241
|
+
})]
|
|
13242
|
+
});
|
|
13243
|
+
}
|
|
13244
|
+
function CompareSide({ title, text, media, emphasize = false }) {
|
|
13245
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13246
|
+
className: cn("dy-space-y-3 dy-rounded-2xl dy-border dy-p-4", emphasize ? "dy-border-primary/20 dy-bg-primary/5" : "dy-border-border/60 dy-bg-background"),
|
|
13247
|
+
children: [
|
|
13248
|
+
/* @__PURE__ */ jsx("div", {
|
|
13249
|
+
className: "dy-flex dy-items-center dy-justify-between",
|
|
13250
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
13251
|
+
className: "dy-text-[11px] dy-font-bold dy-uppercase dy-tracking-[0.18em] dy-text-muted-foreground/70",
|
|
13252
|
+
children: title
|
|
13253
|
+
})
|
|
13254
|
+
}),
|
|
13255
|
+
media && /* @__PURE__ */ jsx(MediaPreview, { media }),
|
|
13256
|
+
/* @__PURE__ */ jsx("div", {
|
|
13257
|
+
className: "dy-rounded-xl dy-bg-muted/35 dy-p-3",
|
|
13258
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
13259
|
+
className: "dy-whitespace-pre-wrap dy-break-words dy-text-sm dy-leading-6 dy-text-foreground/90",
|
|
13260
|
+
children: text
|
|
13261
|
+
})
|
|
13262
|
+
})
|
|
13263
|
+
]
|
|
13264
|
+
});
|
|
13265
|
+
}
|
|
13266
|
+
function MediaPreview({ media }) {
|
|
13267
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13268
|
+
className: "dy-flex dy-gap-3",
|
|
13269
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
13270
|
+
className: "dy-flex dy-h-20 dy-w-20 dy-shrink-0 dy-items-center dy-justify-center dy-overflow-hidden dy-rounded-xl dy-border dy-border-border/60 dy-bg-muted/30",
|
|
13271
|
+
children: media.url ? /* @__PURE__ */ jsx("img", {
|
|
13272
|
+
src: media.url,
|
|
13273
|
+
alt: media.alt || media.filename || "Media preview",
|
|
13274
|
+
className: "dy-h-full dy-w-full dy-object-cover"
|
|
13275
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
13276
|
+
className: "dy-text-[11px] dy-font-semibold dy-text-muted-foreground",
|
|
13277
|
+
children: "No preview"
|
|
13278
|
+
})
|
|
13279
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
13280
|
+
className: "dy-min-w-0 dy-space-y-1",
|
|
13281
|
+
children: [
|
|
13282
|
+
media.filename && /* @__PURE__ */ jsx("p", {
|
|
13283
|
+
className: "dy-text-sm dy-font-semibold dy-text-foreground dy-break-all",
|
|
13284
|
+
children: media.filename
|
|
13285
|
+
}),
|
|
13286
|
+
media.alt && /* @__PURE__ */ jsx("p", {
|
|
13287
|
+
className: "dy-text-xs dy-text-muted-foreground",
|
|
13288
|
+
children: media.alt
|
|
13289
|
+
}),
|
|
13290
|
+
media.url && /* @__PURE__ */ jsx(Button, {
|
|
13291
|
+
type: "button",
|
|
13292
|
+
variant: "ghost",
|
|
13293
|
+
size: "sm",
|
|
13294
|
+
className: "dy-h-auto dy-justify-start dy-p-0 dy-text-xs dy-text-primary hover:dy-bg-transparent hover:dy-text-primary/80",
|
|
13295
|
+
onClick: () => window.open(media.url, "_blank", "noopener,noreferrer"),
|
|
13296
|
+
children: "Open asset"
|
|
13297
|
+
})
|
|
13298
|
+
]
|
|
13299
|
+
})]
|
|
13300
|
+
});
|
|
13301
|
+
}
|
|
13302
|
+
//#endregion
|
|
13303
|
+
//#region src/hooks/useLayoutPreference.ts
|
|
13304
|
+
function useLayoutPreference({ key, defaultKeys }) {
|
|
13305
|
+
const { client } = useDyrected();
|
|
13306
|
+
const queryClient = useQueryClient();
|
|
13307
|
+
const { data, isLoading } = useQuery({
|
|
13308
|
+
queryKey: ["preferences", key],
|
|
13309
|
+
queryFn: async () => {
|
|
13310
|
+
if (!client) return null;
|
|
13311
|
+
return (await client.getPreference(key)).value;
|
|
13312
|
+
},
|
|
13313
|
+
enabled: !!client,
|
|
13314
|
+
staleTime: 5e3,
|
|
13315
|
+
refetchOnWindowFocus: true
|
|
13316
|
+
});
|
|
13317
|
+
const reconciledLayout = useMemo(() => {
|
|
13318
|
+
const isStringArray = defaultKeys.every((k) => typeof k === "string");
|
|
13319
|
+
if (!data || !Array.isArray(data)) return defaultKeys;
|
|
13320
|
+
if (isStringArray) {
|
|
13321
|
+
const normalizedData = data.map((k) => typeof k === "string" ? k : k?.name || "").filter(Boolean);
|
|
13322
|
+
const defaultStrings = defaultKeys;
|
|
13323
|
+
const validKeys = normalizedData.filter((k) => defaultStrings.includes(k));
|
|
13324
|
+
const missingKeys = defaultStrings.filter((k) => !validKeys.includes(k));
|
|
13325
|
+
return [...validKeys, ...missingKeys];
|
|
13326
|
+
} else {
|
|
13327
|
+
const normalizedData = data.map((item) => {
|
|
13328
|
+
if (typeof item === "string") return { name: item };
|
|
13329
|
+
if (item && typeof item === "object" && "name" in item && typeof item.name === "string") return {
|
|
13330
|
+
name: item.name,
|
|
13331
|
+
width: item.width
|
|
13332
|
+
};
|
|
13333
|
+
return { name: "" };
|
|
13334
|
+
}).filter((item) => item.name !== "");
|
|
12390
13335
|
const defaultItems = defaultKeys;
|
|
12391
13336
|
const defaultNames = defaultItems.map((item) => item.name);
|
|
12392
13337
|
const defaultMap = new Map(defaultItems.map((item) => [item.name, item]));
|
|
@@ -12449,6 +13394,292 @@ function useLayoutPreference({ key, defaultKeys }) {
|
|
|
12449
13394
|
};
|
|
12450
13395
|
}
|
|
12451
13396
|
//#endregion
|
|
13397
|
+
//#region src/lib/draft-live-compare.ts
|
|
13398
|
+
var GROUP_ORDER = [
|
|
13399
|
+
"Text changes",
|
|
13400
|
+
"Media changes",
|
|
13401
|
+
"Layout / sections changed",
|
|
13402
|
+
"Settings changed"
|
|
13403
|
+
];
|
|
13404
|
+
var SYSTEM_KEYS = /* @__PURE__ */ new Set([
|
|
13405
|
+
"id",
|
|
13406
|
+
"_workflow",
|
|
13407
|
+
"__workflow",
|
|
13408
|
+
"__published",
|
|
13409
|
+
"createdAt",
|
|
13410
|
+
"createdBy",
|
|
13411
|
+
"updatedAt",
|
|
13412
|
+
"updatedBy",
|
|
13413
|
+
"password",
|
|
13414
|
+
"oldPassword",
|
|
13415
|
+
"newPassword",
|
|
13416
|
+
"confirmPassword"
|
|
13417
|
+
]);
|
|
13418
|
+
function buildDraftLiveComparison(args) {
|
|
13419
|
+
const draft = args.draft ?? {};
|
|
13420
|
+
const live = args.live ?? {};
|
|
13421
|
+
const cards = [];
|
|
13422
|
+
const counters = {
|
|
13423
|
+
sectionsAdded: 0,
|
|
13424
|
+
sectionsRemoved: 0
|
|
13425
|
+
};
|
|
13426
|
+
compareFields({
|
|
13427
|
+
fields: args.fields,
|
|
13428
|
+
draft,
|
|
13429
|
+
live,
|
|
13430
|
+
pathPrefix: "",
|
|
13431
|
+
cards,
|
|
13432
|
+
counters
|
|
13433
|
+
});
|
|
13434
|
+
const groups = GROUP_ORDER.map((title) => ({
|
|
13435
|
+
title,
|
|
13436
|
+
cards: cards.filter((card) => card.group === title)
|
|
13437
|
+
})).filter((group) => group.cards.length > 0);
|
|
13438
|
+
return {
|
|
13439
|
+
hasPublishedVersion: !isStructurallyEmpty(live),
|
|
13440
|
+
hasChanges: cards.length > 0,
|
|
13441
|
+
fieldChangeCount: cards.length,
|
|
13442
|
+
sectionsAdded: counters.sectionsAdded,
|
|
13443
|
+
sectionsRemoved: counters.sectionsRemoved,
|
|
13444
|
+
groups
|
|
13445
|
+
};
|
|
13446
|
+
}
|
|
13447
|
+
function compareFields(args) {
|
|
13448
|
+
const draftRecord = args.draft ?? {};
|
|
13449
|
+
const liveRecord = args.live ?? {};
|
|
13450
|
+
for (const field of args.fields) {
|
|
13451
|
+
if (!field?.name || SYSTEM_KEYS.has(field.name) || field.admin?.hidden) continue;
|
|
13452
|
+
const path = args.pathPrefix ? `${args.pathPrefix}.${field.name}` : field.name;
|
|
13453
|
+
const draftValue = draftRecord[field.name];
|
|
13454
|
+
const liveValue = liveRecord[field.name];
|
|
13455
|
+
if (field.type === "object" && Array.isArray(field.fields)) {
|
|
13456
|
+
compareFields({
|
|
13457
|
+
fields: field.fields,
|
|
13458
|
+
draft: asRecord(draftValue),
|
|
13459
|
+
live: asRecord(liveValue),
|
|
13460
|
+
pathPrefix: path,
|
|
13461
|
+
cards: args.cards,
|
|
13462
|
+
counters: args.counters
|
|
13463
|
+
});
|
|
13464
|
+
continue;
|
|
13465
|
+
}
|
|
13466
|
+
if (field.type === "array" || field.type === "blocks") {
|
|
13467
|
+
compareArrayField({
|
|
13468
|
+
field,
|
|
13469
|
+
path,
|
|
13470
|
+
draftValue,
|
|
13471
|
+
liveValue,
|
|
13472
|
+
cards: args.cards,
|
|
13473
|
+
counters: args.counters
|
|
13474
|
+
});
|
|
13475
|
+
continue;
|
|
13476
|
+
}
|
|
13477
|
+
if (deepEqual(draftValue, liveValue)) continue;
|
|
13478
|
+
args.cards.push(buildCard({
|
|
13479
|
+
field,
|
|
13480
|
+
label: field.label || humanizeKey(field.name),
|
|
13481
|
+
path,
|
|
13482
|
+
draftValue,
|
|
13483
|
+
liveValue
|
|
13484
|
+
}));
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
function compareArrayField(args) {
|
|
13488
|
+
const draftItems = Array.isArray(args.draftValue) ? args.draftValue : [];
|
|
13489
|
+
const liveItems = Array.isArray(args.liveValue) ? args.liveValue : [];
|
|
13490
|
+
const max = Math.max(draftItems.length, liveItems.length);
|
|
13491
|
+
for (let index = 0; index < max; index += 1) {
|
|
13492
|
+
const draftItem = draftItems[index];
|
|
13493
|
+
const liveItem = liveItems[index];
|
|
13494
|
+
if (deepEqual(draftItem, liveItem)) continue;
|
|
13495
|
+
const label = describeArrayItem(args.field, draftItem, liveItem, index);
|
|
13496
|
+
const status = resolveStatus(liveItem, draftItem);
|
|
13497
|
+
if (status === "Added") args.counters.sectionsAdded += 1;
|
|
13498
|
+
if (status === "Removed") args.counters.sectionsRemoved += 1;
|
|
13499
|
+
args.cards.push({
|
|
13500
|
+
id: `${args.path}.${index}`,
|
|
13501
|
+
group: "Layout / sections changed",
|
|
13502
|
+
label,
|
|
13503
|
+
status,
|
|
13504
|
+
path: `${args.path}.${index}`,
|
|
13505
|
+
kind: "array",
|
|
13506
|
+
liveValue: liveItem,
|
|
13507
|
+
draftValue: draftItem,
|
|
13508
|
+
liveText: summarizeArrayItem(liveItem),
|
|
13509
|
+
draftText: summarizeArrayItem(draftItem)
|
|
13510
|
+
});
|
|
13511
|
+
}
|
|
13512
|
+
}
|
|
13513
|
+
function buildCard(args) {
|
|
13514
|
+
const kind = resolveKind(args.field, args.draftValue, args.liveValue);
|
|
13515
|
+
const status = resolveStatus(args.liveValue, args.draftValue);
|
|
13516
|
+
const liveMedia = kind === "media" ? extractMediaPreview(args.liveValue) : null;
|
|
13517
|
+
const draftMedia = kind === "media" ? extractMediaPreview(args.draftValue) : null;
|
|
13518
|
+
return {
|
|
13519
|
+
id: args.path,
|
|
13520
|
+
group: resolveGroup(args.field, kind),
|
|
13521
|
+
label: args.label,
|
|
13522
|
+
status,
|
|
13523
|
+
path: args.path,
|
|
13524
|
+
kind,
|
|
13525
|
+
liveValue: args.liveValue,
|
|
13526
|
+
draftValue: args.draftValue,
|
|
13527
|
+
liveText: formatValueForPreview(args.liveValue, kind),
|
|
13528
|
+
draftText: formatValueForPreview(args.draftValue, kind),
|
|
13529
|
+
liveMedia,
|
|
13530
|
+
draftMedia
|
|
13531
|
+
};
|
|
13532
|
+
}
|
|
13533
|
+
function resolveKind(field, draftValue, liveValue) {
|
|
13534
|
+
if (field.type === "richText" || field.type === "json") return "richText";
|
|
13535
|
+
if (field.type === "image" || isMediaLike(draftValue) || isMediaLike(liveValue)) return "media";
|
|
13536
|
+
if (field.type === "relationship" && (field.hasMany || Array.isArray(draftValue) || Array.isArray(liveValue))) return "array";
|
|
13537
|
+
if (isLikelySettingField(field)) return "setting";
|
|
13538
|
+
return "text";
|
|
13539
|
+
}
|
|
13540
|
+
function resolveGroup(field, kind) {
|
|
13541
|
+
if (kind === "media") return "Media changes";
|
|
13542
|
+
if (kind === "array") return "Layout / sections changed";
|
|
13543
|
+
if (kind === "setting" || isLikelySettingField(field)) return "Settings changed";
|
|
13544
|
+
return "Text changes";
|
|
13545
|
+
}
|
|
13546
|
+
function resolveStatus(liveValue, draftValue) {
|
|
13547
|
+
if (isStructurallyEmpty(liveValue) && !isStructurallyEmpty(draftValue)) return "Added";
|
|
13548
|
+
if (!isStructurallyEmpty(liveValue) && isStructurallyEmpty(draftValue)) return "Removed";
|
|
13549
|
+
return "Changed";
|
|
13550
|
+
}
|
|
13551
|
+
function describeArrayItem(field, draftItem, liveItem, index) {
|
|
13552
|
+
const sample = asRecord(draftItem) || asRecord(liveItem);
|
|
13553
|
+
if (field.type === "blocks") {
|
|
13554
|
+
const blockType = typeof sample?.blockType === "string" ? sample.blockType : null;
|
|
13555
|
+
const blockLabel = blockType ? field.blocks?.find((block) => block.slug === blockType)?.labels?.singular ?? humanizeKey(blockType) : `Section ${index + 1}`;
|
|
13556
|
+
return blockLabel.includes("section") || blockLabel.includes("Section") ? blockLabel : `${blockLabel} section`;
|
|
13557
|
+
}
|
|
13558
|
+
const title = extractTitle(sample);
|
|
13559
|
+
if (title) return title;
|
|
13560
|
+
return `${field.label?.replace(/s$/, "") || "Section"} ${index + 1}`;
|
|
13561
|
+
}
|
|
13562
|
+
function summarizeArrayItem(value) {
|
|
13563
|
+
if (isStructurallyEmpty(value)) return "No content";
|
|
13564
|
+
const record = asRecord(value);
|
|
13565
|
+
if (!record) return formatValueForPreview(value, "text");
|
|
13566
|
+
const summaryParts = [];
|
|
13567
|
+
const title = extractTitle(record);
|
|
13568
|
+
if (title) summaryParts.push(title);
|
|
13569
|
+
const textSnippet = extractTextContent(record);
|
|
13570
|
+
if (textSnippet && textSnippet !== title) summaryParts.push(textSnippet);
|
|
13571
|
+
if (summaryParts.length === 0) {
|
|
13572
|
+
const keys = Object.keys(record).filter((key) => !SYSTEM_KEYS.has(key));
|
|
13573
|
+
if (keys.length > 0) summaryParts.push(`${keys.length} fields`);
|
|
13574
|
+
}
|
|
13575
|
+
return summaryParts.join(" • ") || "No content";
|
|
13576
|
+
}
|
|
13577
|
+
function formatValueForPreview(value, kind) {
|
|
13578
|
+
if (isStructurallyEmpty(value)) return "Empty";
|
|
13579
|
+
if (kind === "media") {
|
|
13580
|
+
const media = extractMediaPreview(value);
|
|
13581
|
+
return media?.filename || media?.url || media?.alt || "Media updated";
|
|
13582
|
+
}
|
|
13583
|
+
if (kind === "richText") return extractTextContent(value) || "Rich text updated";
|
|
13584
|
+
if (Array.isArray(value)) return value.map((item) => summarizeArrayItem(item)).filter(Boolean).slice(0, 3).join("\n") || `${value.length} items`;
|
|
13585
|
+
if (typeof value === "object") return extractTextContent(value) || JSON.stringify(value);
|
|
13586
|
+
return String(value);
|
|
13587
|
+
}
|
|
13588
|
+
function extractMediaPreview(value) {
|
|
13589
|
+
const record = asRecord(value);
|
|
13590
|
+
if (!record) return null;
|
|
13591
|
+
const filename = readString(record.filename) || readString(record.name);
|
|
13592
|
+
const url = readString(record.url) || readString(record.src);
|
|
13593
|
+
const alt = readString(record.alt) || readString(record.caption);
|
|
13594
|
+
if (!filename && !url && !alt) return null;
|
|
13595
|
+
return {
|
|
13596
|
+
filename,
|
|
13597
|
+
url,
|
|
13598
|
+
alt
|
|
13599
|
+
};
|
|
13600
|
+
}
|
|
13601
|
+
function isMediaLike(value) {
|
|
13602
|
+
return !!extractMediaPreview(value);
|
|
13603
|
+
}
|
|
13604
|
+
function isLikelySettingField(field) {
|
|
13605
|
+
const source = `${field.name ?? ""} ${field.label ?? ""}`.toLowerCase();
|
|
13606
|
+
return [
|
|
13607
|
+
"status",
|
|
13608
|
+
"slug",
|
|
13609
|
+
"seo",
|
|
13610
|
+
"settings",
|
|
13611
|
+
"theme",
|
|
13612
|
+
"variant",
|
|
13613
|
+
"url",
|
|
13614
|
+
"path",
|
|
13615
|
+
"template"
|
|
13616
|
+
].some((token) => source.includes(token));
|
|
13617
|
+
}
|
|
13618
|
+
function extractTitle(value) {
|
|
13619
|
+
if (!value) return "";
|
|
13620
|
+
for (const key of [
|
|
13621
|
+
"title",
|
|
13622
|
+
"heading",
|
|
13623
|
+
"headline",
|
|
13624
|
+
"label",
|
|
13625
|
+
"name",
|
|
13626
|
+
"question",
|
|
13627
|
+
"slug"
|
|
13628
|
+
]) {
|
|
13629
|
+
const next = readString(value[key]);
|
|
13630
|
+
if (next) return next;
|
|
13631
|
+
}
|
|
13632
|
+
return "";
|
|
13633
|
+
}
|
|
13634
|
+
function extractTextContent(value) {
|
|
13635
|
+
const parts = [];
|
|
13636
|
+
collectText(value, parts);
|
|
13637
|
+
return truncateText(parts.join(" ").replace(/\s+/g, " ").trim(), 220);
|
|
13638
|
+
}
|
|
13639
|
+
function collectText(value, parts) {
|
|
13640
|
+
if (value === null || value === void 0) return;
|
|
13641
|
+
if (typeof value === "string") {
|
|
13642
|
+
const cleaned = value.replace(/<[^>]+>/g, " ").replace(/ /g, " ").trim();
|
|
13643
|
+
if (cleaned) parts.push(cleaned);
|
|
13644
|
+
return;
|
|
13645
|
+
}
|
|
13646
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
13647
|
+
parts.push(String(value));
|
|
13648
|
+
return;
|
|
13649
|
+
}
|
|
13650
|
+
if (Array.isArray(value)) {
|
|
13651
|
+
for (const item of value) collectText(item, parts);
|
|
13652
|
+
return;
|
|
13653
|
+
}
|
|
13654
|
+
if (typeof value === "object") for (const [key, nested] of Object.entries(value)) {
|
|
13655
|
+
if (SYSTEM_KEYS.has(key) || key === "blockType") continue;
|
|
13656
|
+
collectText(nested, parts);
|
|
13657
|
+
}
|
|
13658
|
+
}
|
|
13659
|
+
function truncateText(value, max) {
|
|
13660
|
+
if (value.length <= max) return value;
|
|
13661
|
+
return `${value.slice(0, max - 1).trimEnd()}…`;
|
|
13662
|
+
}
|
|
13663
|
+
function asRecord(value) {
|
|
13664
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
13665
|
+
}
|
|
13666
|
+
function readString(value) {
|
|
13667
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
13668
|
+
}
|
|
13669
|
+
function deepEqual(left, right) {
|
|
13670
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
13671
|
+
}
|
|
13672
|
+
function isStructurallyEmpty(value) {
|
|
13673
|
+
if (value === null || value === void 0) return true;
|
|
13674
|
+
if (typeof value === "string") return value.trim().length === 0;
|
|
13675
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
13676
|
+
if (typeof value === "object") return Object.keys(value).length === 0;
|
|
13677
|
+
return false;
|
|
13678
|
+
}
|
|
13679
|
+
function humanizeKey(value) {
|
|
13680
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/^./, (char) => char.toUpperCase());
|
|
13681
|
+
}
|
|
13682
|
+
//#endregion
|
|
12452
13683
|
//#region src/pages/collections/edit-page.tsx
|
|
12453
13684
|
function SortableFieldItem({ id, label, type, width, onChangeWidth }) {
|
|
12454
13685
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
@@ -12522,19 +13753,59 @@ function SortableFieldItem({ id, label, type, width, onChangeWidth }) {
|
|
|
12522
13753
|
});
|
|
12523
13754
|
}
|
|
12524
13755
|
/**
|
|
12525
|
-
* A single top-bar action
|
|
12526
|
-
*
|
|
12527
|
-
*
|
|
13756
|
+
* A single top-bar action. It stays icon-only on smaller screens and expands to
|
|
13757
|
+
* an icon + label button on desktop so action meaning is visible without relying
|
|
13758
|
+
* on tooltips.
|
|
12528
13759
|
*/
|
|
12529
13760
|
function HeaderAction({ icon: Icon, label, onClick, active, disabled, busy, title, className }) {
|
|
12530
|
-
return /* @__PURE__ */
|
|
13761
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
12531
13762
|
type: "button",
|
|
12532
13763
|
onClick,
|
|
12533
13764
|
disabled,
|
|
12534
13765
|
title: title || label,
|
|
12535
13766
|
"aria-label": title || label,
|
|
12536
|
-
className: cn("dy-flex dy-h-9 dy-w-9 dy-items-center dy-justify-center dy-rounded-lg dy-transition-all", active ? "dy-bg-muted dy-text-primary" : "dy-text-muted-foreground hover:dy-bg-muted/60 hover:dy-text-foreground", disabled && "dy-opacity-50 dy-pointer-events-none", className),
|
|
12537
|
-
children: busy ? /* @__PURE__ */ jsx("span", { className: "dy-h-4 dy-w-4 dy-animate-spin dy-rounded-full dy-border-2 dy-border-current dy-border-t-transparent" }) : /* @__PURE__ */ jsx(Icon, { className: "dy-h-4 dy-w-4" })
|
|
13767
|
+
className: cn("dy-inline-flex dy-h-9 dy-w-9 lg:dy-w-auto dy-items-center dy-justify-center lg:dy-justify-start lg:dy-gap-2 dy-rounded-lg lg:dy-px-3 dy-transition-all", active ? "dy-bg-muted dy-text-primary" : "dy-text-muted-foreground hover:dy-bg-muted/60 hover:dy-text-foreground", disabled && "dy-opacity-50 dy-pointer-events-none", className),
|
|
13768
|
+
children: [busy ? /* @__PURE__ */ jsx("span", { className: "dy-h-4 dy-w-4 dy-animate-spin dy-rounded-full dy-border-2 dy-border-current dy-border-t-transparent" }) : /* @__PURE__ */ jsx(Icon, { className: "dy-h-4 dy-w-4" }), /* @__PURE__ */ jsx("span", {
|
|
13769
|
+
className: "dy-hidden lg:dy-inline dy-text-xs dy-font-medium",
|
|
13770
|
+
children: label
|
|
13771
|
+
})]
|
|
13772
|
+
});
|
|
13773
|
+
}
|
|
13774
|
+
function DraftSaveStatusBadge({ state }) {
|
|
13775
|
+
const presentation = (() => {
|
|
13776
|
+
switch (state) {
|
|
13777
|
+
case "dirty": return {
|
|
13778
|
+
icon: Save,
|
|
13779
|
+
label: "Unsaved changes",
|
|
13780
|
+
className: "dy-text-amber-700 dy-bg-amber-50 dy-border-amber-200"
|
|
13781
|
+
};
|
|
13782
|
+
case "saving": return {
|
|
13783
|
+
icon: Loader2,
|
|
13784
|
+
label: "Saving...",
|
|
13785
|
+
className: "dy-text-sky-700 dy-bg-sky-50 dy-border-sky-200"
|
|
13786
|
+
};
|
|
13787
|
+
case "saved":
|
|
13788
|
+
case "idle": return {
|
|
13789
|
+
icon: Save,
|
|
13790
|
+
label: "Changes saved",
|
|
13791
|
+
className: "dy-text-emerald-700 dy-bg-emerald-50 dy-border-emerald-200"
|
|
13792
|
+
};
|
|
13793
|
+
case "conflict": return {
|
|
13794
|
+
icon: AlertCircle,
|
|
13795
|
+
label: "Refresh required",
|
|
13796
|
+
className: "dy-text-rose-700 dy-bg-rose-50 dy-border-rose-200"
|
|
13797
|
+
};
|
|
13798
|
+
default: return {
|
|
13799
|
+
icon: AlertCircle,
|
|
13800
|
+
label: "Save failed",
|
|
13801
|
+
className: "dy-text-rose-700 dy-bg-rose-50 dy-border-rose-200"
|
|
13802
|
+
};
|
|
13803
|
+
}
|
|
13804
|
+
})();
|
|
13805
|
+
const Icon = presentation.icon;
|
|
13806
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13807
|
+
className: cn("dy-inline-flex dy-items-center dy-gap-1.5 dy-rounded-full dy-border dy-px-2.5 dy-py-1 dy-text-[11px] dy-font-semibold", presentation.className),
|
|
13808
|
+
children: [/* @__PURE__ */ jsx(Icon, { className: cn("dy-h-3.5 dy-w-3.5", state === "saving" && "dy-animate-spin") }), /* @__PURE__ */ jsx("span", { children: presentation.label })]
|
|
12538
13809
|
});
|
|
12539
13810
|
}
|
|
12540
13811
|
/**
|
|
@@ -12595,20 +13866,6 @@ function PreviewPaneWithNav({ previewUrl, data, mode, collectionSlug, documentId
|
|
|
12595
13866
|
onFieldFocus: handleFieldFocus
|
|
12596
13867
|
});
|
|
12597
13868
|
}
|
|
12598
|
-
function getEntryTitle(entry, schema) {
|
|
12599
|
-
if (!entry) return "";
|
|
12600
|
-
const titleField = schema?.admin?.useAsTitle || "title";
|
|
12601
|
-
if (entry[titleField]) return String(entry[titleField]);
|
|
12602
|
-
for (const f of [
|
|
12603
|
-
"title",
|
|
12604
|
-
"name",
|
|
12605
|
-
"label",
|
|
12606
|
-
"heading",
|
|
12607
|
-
"email",
|
|
12608
|
-
"subject"
|
|
12609
|
-
]) if (entry[f]) return String(entry[f]);
|
|
12610
|
-
return String(entry.id);
|
|
12611
|
-
}
|
|
12612
13869
|
function EditEntryPage() {
|
|
12613
13870
|
const { slug, id } = useParams();
|
|
12614
13871
|
const [searchParams] = useSearchParams();
|
|
@@ -12620,6 +13877,11 @@ function EditEntryPage() {
|
|
|
12620
13877
|
const [mobilePreview, setMobilePreview] = useState(false);
|
|
12621
13878
|
const [isDirty, setIsDirty] = useState(false);
|
|
12622
13879
|
const [previewData, setPreviewData] = useState(null);
|
|
13880
|
+
const [workflowAutosaveState, setWorkflowAutosaveState] = useState("idle");
|
|
13881
|
+
const [workflowTransitionPending, setWorkflowTransitionPending] = useState(false);
|
|
13882
|
+
const [compareSheetOpen, setCompareSheetOpen] = useState(false);
|
|
13883
|
+
const formEngineRef = useRef(null);
|
|
13884
|
+
const draftSavePromiseRef = useRef(null);
|
|
12623
13885
|
const isEdit = !!id;
|
|
12624
13886
|
const [isConfiguringView, setIsConfiguringView] = useState(false);
|
|
12625
13887
|
const { data: schemas } = useQuery({
|
|
@@ -12702,16 +13964,6 @@ function EditEntryPage() {
|
|
|
12702
13964
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
12703
13965
|
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
12704
13966
|
}, [isDirty]);
|
|
12705
|
-
useEffect(() => {
|
|
12706
|
-
const handleSave = (e) => {
|
|
12707
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
12708
|
-
e.preventDefault();
|
|
12709
|
-
document.getElementById("dyrected-form-submit")?.click();
|
|
12710
|
-
}
|
|
12711
|
-
};
|
|
12712
|
-
window.addEventListener("keydown", handleSave);
|
|
12713
|
-
return () => window.removeEventListener("keydown", handleSave);
|
|
12714
|
-
}, []);
|
|
12715
13967
|
const schemaSlug = schema?.slug;
|
|
12716
13968
|
const schemaPreviewUrl = schema?.admin?.previewUrl;
|
|
12717
13969
|
const syncShowPreview = useState(() => (next) => {
|
|
@@ -12725,18 +13977,6 @@ function EditEntryPage() {
|
|
|
12725
13977
|
schemaPreviewUrl,
|
|
12726
13978
|
syncShowPreview
|
|
12727
13979
|
]);
|
|
12728
|
-
const hasWorkflow = !!schema?.workflow;
|
|
12729
|
-
const syncShowWorkflow = useState(() => (next) => {
|
|
12730
|
-
setActiveTab((prev) => next ? "workflow" : prev === "workflow" ? "edit" : prev);
|
|
12731
|
-
})[0];
|
|
12732
|
-
useEffect(() => {
|
|
12733
|
-
if (!hasWorkflow || schemaPreviewUrl) return;
|
|
12734
|
-
syncShowWorkflow(true);
|
|
12735
|
-
}, [
|
|
12736
|
-
hasWorkflow,
|
|
12737
|
-
schemaPreviewUrl,
|
|
12738
|
-
syncShowWorkflow
|
|
12739
|
-
]);
|
|
12740
13980
|
const { data: entry, isLoading: isEntryLoading } = useQuery({
|
|
12741
13981
|
queryKey: [
|
|
12742
13982
|
"entry",
|
|
@@ -12761,12 +14001,10 @@ function EditEntryPage() {
|
|
|
12761
14001
|
slug,
|
|
12762
14002
|
debouncedSearchQuery
|
|
12763
14003
|
],
|
|
12764
|
-
queryFn:
|
|
12765
|
-
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
return qb.exec();
|
|
12769
|
-
},
|
|
14004
|
+
queryFn: () => client.collection(slug).find({
|
|
14005
|
+
limit: 50,
|
|
14006
|
+
search: debouncedSearchQuery || void 0
|
|
14007
|
+
}).exec(),
|
|
12770
14008
|
enabled: !!client && !!slug && isEdit && switcherOpen
|
|
12771
14009
|
});
|
|
12772
14010
|
const syncPreviewData = useState(() => (next) => {
|
|
@@ -12780,7 +14018,7 @@ function EditEntryPage() {
|
|
|
12780
14018
|
const isSelf = !!user && !!id && (user.id === id || user.sub === id);
|
|
12781
14019
|
const passwordChangeMode = schema?.auth ? isSelf ? "self" : isAdminUser ? "admin" : null : null;
|
|
12782
14020
|
const saveMutation = useMutation({
|
|
12783
|
-
mutationFn: async (data) => {
|
|
14021
|
+
mutationFn: async ({ data }) => {
|
|
12784
14022
|
const { oldPassword, newPassword, confirmPassword, ...rest } = data;
|
|
12785
14023
|
const results = {};
|
|
12786
14024
|
if (isEdit) {
|
|
@@ -12796,7 +14034,7 @@ function EditEntryPage() {
|
|
|
12796
14034
|
} else results.doc = await client.collection(slug).create(rest);
|
|
12797
14035
|
return results;
|
|
12798
14036
|
},
|
|
12799
|
-
onSuccess: (results) => {
|
|
14037
|
+
onSuccess: (results, variables) => {
|
|
12800
14038
|
setIsDirty(false);
|
|
12801
14039
|
queryClient.invalidateQueries({ queryKey: ["collection", slug] });
|
|
12802
14040
|
if (isEdit) queryClient.invalidateQueries({ queryKey: [
|
|
@@ -12804,45 +14042,26 @@ function EditEntryPage() {
|
|
|
12804
14042
|
slug,
|
|
12805
14043
|
id
|
|
12806
14044
|
] });
|
|
12807
|
-
toast.success(isEdit ? "Entry updated successfully" : "Entry created successfully", { description: `${schema?.labels?.singular || schema?.slug} has been saved.` });
|
|
12808
|
-
if (results.passwordChanged) toast.success("Password changed successfully");
|
|
14045
|
+
if (variables.mode === "manual") toast.success(isEdit ? "Entry updated successfully" : "Entry created successfully", { description: `${schema?.labels?.singular || schema?.slug} has been saved.` });
|
|
14046
|
+
if (results.passwordChanged && variables.mode === "manual") toast.success("Password changed successfully");
|
|
12809
14047
|
const doc = results.doc;
|
|
12810
14048
|
if (!isEdit && doc?.id) navigate(`/collections/${slug}/edit/${doc.id}`, { replace: true });
|
|
12811
14049
|
},
|
|
12812
|
-
onError: (error) => {
|
|
14050
|
+
onError: (error, variables) => {
|
|
14051
|
+
if (variables.mode === "autosave") return;
|
|
12813
14052
|
toast.error("Failed to save entry", { description: error.message || "An unexpected error occurred." });
|
|
12814
14053
|
}
|
|
12815
14054
|
});
|
|
12816
|
-
const
|
|
12817
|
-
const handleSendResetLink = async () => {
|
|
12818
|
-
if (!entry?.email) return;
|
|
12819
|
-
setSendingReset(true);
|
|
12820
|
-
try {
|
|
12821
|
-
await client.collection(slug).sendResetLink(entry.email);
|
|
12822
|
-
toast.success("Reset link sent", { description: `A password reset email has been sent to ${entry.email}.` });
|
|
12823
|
-
} catch (err) {
|
|
12824
|
-
toast.error("Failed to send reset link", { description: err instanceof Error ? err.message : "An unexpected error occurred." });
|
|
12825
|
-
} finally {
|
|
12826
|
-
setSendingReset(false);
|
|
12827
|
-
}
|
|
12828
|
-
};
|
|
12829
|
-
if (!schema) return /* @__PURE__ */ jsx("div", { children: "Collection not found" });
|
|
12830
|
-
if (isEdit && isEntryLoading) return /* @__PURE__ */ jsx("div", { children: "Loading entry..." });
|
|
12831
|
-
const workflowConfig = schema.workflow ?? null;
|
|
14055
|
+
const workflowConfig = schema?.workflow ?? null;
|
|
12832
14056
|
const workflowMeta = isEdit && entry ? entry._workflow ?? null : null;
|
|
12833
14057
|
const hasStatus = schema?.fields.some((f) => f.name === "status");
|
|
12834
|
-
const
|
|
12835
|
-
const
|
|
12836
|
-
const
|
|
12837
|
-
const statusBadgeLabel = workflowState ? workflowState.label || workflowState.name : currentStatus === "published" ? "Published" : currentStatus === "draft" ? "Draft" : String(currentStatus);
|
|
12838
|
-
const workflowBadgePresentation = workflowState ? getWorkflowBadgePresentation(workflowState.color) : {
|
|
12839
|
-
className: isPublished ? WORKFLOW_BADGE_COLORS.success : WORKFLOW_BADGE_COLORS.warning,
|
|
12840
|
-
style: void 0
|
|
12841
|
-
};
|
|
14058
|
+
const workflowState = resolveWorkflowState(workflowConfig, workflowMeta);
|
|
14059
|
+
const publishingStatus = resolvePublishingStatus(schema, entry ?? {});
|
|
14060
|
+
const workflowStagePresentation = workflowState && publishingStatus?.workflowStateLabel ? getWorkflowBadgePresentation(workflowState.color) : null;
|
|
12842
14061
|
const showStatusBadge = workflowConfig ? !!workflowMeta : hasStatus;
|
|
12843
14062
|
const siteUrl = getSiteUrl(schemas?.admin?.siteUrl);
|
|
12844
|
-
const previewUrl = resolvePreviewUrl(schema
|
|
12845
|
-
const readAccess = schema
|
|
14063
|
+
const previewUrl = resolvePreviewUrl(schema?.admin?.previewUrl, previewData || entry, siteUrl);
|
|
14064
|
+
const readAccess = (schema?.access)?.read;
|
|
12846
14065
|
let canRead = true;
|
|
12847
14066
|
if (readAccess === false) canRead = false;
|
|
12848
14067
|
else if (typeof readAccess === "string") try {
|
|
@@ -12853,27 +14072,7 @@ function EditEntryPage() {
|
|
|
12853
14072
|
} catch (e) {
|
|
12854
14073
|
console.warn("Read access eval failed:", e);
|
|
12855
14074
|
}
|
|
12856
|
-
|
|
12857
|
-
className: "dy-flex dy-items-center dy-justify-center dy-h-[calc(100vh-200px)]",
|
|
12858
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
12859
|
-
className: "dy-text-center dy-space-y-3",
|
|
12860
|
-
children: [
|
|
12861
|
-
/* @__PURE__ */ jsx("div", {
|
|
12862
|
-
className: "dy-p-3 dy-bg-destructive/10 dy-text-destructive dy-rounded-full dy-w-12 dy-h-12 dy-mx-auto dy-flex dy-items-center dy-justify-center",
|
|
12863
|
-
children: /* @__PURE__ */ jsx(Archive, { className: "dy-h-6 dy-w-6" })
|
|
12864
|
-
}),
|
|
12865
|
-
/* @__PURE__ */ jsx("h3", {
|
|
12866
|
-
className: "dy-text-lg dy-font-bold",
|
|
12867
|
-
children: "Access Denied"
|
|
12868
|
-
}),
|
|
12869
|
-
/* @__PURE__ */ jsx("p", {
|
|
12870
|
-
className: "dy-text-sm dy-text-muted-foreground",
|
|
12871
|
-
children: "You do not have permission to view this entry."
|
|
12872
|
-
})
|
|
12873
|
-
]
|
|
12874
|
-
})
|
|
12875
|
-
});
|
|
12876
|
-
const createAccess = schema.access?.create;
|
|
14075
|
+
const createAccess = (schema?.access)?.create;
|
|
12877
14076
|
let canCreate = true;
|
|
12878
14077
|
if (createAccess === false) canCreate = false;
|
|
12879
14078
|
else if (typeof createAccess === "string") try {
|
|
@@ -12881,7 +14080,7 @@ function EditEntryPage() {
|
|
|
12881
14080
|
} catch (e) {
|
|
12882
14081
|
console.warn("Create access eval failed:", e);
|
|
12883
14082
|
}
|
|
12884
|
-
const updateAccess = schema
|
|
14083
|
+
const updateAccess = (schema?.access)?.update;
|
|
12885
14084
|
let canUpdate = true;
|
|
12886
14085
|
if (updateAccess === false) canUpdate = false;
|
|
12887
14086
|
else if (typeof updateAccess === "string") try {
|
|
@@ -12892,7 +14091,7 @@ function EditEntryPage() {
|
|
|
12892
14091
|
} catch (e) {
|
|
12893
14092
|
console.warn("Update access eval failed:", e);
|
|
12894
14093
|
}
|
|
12895
|
-
const auditAccess = schema
|
|
14094
|
+
const auditAccess = (schema?.access)?.readAudit ?? readAccess;
|
|
12896
14095
|
let canReadAudit = true;
|
|
12897
14096
|
if (auditAccess === false) canReadAudit = false;
|
|
12898
14097
|
else if (typeof auditAccess === "string") try {
|
|
@@ -12904,11 +14103,158 @@ function EditEntryPage() {
|
|
|
12904
14103
|
canReadAudit = false;
|
|
12905
14104
|
}
|
|
12906
14105
|
const workflowAvailable = !!(workflowConfig && isEdit && workflowMeta);
|
|
14106
|
+
const liveSnapshot = entry?.__published && typeof entry.__published === "object" ? entry.__published : null;
|
|
14107
|
+
const compareToLiveEnabled = workflowAvailable && !!liveSnapshot;
|
|
14108
|
+
const compareToLiveReason = !workflowAvailable ? "Workflow is not enabled for this entry." : liveSnapshot ? null : "No live snapshot exists yet. Publish this entry once to compare future draft changes.";
|
|
14109
|
+
const draftLiveComparison = useMemo(() => buildDraftLiveComparison({
|
|
14110
|
+
fields: orderedFields,
|
|
14111
|
+
draft: previewData || entry || null,
|
|
14112
|
+
live: liveSnapshot
|
|
14113
|
+
}), [
|
|
14114
|
+
entry,
|
|
14115
|
+
liveSnapshot,
|
|
14116
|
+
orderedFields,
|
|
14117
|
+
previewData
|
|
14118
|
+
]);
|
|
14119
|
+
const workflowAutosaveConfig = resolveWorkflowAutosaveSettings(schema);
|
|
14120
|
+
const workflowAutosaveEnabled = Boolean(isEdit && workflowAutosaveConfig.enabled && (schema?.workflow || schema?.drafts) && canUpdate);
|
|
14121
|
+
const documentLabel = useMemo(() => entry ? resolveDocumentTitle({
|
|
14122
|
+
entry,
|
|
14123
|
+
collection: schema,
|
|
14124
|
+
collections: schemas?.collections
|
|
14125
|
+
}) : void 0, [
|
|
14126
|
+
entry,
|
|
14127
|
+
schema,
|
|
14128
|
+
schemas?.collections
|
|
14129
|
+
]);
|
|
14130
|
+
const persistDraft = useCallback((data, mode) => {
|
|
14131
|
+
const promise = saveMutation.mutateAsync({
|
|
14132
|
+
data,
|
|
14133
|
+
mode
|
|
14134
|
+
});
|
|
14135
|
+
draftSavePromiseRef.current = promise.finally(() => {
|
|
14136
|
+
if (draftSavePromiseRef.current === promise) draftSavePromiseRef.current = null;
|
|
14137
|
+
});
|
|
14138
|
+
return promise;
|
|
14139
|
+
}, [saveMutation]);
|
|
14140
|
+
const handleManualSave = useCallback((data) => {
|
|
14141
|
+
return persistDraft(data, "manual");
|
|
14142
|
+
}, [persistDraft]);
|
|
14143
|
+
const handleWorkflowAutosave = useCallback(async (data) => {
|
|
14144
|
+
await persistDraft(data, "autosave");
|
|
14145
|
+
}, [persistDraft]);
|
|
14146
|
+
const autosaveConfig = useMemo(() => {
|
|
14147
|
+
if (!workflowAutosaveEnabled) return void 0;
|
|
14148
|
+
return {
|
|
14149
|
+
enabled: true,
|
|
14150
|
+
delayMs: workflowAutosaveConfig.delayMs,
|
|
14151
|
+
onSave: handleWorkflowAutosave,
|
|
14152
|
+
onStatusChange: setWorkflowAutosaveState
|
|
14153
|
+
};
|
|
14154
|
+
}, [
|
|
14155
|
+
handleWorkflowAutosave,
|
|
14156
|
+
workflowAutosaveConfig.delayMs,
|
|
14157
|
+
workflowAutosaveEnabled
|
|
14158
|
+
]);
|
|
14159
|
+
const showWorkflowAutosaveStatus = workflowAutosaveEnabled && activeTab === "edit";
|
|
14160
|
+
const showManualSaveChrome = !workflowAutosaveEnabled;
|
|
12907
14161
|
const showLivePreview = activeTab === "edit" && showPreview && !!previewUrl;
|
|
14162
|
+
const resolveTransitionContextFromDoc = useCallback((doc) => {
|
|
14163
|
+
const resolvedDocumentId = String(doc?.id ?? id ?? "");
|
|
14164
|
+
const resolvedWorkflowMeta = doc?._workflow ?? workflowMeta ?? null;
|
|
14165
|
+
const resolvedDocumentLabel = doc ? resolveDocumentTitle({
|
|
14166
|
+
entry: doc,
|
|
14167
|
+
collection: schema,
|
|
14168
|
+
collections: schemas?.collections
|
|
14169
|
+
}) : documentLabel;
|
|
14170
|
+
return {
|
|
14171
|
+
documentIds: resolvedDocumentId ? [resolvedDocumentId] : [],
|
|
14172
|
+
documentLabels: resolvedDocumentId ? { [resolvedDocumentId]: resolvedDocumentLabel } : void 0,
|
|
14173
|
+
expectedRevisions: resolvedDocumentId && typeof resolvedWorkflowMeta?.revision === "number" ? { [resolvedDocumentId]: resolvedWorkflowMeta.revision } : void 0,
|
|
14174
|
+
invalidateQueryKeys: resolvedDocumentId ? [
|
|
14175
|
+
[
|
|
14176
|
+
"entry",
|
|
14177
|
+
slug,
|
|
14178
|
+
resolvedDocumentId
|
|
14179
|
+
],
|
|
14180
|
+
["collection", slug],
|
|
14181
|
+
[
|
|
14182
|
+
"workflow-history",
|
|
14183
|
+
slug,
|
|
14184
|
+
resolvedDocumentId
|
|
14185
|
+
]
|
|
14186
|
+
] : [["collection", slug]]
|
|
14187
|
+
};
|
|
14188
|
+
}, [
|
|
14189
|
+
documentLabel,
|
|
14190
|
+
id,
|
|
14191
|
+
schema,
|
|
14192
|
+
schemas?.collections,
|
|
14193
|
+
slug,
|
|
14194
|
+
workflowMeta
|
|
14195
|
+
]);
|
|
14196
|
+
const saveCurrentDraftNow = useCallback(async (mode = "manual") => {
|
|
14197
|
+
if (draftSavePromiseRef.current) await draftSavePromiseRef.current;
|
|
14198
|
+
if (!formEngineRef.current) throw new Error("The editor is not ready yet.");
|
|
14199
|
+
const result = await formEngineRef.current.submitCurrentDraft();
|
|
14200
|
+
const savedDoc = result?.doc ?? null;
|
|
14201
|
+
return {
|
|
14202
|
+
result,
|
|
14203
|
+
context: resolveTransitionContextFromDoc(savedDoc),
|
|
14204
|
+
mode
|
|
14205
|
+
};
|
|
14206
|
+
}, [resolveTransitionContextFromDoc]);
|
|
14207
|
+
const prepareWorkflowTransition = useCallback(async (_transition) => {
|
|
14208
|
+
if (draftSavePromiseRef.current) await draftSavePromiseRef.current;
|
|
14209
|
+
if (formEngineRef.current?.isDirty()) {
|
|
14210
|
+
const { context } = await saveCurrentDraftNow("transition");
|
|
14211
|
+
return context;
|
|
14212
|
+
}
|
|
14213
|
+
return resolveTransitionContextFromDoc(entry ?? null);
|
|
14214
|
+
}, [
|
|
14215
|
+
entry,
|
|
14216
|
+
resolveTransitionContextFromDoc,
|
|
14217
|
+
saveCurrentDraftNow
|
|
14218
|
+
]);
|
|
14219
|
+
const [sendingReset, setSendingReset] = useState(false);
|
|
14220
|
+
const handleSendResetLink = async () => {
|
|
14221
|
+
if (!entry?.email) return;
|
|
14222
|
+
setSendingReset(true);
|
|
14223
|
+
try {
|
|
14224
|
+
await client.collection(slug).sendResetLink(entry.email);
|
|
14225
|
+
toast.success("Reset link sent", { description: `A password reset email has been sent to ${entry.email}.` });
|
|
14226
|
+
} catch (err) {
|
|
14227
|
+
toast.error("Failed to send reset link", { description: err instanceof Error ? err.message : "An unexpected error occurred." });
|
|
14228
|
+
} finally {
|
|
14229
|
+
setSendingReset(false);
|
|
14230
|
+
}
|
|
14231
|
+
};
|
|
14232
|
+
if (!schema) return /* @__PURE__ */ jsx("div", { children: "Collection not found" });
|
|
14233
|
+
if (isEdit && isEntryLoading) return /* @__PURE__ */ jsx("div", { children: "Loading entry..." });
|
|
14234
|
+
if (!canRead) return /* @__PURE__ */ jsx("div", {
|
|
14235
|
+
className: "dy-flex dy-items-center dy-justify-center dy-h-[calc(100vh-200px)]",
|
|
14236
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
14237
|
+
className: "dy-text-center dy-space-y-3",
|
|
14238
|
+
children: [
|
|
14239
|
+
/* @__PURE__ */ jsx("div", {
|
|
14240
|
+
className: "dy-p-3 dy-bg-destructive/10 dy-text-destructive dy-rounded-full dy-w-12 dy-h-12 dy-mx-auto dy-flex dy-items-center dy-justify-center",
|
|
14241
|
+
children: /* @__PURE__ */ jsx(Archive, { className: "dy-h-6 dy-w-6" })
|
|
14242
|
+
}),
|
|
14243
|
+
/* @__PURE__ */ jsx("h3", {
|
|
14244
|
+
className: "dy-text-lg dy-font-bold",
|
|
14245
|
+
children: "Access Denied"
|
|
14246
|
+
}),
|
|
14247
|
+
/* @__PURE__ */ jsx("p", {
|
|
14248
|
+
className: "dy-text-sm dy-text-muted-foreground",
|
|
14249
|
+
children: "You do not have permission to view this entry."
|
|
14250
|
+
})
|
|
14251
|
+
]
|
|
14252
|
+
})
|
|
14253
|
+
});
|
|
12908
14254
|
const docsToDisplay = debouncedSearchQuery ? siblingEntries?.docs || [] : siblingEntries?.docs?.filter((d) => String(d.id) !== id).slice(0, 4) || [];
|
|
12909
|
-
return /* @__PURE__ */
|
|
14255
|
+
return /* @__PURE__ */ jsxs(NestedEditorProvider, {
|
|
12910
14256
|
drillInEnabled: !!showLivePreview,
|
|
12911
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
14257
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
12912
14258
|
className: cn("dy-flex dy-flex-col dy--mt-6 dy--mb-6 dy--mx-4 lg:dy--mt-10 lg:dy--mb-10 lg:dy--mx-6", showLivePreview ? "dy-h-screen" : ""),
|
|
12913
14259
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
12914
14260
|
className: "dy-flex dy-flex-wrap dy-shrink-0 dy-items-center dy-gap-2 dy-border-b dy-border-border/50 dy-bg-background dy-px-3 dy-py-2",
|
|
@@ -12933,7 +14279,11 @@ function EditEntryPage() {
|
|
|
12933
14279
|
className: "dy-flex dy-items-center dy-gap-1 dy-text-base dy-font-serif dy-font-bold dy-tracking-tight dy-text-foreground hover:dy-bg-muted/80 dy-px-2 dy-py-1 dy-rounded-lg dy-transition-all dy-outline-none dy-min-w-0",
|
|
12934
14280
|
children: [/* @__PURE__ */ jsx("span", {
|
|
12935
14281
|
className: "dy-truncate dy-max-w-[150px] sm:dy-max-w-none dy-inline-block",
|
|
12936
|
-
children: isEdit && entry ?
|
|
14282
|
+
children: isEdit && entry ? resolveDocumentTitle({
|
|
14283
|
+
entry,
|
|
14284
|
+
collection: schema,
|
|
14285
|
+
collections: schemas?.collections
|
|
14286
|
+
}) : "..."
|
|
12937
14287
|
}), /* @__PURE__ */ jsx(ChevronDown, { className: "dy-h-4 dy-w-4 dy-text-muted-foreground/80 dy-shrink-0" })]
|
|
12938
14288
|
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
12939
14289
|
align: "start",
|
|
@@ -12957,7 +14307,11 @@ function EditEntryPage() {
|
|
|
12957
14307
|
className: "dy-cursor-pointer dy-py-2 dy-px-3 dy-rounded-lg",
|
|
12958
14308
|
children: /* @__PURE__ */ jsx("span", {
|
|
12959
14309
|
className: "dy-text-sm dy-text-foreground",
|
|
12960
|
-
children:
|
|
14310
|
+
children: resolveDocumentTitle({
|
|
14311
|
+
entry: sibling,
|
|
14312
|
+
collection: schema,
|
|
14313
|
+
collections: schemas?.collections
|
|
14314
|
+
})
|
|
12961
14315
|
})
|
|
12962
14316
|
}, sibling.id)) }) })]
|
|
12963
14317
|
})
|
|
@@ -12965,12 +14319,12 @@ function EditEntryPage() {
|
|
|
12965
14319
|
})] }) : /* @__PURE__ */ jsxs("h1", {
|
|
12966
14320
|
className: "dy-text-base dy-font-serif dy-font-bold dy-tracking-tight dy-text-foreground dy-truncate",
|
|
12967
14321
|
children: ["New ", schema?.labels?.singular || schema?.slug]
|
|
12968
|
-
}), showStatusBadge && /* @__PURE__ */ jsx(Badge, {
|
|
12969
|
-
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-
|
|
12970
|
-
style:
|
|
14322
|
+
}), showStatusBadge && /* @__PURE__ */ jsxs(Fragment, { children: [publishingStatus?.workflowStateLabel && workflowStagePresentation && /* @__PURE__ */ jsx(Badge, {
|
|
14323
|
+
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-semibold dy-tracking-wide dy-shrink-0", workflowStagePresentation.className),
|
|
14324
|
+
style: workflowStagePresentation.style,
|
|
12971
14325
|
variant: "outline",
|
|
12972
|
-
children:
|
|
12973
|
-
})]
|
|
14326
|
+
children: publishingStatus.workflowStateLabel
|
|
14327
|
+
}), showWorkflowAutosaveStatus && /* @__PURE__ */ jsx(DraftSaveStatusBadge, { state: workflowAutosaveState })] })]
|
|
12974
14328
|
}),
|
|
12975
14329
|
/* @__PURE__ */ jsxs("div", {
|
|
12976
14330
|
className: "dy-ml-auto dy-flex dy-items-center dy-gap-1",
|
|
@@ -12998,9 +14352,9 @@ function EditEntryPage() {
|
|
|
12998
14352
|
}),
|
|
12999
14353
|
workflowAvailable && /* @__PURE__ */ jsx(HeaderAction, {
|
|
13000
14354
|
icon: Workflow,
|
|
13001
|
-
label: "Workflow",
|
|
14355
|
+
label: "Workflow details",
|
|
13002
14356
|
active: activeTab === "workflow",
|
|
13003
|
-
title: activeTab === "workflow" ? "Hide workflow
|
|
14357
|
+
title: activeTab === "workflow" ? "Hide workflow details" : "Show workflow details",
|
|
13004
14358
|
onClick: () => setActiveTab((tab) => tab === "workflow" ? "edit" : "workflow")
|
|
13005
14359
|
}),
|
|
13006
14360
|
schema?.audit && isEdit && canReadAudit && /* @__PURE__ */ jsx(HeaderAction, {
|
|
@@ -13068,12 +14422,35 @@ function EditEntryPage() {
|
|
|
13068
14422
|
})]
|
|
13069
14423
|
})] })]
|
|
13070
14424
|
})] }),
|
|
13071
|
-
(isEdit ? canUpdate : canCreate) && activeTab === "edit" && /* @__PURE__ */ jsxs(
|
|
14425
|
+
(isEdit ? canUpdate : canCreate) && activeTab === "edit" && /* @__PURE__ */ jsxs(Fragment, { children: [workflowAvailable && /* @__PURE__ */ jsx(WorkflowTransitionSplitButton, {
|
|
14426
|
+
collection: slug,
|
|
14427
|
+
documentId: id,
|
|
14428
|
+
documentLabel,
|
|
14429
|
+
workflowConfig,
|
|
14430
|
+
workflowMeta,
|
|
14431
|
+
onPendingChange: setWorkflowTransitionPending,
|
|
14432
|
+
onSaveDraft: () => saveCurrentDraftNow("manual").then(() => void 0),
|
|
14433
|
+
saveDraftPending: saveMutation.isPending,
|
|
14434
|
+
prepareTransition: prepareWorkflowTransition,
|
|
14435
|
+
invalidateQueryKeys: [
|
|
14436
|
+
[
|
|
14437
|
+
"entry",
|
|
14438
|
+
slug,
|
|
14439
|
+
id
|
|
14440
|
+
],
|
|
14441
|
+
["collection", slug],
|
|
14442
|
+
[
|
|
14443
|
+
"workflow-history",
|
|
14444
|
+
slug,
|
|
14445
|
+
id
|
|
14446
|
+
]
|
|
14447
|
+
]
|
|
14448
|
+
}), showManualSaveChrome && /* @__PURE__ */ jsxs("div", {
|
|
13072
14449
|
className: "dy-hidden md:dy-flex dy-items-center",
|
|
13073
14450
|
children: [/* @__PURE__ */ jsx("div", { className: "dy-mx-1 dy-h-6 dy-w-px dy-bg-border/60" }), /* @__PURE__ */ jsx(Button, {
|
|
13074
14451
|
size: "sm",
|
|
13075
14452
|
className: "dy-h-9 dy-rounded-lg dy-px-4 dy-font-bold dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90 dy-shadow-sm dy-shrink-0",
|
|
13076
|
-
onClick: () =>
|
|
14453
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13077
14454
|
disabled: saveMutation.isPending,
|
|
13078
14455
|
title: isEdit ? "Save Changes (⌘S)" : "Create Entry (⌘S)",
|
|
13079
14456
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("span", {
|
|
@@ -13084,7 +14461,7 @@ function EditEntryPage() {
|
|
|
13084
14461
|
children: [/* @__PURE__ */ jsx(Save, { className: "dy-h-3.5 dy-w-3.5" }), isEdit ? "Save" : "Create"]
|
|
13085
14462
|
})
|
|
13086
14463
|
})]
|
|
13087
|
-
})
|
|
14464
|
+
})] })
|
|
13088
14465
|
]
|
|
13089
14466
|
})
|
|
13090
14467
|
]
|
|
@@ -13116,7 +14493,13 @@ function EditEntryPage() {
|
|
|
13116
14493
|
collection: slug,
|
|
13117
14494
|
documentId: id,
|
|
13118
14495
|
workflowMeta,
|
|
13119
|
-
workflowConfig
|
|
14496
|
+
workflowConfig,
|
|
14497
|
+
compareToLiveEnabled,
|
|
14498
|
+
compareToLiveReason,
|
|
14499
|
+
onCompareToLive: () => setCompareSheetOpen(true),
|
|
14500
|
+
onSaveDraft: () => saveCurrentDraftNow("manual").then(() => void 0),
|
|
14501
|
+
saveDraftPending: saveMutation.isPending,
|
|
14502
|
+
prepareTransition: prepareWorkflowTransition
|
|
13120
14503
|
})
|
|
13121
14504
|
}),
|
|
13122
14505
|
activeTab === "audit" && /* @__PURE__ */ jsx("div", {
|
|
@@ -13293,19 +14676,21 @@ function EditEntryPage() {
|
|
|
13293
14676
|
queryParamsDefaults[key] = value;
|
|
13294
14677
|
});
|
|
13295
14678
|
return /* @__PURE__ */ jsx(FormEngine, {
|
|
14679
|
+
ref: formEngineRef,
|
|
13296
14680
|
collection: slug,
|
|
13297
14681
|
fields: orderedFields,
|
|
13298
14682
|
defaultValues: isEdit ? entry : {
|
|
13299
14683
|
...queryParamsDefaults,
|
|
13300
14684
|
...entry
|
|
13301
14685
|
},
|
|
13302
|
-
onSubmit:
|
|
14686
|
+
onSubmit: handleManualSave,
|
|
14687
|
+
autosave: autosaveConfig,
|
|
13303
14688
|
onDataChange: (newData) => setPreviewData({
|
|
13304
14689
|
...entry,
|
|
13305
14690
|
...newData
|
|
13306
14691
|
}),
|
|
13307
14692
|
onChange: (dirty) => setIsDirty(dirty),
|
|
13308
|
-
isLoading: saveMutation.isPending || isPreferenceLoading,
|
|
14693
|
+
isLoading: saveMutation.isPending || workflowTransitionPending || isPreferenceLoading,
|
|
13309
14694
|
submitLabel: isEdit ? "Save Changes" : "Create Entry",
|
|
13310
14695
|
hideSubmit: true,
|
|
13311
14696
|
readOnly: isEdit ? !canUpdate : !canCreate,
|
|
@@ -13320,7 +14705,7 @@ function EditEntryPage() {
|
|
|
13320
14705
|
form: "dyrected-edit-form",
|
|
13321
14706
|
className: "dy-hidden"
|
|
13322
14707
|
}),
|
|
13323
|
-
(isDirty || !isEdit) && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
14708
|
+
showManualSaveChrome && (isDirty || !isEdit) && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
13324
14709
|
className: "dy-hidden md:dy-block dy-sticky dy-bottom-0 dy-left-0 dy-right-0 dy-z-20 dy-pointer-events-none",
|
|
13325
14710
|
children: /* @__PURE__ */ jsx("div", {
|
|
13326
14711
|
className: "dy-pointer-events-auto dy-mx-auto dy-max-w-2xl dy-px-4 dy-pb-4",
|
|
@@ -13332,7 +14717,7 @@ function EditEntryPage() {
|
|
|
13332
14717
|
}), /* @__PURE__ */ jsx(Button, {
|
|
13333
14718
|
size: "sm",
|
|
13334
14719
|
className: "dy-h-9 dy-px-5 dy-rounded-xl dy-font-bold dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90 dy-shadow-sm dy-shrink-0",
|
|
13335
|
-
onClick: () =>
|
|
14720
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13336
14721
|
disabled: saveMutation.isPending,
|
|
13337
14722
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("div", {
|
|
13338
14723
|
className: "dy-flex dy-items-center dy-gap-2",
|
|
@@ -13345,11 +14730,11 @@ function EditEntryPage() {
|
|
|
13345
14730
|
})
|
|
13346
14731
|
})
|
|
13347
14732
|
}),
|
|
13348
|
-
(isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
14733
|
+
showManualSaveChrome && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
13349
14734
|
className: "md:dy-hidden dy-sticky dy-bottom-0 dy-left-0 dy-right-0 dy-z-20 dy-border-t dy-border-border dy-bg-background/95 dy-backdrop-blur-sm dy-px-4 dy-py-3",
|
|
13350
14735
|
children: /* @__PURE__ */ jsx(Button, {
|
|
13351
14736
|
className: "dy-w-full dy-h-11 dy-rounded-xl dy-font-bold dy-bg-primary dy-text-primary-foreground hover:dy-bg-primary/90 dy-shadow-sm",
|
|
13352
|
-
onClick: () =>
|
|
14737
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13353
14738
|
disabled: saveMutation.isPending || !(isDirty || !isEdit),
|
|
13354
14739
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("span", {
|
|
13355
14740
|
className: "dy-flex dy-items-center dy-gap-2",
|
|
@@ -13366,7 +14751,11 @@ function EditEntryPage() {
|
|
|
13366
14751
|
})
|
|
13367
14752
|
})]
|
|
13368
14753
|
})]
|
|
13369
|
-
}, id || "new")
|
|
14754
|
+
}, id || "new"), workflowAvailable && compareToLiveEnabled && /* @__PURE__ */ jsx(DraftLiveCompareSheet, {
|
|
14755
|
+
open: compareSheetOpen,
|
|
14756
|
+
onOpenChange: setCompareSheetOpen,
|
|
14757
|
+
comparison: draftLiveComparison
|
|
14758
|
+
})]
|
|
13370
14759
|
});
|
|
13371
14760
|
}
|
|
13372
14761
|
function AuditPanel({ collection, documentId }) {
|
|
@@ -13497,44 +14886,6 @@ function AuditPanel({ collection, documentId }) {
|
|
|
13497
14886
|
});
|
|
13498
14887
|
}
|
|
13499
14888
|
//#endregion
|
|
13500
|
-
//#region src/components/ui/card.tsx
|
|
13501
|
-
var Card = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13502
|
-
ref,
|
|
13503
|
-
className: cn("dy-rounded-lg dy-border dy-bg-card dy-text-card-foreground dy-shadow-sm", className),
|
|
13504
|
-
...props
|
|
13505
|
-
}));
|
|
13506
|
-
Card.displayName = "Card";
|
|
13507
|
-
var CardHeader = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13508
|
-
ref,
|
|
13509
|
-
className: cn("dy-flex dy-flex-col dy-space-y-1.5 dy-p-6", className),
|
|
13510
|
-
...props
|
|
13511
|
-
}));
|
|
13512
|
-
CardHeader.displayName = "CardHeader";
|
|
13513
|
-
var CardTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13514
|
-
ref,
|
|
13515
|
-
className: cn("dy-text-2xl dy-font-semibold dy-leading-none dy-tracking-tight", className),
|
|
13516
|
-
...props
|
|
13517
|
-
}));
|
|
13518
|
-
CardTitle.displayName = "CardTitle";
|
|
13519
|
-
var CardDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13520
|
-
ref,
|
|
13521
|
-
className: cn("dy-text-sm dy-text-muted-foreground", className),
|
|
13522
|
-
...props
|
|
13523
|
-
}));
|
|
13524
|
-
CardDescription.displayName = "CardDescription";
|
|
13525
|
-
var CardContent = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13526
|
-
ref,
|
|
13527
|
-
className: cn("dy-p-6 dy-pt-0", className),
|
|
13528
|
-
...props
|
|
13529
|
-
}));
|
|
13530
|
-
CardContent.displayName = "CardContent";
|
|
13531
|
-
var CardFooter = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13532
|
-
ref,
|
|
13533
|
-
className: cn("dy-flex dy-items-center dy-p-6 dy-pt-0", className),
|
|
13534
|
-
...props
|
|
13535
|
-
}));
|
|
13536
|
-
CardFooter.displayName = "CardFooter";
|
|
13537
|
-
//#endregion
|
|
13538
14889
|
//#region src/components/ui/aspect-ratio.tsx
|
|
13539
14890
|
var AspectRatio = AspectRatioPrimitive.Root;
|
|
13540
14891
|
//#endregion
|
|
@@ -14814,7 +16165,7 @@ function SetupPromptUI({ config }) {
|
|
|
14814
16165
|
setCopied(true);
|
|
14815
16166
|
window.setTimeout(() => setCopied(false), 1800);
|
|
14816
16167
|
}
|
|
14817
|
-
const currentVersion = "2.
|
|
16168
|
+
const currentVersion = "2.6.0";
|
|
14818
16169
|
const [latestVersion, setLatestVersion] = useState(() => {
|
|
14819
16170
|
if (typeof window === "undefined") return null;
|
|
14820
16171
|
return localStorage.getItem("dyrected_latest_release");
|