@questpie/admin 3.2.7 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
- package/dist/client/components/fields/array-field.mjs +105 -122
- package/dist/client/components/fields/asset-preview-field.mjs +1 -1
- package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
- package/dist/client/components/fields/boolean-field.mjs +1 -1
- package/dist/client/components/fields/date-field.mjs +1 -1
- package/dist/client/components/fields/datetime-field.mjs +1 -1
- package/dist/client/components/fields/email-field.mjs +1 -1
- package/dist/client/components/fields/field-wrapper.mjs +44 -15
- package/dist/client/components/fields/number-field.mjs +1 -1
- package/dist/client/components/fields/object-array-field.mjs +179 -149
- package/dist/client/components/fields/object-field.mjs +96 -87
- package/dist/client/components/fields/relation-picker.mjs +1 -1
- package/dist/client/components/fields/relation-select.mjs +1 -1
- package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
- package/dist/client/components/fields/select-field.mjs +1 -1
- package/dist/client/components/fields/text-field.mjs +1 -1
- package/dist/client/components/fields/textarea-field.mjs +1 -1
- package/dist/client/components/fields/time-field.mjs +1 -1
- package/dist/client/components/fields/upload-field.mjs +1 -1
- package/dist/client/components/history-sidebar.mjs +10 -4
- package/dist/client/components/structured-diff.mjs +367 -0
- package/dist/client/components/ui/sidebar.mjs +1 -1
- package/dist/client/hooks/use-field-options.mjs +34 -15
- package/dist/client/hooks/use-transition-stage.mjs +2 -2
- package/dist/client/utils/auto-expand-fields.mjs +1 -1
- package/dist/client/views/collection/auto-form-fields.mjs +23 -19
- package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
- package/dist/client/views/collection/columns/build-columns.mjs +1 -1
- package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
- package/dist/client/views/collection/field-renderer.mjs +19 -7
- package/dist/client/views/collection/form-view.mjs +10 -6
- package/dist/client/views/collection/table-view.mjs +19 -13
- package/dist/client/views/globals/global-form-view.mjs +47 -27
- package/dist/client/views/layout/admin-sidebar.mjs +2 -2
- package/dist/client.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/server/i18n/messages/cs.mjs +8 -0
- package/dist/server/i18n/messages/de.mjs +8 -0
- package/dist/server/i18n/messages/en.mjs +8 -0
- package/dist/server/i18n/messages/es.mjs +8 -0
- package/dist/server/i18n/messages/fr.mjs +8 -0
- package/dist/server/i18n/messages/pl.mjs +8 -0
- package/dist/server/i18n/messages/pt.mjs +8 -0
- package/dist/server/i18n/messages/sk.mjs +8 -0
- package/dist/server/modules/admin/collections/account.d.mts +50 -50
- package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
- package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
- package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
- package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
- package/dist/server/modules/admin/collections/assets.d.mts +39 -39
- package/dist/server/modules/admin/collections/session.d.mts +42 -42
- package/dist/server/modules/admin/collections/user.d.mts +63 -63
- package/dist/server/modules/admin/collections/verification.d.mts +36 -36
- package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/locales.d.mts +2 -2
- package/dist/server/modules/admin/routes/preview.d.mts +24 -19
- package/dist/server/modules/admin/routes/preview.mjs +83 -62
- package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
- package/dist/server/modules/admin/routes/setup.d.mts +7 -7
- package/dist/server/modules/admin/routes/translations.d.mts +4 -4
- package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +41 -41
- package/dist/server/modules/audit/.generated/module.d.mts +6 -6
- package/dist/server/modules/audit/collections/audit-log.d.mts +80 -78
- package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
- package/dist/server/modules/audit/config/localize-title.mjs +67 -0
- package/dist/server/modules/audit/index.d.mts +2 -1
- package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
- package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
- package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
- package/dist/server/plugin.mjs +4 -4
- package/dist/server.d.mts +2 -1
- package/dist/server.mjs +2 -1
- package/dist/shared/preview-utils.d.mts +4 -4
- package/dist/shared/preview-utils.mjs +5 -7
- package/package.json +3 -3
- package/dist/client/hooks/use-audit-history.mjs +0 -38
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.mjs";
|
|
2
2
|
import { isComponentReference, resolveIconElement } from "../component-renderer.mjs";
|
|
3
|
-
import { useResolvedControl } from "./field-utils.mjs";
|
|
4
3
|
import { FieldWrapper } from "./field-wrapper.mjs";
|
|
4
|
+
import { useResolvedControl } from "./field-utils.mjs";
|
|
5
5
|
import { SelectSingle } from "../primitives/select-single.mjs";
|
|
6
6
|
import { SelectMulti } from "../primitives/select-multi.mjs";
|
|
7
7
|
import { useMemo } from "react";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.mjs";
|
|
2
|
-
import { useResolvedControl } from "./field-utils.mjs";
|
|
3
2
|
import { FieldWrapper } from "./field-wrapper.mjs";
|
|
3
|
+
import { useResolvedControl } from "./field-utils.mjs";
|
|
4
4
|
import { TextInput } from "../primitives/text-input.mjs";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.mjs";
|
|
2
|
-
import { useResolvedControl } from "./field-utils.mjs";
|
|
3
2
|
import { FieldWrapper } from "./field-wrapper.mjs";
|
|
3
|
+
import { useResolvedControl } from "./field-utils.mjs";
|
|
4
4
|
import { TextareaInput } from "../primitives/textarea-input.mjs";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.mjs";
|
|
2
|
-
import { useResolvedControl } from "./field-utils.mjs";
|
|
3
2
|
import { FieldWrapper } from "./field-wrapper.mjs";
|
|
3
|
+
import { useResolvedControl } from "./field-utils.mjs";
|
|
4
4
|
import { TimeInput } from "../primitives/time-input.mjs";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { Controller } from "react-hook-form";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useResolveText, useTranslation } from "../../i18n/hooks.mjs";
|
|
2
2
|
import { cn } from "../../lib/utils.mjs";
|
|
3
3
|
import { selectClient, useAdminStore } from "../../runtime/provider.mjs";
|
|
4
|
-
import { sanitizeFilename, useResolvedControl } from "./field-utils.mjs";
|
|
5
4
|
import { FieldWrapper } from "./field-wrapper.mjs";
|
|
5
|
+
import { sanitizeFilename, useResolvedControl } from "./field-utils.mjs";
|
|
6
6
|
import { useCollectionItem } from "../../hooks/use-collection.mjs";
|
|
7
7
|
import { ResourceSheet } from "../sheets/resource-sheet.mjs";
|
|
8
8
|
import { useUploadCollection } from "../../hooks/use-upload-collection.mjs";
|
|
@@ -2,11 +2,12 @@ import { useResolveText, useTranslation } from "../i18n/hooks.mjs";
|
|
|
2
2
|
import { cn, formatLabel } from "../lib/utils.mjs";
|
|
3
3
|
import { resolveOptionLabelForValue } from "./primitives/option-label.mjs";
|
|
4
4
|
import { Button } from "./ui/button.mjs";
|
|
5
|
-
import { Badge } from "./ui/badge.mjs";
|
|
6
|
-
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./ui/sheet.mjs";
|
|
7
5
|
import { Separator } from "./ui/separator.mjs";
|
|
6
|
+
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./ui/sheet.mjs";
|
|
7
|
+
import { Badge } from "./ui/badge.mjs";
|
|
8
8
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./ui/accordion.mjs";
|
|
9
9
|
import { Skeleton } from "./ui/skeleton.mjs";
|
|
10
|
+
import { StructuredFieldDiff, shouldUseStructuredDiff } from "./structured-diff.mjs";
|
|
10
11
|
import { EmptyState } from "./ui/empty-state.mjs";
|
|
11
12
|
import { ScrollFade } from "./ui/scroll-fade.mjs";
|
|
12
13
|
import { Icon } from "@iconify/react";
|
|
@@ -157,7 +158,7 @@ function summarizeBlocks(value) {
|
|
|
157
158
|
const blockNames = [];
|
|
158
159
|
for (const item of value) {
|
|
159
160
|
if (!isObjectRecord(item)) continue;
|
|
160
|
-
const type = item.blockType ?? item.block ?? item.type ?? item
|
|
161
|
+
const type = item.blockType ?? item.block ?? item.type ?? item["_type"] ?? item.name;
|
|
161
162
|
if (typeof type === "string") blockNames.push(formatLabel(type));
|
|
162
163
|
}
|
|
163
164
|
if (blockNames.length === 0) return null;
|
|
@@ -345,7 +346,12 @@ function VersionDiffPanel({ previousVersion, changes, fields }) {
|
|
|
345
346
|
})]
|
|
346
347
|
}),
|
|
347
348
|
/* @__PURE__ */ jsx(Separator, { className: "my-3" }),
|
|
348
|
-
/* @__PURE__ */
|
|
349
|
+
shouldUseStructuredDiff(change.type, change.from, change.to) ? /* @__PURE__ */ jsx(StructuredFieldDiff, {
|
|
350
|
+
type: change.type,
|
|
351
|
+
from: change.from,
|
|
352
|
+
to: change.to,
|
|
353
|
+
nestedFields: field?.metadata?.nestedFields
|
|
354
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
349
355
|
className: "grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]",
|
|
350
356
|
children: [
|
|
351
357
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { useResolveText, useTranslation } from "../i18n/hooks.mjs";
|
|
2
|
+
import { cn, formatLabel } from "../lib/utils.mjs";
|
|
3
|
+
import { Badge } from "./ui/badge.mjs";
|
|
4
|
+
import { useAdminConfig } from "../hooks/use-admin-config.mjs";
|
|
5
|
+
import { Icon } from "@iconify/react";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/client/components/structured-diff.tsx
|
|
9
|
+
function isRecord(v) {
|
|
10
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11
|
+
}
|
|
12
|
+
function isBlockDocument(value) {
|
|
13
|
+
return isRecord(value) && Array.isArray(value["_tree"]) && isRecord(value["_values"]);
|
|
14
|
+
}
|
|
15
|
+
function isEmpty(v) {
|
|
16
|
+
if (v === null || v === void 0 || v === "") return true;
|
|
17
|
+
if (Array.isArray(v)) return v.length === 0;
|
|
18
|
+
if (isRecord(v)) return Object.keys(v).length === 0;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function deepEqual(a, b) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
24
|
+
} catch {
|
|
25
|
+
return a === b;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function flattenTree(nodes) {
|
|
29
|
+
const result = /* @__PURE__ */ new Map();
|
|
30
|
+
function walk(list) {
|
|
31
|
+
for (const n of list) {
|
|
32
|
+
result.set(n.id, n.type);
|
|
33
|
+
if (n.children) walk(n.children);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
walk(nodes);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function formatPrimitive(value) {
|
|
40
|
+
if (value === null || value === void 0) return "-";
|
|
41
|
+
if (typeof value === "string") return value.length > 120 ? `${value.slice(0, 120)}…` : value;
|
|
42
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
43
|
+
if (Array.isArray(value)) return `[${value.length}]`;
|
|
44
|
+
if (isRecord(value)) {
|
|
45
|
+
for (const key of [
|
|
46
|
+
"_title",
|
|
47
|
+
"title",
|
|
48
|
+
"name",
|
|
49
|
+
"label"
|
|
50
|
+
]) if (typeof value[key] === "string") return value[key];
|
|
51
|
+
return `{${Object.keys(value).length}}`;
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
}
|
|
55
|
+
function summarizeItem(item, index) {
|
|
56
|
+
for (const key of [
|
|
57
|
+
"_title",
|
|
58
|
+
"title",
|
|
59
|
+
"name",
|
|
60
|
+
"label",
|
|
61
|
+
"email",
|
|
62
|
+
"filename"
|
|
63
|
+
]) {
|
|
64
|
+
const val = item[key];
|
|
65
|
+
if (typeof val === "string" && val.trim()) return val.length > 60 ? `${val.slice(0, 60)}…` : val;
|
|
66
|
+
}
|
|
67
|
+
return `#${index}`;
|
|
68
|
+
}
|
|
69
|
+
function getChangeKind(from, to) {
|
|
70
|
+
if (isEmpty(from) && !isEmpty(to)) return "added";
|
|
71
|
+
if (!isEmpty(from) && isEmpty(to)) return "removed";
|
|
72
|
+
return "changed";
|
|
73
|
+
}
|
|
74
|
+
function getFieldMetadata(field) {
|
|
75
|
+
if (!field) return void 0;
|
|
76
|
+
return field.metadata ?? field;
|
|
77
|
+
}
|
|
78
|
+
function getFieldLabel(field, fallback, resolveText) {
|
|
79
|
+
const label = getFieldMetadata(field)?.label;
|
|
80
|
+
return label ? resolveText(label, fallback) : fallback;
|
|
81
|
+
}
|
|
82
|
+
function getArrayItemFields(nestedFields) {
|
|
83
|
+
return getFieldMetadata(nestedFields?.item)?.nestedFields ?? nestedFields;
|
|
84
|
+
}
|
|
85
|
+
function diffProperties(from, to, fields, resolveText) {
|
|
86
|
+
const allKeys = new Set([...Object.keys(from ?? {}), ...Object.keys(to ?? {})]);
|
|
87
|
+
const changes = [];
|
|
88
|
+
for (const key of allKeys) {
|
|
89
|
+
if (key.startsWith("_") || key === "id") continue;
|
|
90
|
+
const fromVal = from?.[key];
|
|
91
|
+
const toVal = to?.[key];
|
|
92
|
+
if (deepEqual(fromVal, toVal)) continue;
|
|
93
|
+
const label = getFieldLabel(fields?.[key], formatLabel(key), resolveText);
|
|
94
|
+
changes.push({
|
|
95
|
+
key,
|
|
96
|
+
label,
|
|
97
|
+
kind: getChangeKind(fromVal, toVal),
|
|
98
|
+
from: fromVal,
|
|
99
|
+
to: toVal
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return changes;
|
|
103
|
+
}
|
|
104
|
+
function computeBlockChanges(from, to, blockSchemas, resolveText) {
|
|
105
|
+
const fromVal = isBlockDocument(from) ? from : null;
|
|
106
|
+
const toVal = isBlockDocument(to) ? to : null;
|
|
107
|
+
const fromTree = flattenTree(fromVal?.["_tree"] ?? []);
|
|
108
|
+
const toTree = flattenTree(toVal?.["_tree"] ?? []);
|
|
109
|
+
const fromValues = fromVal?.["_values"] ?? {};
|
|
110
|
+
const toValues = toVal?.["_values"] ?? {};
|
|
111
|
+
const changes = [];
|
|
112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const [id, type] of toTree) {
|
|
114
|
+
seen.add(id);
|
|
115
|
+
const schema = blockSchemas?.[type];
|
|
116
|
+
const label = schema?.admin?.label ? resolveText(schema.admin.label, formatLabel(type)) : formatLabel(type);
|
|
117
|
+
if (!fromTree.has(id)) changes.push({
|
|
118
|
+
id,
|
|
119
|
+
label,
|
|
120
|
+
kind: "added",
|
|
121
|
+
properties: diffProperties(void 0, toValues[id], schema?.fields, resolveText)
|
|
122
|
+
});
|
|
123
|
+
else {
|
|
124
|
+
const props = diffProperties(fromValues[id], toValues[id], schema?.fields, resolveText);
|
|
125
|
+
if (props.length > 0) changes.push({
|
|
126
|
+
id,
|
|
127
|
+
label,
|
|
128
|
+
kind: "changed",
|
|
129
|
+
properties: props
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const [id, type] of fromTree) {
|
|
134
|
+
if (seen.has(id)) continue;
|
|
135
|
+
const schema = blockSchemas?.[type];
|
|
136
|
+
const label = schema?.admin?.label ? resolveText(schema.admin.label, formatLabel(type)) : formatLabel(type);
|
|
137
|
+
changes.push({
|
|
138
|
+
id,
|
|
139
|
+
label,
|
|
140
|
+
kind: "removed",
|
|
141
|
+
properties: diffProperties(fromValues[id], void 0, schema?.fields, resolveText)
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return changes;
|
|
145
|
+
}
|
|
146
|
+
function computeArrayChanges(from, to, nestedFields, resolveText) {
|
|
147
|
+
const fromArr = Array.isArray(from) ? from : [];
|
|
148
|
+
const toArr = Array.isArray(to) ? to : [];
|
|
149
|
+
if (fromArr.some(itemHasStringId) || toArr.some(itemHasStringId)) return diffArrayById(fromArr, toArr, nestedFields, resolveText);
|
|
150
|
+
return diffArrayByIndex(fromArr, toArr, nestedFields, resolveText);
|
|
151
|
+
}
|
|
152
|
+
function itemHasStringId(item) {
|
|
153
|
+
return isRecord(item) && typeof item.id === "string";
|
|
154
|
+
}
|
|
155
|
+
function diffArrayById(from, to, nestedFields, resolveText) {
|
|
156
|
+
const fromMap = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const item of from) if (isRecord(item) && typeof item.id === "string") fromMap.set(item.id, item);
|
|
158
|
+
const changes = [];
|
|
159
|
+
const seen = /* @__PURE__ */ new Set();
|
|
160
|
+
let idx = 0;
|
|
161
|
+
for (const item of to) {
|
|
162
|
+
idx++;
|
|
163
|
+
if (!isRecord(item) || typeof item.id !== "string") continue;
|
|
164
|
+
seen.add(item.id);
|
|
165
|
+
const label = summarizeItem(item, idx);
|
|
166
|
+
if (!fromMap.has(item.id)) changes.push({
|
|
167
|
+
id: item.id,
|
|
168
|
+
label,
|
|
169
|
+
kind: "added",
|
|
170
|
+
properties: diffProperties(void 0, item, nestedFields, resolveText)
|
|
171
|
+
});
|
|
172
|
+
else {
|
|
173
|
+
const props = diffProperties(fromMap.get(item.id), item, nestedFields, resolveText);
|
|
174
|
+
if (props.length > 0) changes.push({
|
|
175
|
+
id: item.id,
|
|
176
|
+
label,
|
|
177
|
+
kind: "changed",
|
|
178
|
+
properties: props
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let fromIdx = 0;
|
|
183
|
+
for (const item of from) {
|
|
184
|
+
fromIdx++;
|
|
185
|
+
if (!isRecord(item) || typeof item.id !== "string") continue;
|
|
186
|
+
if (seen.has(item.id)) continue;
|
|
187
|
+
changes.push({
|
|
188
|
+
id: item.id,
|
|
189
|
+
label: summarizeItem(item, fromIdx),
|
|
190
|
+
kind: "removed",
|
|
191
|
+
properties: diffProperties(item, void 0, nestedFields, resolveText)
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return changes;
|
|
195
|
+
}
|
|
196
|
+
function diffArrayByIndex(from, to, nestedFields, resolveText) {
|
|
197
|
+
const maxLen = Math.max(from.length, to.length);
|
|
198
|
+
const changes = [];
|
|
199
|
+
for (let i = 0; i < maxLen; i++) {
|
|
200
|
+
const fromItem = from[i];
|
|
201
|
+
const toItem = to[i];
|
|
202
|
+
if (deepEqual(fromItem, toItem)) continue;
|
|
203
|
+
const fromRec = isRecord(fromItem) ? fromItem : void 0;
|
|
204
|
+
const toRec = isRecord(toItem) ? toItem : void 0;
|
|
205
|
+
const label = `#${i + 1}`;
|
|
206
|
+
if (fromItem === void 0) changes.push({
|
|
207
|
+
id: String(i),
|
|
208
|
+
label,
|
|
209
|
+
kind: "added",
|
|
210
|
+
properties: toRec ? diffProperties(void 0, toRec, nestedFields, resolveText) : []
|
|
211
|
+
});
|
|
212
|
+
else if (toItem === void 0) changes.push({
|
|
213
|
+
id: String(i),
|
|
214
|
+
label,
|
|
215
|
+
kind: "removed",
|
|
216
|
+
properties: fromRec ? diffProperties(fromRec, void 0, nestedFields, resolveText) : []
|
|
217
|
+
});
|
|
218
|
+
else changes.push({
|
|
219
|
+
id: String(i),
|
|
220
|
+
label,
|
|
221
|
+
kind: "changed",
|
|
222
|
+
properties: fromRec && toRec ? diffProperties(fromRec, toRec, nestedFields, resolveText) : []
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return changes;
|
|
226
|
+
}
|
|
227
|
+
function computeObjectChanges(from, to, nestedFields, resolveText) {
|
|
228
|
+
return diffProperties(isRecord(from) ? from : {}, isRecord(to) ? to : {}, nestedFields, resolveText);
|
|
229
|
+
}
|
|
230
|
+
const CHANGE_STYLES = {
|
|
231
|
+
added: {
|
|
232
|
+
icon: "ph:plus",
|
|
233
|
+
badge: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
234
|
+
border: "border-l-emerald-500/50",
|
|
235
|
+
bg: "bg-emerald-500/5",
|
|
236
|
+
text: "text-emerald-600 dark:text-emerald-400",
|
|
237
|
+
labelKey: "history.changeAdded"
|
|
238
|
+
},
|
|
239
|
+
removed: {
|
|
240
|
+
icon: "ph:minus",
|
|
241
|
+
badge: "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-300",
|
|
242
|
+
border: "border-l-red-500/50",
|
|
243
|
+
bg: "bg-red-500/5",
|
|
244
|
+
text: "text-red-600 dark:text-red-400",
|
|
245
|
+
labelKey: "history.changeRemoved"
|
|
246
|
+
},
|
|
247
|
+
changed: {
|
|
248
|
+
icon: "ph:pencil-simple",
|
|
249
|
+
badge: "border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300",
|
|
250
|
+
border: "border-l-blue-500/50",
|
|
251
|
+
bg: "bg-blue-500/5",
|
|
252
|
+
text: "text-blue-600 dark:text-blue-400",
|
|
253
|
+
labelKey: "history.changeChanged"
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const ADDED_VALUE_CLASS = "text-emerald-600 dark:text-emerald-400";
|
|
257
|
+
const REMOVED_VALUE_CLASS = "text-red-500/80 line-through dark:text-red-400/80";
|
|
258
|
+
function PropertyValueDelta({ change }) {
|
|
259
|
+
if (change.kind === "added") return /* @__PURE__ */ jsx("span", {
|
|
260
|
+
className: ADDED_VALUE_CLASS,
|
|
261
|
+
children: formatPrimitive(change.to)
|
|
262
|
+
});
|
|
263
|
+
if (change.kind === "removed") return /* @__PURE__ */ jsx("span", {
|
|
264
|
+
className: REMOVED_VALUE_CLASS,
|
|
265
|
+
children: formatPrimitive(change.from)
|
|
266
|
+
});
|
|
267
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
268
|
+
/* @__PURE__ */ jsx("span", {
|
|
269
|
+
className: REMOVED_VALUE_CLASS,
|
|
270
|
+
children: formatPrimitive(change.from)
|
|
271
|
+
}),
|
|
272
|
+
/* @__PURE__ */ jsx("span", {
|
|
273
|
+
className: "text-muted-foreground mx-1.5",
|
|
274
|
+
children: "→"
|
|
275
|
+
}),
|
|
276
|
+
/* @__PURE__ */ jsx("span", {
|
|
277
|
+
className: ADDED_VALUE_CLASS,
|
|
278
|
+
children: formatPrimitive(change.to)
|
|
279
|
+
})
|
|
280
|
+
] });
|
|
281
|
+
}
|
|
282
|
+
function PropertyChangeRow({ change }) {
|
|
283
|
+
const style = CHANGE_STYLES[change.kind];
|
|
284
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
285
|
+
className: "flex items-baseline gap-2 text-xs leading-relaxed",
|
|
286
|
+
children: [
|
|
287
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
288
|
+
icon: style.icon,
|
|
289
|
+
className: cn("size-3 shrink-0", style.text)
|
|
290
|
+
}),
|
|
291
|
+
/* @__PURE__ */ jsxs("span", {
|
|
292
|
+
className: "text-muted-foreground shrink-0 font-medium",
|
|
293
|
+
children: [change.label, ":"]
|
|
294
|
+
}),
|
|
295
|
+
/* @__PURE__ */ jsx("span", {
|
|
296
|
+
className: "min-w-0 break-words",
|
|
297
|
+
children: /* @__PURE__ */ jsx(PropertyValueDelta, { change })
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function ItemChangeCard({ item }) {
|
|
303
|
+
const { t } = useTranslation();
|
|
304
|
+
const style = CHANGE_STYLES[item.kind];
|
|
305
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
306
|
+
className: cn("rounded-sm border-l-2 py-2 pr-2 pl-3", style.border, style.bg),
|
|
307
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
308
|
+
className: "flex items-center gap-2",
|
|
309
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
310
|
+
className: "text-foreground text-xs font-medium",
|
|
311
|
+
children: item.label
|
|
312
|
+
}), /* @__PURE__ */ jsxs(Badge, {
|
|
313
|
+
variant: "outline",
|
|
314
|
+
className: cn("text-[0.625rem]", style.badge),
|
|
315
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
316
|
+
icon: style.icon,
|
|
317
|
+
className: "size-2.5"
|
|
318
|
+
}), t(style.labelKey)]
|
|
319
|
+
})]
|
|
320
|
+
}), item.properties.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
321
|
+
className: "mt-1.5 space-y-1 pl-1",
|
|
322
|
+
children: item.properties.map((prop) => /* @__PURE__ */ jsx(PropertyChangeRow, { change: prop }, prop.key))
|
|
323
|
+
})]
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function NoChangesPlaceholder() {
|
|
327
|
+
const { t } = useTranslation();
|
|
328
|
+
return /* @__PURE__ */ jsx("div", {
|
|
329
|
+
className: "text-muted-foreground text-xs",
|
|
330
|
+
children: t("history.noFieldChanges")
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function ItemChangeList({ items }) {
|
|
334
|
+
if (items.length === 0) return /* @__PURE__ */ jsx(NoChangesPlaceholder, {});
|
|
335
|
+
return /* @__PURE__ */ jsx("div", {
|
|
336
|
+
className: "space-y-2",
|
|
337
|
+
children: items.map((item) => /* @__PURE__ */ jsx(ItemChangeCard, { item }, item.id))
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function StructuredFieldDiff({ type, from, to, nestedFields }) {
|
|
341
|
+
const resolveText = useResolveText();
|
|
342
|
+
const { data: adminConfig } = useAdminConfig();
|
|
343
|
+
if (type === "blocks") return /* @__PURE__ */ jsx(ItemChangeList, { items: computeBlockChanges(from, to, adminConfig?.blocks, resolveText) });
|
|
344
|
+
if (type === "array") return /* @__PURE__ */ jsx(ItemChangeList, { items: computeArrayChanges(from, to, getArrayItemFields(nestedFields), resolveText) });
|
|
345
|
+
if (type === "object") {
|
|
346
|
+
const props = computeObjectChanges(from, to, nestedFields, resolveText);
|
|
347
|
+
if (props.length === 0) return /* @__PURE__ */ jsx(NoChangesPlaceholder, {});
|
|
348
|
+
return /* @__PURE__ */ jsx("div", {
|
|
349
|
+
className: "space-y-1.5 py-1",
|
|
350
|
+
children: props.map((prop) => /* @__PURE__ */ jsx(PropertyChangeRow, { change: prop }, prop.key))
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function shouldUseStructuredDiff(type, from, to) {
|
|
356
|
+
if (type === "blocks") return isBlockDocument(from) || isBlockDocument(to);
|
|
357
|
+
if (type === "object") return isRecord(from) || isRecord(to);
|
|
358
|
+
if (type === "array") {
|
|
359
|
+
const fromArr = Array.isArray(from) ? from : [];
|
|
360
|
+
const toArr = Array.isArray(to) ? to : [];
|
|
361
|
+
return fromArr.some(isRecord) || toArr.some(isRecord);
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
//#endregion
|
|
367
|
+
export { StructuredFieldDiff, shouldUseStructuredDiff };
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../lib/utils.mjs";
|
|
4
4
|
import { Button } from "./button.mjs";
|
|
5
|
-
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
|
|
6
5
|
import { Separator } from "./separator.mjs";
|
|
6
|
+
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "./sheet.mjs";
|
|
7
7
|
import { useIsMobile } from "../../hooks/use-media-query.mjs";
|
|
8
8
|
import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
|
|
9
9
|
import { Icon } from "@iconify/react";
|
|
@@ -15,30 +15,49 @@ function getSiblingData(values, fieldPath) {
|
|
|
15
15
|
else return null;
|
|
16
16
|
return typeof sibling === "object" ? sibling : null;
|
|
17
17
|
}
|
|
18
|
-
function
|
|
18
|
+
function getSiblingDepPath(fieldPath, dep) {
|
|
19
|
+
const siblingKey = dep.slice(9);
|
|
20
|
+
if (!siblingKey) return null;
|
|
21
|
+
const parts = fieldPath.split(".");
|
|
22
|
+
const numericIndex = parts.findIndex((p) => /^\d+$/.test(p));
|
|
23
|
+
if (numericIndex === -1) return null;
|
|
24
|
+
return [...parts.slice(0, numericIndex + 1), siblingKey].join(".");
|
|
25
|
+
}
|
|
26
|
+
function getWatchPathForDep(fieldPath, dep) {
|
|
27
|
+
if (dep.startsWith("$sibling.")) return getSiblingDepPath(fieldPath, dep);
|
|
28
|
+
if (dep.startsWith("$")) return null;
|
|
29
|
+
return dep;
|
|
30
|
+
}
|
|
31
|
+
function getWatchedValuesForDeps(watchEntries, watchedValues) {
|
|
19
32
|
const result = {};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} else if (!dep.startsWith("$")) result[dep] = allValues[dep];
|
|
33
|
+
if (watchEntries.length === 0) return result;
|
|
34
|
+
const values = Array.isArray(watchedValues) ? watchedValues : [watchedValues];
|
|
35
|
+
for (const [index, entry] of watchEntries.entries()) result[entry.dep] = values[index];
|
|
24
36
|
return result;
|
|
25
37
|
}
|
|
38
|
+
const EMPTY_WATCH_DEPS = [];
|
|
26
39
|
function useFieldOptions({ collection, mode = "collection", field, optionsConfig, staticOptions, initialSearch = "", limit = 20, enabled = true }) {
|
|
27
40
|
const client = useAdminStore((s) => s.client);
|
|
28
41
|
const form = useFormContext();
|
|
29
42
|
const queryClient = useQueryClient();
|
|
30
43
|
const [search, setSearch] = React.useState(initialSearch);
|
|
31
44
|
const debouncedSearch = useDebouncedValue(search, 300);
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
]);
|
|
41
|
-
const
|
|
45
|
+
const watchDeps = optionsConfig?.watch ?? EMPTY_WATCH_DEPS;
|
|
46
|
+
const watchEntries = React.useMemo(() => watchDeps.map((dep) => {
|
|
47
|
+
const path = getWatchPathForDep(field, dep);
|
|
48
|
+
return path ? {
|
|
49
|
+
dep,
|
|
50
|
+
path
|
|
51
|
+
} : null;
|
|
52
|
+
}).filter((entry) => !!entry), [watchDeps, field]);
|
|
53
|
+
const watchPaths = React.useMemo(() => watchEntries.map((entry) => entry.path), [watchEntries]);
|
|
54
|
+
const watchedValues = useWatch({
|
|
55
|
+
control: form.control,
|
|
56
|
+
name: watchPaths,
|
|
57
|
+
disabled: watchPaths.length === 0
|
|
58
|
+
});
|
|
59
|
+
const depValues = React.useMemo(() => getWatchedValuesForDeps(watchEntries, watchedValues), [watchEntries, watchedValues]);
|
|
60
|
+
const depKey = React.useMemo(() => JSON.stringify(depValues), [depValues]);
|
|
42
61
|
const filteredStaticOptions = React.useMemo(() => {
|
|
43
62
|
if (optionsConfig || !staticOptions) return null;
|
|
44
63
|
if (!debouncedSearch) return staticOptions;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { selectClient, useAdminStore } from "../runtime/provider.mjs";
|
|
2
2
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
|
|
4
4
|
//#region src/client/hooks/use-transition-stage.ts
|
|
@@ -40,7 +40,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
40
40
|
*/
|
|
41
41
|
function useTransitionStage(resourceName, options) {
|
|
42
42
|
const mode = options?.mode ?? "collection";
|
|
43
|
-
const basePath = useAdminStore(
|
|
43
|
+
const basePath = useAdminStore(selectClient).getBasePath?.() ?? "/api";
|
|
44
44
|
const queryClient = useQueryClient();
|
|
45
45
|
return useMutation({
|
|
46
46
|
mutationFn: async (params) => {
|
|
@@ -44,7 +44,7 @@ function addRelationExpand(withFields, relationName, nestedRelation) {
|
|
|
44
44
|
function autoExpandFields(config) {
|
|
45
45
|
const withFields = {};
|
|
46
46
|
if (config.list?.with) for (const rel of config.list.with) withFields[rel] = true;
|
|
47
|
-
const columnFields = config.list?.columns ?? [];
|
|
47
|
+
const columnFields = config.visibleColumns?.length ? config.visibleColumns : config.list?.columns ?? [];
|
|
48
48
|
const fieldsToCheck = [];
|
|
49
49
|
if (columnFields.length > 0) for (const col of columnFields) {
|
|
50
50
|
const fieldName = typeof col === "string" ? col : col.field;
|
|
@@ -385,20 +385,21 @@ function extractFieldNamesFromFieldItems(fieldItems) {
|
|
|
385
385
|
* Supports recursive tabs/sections nesting and auto-generates
|
|
386
386
|
* field list when no form config is defined.
|
|
387
387
|
*/
|
|
388
|
-
function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig }) {
|
|
388
|
+
function AutoFormFields({ app: _cms, collection, mode = "collection", config, registry, fieldResolver, fieldPrefix, allCollectionsConfig, resolvedFields: providedFields, schema: providedSchema }) {
|
|
389
389
|
const isActionForm = collection === "__action__";
|
|
390
|
+
const shouldFetchSchema = !providedFields || !providedSchema;
|
|
390
391
|
const collectionResult = useCollectionFields(mode === "collection" ? collection : "", {
|
|
391
392
|
fallbackFields: mode === "collection" ? config?.fields : void 0,
|
|
392
|
-
schemaQueryOptions: { enabled: mode === "collection" && !isActionForm }
|
|
393
|
+
schemaQueryOptions: { enabled: mode === "collection" && !isActionForm && shouldFetchSchema }
|
|
393
394
|
});
|
|
394
|
-
const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" } });
|
|
395
|
+
const globalResult = useGlobalFields(mode === "global" ? collection : "", { schemaQueryOptions: { enabled: mode === "global" && shouldFetchSchema } });
|
|
395
396
|
const { data: collectionMeta } = useCollectionMeta(collection, { enabled: mode === "collection" && !isActionForm });
|
|
396
397
|
const { data: globalMeta } = useGlobalMeta(collection, { enabled: mode === "global" });
|
|
397
398
|
const resolvedFields = mode === "global" ? {
|
|
398
|
-
...globalResult.fields,
|
|
399
|
+
...providedFields ?? globalResult.fields,
|
|
399
400
|
...config?.fields
|
|
400
|
-
} : collectionResult.fields;
|
|
401
|
-
const schema = mode === "global" ? globalResult.schema : collectionResult.schema;
|
|
401
|
+
} : providedFields ?? collectionResult.fields;
|
|
402
|
+
const schema = providedSchema ?? (mode === "global" ? globalResult.schema : collectionResult.schema);
|
|
402
403
|
const entityMeta = mode === "global" ? globalMeta : collectionMeta;
|
|
403
404
|
const form = useFormContext();
|
|
404
405
|
const resolveText = useResolveText();
|
|
@@ -476,20 +477,23 @@ function AutoFormFields({ app: _cms, collection, mode = "collection", config, re
|
|
|
476
477
|
className: "qa-form-fields__main min-w-0 flex-1",
|
|
477
478
|
children: mainContent
|
|
478
479
|
}), /* @__PURE__ */ jsx("aside", {
|
|
479
|
-
className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:max-w-[18rem] @2xl:pl-1"),
|
|
480
|
+
className: cn("qa-form-fields__sidebar", "w-full @max-2xl:pb-2 @2xl:sticky @2xl:top-4 @2xl:h-[calc(100svh-7rem)] @2xl:min-h-0 @2xl:max-w-[18rem] @2xl:shrink-0 @2xl:self-start @2xl:pl-1"),
|
|
480
481
|
children: /* @__PURE__ */ jsx("div", {
|
|
481
|
-
className: "bg-surface-low/45
|
|
482
|
-
children: /* @__PURE__ */ jsx(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
482
|
+
className: "bg-surface-low/45 flex min-h-0 flex-col rounded-md @2xl:h-full",
|
|
483
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
484
|
+
className: "scrollbar-thin min-h-0 space-y-4 overflow-y-auto px-3 py-3 @2xl:flex-1",
|
|
485
|
+
children: /* @__PURE__ */ jsx(SidebarRenderer, {
|
|
486
|
+
sidebar: formConfig.sidebar,
|
|
487
|
+
fields,
|
|
488
|
+
collection,
|
|
489
|
+
mode,
|
|
490
|
+
registry,
|
|
491
|
+
fieldPrefix,
|
|
492
|
+
allCollectionsConfig,
|
|
493
|
+
entityMeta,
|
|
494
|
+
formValues,
|
|
495
|
+
resolveText
|
|
496
|
+
})
|
|
493
497
|
})
|
|
494
498
|
})
|
|
495
499
|
})]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useResolveText, useTranslation } from "../../../i18n/hooks.mjs";
|
|
2
2
|
import { cn } from "../../../lib/utils.mjs";
|
|
3
|
-
import { Badge } from "../../../components/ui/badge.mjs";
|
|
4
3
|
import { isBlockContent } from "../../../blocks/types.mjs";
|
|
4
|
+
import { Badge } from "../../../components/ui/badge.mjs";
|
|
5
5
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../components/ui/tooltip.mjs";
|
|
6
6
|
import { useAdminConfig } from "../../../hooks/use-admin-config.mjs";
|
|
7
7
|
import { formatFieldLabel, formatPrimitiveValue, getFieldLabel, getItemLabel, summarizeValue } from "./shared/cell-helpers.mjs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useResolveText } from "../../../i18n/hooks.mjs";
|
|
2
2
|
import { useSafeContentLocales } from "../../../runtime/content-locales-provider.mjs";
|
|
3
3
|
import { useScopedLocale } from "../../../runtime/locale-scope.mjs";
|
|
4
|
-
import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
|
|
5
4
|
import { LocaleSwitcher } from "../../../components/locale-switcher.mjs";
|
|
5
|
+
import { DefaultCell, TextCell } from "../cells/primitive-cells.mjs";
|
|
6
6
|
import { normalizeColumnConfig } from "../../../builder/types/collection-types.mjs";
|
|
7
7
|
import { computeDefaultColumns, formatHeader } from "./column-defaults.mjs";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|