@dyrected/admin 2.6.0 → 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/form-engine.d.ts +13 -2
- 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 +1501 -332
- 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 +4 -4
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";
|
|
@@ -1045,7 +1045,7 @@ function isNewerVersion(latest, current) {
|
|
|
1045
1045
|
return false;
|
|
1046
1046
|
}
|
|
1047
1047
|
function useUpdateCheck() {
|
|
1048
|
-
const currentVersion = "2.
|
|
1048
|
+
const currentVersion = "2.6.0";
|
|
1049
1049
|
const [updateInfo, setUpdateInfo] = useState(() => {
|
|
1050
1050
|
if (typeof window === "undefined") return null;
|
|
1051
1051
|
const cacheKey = "dyrected_latest_release";
|
|
@@ -1271,7 +1271,7 @@ function getStatusLabel(doc) {
|
|
|
1271
1271
|
}
|
|
1272
1272
|
function Dashboard() {
|
|
1273
1273
|
const { client, components, user } = useDyrected();
|
|
1274
|
-
const currentVersion = "2.
|
|
1274
|
+
const currentVersion = "2.6.0";
|
|
1275
1275
|
const [latestVersion, setLatestVersion] = useState(() => {
|
|
1276
1276
|
if (typeof window === "undefined") return null;
|
|
1277
1277
|
return localStorage.getItem("dyrected_latest_release");
|
|
@@ -9147,7 +9147,7 @@ function ArrayFieldRenderer({ schema, basePath, control, renderField }) {
|
|
|
9147
9147
|
const { schemas } = useDyrected();
|
|
9148
9148
|
const { drillInEnabled, drillInto, reconcileAfterMutation, activePath, registerFieldArray, unregisterFieldArray } = useNestedEditor();
|
|
9149
9149
|
const [isBulkOpen, setIsBulkOpen] = React$1.useState(false);
|
|
9150
|
-
const [expandedIds, setExpandedIds] = React$1.useState(
|
|
9150
|
+
const [expandedIds, setExpandedIds] = React$1.useState(() => Object.fromEntries(fields.map((field) => [field.id, true])));
|
|
9151
9151
|
const watchedItems = useWatch({
|
|
9152
9152
|
control,
|
|
9153
9153
|
name: basePath
|
|
@@ -9234,7 +9234,7 @@ function ArrayFieldRenderer({ schema, basePath, control, renderField }) {
|
|
|
9234
9234
|
});
|
|
9235
9235
|
nextIds.forEach((id) => {
|
|
9236
9236
|
if (next[id] === void 0) {
|
|
9237
|
-
next[id] =
|
|
9237
|
+
next[id] = true;
|
|
9238
9238
|
changed = true;
|
|
9239
9239
|
}
|
|
9240
9240
|
});
|
|
@@ -9729,10 +9729,29 @@ function FormFieldRendererInner({ schema, basePath, control, collection, canUpda
|
|
|
9729
9729
|
});
|
|
9730
9730
|
}
|
|
9731
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
|
|
9732
9751
|
//#region src/components/forms/form-engine.tsx
|
|
9733
9752
|
/** Query-string key holding the active form tab (replace-navigated). */
|
|
9734
9753
|
var TAB_PARAM = "tab";
|
|
9735
|
-
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) {
|
|
9736
9755
|
const { activePath, navigateToPath, getStableId } = useNestedEditor();
|
|
9737
9756
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
9738
9757
|
const isDrilledIn = activePath.length > 0;
|
|
@@ -9780,6 +9799,12 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9780
9799
|
});
|
|
9781
9800
|
const { isDirty } = form.formState;
|
|
9782
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]);
|
|
9783
9808
|
useEffect(() => {
|
|
9784
9809
|
if (flatErrors.length > 0 && form.formState.submitCount > 0) console.warn("[Validation] Submission failed with errors:", form.formState.errors);
|
|
9785
9810
|
}, [
|
|
@@ -9790,16 +9815,71 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9790
9815
|
useEffect(() => {
|
|
9791
9816
|
onChange?.(isDirty);
|
|
9792
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
|
+
]);
|
|
9793
9833
|
const watchedValues = useWatch({ control: form.control });
|
|
9794
|
-
const
|
|
9834
|
+
const persistFormData = useCallback(async (data, persist, options) => {
|
|
9795
9835
|
const draftKey = `dyrected_draft:${collection}:${defaultValues?.id || "global"}`;
|
|
9796
|
-
|
|
9797
|
-
|
|
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
|
+
}
|
|
9798
9847
|
}, [
|
|
9799
9848
|
collection,
|
|
9800
9849
|
defaultValues,
|
|
9801
|
-
|
|
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
|
|
9802
9878
|
]);
|
|
9879
|
+
React.useImperativeHandle(ref, () => ({
|
|
9880
|
+
submitCurrentDraft,
|
|
9881
|
+
isDirty: () => form.formState.isDirty
|
|
9882
|
+
}), [form.formState.isDirty, submitCurrentDraft]);
|
|
9803
9883
|
useEffect(() => {
|
|
9804
9884
|
if (readOnly || isLoading) return;
|
|
9805
9885
|
const handleKeyDown = (e) => {
|
|
@@ -9868,6 +9948,24 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
9868
9948
|
collection,
|
|
9869
9949
|
defaultValues
|
|
9870
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
|
+
]);
|
|
9871
9969
|
useEffect(() => {
|
|
9872
9970
|
if (onDataChange) {
|
|
9873
9971
|
const handler = setTimeout(() => {
|
|
@@ -10213,10 +10311,17 @@ function FormEngineInner({ collection, fields, defaultValues = {}, onSubmit, onC
|
|
|
10213
10311
|
})
|
|
10214
10312
|
})] });
|
|
10215
10313
|
}
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
|
|
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
|
+
});
|
|
10220
10325
|
//#endregion
|
|
10221
10326
|
//#region src/components/forms/fields/relationship-picker.tsx
|
|
10222
10327
|
var PAGE_SIZE = 50;
|
|
@@ -11090,6 +11195,449 @@ function SpreadsheetEditor({ slug, schema, data, onSave, isSaving }) {
|
|
|
11090
11195
|
});
|
|
11091
11196
|
}
|
|
11092
11197
|
//#endregion
|
|
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;
|
|
11202
|
+
}
|
|
11203
|
+
function resolveWorkflowStateFromDocument(workflowConfig, item) {
|
|
11204
|
+
return resolveWorkflowState(workflowConfig, item._workflow ?? null);
|
|
11205
|
+
}
|
|
11206
|
+
function resolvePublishingStatus(schema, item) {
|
|
11207
|
+
const hasPublishingState = !!(schema?.workflow || schema?.drafts);
|
|
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
|
+
}
|
|
11229
|
+
if (!hasPublishingState) return null;
|
|
11230
|
+
const plainStatus = item.status;
|
|
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
|
+
};
|
|
11241
|
+
return null;
|
|
11242
|
+
}
|
|
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));
|
|
11247
|
+
}
|
|
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();
|
|
11342
|
+
const queryClient = useQueryClient();
|
|
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 });
|
|
11395
|
+
}
|
|
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
|
|
11093
11641
|
//#region src/pages/collections/list-page.tsx
|
|
11094
11642
|
function SortableColumnItem({ id, label, visible, onToggleVisible }) {
|
|
11095
11643
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
@@ -11119,24 +11667,74 @@ function SortableColumnItem({ id, label, visible, onToggleVisible }) {
|
|
|
11119
11667
|
]
|
|
11120
11668
|
});
|
|
11121
11669
|
}
|
|
11122
|
-
function
|
|
11123
|
-
const
|
|
11124
|
-
const
|
|
11125
|
-
const
|
|
11126
|
-
const
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
if (
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
const
|
|
11136
|
-
const
|
|
11137
|
-
|
|
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
|
+
});
|
|
11138
11733
|
}
|
|
11139
11734
|
function CollectionListPage({ slug }) {
|
|
11735
|
+
return /* @__PURE__ */ jsx(CollectionListPageContent, { slug }, slug);
|
|
11736
|
+
}
|
|
11737
|
+
function CollectionListPageContent({ slug }) {
|
|
11140
11738
|
const { client, components, user } = useDyrected();
|
|
11141
11739
|
const queryClient = useQueryClient();
|
|
11142
11740
|
const [page, setPage] = React$1.useState(1);
|
|
@@ -11162,12 +11760,6 @@ function CollectionListPage({ slug }) {
|
|
|
11162
11760
|
}, { replace: true });
|
|
11163
11761
|
setPage(1);
|
|
11164
11762
|
}, [setSearchParams]);
|
|
11165
|
-
const [prevSlug, setPrevSlug] = React$1.useState(slug);
|
|
11166
|
-
if (prevSlug !== slug) {
|
|
11167
|
-
setPrevSlug(slug);
|
|
11168
|
-
setPage(1);
|
|
11169
|
-
setRowSelection({});
|
|
11170
|
-
}
|
|
11171
11763
|
const { data: schemas } = useQuery({
|
|
11172
11764
|
queryKey: ["schemas"],
|
|
11173
11765
|
queryFn: () => client.getSchemas(),
|
|
@@ -11475,6 +12067,10 @@ function CollectionListPage({ slug }) {
|
|
|
11475
12067
|
}
|
|
11476
12068
|
if (window.confirm(`Delete ${cleanIds.length} entries? This cannot be undone.`)) bulkDeleteMutation.mutate(cleanIds);
|
|
11477
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]);
|
|
11478
12074
|
const columns = React$1.useMemo(() => {
|
|
11479
12075
|
if (!schema) return [];
|
|
11480
12076
|
const allDisplayFields = schema.fields.filter((f) => f.name && f.name !== "password" && !f.admin?.hidden && f.type !== "row" && f.type !== "join");
|
|
@@ -11632,21 +12228,10 @@ function CollectionListPage({ slug }) {
|
|
|
11632
12228
|
header: "Status",
|
|
11633
12229
|
enableHiding: false,
|
|
11634
12230
|
cell: ({ row }) => {
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
});
|
|
11640
|
-
const workflowState = resolveWorkflowState(schema, row.original);
|
|
11641
|
-
const badgePresentation = workflowState ? getWorkflowBadgePresentation(workflowState.color) : {
|
|
11642
|
-
className: status === "Published" ? WORKFLOW_BADGE_COLORS.success : WORKFLOW_BADGE_COLORS.warning,
|
|
11643
|
-
style: void 0
|
|
11644
|
-
};
|
|
11645
|
-
return /* @__PURE__ */ jsx(Badge, {
|
|
11646
|
-
variant: "outline",
|
|
11647
|
-
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),
|
|
11648
|
-
style: badgePresentation.style,
|
|
11649
|
-
children: status
|
|
12231
|
+
return /* @__PURE__ */ jsx(WorkflowStatusCell, {
|
|
12232
|
+
schema,
|
|
12233
|
+
slug,
|
|
12234
|
+
item: row.original
|
|
11650
12235
|
});
|
|
11651
12236
|
}
|
|
11652
12237
|
});
|
|
@@ -12060,48 +12645,80 @@ function CollectionListPage({ slug }) {
|
|
|
12060
12645
|
}
|
|
12061
12646
|
return canDelete;
|
|
12062
12647
|
});
|
|
12063
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
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
|
+
] });
|
|
12705
|
+
}
|
|
12706
|
+
}, slug),
|
|
12707
|
+
/* @__PURE__ */ jsx(AdminComponentSlot, {
|
|
12708
|
+
slot: "afterListTable",
|
|
12709
|
+
componentKeys: collectionSlots?.afterListTable,
|
|
12710
|
+
registry: components?.collectionList,
|
|
12711
|
+
componentProps: collectionComponentProps
|
|
12712
|
+
}),
|
|
12713
|
+
/* @__PURE__ */ jsx(Pagination, {
|
|
12714
|
+
page,
|
|
12715
|
+
totalPages,
|
|
12716
|
+
total: response?.total,
|
|
12717
|
+
hasPrevPage,
|
|
12718
|
+
hasNextPage,
|
|
12719
|
+
onPageChange: setPage
|
|
12720
|
+
}),
|
|
12721
|
+
/* @__PURE__ */ jsx(AdminComponentSlot, {
|
|
12105
12722
|
slot: "afterList",
|
|
12106
12723
|
componentKeys: collectionSlots?.afterList,
|
|
12107
12724
|
registry: components?.collectionList,
|
|
@@ -12358,55 +12975,11 @@ function StateIcon({ color }) {
|
|
|
12358
12975
|
if (color === "info") return /* @__PURE__ */ jsx(AlertCircle, { className: "dy-h-3.5 dy-w-3.5" });
|
|
12359
12976
|
return /* @__PURE__ */ jsx(Clock, { className: "dy-h-3.5 dy-w-3.5" });
|
|
12360
12977
|
}
|
|
12361
|
-
function
|
|
12362
|
-
const [comment, setComment] = useState("");
|
|
12363
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
12364
|
-
className: "dy-mt-3 dy-space-y-2",
|
|
12365
|
-
children: [
|
|
12366
|
-
/* @__PURE__ */ jsxs("label", {
|
|
12367
|
-
className: "dy-text-xs dy-font-medium dy-text-muted-foreground",
|
|
12368
|
-
children: ["Comment ", /* @__PURE__ */ jsx("span", {
|
|
12369
|
-
className: "dy-text-destructive",
|
|
12370
|
-
children: "*"
|
|
12371
|
-
})]
|
|
12372
|
-
}),
|
|
12373
|
-
/* @__PURE__ */ jsx("textarea", {
|
|
12374
|
-
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",
|
|
12375
|
-
rows: 3,
|
|
12376
|
-
placeholder: `Required for "${transitionLabel}"…`,
|
|
12377
|
-
value: comment,
|
|
12378
|
-
onChange: (e) => setComment(e.target.value),
|
|
12379
|
-
autoFocus: true
|
|
12380
|
-
}),
|
|
12381
|
-
/* @__PURE__ */ jsxs("div", {
|
|
12382
|
-
className: "dy-flex dy-gap-2",
|
|
12383
|
-
children: [/* @__PURE__ */ jsx(Button, {
|
|
12384
|
-
size: "sm",
|
|
12385
|
-
className: "dy-h-8 dy-px-3 dy-rounded-lg dy-text-xs dy-font-semibold",
|
|
12386
|
-
disabled: !comment.trim() || isPending,
|
|
12387
|
-
onClick: () => onConfirm(comment.trim()),
|
|
12388
|
-
children: isPending ? /* @__PURE__ */ jsx(Loader2, { className: "dy-h-3 dy-w-3 dy-animate-spin" }) : transitionLabel
|
|
12389
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
12390
|
-
size: "sm",
|
|
12391
|
-
variant: "ghost",
|
|
12392
|
-
className: "dy-h-8 dy-px-3 dy-rounded-lg dy-text-xs",
|
|
12393
|
-
onClick: onCancel,
|
|
12394
|
-
disabled: isPending,
|
|
12395
|
-
children: "Cancel"
|
|
12396
|
-
})]
|
|
12397
|
-
})
|
|
12398
|
-
]
|
|
12399
|
-
});
|
|
12400
|
-
}
|
|
12401
|
-
function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig }) {
|
|
12978
|
+
function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig, onCompareToLive, compareToLiveEnabled = false, compareToLiveReason = null, onSaveDraft, saveDraftPending, prepareTransition }) {
|
|
12402
12979
|
const { client } = useDyrected();
|
|
12403
|
-
const queryClient = useQueryClient();
|
|
12404
12980
|
const [showHistory, setShowHistory] = useState(false);
|
|
12405
|
-
const [pendingComment, setPendingComment] = useState(null);
|
|
12406
12981
|
const currentState = workflowConfig.states.find((s) => s.name === workflowMeta.state);
|
|
12407
12982
|
const colors = stateColors(currentState?.color);
|
|
12408
|
-
const available = workflowMeta.availableTransitions ?? [];
|
|
12409
|
-
const availableTransitions = workflowConfig.transitions.filter((t) => available.includes(t.name));
|
|
12410
12983
|
const wfClient = client;
|
|
12411
12984
|
const { data: historyResult, isLoading: historyLoading } = useQuery({
|
|
12412
12985
|
queryKey: [
|
|
@@ -12418,34 +12991,6 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12418
12991
|
enabled: showHistory && !!client
|
|
12419
12992
|
});
|
|
12420
12993
|
const history = historyResult?.docs ?? [];
|
|
12421
|
-
const transitionMutation = useMutation({
|
|
12422
|
-
mutationFn: ({ name, comment }) => wfClient.transition(collection, documentId, name, {
|
|
12423
|
-
expectedRevision: workflowMeta.revision,
|
|
12424
|
-
comment
|
|
12425
|
-
}),
|
|
12426
|
-
onSuccess: () => {
|
|
12427
|
-
setPendingComment(null);
|
|
12428
|
-
queryClient.invalidateQueries({ queryKey: [
|
|
12429
|
-
"entry",
|
|
12430
|
-
collection,
|
|
12431
|
-
documentId
|
|
12432
|
-
] });
|
|
12433
|
-
queryClient.invalidateQueries({ queryKey: [
|
|
12434
|
-
"workflow-history",
|
|
12435
|
-
collection,
|
|
12436
|
-
documentId
|
|
12437
|
-
] });
|
|
12438
|
-
toast.success("Transition applied");
|
|
12439
|
-
},
|
|
12440
|
-
onError: (err) => {
|
|
12441
|
-
setPendingComment(null);
|
|
12442
|
-
toast.error("Transition failed", { description: err.message });
|
|
12443
|
-
}
|
|
12444
|
-
});
|
|
12445
|
-
function handleTransitionClick(t) {
|
|
12446
|
-
if (t.requireComment) setPendingComment(t.name);
|
|
12447
|
-
else transitionMutation.mutate({ name: t.name });
|
|
12448
|
-
}
|
|
12449
12994
|
return /* @__PURE__ */ jsxs("div", {
|
|
12450
12995
|
className: "dy-rounded-2xl dy-border dy-border-border/50 dy-bg-muted/10 dy-p-4 dy-space-y-4",
|
|
12451
12996
|
children: [
|
|
@@ -12463,32 +13008,32 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12463
13008
|
className: "dy-flex dy-gap-4 dy-text-[11px] dy-text-muted-foreground/60",
|
|
12464
13009
|
children: [/* @__PURE__ */ jsxs("span", { children: ["Revision ", workflowMeta.revision] }), workflowMeta.publishedAt && /* @__PURE__ */ jsxs("span", { children: ["Published ", new Date(workflowMeta.publishedAt).toLocaleDateString()] })]
|
|
12465
13010
|
}),
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
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"),
|
|
12475
|
-
disabled: transitionMutation.isPending,
|
|
12476
|
-
onClick: () => handleTransitionClick(t),
|
|
12477
|
-
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]
|
|
12478
|
-
}), isWaiting && /* @__PURE__ */ jsx(CommentDialog, {
|
|
12479
|
-
transitionLabel: t.label,
|
|
12480
|
-
isPending: transitionMutation.isPending,
|
|
12481
|
-
onConfirm: (comment) => transitionMutation.mutate({
|
|
12482
|
-
name: t.name,
|
|
12483
|
-
comment
|
|
12484
|
-
}),
|
|
12485
|
-
onCancel: () => setPendingComment(null)
|
|
12486
|
-
})] }, t.name);
|
|
12487
|
-
})
|
|
13011
|
+
/* @__PURE__ */ jsx(WorkflowTransitionPanelActions, {
|
|
13012
|
+
collection,
|
|
13013
|
+
documentId,
|
|
13014
|
+
workflowConfig,
|
|
13015
|
+
workflowMeta,
|
|
13016
|
+
onSaveDraft,
|
|
13017
|
+
saveDraftPending,
|
|
13018
|
+
prepareTransition
|
|
12488
13019
|
}),
|
|
12489
|
-
|
|
12490
|
-
|
|
12491
|
-
|
|
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
|
+
})
|
|
12492
13037
|
}),
|
|
12493
13038
|
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("button", {
|
|
12494
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",
|
|
@@ -12545,6 +13090,216 @@ function WorkflowPanel({ collection, documentId, workflowMeta, workflowConfig })
|
|
|
12545
13090
|
});
|
|
12546
13091
|
}
|
|
12547
13092
|
//#endregion
|
|
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
|
|
12548
13303
|
//#region src/hooks/useLayoutPreference.ts
|
|
12549
13304
|
function useLayoutPreference({ key, defaultKeys }) {
|
|
12550
13305
|
const { client } = useDyrected();
|
|
@@ -12639,6 +13394,292 @@ function useLayoutPreference({ key, defaultKeys }) {
|
|
|
12639
13394
|
};
|
|
12640
13395
|
}
|
|
12641
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
|
|
12642
13683
|
//#region src/pages/collections/edit-page.tsx
|
|
12643
13684
|
function SortableFieldItem({ id, label, type, width, onChangeWidth }) {
|
|
12644
13685
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
@@ -12712,19 +13753,59 @@ function SortableFieldItem({ id, label, type, width, onChangeWidth }) {
|
|
|
12712
13753
|
});
|
|
12713
13754
|
}
|
|
12714
13755
|
/**
|
|
12715
|
-
* A single top-bar action
|
|
12716
|
-
*
|
|
12717
|
-
*
|
|
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.
|
|
12718
13759
|
*/
|
|
12719
13760
|
function HeaderAction({ icon: Icon, label, onClick, active, disabled, busy, title, className }) {
|
|
12720
|
-
return /* @__PURE__ */
|
|
13761
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
12721
13762
|
type: "button",
|
|
12722
13763
|
onClick,
|
|
12723
13764
|
disabled,
|
|
12724
13765
|
title: title || label,
|
|
12725
13766
|
"aria-label": title || label,
|
|
12726
|
-
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),
|
|
12727
|
-
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 })]
|
|
12728
13809
|
});
|
|
12729
13810
|
}
|
|
12730
13811
|
/**
|
|
@@ -12796,6 +13877,11 @@ function EditEntryPage() {
|
|
|
12796
13877
|
const [mobilePreview, setMobilePreview] = useState(false);
|
|
12797
13878
|
const [isDirty, setIsDirty] = useState(false);
|
|
12798
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);
|
|
12799
13885
|
const isEdit = !!id;
|
|
12800
13886
|
const [isConfiguringView, setIsConfiguringView] = useState(false);
|
|
12801
13887
|
const { data: schemas } = useQuery({
|
|
@@ -12878,16 +13964,6 @@ function EditEntryPage() {
|
|
|
12878
13964
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
12879
13965
|
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
12880
13966
|
}, [isDirty]);
|
|
12881
|
-
useEffect(() => {
|
|
12882
|
-
const handleSave = (e) => {
|
|
12883
|
-
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
|
12884
|
-
e.preventDefault();
|
|
12885
|
-
document.getElementById("dyrected-form-submit")?.click();
|
|
12886
|
-
}
|
|
12887
|
-
};
|
|
12888
|
-
window.addEventListener("keydown", handleSave);
|
|
12889
|
-
return () => window.removeEventListener("keydown", handleSave);
|
|
12890
|
-
}, []);
|
|
12891
13967
|
const schemaSlug = schema?.slug;
|
|
12892
13968
|
const schemaPreviewUrl = schema?.admin?.previewUrl;
|
|
12893
13969
|
const syncShowPreview = useState(() => (next) => {
|
|
@@ -12901,18 +13977,6 @@ function EditEntryPage() {
|
|
|
12901
13977
|
schemaPreviewUrl,
|
|
12902
13978
|
syncShowPreview
|
|
12903
13979
|
]);
|
|
12904
|
-
const hasWorkflow = !!schema?.workflow;
|
|
12905
|
-
const syncShowWorkflow = useState(() => (next) => {
|
|
12906
|
-
setActiveTab((prev) => next ? "workflow" : prev === "workflow" ? "edit" : prev);
|
|
12907
|
-
})[0];
|
|
12908
|
-
useEffect(() => {
|
|
12909
|
-
if (!hasWorkflow || schemaPreviewUrl) return;
|
|
12910
|
-
syncShowWorkflow(true);
|
|
12911
|
-
}, [
|
|
12912
|
-
hasWorkflow,
|
|
12913
|
-
schemaPreviewUrl,
|
|
12914
|
-
syncShowWorkflow
|
|
12915
|
-
]);
|
|
12916
13980
|
const { data: entry, isLoading: isEntryLoading } = useQuery({
|
|
12917
13981
|
queryKey: [
|
|
12918
13982
|
"entry",
|
|
@@ -12954,7 +14018,7 @@ function EditEntryPage() {
|
|
|
12954
14018
|
const isSelf = !!user && !!id && (user.id === id || user.sub === id);
|
|
12955
14019
|
const passwordChangeMode = schema?.auth ? isSelf ? "self" : isAdminUser ? "admin" : null : null;
|
|
12956
14020
|
const saveMutation = useMutation({
|
|
12957
|
-
mutationFn: async (data) => {
|
|
14021
|
+
mutationFn: async ({ data }) => {
|
|
12958
14022
|
const { oldPassword, newPassword, confirmPassword, ...rest } = data;
|
|
12959
14023
|
const results = {};
|
|
12960
14024
|
if (isEdit) {
|
|
@@ -12970,7 +14034,7 @@ function EditEntryPage() {
|
|
|
12970
14034
|
} else results.doc = await client.collection(slug).create(rest);
|
|
12971
14035
|
return results;
|
|
12972
14036
|
},
|
|
12973
|
-
onSuccess: (results) => {
|
|
14037
|
+
onSuccess: (results, variables) => {
|
|
12974
14038
|
setIsDirty(false);
|
|
12975
14039
|
queryClient.invalidateQueries({ queryKey: ["collection", slug] });
|
|
12976
14040
|
if (isEdit) queryClient.invalidateQueries({ queryKey: [
|
|
@@ -12978,45 +14042,26 @@ function EditEntryPage() {
|
|
|
12978
14042
|
slug,
|
|
12979
14043
|
id
|
|
12980
14044
|
] });
|
|
12981
|
-
toast.success(isEdit ? "Entry updated successfully" : "Entry created successfully", { description: `${schema?.labels?.singular || schema?.slug} has been saved.` });
|
|
12982
|
-
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");
|
|
12983
14047
|
const doc = results.doc;
|
|
12984
14048
|
if (!isEdit && doc?.id) navigate(`/collections/${slug}/edit/${doc.id}`, { replace: true });
|
|
12985
14049
|
},
|
|
12986
|
-
onError: (error) => {
|
|
14050
|
+
onError: (error, variables) => {
|
|
14051
|
+
if (variables.mode === "autosave") return;
|
|
12987
14052
|
toast.error("Failed to save entry", { description: error.message || "An unexpected error occurred." });
|
|
12988
14053
|
}
|
|
12989
14054
|
});
|
|
12990
|
-
const
|
|
12991
|
-
const handleSendResetLink = async () => {
|
|
12992
|
-
if (!entry?.email) return;
|
|
12993
|
-
setSendingReset(true);
|
|
12994
|
-
try {
|
|
12995
|
-
await client.collection(slug).sendResetLink(entry.email);
|
|
12996
|
-
toast.success("Reset link sent", { description: `A password reset email has been sent to ${entry.email}.` });
|
|
12997
|
-
} catch (err) {
|
|
12998
|
-
toast.error("Failed to send reset link", { description: err instanceof Error ? err.message : "An unexpected error occurred." });
|
|
12999
|
-
} finally {
|
|
13000
|
-
setSendingReset(false);
|
|
13001
|
-
}
|
|
13002
|
-
};
|
|
13003
|
-
if (!schema) return /* @__PURE__ */ jsx("div", { children: "Collection not found" });
|
|
13004
|
-
if (isEdit && isEntryLoading) return /* @__PURE__ */ jsx("div", { children: "Loading entry..." });
|
|
13005
|
-
const workflowConfig = schema.workflow ?? null;
|
|
14055
|
+
const workflowConfig = schema?.workflow ?? null;
|
|
13006
14056
|
const workflowMeta = isEdit && entry ? entry._workflow ?? null : null;
|
|
13007
14057
|
const hasStatus = schema?.fields.some((f) => f.name === "status");
|
|
13008
|
-
const
|
|
13009
|
-
const
|
|
13010
|
-
const
|
|
13011
|
-
const statusBadgeLabel = workflowState ? workflowState.label || workflowState.name : currentStatus === "published" ? "Published" : currentStatus === "draft" ? "Draft" : String(currentStatus);
|
|
13012
|
-
const workflowBadgePresentation = workflowState ? getWorkflowBadgePresentation(workflowState.color) : {
|
|
13013
|
-
className: isPublished ? WORKFLOW_BADGE_COLORS.success : WORKFLOW_BADGE_COLORS.warning,
|
|
13014
|
-
style: void 0
|
|
13015
|
-
};
|
|
14058
|
+
const workflowState = resolveWorkflowState(workflowConfig, workflowMeta);
|
|
14059
|
+
const publishingStatus = resolvePublishingStatus(schema, entry ?? {});
|
|
14060
|
+
const workflowStagePresentation = workflowState && publishingStatus?.workflowStateLabel ? getWorkflowBadgePresentation(workflowState.color) : null;
|
|
13016
14061
|
const showStatusBadge = workflowConfig ? !!workflowMeta : hasStatus;
|
|
13017
14062
|
const siteUrl = getSiteUrl(schemas?.admin?.siteUrl);
|
|
13018
|
-
const previewUrl = resolvePreviewUrl(schema
|
|
13019
|
-
const readAccess = schema
|
|
14063
|
+
const previewUrl = resolvePreviewUrl(schema?.admin?.previewUrl, previewData || entry, siteUrl);
|
|
14064
|
+
const readAccess = (schema?.access)?.read;
|
|
13020
14065
|
let canRead = true;
|
|
13021
14066
|
if (readAccess === false) canRead = false;
|
|
13022
14067
|
else if (typeof readAccess === "string") try {
|
|
@@ -13027,27 +14072,7 @@ function EditEntryPage() {
|
|
|
13027
14072
|
} catch (e) {
|
|
13028
14073
|
console.warn("Read access eval failed:", e);
|
|
13029
14074
|
}
|
|
13030
|
-
|
|
13031
|
-
className: "dy-flex dy-items-center dy-justify-center dy-h-[calc(100vh-200px)]",
|
|
13032
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
13033
|
-
className: "dy-text-center dy-space-y-3",
|
|
13034
|
-
children: [
|
|
13035
|
-
/* @__PURE__ */ jsx("div", {
|
|
13036
|
-
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",
|
|
13037
|
-
children: /* @__PURE__ */ jsx(Archive, { className: "dy-h-6 dy-w-6" })
|
|
13038
|
-
}),
|
|
13039
|
-
/* @__PURE__ */ jsx("h3", {
|
|
13040
|
-
className: "dy-text-lg dy-font-bold",
|
|
13041
|
-
children: "Access Denied"
|
|
13042
|
-
}),
|
|
13043
|
-
/* @__PURE__ */ jsx("p", {
|
|
13044
|
-
className: "dy-text-sm dy-text-muted-foreground",
|
|
13045
|
-
children: "You do not have permission to view this entry."
|
|
13046
|
-
})
|
|
13047
|
-
]
|
|
13048
|
-
})
|
|
13049
|
-
});
|
|
13050
|
-
const createAccess = schema.access?.create;
|
|
14075
|
+
const createAccess = (schema?.access)?.create;
|
|
13051
14076
|
let canCreate = true;
|
|
13052
14077
|
if (createAccess === false) canCreate = false;
|
|
13053
14078
|
else if (typeof createAccess === "string") try {
|
|
@@ -13055,7 +14080,7 @@ function EditEntryPage() {
|
|
|
13055
14080
|
} catch (e) {
|
|
13056
14081
|
console.warn("Create access eval failed:", e);
|
|
13057
14082
|
}
|
|
13058
|
-
const updateAccess = schema
|
|
14083
|
+
const updateAccess = (schema?.access)?.update;
|
|
13059
14084
|
let canUpdate = true;
|
|
13060
14085
|
if (updateAccess === false) canUpdate = false;
|
|
13061
14086
|
else if (typeof updateAccess === "string") try {
|
|
@@ -13066,7 +14091,7 @@ function EditEntryPage() {
|
|
|
13066
14091
|
} catch (e) {
|
|
13067
14092
|
console.warn("Update access eval failed:", e);
|
|
13068
14093
|
}
|
|
13069
|
-
const auditAccess = schema
|
|
14094
|
+
const auditAccess = (schema?.access)?.readAudit ?? readAccess;
|
|
13070
14095
|
let canReadAudit = true;
|
|
13071
14096
|
if (auditAccess === false) canReadAudit = false;
|
|
13072
14097
|
else if (typeof auditAccess === "string") try {
|
|
@@ -13078,11 +14103,158 @@ function EditEntryPage() {
|
|
|
13078
14103
|
canReadAudit = false;
|
|
13079
14104
|
}
|
|
13080
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;
|
|
13081
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
|
+
});
|
|
13082
14254
|
const docsToDisplay = debouncedSearchQuery ? siblingEntries?.docs || [] : siblingEntries?.docs?.filter((d) => String(d.id) !== id).slice(0, 4) || [];
|
|
13083
|
-
return /* @__PURE__ */
|
|
14255
|
+
return /* @__PURE__ */ jsxs(NestedEditorProvider, {
|
|
13084
14256
|
drillInEnabled: !!showLivePreview,
|
|
13085
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
14257
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
13086
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" : ""),
|
|
13087
14259
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
13088
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",
|
|
@@ -13147,12 +14319,12 @@ function EditEntryPage() {
|
|
|
13147
14319
|
})] }) : /* @__PURE__ */ jsxs("h1", {
|
|
13148
14320
|
className: "dy-text-base dy-font-serif dy-font-bold dy-tracking-tight dy-text-foreground dy-truncate",
|
|
13149
14321
|
children: ["New ", schema?.labels?.singular || schema?.slug]
|
|
13150
|
-
}), showStatusBadge && /* @__PURE__ */ jsx(Badge, {
|
|
13151
|
-
className: cn("dy-px-2 dy-py-0 dy-rounded-full dy-text-[10px] dy-font-
|
|
13152
|
-
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,
|
|
13153
14325
|
variant: "outline",
|
|
13154
|
-
children:
|
|
13155
|
-
})]
|
|
14326
|
+
children: publishingStatus.workflowStateLabel
|
|
14327
|
+
}), showWorkflowAutosaveStatus && /* @__PURE__ */ jsx(DraftSaveStatusBadge, { state: workflowAutosaveState })] })]
|
|
13156
14328
|
}),
|
|
13157
14329
|
/* @__PURE__ */ jsxs("div", {
|
|
13158
14330
|
className: "dy-ml-auto dy-flex dy-items-center dy-gap-1",
|
|
@@ -13180,9 +14352,9 @@ function EditEntryPage() {
|
|
|
13180
14352
|
}),
|
|
13181
14353
|
workflowAvailable && /* @__PURE__ */ jsx(HeaderAction, {
|
|
13182
14354
|
icon: Workflow,
|
|
13183
|
-
label: "Workflow",
|
|
14355
|
+
label: "Workflow details",
|
|
13184
14356
|
active: activeTab === "workflow",
|
|
13185
|
-
title: activeTab === "workflow" ? "Hide workflow
|
|
14357
|
+
title: activeTab === "workflow" ? "Hide workflow details" : "Show workflow details",
|
|
13186
14358
|
onClick: () => setActiveTab((tab) => tab === "workflow" ? "edit" : "workflow")
|
|
13187
14359
|
}),
|
|
13188
14360
|
schema?.audit && isEdit && canReadAudit && /* @__PURE__ */ jsx(HeaderAction, {
|
|
@@ -13250,12 +14422,35 @@ function EditEntryPage() {
|
|
|
13250
14422
|
})]
|
|
13251
14423
|
})] })]
|
|
13252
14424
|
})] }),
|
|
13253
|
-
(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", {
|
|
13254
14449
|
className: "dy-hidden md:dy-flex dy-items-center",
|
|
13255
14450
|
children: [/* @__PURE__ */ jsx("div", { className: "dy-mx-1 dy-h-6 dy-w-px dy-bg-border/60" }), /* @__PURE__ */ jsx(Button, {
|
|
13256
14451
|
size: "sm",
|
|
13257
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",
|
|
13258
|
-
onClick: () =>
|
|
14453
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13259
14454
|
disabled: saveMutation.isPending,
|
|
13260
14455
|
title: isEdit ? "Save Changes (⌘S)" : "Create Entry (⌘S)",
|
|
13261
14456
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("span", {
|
|
@@ -13266,7 +14461,7 @@ function EditEntryPage() {
|
|
|
13266
14461
|
children: [/* @__PURE__ */ jsx(Save, { className: "dy-h-3.5 dy-w-3.5" }), isEdit ? "Save" : "Create"]
|
|
13267
14462
|
})
|
|
13268
14463
|
})]
|
|
13269
|
-
})
|
|
14464
|
+
})] })
|
|
13270
14465
|
]
|
|
13271
14466
|
})
|
|
13272
14467
|
]
|
|
@@ -13298,7 +14493,13 @@ function EditEntryPage() {
|
|
|
13298
14493
|
collection: slug,
|
|
13299
14494
|
documentId: id,
|
|
13300
14495
|
workflowMeta,
|
|
13301
|
-
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
|
|
13302
14503
|
})
|
|
13303
14504
|
}),
|
|
13304
14505
|
activeTab === "audit" && /* @__PURE__ */ jsx("div", {
|
|
@@ -13475,19 +14676,21 @@ function EditEntryPage() {
|
|
|
13475
14676
|
queryParamsDefaults[key] = value;
|
|
13476
14677
|
});
|
|
13477
14678
|
return /* @__PURE__ */ jsx(FormEngine, {
|
|
14679
|
+
ref: formEngineRef,
|
|
13478
14680
|
collection: slug,
|
|
13479
14681
|
fields: orderedFields,
|
|
13480
14682
|
defaultValues: isEdit ? entry : {
|
|
13481
14683
|
...queryParamsDefaults,
|
|
13482
14684
|
...entry
|
|
13483
14685
|
},
|
|
13484
|
-
onSubmit:
|
|
14686
|
+
onSubmit: handleManualSave,
|
|
14687
|
+
autosave: autosaveConfig,
|
|
13485
14688
|
onDataChange: (newData) => setPreviewData({
|
|
13486
14689
|
...entry,
|
|
13487
14690
|
...newData
|
|
13488
14691
|
}),
|
|
13489
14692
|
onChange: (dirty) => setIsDirty(dirty),
|
|
13490
|
-
isLoading: saveMutation.isPending || isPreferenceLoading,
|
|
14693
|
+
isLoading: saveMutation.isPending || workflowTransitionPending || isPreferenceLoading,
|
|
13491
14694
|
submitLabel: isEdit ? "Save Changes" : "Create Entry",
|
|
13492
14695
|
hideSubmit: true,
|
|
13493
14696
|
readOnly: isEdit ? !canUpdate : !canCreate,
|
|
@@ -13502,7 +14705,7 @@ function EditEntryPage() {
|
|
|
13502
14705
|
form: "dyrected-edit-form",
|
|
13503
14706
|
className: "dy-hidden"
|
|
13504
14707
|
}),
|
|
13505
|
-
(isDirty || !isEdit) && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
14708
|
+
showManualSaveChrome && (isDirty || !isEdit) && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
13506
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",
|
|
13507
14710
|
children: /* @__PURE__ */ jsx("div", {
|
|
13508
14711
|
className: "dy-pointer-events-auto dy-mx-auto dy-max-w-2xl dy-px-4 dy-pb-4",
|
|
@@ -13514,7 +14717,7 @@ function EditEntryPage() {
|
|
|
13514
14717
|
}), /* @__PURE__ */ jsx(Button, {
|
|
13515
14718
|
size: "sm",
|
|
13516
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",
|
|
13517
|
-
onClick: () =>
|
|
14720
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13518
14721
|
disabled: saveMutation.isPending,
|
|
13519
14722
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("div", {
|
|
13520
14723
|
className: "dy-flex dy-items-center dy-gap-2",
|
|
@@ -13527,11 +14730,11 @@ function EditEntryPage() {
|
|
|
13527
14730
|
})
|
|
13528
14731
|
})
|
|
13529
14732
|
}),
|
|
13530
|
-
(isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
14733
|
+
showManualSaveChrome && (isEdit ? canUpdate : canCreate) && /* @__PURE__ */ jsx("div", {
|
|
13531
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",
|
|
13532
14735
|
children: /* @__PURE__ */ jsx(Button, {
|
|
13533
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",
|
|
13534
|
-
onClick: () =>
|
|
14737
|
+
onClick: () => void saveCurrentDraftNow("manual"),
|
|
13535
14738
|
disabled: saveMutation.isPending || !(isDirty || !isEdit),
|
|
13536
14739
|
children: saveMutation.isPending ? /* @__PURE__ */ jsxs("span", {
|
|
13537
14740
|
className: "dy-flex dy-items-center dy-gap-2",
|
|
@@ -13548,7 +14751,11 @@ function EditEntryPage() {
|
|
|
13548
14751
|
})
|
|
13549
14752
|
})]
|
|
13550
14753
|
})]
|
|
13551
|
-
}, id || "new")
|
|
14754
|
+
}, id || "new"), workflowAvailable && compareToLiveEnabled && /* @__PURE__ */ jsx(DraftLiveCompareSheet, {
|
|
14755
|
+
open: compareSheetOpen,
|
|
14756
|
+
onOpenChange: setCompareSheetOpen,
|
|
14757
|
+
comparison: draftLiveComparison
|
|
14758
|
+
})]
|
|
13552
14759
|
});
|
|
13553
14760
|
}
|
|
13554
14761
|
function AuditPanel({ collection, documentId }) {
|
|
@@ -13679,44 +14886,6 @@ function AuditPanel({ collection, documentId }) {
|
|
|
13679
14886
|
});
|
|
13680
14887
|
}
|
|
13681
14888
|
//#endregion
|
|
13682
|
-
//#region src/components/ui/card.tsx
|
|
13683
|
-
var Card = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13684
|
-
ref,
|
|
13685
|
-
className: cn("dy-rounded-lg dy-border dy-bg-card dy-text-card-foreground dy-shadow-sm", className),
|
|
13686
|
-
...props
|
|
13687
|
-
}));
|
|
13688
|
-
Card.displayName = "Card";
|
|
13689
|
-
var CardHeader = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13690
|
-
ref,
|
|
13691
|
-
className: cn("dy-flex dy-flex-col dy-space-y-1.5 dy-p-6", className),
|
|
13692
|
-
...props
|
|
13693
|
-
}));
|
|
13694
|
-
CardHeader.displayName = "CardHeader";
|
|
13695
|
-
var CardTitle = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13696
|
-
ref,
|
|
13697
|
-
className: cn("dy-text-2xl dy-font-semibold dy-leading-none dy-tracking-tight", className),
|
|
13698
|
-
...props
|
|
13699
|
-
}));
|
|
13700
|
-
CardTitle.displayName = "CardTitle";
|
|
13701
|
-
var CardDescription = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13702
|
-
ref,
|
|
13703
|
-
className: cn("dy-text-sm dy-text-muted-foreground", className),
|
|
13704
|
-
...props
|
|
13705
|
-
}));
|
|
13706
|
-
CardDescription.displayName = "CardDescription";
|
|
13707
|
-
var CardContent = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13708
|
-
ref,
|
|
13709
|
-
className: cn("dy-p-6 dy-pt-0", className),
|
|
13710
|
-
...props
|
|
13711
|
-
}));
|
|
13712
|
-
CardContent.displayName = "CardContent";
|
|
13713
|
-
var CardFooter = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
13714
|
-
ref,
|
|
13715
|
-
className: cn("dy-flex dy-items-center dy-p-6 dy-pt-0", className),
|
|
13716
|
-
...props
|
|
13717
|
-
}));
|
|
13718
|
-
CardFooter.displayName = "CardFooter";
|
|
13719
|
-
//#endregion
|
|
13720
14889
|
//#region src/components/ui/aspect-ratio.tsx
|
|
13721
14890
|
var AspectRatio = AspectRatioPrimitive.Root;
|
|
13722
14891
|
//#endregion
|
|
@@ -14996,7 +16165,7 @@ function SetupPromptUI({ config }) {
|
|
|
14996
16165
|
setCopied(true);
|
|
14997
16166
|
window.setTimeout(() => setCopied(false), 1800);
|
|
14998
16167
|
}
|
|
14999
|
-
const currentVersion = "2.
|
|
16168
|
+
const currentVersion = "2.6.0";
|
|
15000
16169
|
const [latestVersion, setLatestVersion] = useState(() => {
|
|
15001
16170
|
if (typeof window === "undefined") return null;
|
|
15002
16171
|
return localStorage.getItem("dyrected_latest_release");
|