@questpie/admin 3.2.6 → 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/admin-link.d.mts +2 -2
- 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/preview/preview-banner.d.mts +2 -2
- package/dist/client/preview/preview-field.d.mts +4 -4
- package/dist/client/preview/use-collection-preview.mjs +0 -35
- 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/views/pages/login-page.d.mts +2 -2
- package/dist/client/views/pages/reset-password-page.d.mts +2 -2
- package/dist/client/views/pages/setup-page.d.mts +2 -2
- package/dist/client.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/server/adapters/nextjs.d.mts +0 -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/.generated/module.d.mts +0 -1
- package/dist/server/modules/admin/collections/account.d.mts +46 -46
- 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 +34 -34
- package/dist/server/modules/admin/collections/session.d.mts +38 -38
- package/dist/server/modules/admin/collections/user.d.mts +53 -53
- package/dist/server/modules/admin/collections/verification.d.mts +2 -2
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/preview.d.mts +13 -8
- 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/widget-data.d.mts +5 -5
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +33 -33
- package/dist/server/modules/audit/.generated/module.d.mts +6 -6
- package/dist/server/modules/audit/collections/audit-log.d.mts +39 -37
- 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/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 -3
- 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
- package/dist/server/adapters/index.d.mts +0 -2
- package/dist/server/auth-helpers.d.mts +0 -1
|
@@ -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) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime24 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region src/client/preview/preview-banner.d.ts
|
|
4
4
|
|
|
@@ -40,6 +40,6 @@ declare function PreviewBanner({
|
|
|
40
40
|
isPreviewMode,
|
|
41
41
|
className,
|
|
42
42
|
exitPreviewUrl
|
|
43
|
-
}: PreviewBannerProps):
|
|
43
|
+
}: PreviewBannerProps): react_jsx_runtime24.JSX.Element | null;
|
|
44
44
|
//#endregion
|
|
45
45
|
export { PreviewBanner, PreviewBannerProps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime21 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/preview/preview-field.d.ts
|
|
5
5
|
|
|
@@ -68,7 +68,7 @@ declare function PreviewProvider({
|
|
|
68
68
|
}) => void;
|
|
69
69
|
onFieldValueEdited?: (payload: PreviewFieldValueEditedPayload) => void;
|
|
70
70
|
children: React.ReactNode;
|
|
71
|
-
}):
|
|
71
|
+
}): react_jsx_runtime21.JSX.Element;
|
|
72
72
|
/**
|
|
73
73
|
* Hook to access preview context.
|
|
74
74
|
*/
|
|
@@ -107,7 +107,7 @@ declare function PreviewField({
|
|
|
107
107
|
style,
|
|
108
108
|
onClick,
|
|
109
109
|
onValueCommit
|
|
110
|
-
}: PreviewFieldProps):
|
|
110
|
+
}: PreviewFieldProps): react_jsx_runtime21.JSX.Element;
|
|
111
111
|
/**
|
|
112
112
|
* Standalone PreviewField that works without context.
|
|
113
113
|
* Useful when you can't use PreviewProvider.
|
|
@@ -131,6 +131,6 @@ declare function StandalonePreviewField({
|
|
|
131
131
|
blockId?: string;
|
|
132
132
|
fieldType?: "regular" | "block" | "relation";
|
|
133
133
|
}) => void;
|
|
134
|
-
}):
|
|
134
|
+
}): react_jsx_runtime21.JSX.Element;
|
|
135
135
|
//#endregion
|
|
136
136
|
export { PreviewField, PreviewFieldProps, PreviewProvider, StandalonePreviewField, usePreviewContext };
|
|
@@ -48,7 +48,6 @@ function useCollectionPreview({ initialData, onRefresh }) {
|
|
|
48
48
|
const [isPreviewMode, setIsPreviewMode] = React.useState(false);
|
|
49
49
|
const lastAppliedSeqRef = React.useRef(null);
|
|
50
50
|
const initialDataRef = React.useRef(initialData);
|
|
51
|
-
const focusScrollTimerRef = React.useRef(null);
|
|
52
51
|
React.useEffect(() => {
|
|
53
52
|
try {
|
|
54
53
|
setIsPreviewMode(window.self !== window.top);
|
|
@@ -67,18 +66,6 @@ function useCollectionPreview({ initialData, onRefresh }) {
|
|
|
67
66
|
React.useEffect(() => {
|
|
68
67
|
if (!isPreviewMode) return;
|
|
69
68
|
window.parent.postMessage({ type: "PREVIEW_READY" }, "*");
|
|
70
|
-
const scheduleFocusScroll = (fieldPath) => {
|
|
71
|
-
if (focusScrollTimerRef.current !== null) window.clearTimeout(focusScrollTimerRef.current);
|
|
72
|
-
focusScrollTimerRef.current = window.setTimeout(() => {
|
|
73
|
-
focusScrollTimerRef.current = null;
|
|
74
|
-
const element = findPreviewFieldElement(fieldPath);
|
|
75
|
-
if (!element || shouldSkipPreviewAutoScroll(element)) return;
|
|
76
|
-
if (!isElementNearViewport(element)) element.scrollIntoView({
|
|
77
|
-
behavior: "auto",
|
|
78
|
-
block: "nearest"
|
|
79
|
-
});
|
|
80
|
-
}, 80);
|
|
81
|
-
};
|
|
82
69
|
const handleMessage = async (event) => {
|
|
83
70
|
const message = event.data;
|
|
84
71
|
if (!isPreviewAdminMessage(message)) return;
|
|
@@ -96,7 +83,6 @@ function useCollectionPreview({ initialData, onRefresh }) {
|
|
|
96
83
|
break;
|
|
97
84
|
case "FOCUS_FIELD":
|
|
98
85
|
setFocusedField((current) => current === message.fieldPath ? current : message.fieldPath);
|
|
99
|
-
scheduleFocusScroll(message.fieldPath);
|
|
100
86
|
break;
|
|
101
87
|
case "INIT_SNAPSHOT":
|
|
102
88
|
lastAppliedSeqRef.current = message.seq;
|
|
@@ -138,10 +124,6 @@ function useCollectionPreview({ initialData, onRefresh }) {
|
|
|
138
124
|
window.addEventListener("message", handleMessage);
|
|
139
125
|
return () => {
|
|
140
126
|
window.removeEventListener("message", handleMessage);
|
|
141
|
-
if (focusScrollTimerRef.current !== null) {
|
|
142
|
-
window.clearTimeout(focusScrollTimerRef.current);
|
|
143
|
-
focusScrollTimerRef.current = null;
|
|
144
|
-
}
|
|
145
127
|
};
|
|
146
128
|
}, [isPreviewMode]);
|
|
147
129
|
const handleFieldClick = React.useCallback((fieldPath, context) => {
|
|
@@ -199,23 +181,6 @@ function isPreviewPatchOp(value) {
|
|
|
199
181
|
const op = value;
|
|
200
182
|
return (op.op === "set" || op.op === "remove") && typeof op.path === "string";
|
|
201
183
|
}
|
|
202
|
-
function findPreviewFieldElement(fieldPath) {
|
|
203
|
-
const escaped = typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(fieldPath) : fieldPath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
204
|
-
return document.querySelector(`[data-preview-field="${escaped}"]`);
|
|
205
|
-
}
|
|
206
|
-
function shouldSkipPreviewAutoScroll(element) {
|
|
207
|
-
const activeElement = document.activeElement;
|
|
208
|
-
if (activeElement && element.contains(activeElement)) return true;
|
|
209
|
-
return element instanceof HTMLElement && element.dataset.previewEditing === "true";
|
|
210
|
-
}
|
|
211
|
-
function isElementNearViewport(element) {
|
|
212
|
-
const rect = element.getBoundingClientRect();
|
|
213
|
-
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
214
|
-
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
215
|
-
const verticalMargin = Math.min(120, viewportHeight * .2);
|
|
216
|
-
const horizontalMargin = Math.min(120, viewportWidth * .2);
|
|
217
|
-
return rect.bottom >= -verticalMargin && rect.top <= viewportHeight + verticalMargin && rect.right >= -horizontalMargin && rect.left <= viewportWidth + horizontalMargin;
|
|
218
|
-
}
|
|
219
184
|
|
|
220
185
|
//#endregion
|
|
221
186
|
export { useCollectionPreview };
|
|
@@ -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;
|