@simmalugnt-se/payload-editor-assistant 0.11.2 → 0.12.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/CHANGELOG.md +21 -0
- package/dist/components/BatchPanel.js +4 -3
- package/dist/components/EditorAssistantAside.js +3 -2
- package/dist/components/ProposalReview.d.ts +11 -0
- package/dist/components/ProposalReview.js +46 -0
- package/dist/components/RichTextPreview.d.ts +6 -0
- package/dist/components/RichTextPreview.js +76 -0
- package/dist/components/assistant-context.d.ts +2 -0
- package/dist/components/batch-link.js +10 -3
- package/dist/components/copy.d.ts +1 -1
- package/dist/components/copy.js +1 -1
- package/dist/components/proposal-review.d.ts +16 -0
- package/dist/components/proposal-review.js +34 -0
- package/dist/domain/batch.js +23 -8
- package/dist/domain/propose.d.ts +10 -0
- package/dist/domain/propose.js +42 -29
- package/dist/form/diff.d.ts +2 -2
- package/dist/form/diff.js +10 -10
- package/dist/form/field-label.d.ts +20 -0
- package/dist/form/field-label.js +72 -0
- package/dist/form/shape.d.ts +4 -1
- package/dist/form/shape.js +18 -0
- package/dist/runtime/agent.d.ts +1 -1
- package/dist/runtime/agent.js +4 -1
- package/dist/runtime/tools.d.ts +31 -2
- package/dist/runtime/tools.js +8 -3
- package/dist/schema/project.d.ts +6 -1
- package/dist/schema/project.js +14 -8
- package/dist/styles/assistant.css +216 -13
- package/package.json +1 -1
package/dist/form/diff.js
CHANGED
|
@@ -41,7 +41,7 @@ const FIELD_PREVIEW = 300;
|
|
|
41
41
|
* `labels` names related documents by id (see `labelRelations`), so a chosen image reads as its
|
|
42
42
|
* filename or alt text instead of an id. Rich text is listed by `richTextLines` instead.
|
|
43
43
|
*/
|
|
44
|
-
export function humanDiff(current, operations, language = "en", labels) {
|
|
44
|
+
export function humanDiff(current, operations, language = "en", labels, fieldLabel = label) {
|
|
45
45
|
const copy = COPY[language];
|
|
46
46
|
const nameOf = (value) => {
|
|
47
47
|
const id = typeof value === "string" || typeof value === "number"
|
|
@@ -65,30 +65,30 @@ export function humanDiff(current, operations, language = "en", labels) {
|
|
|
65
65
|
const before = getAtPath(current, parseFieldPath(operation.path));
|
|
66
66
|
return {
|
|
67
67
|
path: operation.path,
|
|
68
|
-
text: `${
|
|
68
|
+
text: `${fieldLabel(operation.path)}: ${preview(named(before), copy)} → ${preview(named(operation.value), copy)}`,
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
case "unset":
|
|
72
|
-
return { path: operation.path, text: copy.clear(
|
|
72
|
+
return { path: operation.path, text: copy.clear(fieldLabel(operation.path)) };
|
|
73
73
|
case "array.insert":
|
|
74
74
|
return {
|
|
75
75
|
path: operation.path,
|
|
76
|
-
text: withFields(copy.addItem(operation.index + 1,
|
|
76
|
+
text: withFields(copy.addItem(operation.index + 1, fieldLabel(operation.path)), operation.value, copy),
|
|
77
77
|
};
|
|
78
78
|
case "array.remove":
|
|
79
79
|
return {
|
|
80
80
|
path: operation.path,
|
|
81
|
-
text: copy.removeItem(operation.index + 1,
|
|
81
|
+
text: copy.removeItem(operation.index + 1, fieldLabel(operation.path)),
|
|
82
82
|
};
|
|
83
83
|
case "array.move":
|
|
84
84
|
return {
|
|
85
85
|
path: operation.path,
|
|
86
|
-
text: copy.moveItem(operation.from + 1, operation.to + 1,
|
|
86
|
+
text: copy.moveItem(operation.from + 1, operation.to + 1, fieldLabel(operation.path)),
|
|
87
87
|
};
|
|
88
88
|
case "array.replace":
|
|
89
89
|
return {
|
|
90
90
|
path: operation.path,
|
|
91
|
-
text: withFields(copy.replaceItem(operation.index + 1,
|
|
91
|
+
text: withFields(copy.replaceItem(operation.index + 1, fieldLabel(operation.path)), operation.value, copy),
|
|
92
92
|
};
|
|
93
93
|
case "blocks.insert":
|
|
94
94
|
return {
|
|
@@ -98,7 +98,7 @@ export function humanDiff(current, operations, language = "en", labels) {
|
|
|
98
98
|
case "blocks.remove":
|
|
99
99
|
return {
|
|
100
100
|
path: operation.path,
|
|
101
|
-
text: copy.removeBlock(operation.index + 1,
|
|
101
|
+
text: copy.removeBlock(operation.index + 1, fieldLabel(operation.path)),
|
|
102
102
|
};
|
|
103
103
|
case "blocks.move":
|
|
104
104
|
return { path: operation.path, text: copy.moveBlock(operation.from + 1, operation.to + 1) };
|
|
@@ -116,9 +116,9 @@ export function humanDiff(current, operations, language = "en", labels) {
|
|
|
116
116
|
* The parts of a rich text field that changed: `before` as the model read it (Markdown blocks),
|
|
117
117
|
* `after` as compiled, where `{ keep: i }` is block `i` unchanged.
|
|
118
118
|
*/
|
|
119
|
-
export function richTextLines(path, before, after, language = "en") {
|
|
119
|
+
export function richTextLines(path, before, after, language = "en", fieldLabel = label) {
|
|
120
120
|
const copy = COPY[language];
|
|
121
|
-
const field =
|
|
121
|
+
const field = fieldLabel(path);
|
|
122
122
|
const part = (block) => typeof block === "string"
|
|
123
123
|
? preview(block, copy, FIELD_PREVIEW)
|
|
124
124
|
: `[${block.type ?? "?"}]`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FieldNode } from "../schema/fields.ts";
|
|
2
|
+
export type FieldPathGroup = {
|
|
3
|
+
path: string;
|
|
4
|
+
label: string;
|
|
5
|
+
kind: "container" | "block" | "row" | "group";
|
|
6
|
+
position?: number;
|
|
7
|
+
};
|
|
8
|
+
/** Schema labels and one-based row positions for a concrete form field path. */
|
|
9
|
+
export declare function fieldPathLabels(fields: FieldNode[], path: string, data: unknown, language?: "en" | "sv"): {
|
|
10
|
+
label: string;
|
|
11
|
+
context?: string;
|
|
12
|
+
full: string;
|
|
13
|
+
};
|
|
14
|
+
/** Structured parents let the review group fields instead of repeating breadcrumbs. */
|
|
15
|
+
export declare function fieldPathInfo(fields: FieldNode[], path: string, data: unknown, language?: "en" | "sv"): {
|
|
16
|
+
label: string;
|
|
17
|
+
context?: string;
|
|
18
|
+
full: string;
|
|
19
|
+
groups: FieldPathGroup[];
|
|
20
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** Schema labels and one-based row positions for a concrete form field path. */
|
|
2
|
+
export function fieldPathLabels(fields, path, data, language = "en") {
|
|
3
|
+
const { groups: _groups, ...labels } = fieldPathInfo(fields, path, data, language);
|
|
4
|
+
return labels;
|
|
5
|
+
}
|
|
6
|
+
/** Structured parents let the review group fields instead of repeating breadcrumbs. */
|
|
7
|
+
export function fieldPathInfo(fields, path, data, language = "en") {
|
|
8
|
+
const parts = [];
|
|
9
|
+
const groups = [];
|
|
10
|
+
let scope = fields;
|
|
11
|
+
let value = data;
|
|
12
|
+
let field;
|
|
13
|
+
let schemaPath = "";
|
|
14
|
+
let concretePath = "";
|
|
15
|
+
for (const segment of path.split(".")) {
|
|
16
|
+
concretePath = concretePath ? `${concretePath}.${segment}` : segment;
|
|
17
|
+
value = value?.[segment];
|
|
18
|
+
if (/^\d+$/.test(segment)) {
|
|
19
|
+
const position = Number(segment) + 1;
|
|
20
|
+
if (field?.type === "blocks") {
|
|
21
|
+
const blockType = value?.blockType;
|
|
22
|
+
const block = field.blocks?.find((block) => block.slug === blockType);
|
|
23
|
+
parts.push(`${name(block?.label ?? block?.slug ?? "block")} (block ${position})`);
|
|
24
|
+
groups.push({
|
|
25
|
+
path: concretePath,
|
|
26
|
+
label: name(block?.label ?? block?.slug ?? "block"),
|
|
27
|
+
kind: "block",
|
|
28
|
+
position,
|
|
29
|
+
});
|
|
30
|
+
scope = block?.fields ?? [];
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const row = language === "sv" ? "rad" : "row";
|
|
34
|
+
parts[parts.length - 1] = `${parts.at(-1) ?? ""} (${row} ${position})`;
|
|
35
|
+
groups.push({
|
|
36
|
+
path: concretePath,
|
|
37
|
+
label: `${name(row)} ${position}`,
|
|
38
|
+
kind: "row",
|
|
39
|
+
position,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
schemaPath = schemaPath ? `${schemaPath}.${segment}` : segment;
|
|
45
|
+
field = scope.find((entry) => entry.path === schemaPath);
|
|
46
|
+
parts.push(name(field?.label ?? segment));
|
|
47
|
+
if (field?.type === "group" || field?.type === "blocks" || field?.type === "array") {
|
|
48
|
+
groups.push({
|
|
49
|
+
path: concretePath,
|
|
50
|
+
label: name(field.label ?? segment),
|
|
51
|
+
kind: field.type === "group" ? "group" : "container",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else if (!field && scope.some((entry) => entry.path.startsWith(`${schemaPath}.`))) {
|
|
55
|
+
groups.push({ path: concretePath, label: name(segment), kind: "group" });
|
|
56
|
+
}
|
|
57
|
+
// Named tabs are flattened by walkFields, but their name remains in each child's path.
|
|
58
|
+
if (field)
|
|
59
|
+
scope = field.fields ?? [];
|
|
60
|
+
}
|
|
61
|
+
const label = parts.at(-1) ?? name(path);
|
|
62
|
+
const context = parts.length > 1 ? parts.slice(0, -1).join(" › ") : undefined;
|
|
63
|
+
return {
|
|
64
|
+
label,
|
|
65
|
+
...(context ? { context } : {}),
|
|
66
|
+
full: parts.join(" › ") || label,
|
|
67
|
+
groups: groups.filter((group) => group.path !== path),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function name(value) {
|
|
71
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
72
|
+
}
|
package/dist/form/shape.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type FieldNode } from "../schema/fields.ts";
|
|
2
|
+
import type { FormOperation } from "./operations.ts";
|
|
2
3
|
import { type RichTextLink } from "./rich-text.ts";
|
|
4
|
+
/** Rich text fields the operations reach, whole or by block, as form paths. */
|
|
5
|
+
export declare function richTextPaths(operations: FormOperation[], fields: FieldNode[], current: Record<string, unknown>): string[];
|
|
3
6
|
/**
|
|
4
7
|
* A copy of `data` where each rich text field the converters can read is a list of `{ keep: i }`,
|
|
5
8
|
* one per stored block, so operations can set, insert or remove single blocks (`layout.1.content.2`)
|
package/dist/form/shape.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
import { fieldAtPath } from "../schema/fields.js";
|
|
1
2
|
import { fromBlocks, isLexicalState, isRichTextBlocks, toBlocks, } from "./rich-text.js";
|
|
3
|
+
/** Rich text fields the operations reach, whole or by block, as form paths. */
|
|
4
|
+
export function richTextPaths(operations, fields, current) {
|
|
5
|
+
const paths = new Set();
|
|
6
|
+
for (const operation of operations) {
|
|
7
|
+
if (!("path" in operation))
|
|
8
|
+
continue;
|
|
9
|
+
const segments = operation.path.split(".");
|
|
10
|
+
for (let end = 1; end <= segments.length; end++) {
|
|
11
|
+
const prefix = segments.slice(0, end).join(".");
|
|
12
|
+
if (fieldAtPath(fields, prefix, current)?.type === "richText") {
|
|
13
|
+
paths.add(prefix);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [...paths];
|
|
19
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* A copy of `data` where each rich text field the converters can read is a list of `{ keep: i }`,
|
|
4
22
|
* one per stored block, so operations can set, insert or remove single blocks (`layout.1.content.2`)
|
package/dist/runtime/agent.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type AgentChatResult = {
|
|
|
13
13
|
capability: "form.propose" | "draft.create" | "content.update";
|
|
14
14
|
/** The form with the change applied, shown in Admin before the editor keeps it. */
|
|
15
15
|
preview?: ProposalPreview;
|
|
16
|
-
/**
|
|
16
|
+
/** Structured fields and rich text previews for Admin's review. */
|
|
17
17
|
lines?: ProposalLine[];
|
|
18
18
|
};
|
|
19
19
|
navigate?: {
|
package/dist/runtime/agent.js
CHANGED
|
@@ -228,7 +228,10 @@ function buildSdkTools(ai, ctx, captured, previews, emit) {
|
|
|
228
228
|
: { ...executed, preview: undefined };
|
|
229
229
|
if (preview && executed.ok && executed.pendingApproval) {
|
|
230
230
|
previews.set(executed.pendingApproval.approvalId, preview);
|
|
231
|
-
emit({
|
|
231
|
+
emit({
|
|
232
|
+
type: "proposal",
|
|
233
|
+
pendingApproval: { ...executed.pendingApproval, lines: executed.lines, preview },
|
|
234
|
+
});
|
|
232
235
|
}
|
|
233
236
|
emit({
|
|
234
237
|
type: "step",
|
package/dist/runtime/tools.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export type ToolSuccess = {
|
|
|
53
53
|
nonce: string;
|
|
54
54
|
capability: "form.propose" | "draft.create" | "content.update";
|
|
55
55
|
};
|
|
56
|
-
/**
|
|
56
|
+
/** Structured review data for Admin, including full rich text previews. */
|
|
57
57
|
lines?: ProposalLine[];
|
|
58
58
|
navigate?: {
|
|
59
59
|
href: string;
|
|
@@ -94,7 +94,36 @@ export declare function executeTool(name: string, input: Record<string, unknown>
|
|
|
94
94
|
*/
|
|
95
95
|
export declare function toolModelOutput(outcome: ToolOutcome): {
|
|
96
96
|
type: "json";
|
|
97
|
-
value:
|
|
97
|
+
value: ToolFailure;
|
|
98
|
+
} | {
|
|
99
|
+
type: "json";
|
|
100
|
+
value: {
|
|
101
|
+
ok: true;
|
|
102
|
+
capability: string;
|
|
103
|
+
data?: unknown;
|
|
104
|
+
pendingApproval?: {
|
|
105
|
+
title: string;
|
|
106
|
+
summary: string;
|
|
107
|
+
approvalId: string;
|
|
108
|
+
nonce: string;
|
|
109
|
+
capability: "form.propose" | "draft.create" | "content.update";
|
|
110
|
+
};
|
|
111
|
+
navigate?: {
|
|
112
|
+
href: string;
|
|
113
|
+
label: string;
|
|
114
|
+
} | {
|
|
115
|
+
candidates: Array<{
|
|
116
|
+
href: string;
|
|
117
|
+
label: string;
|
|
118
|
+
}>;
|
|
119
|
+
};
|
|
120
|
+
/** For the model only, as file parts (see `toolModelOutput`); never sent to Admin. */
|
|
121
|
+
images?: Array<{
|
|
122
|
+
id?: string | number;
|
|
123
|
+
mediaType: string;
|
|
124
|
+
data: string;
|
|
125
|
+
}>;
|
|
126
|
+
};
|
|
98
127
|
} | {
|
|
99
128
|
type: "content";
|
|
100
129
|
value: ({
|
package/dist/runtime/tools.js
CHANGED
|
@@ -233,7 +233,7 @@ export async function executeTool(name, input, ctx) {
|
|
|
233
233
|
nonce: proposed.nonce,
|
|
234
234
|
capability,
|
|
235
235
|
},
|
|
236
|
-
|
|
236
|
+
lines: proposed.lines,
|
|
237
237
|
preview: proposed.preview,
|
|
238
238
|
};
|
|
239
239
|
}
|
|
@@ -246,10 +246,15 @@ export async function executeTool(name, input, ctx) {
|
|
|
246
246
|
* has one, and the rest as text before them.
|
|
247
247
|
*/
|
|
248
248
|
export function toolModelOutput(outcome) {
|
|
249
|
-
if (!outcome.ok
|
|
249
|
+
if (!outcome.ok) {
|
|
250
250
|
return { type: "json", value: outcome };
|
|
251
251
|
}
|
|
252
|
-
|
|
252
|
+
// Full previews belong to Admin; the model already has its operations and the text summary.
|
|
253
|
+
const { lines: _lines, preview: _preview, ...modelOutcome } = outcome;
|
|
254
|
+
if (!modelOutcome.images?.length) {
|
|
255
|
+
return { type: "json", value: modelOutcome };
|
|
256
|
+
}
|
|
257
|
+
const { images, ...rest } = modelOutcome;
|
|
253
258
|
return {
|
|
254
259
|
type: "content",
|
|
255
260
|
value: [
|
package/dist/schema/project.d.ts
CHANGED
|
@@ -3,10 +3,15 @@ import { type FieldNode } from "./fields.ts";
|
|
|
3
3
|
export declare function projectRecord(data: Record<string, unknown> | null | undefined, fields: FieldNode[], redact?: (input: {
|
|
4
4
|
slug: string;
|
|
5
5
|
data: Record<string, unknown>;
|
|
6
|
-
}) => Record<string, unknown>, slug?: string): {
|
|
6
|
+
}) => Record<string, unknown>, slug?: string, mode?: "model" | "form"): {
|
|
7
7
|
data: Record<string, unknown>;
|
|
8
8
|
truncated: boolean;
|
|
9
9
|
};
|
|
10
|
+
/** Allowlisted, redacted form data keeps stored rich text and is never replaced by a size excerpt. */
|
|
11
|
+
export declare function projectFormRecord(data: Record<string, unknown>, fields: FieldNode[], redact?: (input: {
|
|
12
|
+
slug: string;
|
|
13
|
+
data: Record<string, unknown>;
|
|
14
|
+
}) => Record<string, unknown>, slug?: string): Record<string, unknown>;
|
|
10
15
|
export declare function projectIdentity(doc: Record<string, unknown>, locale?: string): Record<string, unknown>;
|
|
11
16
|
export declare function capRecord(data: Record<string, unknown>): {
|
|
12
17
|
data: Record<string, unknown>;
|
package/dist/schema/project.js
CHANGED
|
@@ -2,7 +2,7 @@ import { isDeniedFieldName } from "../config/denied-fields.js";
|
|
|
2
2
|
import { toBlocks } from "../form/rich-text.js";
|
|
3
3
|
import { isFieldAllowed } from "./fields.js";
|
|
4
4
|
const MAX_CHARS = 16_000;
|
|
5
|
-
export function projectRecord(data, fields, redact, slug = "") {
|
|
5
|
+
export function projectRecord(data, fields, redact, slug = "", mode = "model") {
|
|
6
6
|
const projected = {};
|
|
7
7
|
if (!data) {
|
|
8
8
|
return { data: projected, truncated: false };
|
|
@@ -11,10 +11,14 @@ export function projectRecord(data, fields, redact, slug = "") {
|
|
|
11
11
|
if (!(field.name in data)) {
|
|
12
12
|
continue;
|
|
13
13
|
}
|
|
14
|
-
projected[field.name] = projectValue(data[field.name], field);
|
|
14
|
+
projected[field.name] = projectValue(data[field.name], field, mode);
|
|
15
15
|
}
|
|
16
16
|
const redacted = redact ? redact({ slug, data: projected }) : projected;
|
|
17
|
-
return capRecord(redacted);
|
|
17
|
+
return mode === "form" ? { data: redacted, truncated: false } : capRecord(redacted);
|
|
18
|
+
}
|
|
19
|
+
/** Allowlisted, redacted form data keeps stored rich text and is never replaced by a size excerpt. */
|
|
20
|
+
export function projectFormRecord(data, fields, redact, slug = "") {
|
|
21
|
+
return projectRecord(data, fields, redact, slug, "form").data;
|
|
18
22
|
}
|
|
19
23
|
export function projectIdentity(doc, locale) {
|
|
20
24
|
const identity = {
|
|
@@ -53,13 +57,14 @@ export function filterRootKeys(data, allowlist, denyFields) {
|
|
|
53
57
|
}
|
|
54
58
|
return next;
|
|
55
59
|
}
|
|
56
|
-
function projectValue(value, field) {
|
|
60
|
+
function projectValue(value, field, mode) {
|
|
57
61
|
if (value === null || value === undefined) {
|
|
58
62
|
return value;
|
|
59
63
|
}
|
|
60
64
|
if (field.type === "array" && Array.isArray(value)) {
|
|
61
65
|
return value.map((item) => item && typeof item === "object" && !Array.isArray(item)
|
|
62
|
-
? projectRecord(item, field.fields ?? [])
|
|
66
|
+
? projectRecord(item, field.fields ?? [], undefined, "", mode)
|
|
67
|
+
.data
|
|
63
68
|
: item);
|
|
64
69
|
}
|
|
65
70
|
if (field.type === "blocks" && Array.isArray(value)) {
|
|
@@ -70,7 +75,7 @@ function projectValue(value, field) {
|
|
|
70
75
|
const record = item;
|
|
71
76
|
const blockType = typeof record.blockType === "string" ? record.blockType : "";
|
|
72
77
|
const block = field.blocks?.find((entry) => entry.slug === blockType);
|
|
73
|
-
const projected = projectRecord(record, block?.fields ?? []).data;
|
|
78
|
+
const projected = projectRecord(record, block?.fields ?? [], undefined, "", mode).data;
|
|
74
79
|
return {
|
|
75
80
|
id: record.id,
|
|
76
81
|
blockType,
|
|
@@ -79,13 +84,14 @@ function projectValue(value, field) {
|
|
|
79
84
|
});
|
|
80
85
|
}
|
|
81
86
|
if (field.type === "group" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
82
|
-
return projectRecord(value, field.fields ?? [])
|
|
87
|
+
return projectRecord(value, field.fields ?? [], undefined, "", mode)
|
|
88
|
+
.data;
|
|
83
89
|
}
|
|
84
90
|
if (field.type === "relationship" || field.type === "upload") {
|
|
85
91
|
return projectRelation(value);
|
|
86
92
|
}
|
|
87
93
|
if (field.type === "richText") {
|
|
88
|
-
return toBlocks(value, field.editor) ?? value;
|
|
94
|
+
return mode === "form" ? value : (toBlocks(value, field.editor) ?? value);
|
|
89
95
|
}
|
|
90
96
|
return value;
|
|
91
97
|
}
|
|
@@ -240,6 +240,12 @@ html[data-theme="dark"] .ea-aside {
|
|
|
240
240
|
line-height: 1.5;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
.ea-approval__review {
|
|
244
|
+
max-height: 50vh;
|
|
245
|
+
overflow-y: auto;
|
|
246
|
+
margin-bottom: calc(var(--base) * 0.6);
|
|
247
|
+
}
|
|
248
|
+
|
|
243
249
|
.ea-approval__hint {
|
|
244
250
|
margin: 0 0 calc(var(--base) * 0.4);
|
|
245
251
|
color: var(--theme-elevation-500);
|
|
@@ -479,7 +485,7 @@ html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
|
479
485
|
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.6);
|
|
480
486
|
border: 1px solid var(--theme-elevation-150);
|
|
481
487
|
border-radius: var(--style-radius-m);
|
|
482
|
-
background: var(--theme-elevation-
|
|
488
|
+
background: var(--theme-elevation-0);
|
|
483
489
|
font-size: 13px;
|
|
484
490
|
line-height: 1.45;
|
|
485
491
|
}
|
|
@@ -504,10 +510,17 @@ html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
|
504
510
|
}
|
|
505
511
|
|
|
506
512
|
.ea-batch__card-head {
|
|
513
|
+
position: sticky;
|
|
514
|
+
top: 0;
|
|
515
|
+
z-index: 1;
|
|
507
516
|
display: flex;
|
|
508
517
|
align-items: center;
|
|
509
518
|
gap: calc(var(--base) * 0.4);
|
|
510
519
|
min-width: 0;
|
|
520
|
+
margin: calc(var(--base) * -0.5) calc(var(--base) * -0.6) 0;
|
|
521
|
+
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.6);
|
|
522
|
+
border-bottom: 1px solid var(--theme-elevation-150);
|
|
523
|
+
background: var(--theme-elevation-0);
|
|
511
524
|
}
|
|
512
525
|
|
|
513
526
|
.ea-batch__card-head input[type="checkbox"] {
|
|
@@ -549,29 +562,120 @@ html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
|
549
562
|
font-size: 12px;
|
|
550
563
|
}
|
|
551
564
|
|
|
552
|
-
.ea-
|
|
553
|
-
|
|
554
|
-
|
|
565
|
+
.ea-review {
|
|
566
|
+
display: flex;
|
|
567
|
+
flex-direction: column;
|
|
568
|
+
gap: 16px;
|
|
569
|
+
margin-top: 16px;
|
|
570
|
+
font-size: 13px;
|
|
571
|
+
line-height: 1.5;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.ea-review__collection > h4 {
|
|
575
|
+
margin: 0 0 8px;
|
|
576
|
+
color: var(--theme-elevation-600);
|
|
577
|
+
font-size: 11px;
|
|
578
|
+
font-weight: 600;
|
|
579
|
+
letter-spacing: 0.06em;
|
|
580
|
+
text-transform: uppercase;
|
|
555
581
|
}
|
|
556
582
|
|
|
557
|
-
.ea-
|
|
583
|
+
.ea-review__children {
|
|
558
584
|
display: flex;
|
|
559
585
|
flex-direction: column;
|
|
560
|
-
gap:
|
|
561
|
-
|
|
562
|
-
list-style: none;
|
|
586
|
+
gap: 16px;
|
|
587
|
+
min-width: 0;
|
|
563
588
|
}
|
|
564
589
|
|
|
565
|
-
.ea-
|
|
590
|
+
.ea-review__group {
|
|
591
|
+
padding-top: 12px;
|
|
592
|
+
border-top: 1px solid var(--theme-elevation-150);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.ea-review__group > summary {
|
|
596
|
+
display: flex;
|
|
597
|
+
align-items: center;
|
|
598
|
+
gap: 8px;
|
|
599
|
+
cursor: pointer;
|
|
600
|
+
color: var(--theme-text);
|
|
601
|
+
font-size: 14px;
|
|
566
602
|
font-weight: 600;
|
|
603
|
+
list-style: none;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.ea-review__group > summary::-webkit-details-marker {
|
|
607
|
+
display: none;
|
|
567
608
|
}
|
|
568
609
|
|
|
569
|
-
.ea-
|
|
610
|
+
.ea-review__group > summary::before {
|
|
611
|
+
content: "";
|
|
612
|
+
width: 5px;
|
|
613
|
+
height: 5px;
|
|
614
|
+
flex-shrink: 0;
|
|
615
|
+
border-right: 1.5px solid currentColor;
|
|
616
|
+
border-bottom: 1.5px solid currentColor;
|
|
617
|
+
transform: rotate(-45deg);
|
|
618
|
+
transition: transform 100ms;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.ea-review__group[open] > summary::before {
|
|
622
|
+
transform: rotate(45deg);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.ea-review__group > summary:focus-visible {
|
|
626
|
+
outline: 2px solid var(--ea-accent);
|
|
627
|
+
outline-offset: 3px;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.ea-review__group > .ea-review__children {
|
|
631
|
+
margin-top: 14px;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.ea-review__group--row,
|
|
635
|
+
.ea-review__group--group {
|
|
636
|
+
padding-top: 0;
|
|
637
|
+
padding-inline-start: 10px;
|
|
638
|
+
border-top: 0;
|
|
639
|
+
border-inline-start: 2px solid var(--theme-elevation-150);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.ea-review__group--row > summary,
|
|
643
|
+
.ea-review__group--group > summary {
|
|
644
|
+
font-size: 12px;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.ea-review__badge {
|
|
648
|
+
margin-inline-start: auto;
|
|
570
649
|
color: var(--theme-elevation-600);
|
|
571
|
-
|
|
650
|
+
font-size: 11px;
|
|
651
|
+
font-weight: 400;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.ea-review__field {
|
|
655
|
+
display: flex;
|
|
656
|
+
flex-direction: column;
|
|
657
|
+
gap: 6px;
|
|
658
|
+
min-width: 0;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.ea-review__field-head {
|
|
662
|
+
display: flex;
|
|
663
|
+
align-items: center;
|
|
664
|
+
justify-content: space-between;
|
|
665
|
+
gap: 8px;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.ea-review__field-head label {
|
|
669
|
+
font-size: 12px;
|
|
670
|
+
font-weight: 600;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.ea-review__new {
|
|
674
|
+
color: var(--theme-elevation-500);
|
|
675
|
+
font-size: 10px;
|
|
572
676
|
}
|
|
573
677
|
|
|
574
|
-
.ea-
|
|
678
|
+
.ea-review__field textarea {
|
|
575
679
|
box-sizing: border-box;
|
|
576
680
|
width: 100%;
|
|
577
681
|
padding: calc(var(--base) * 0.3) calc(var(--base) * 0.4);
|
|
@@ -586,12 +690,111 @@ html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
|
586
690
|
min-height: calc(2lh + calc(var(--base) * 0.6));
|
|
587
691
|
}
|
|
588
692
|
|
|
589
|
-
.ea-
|
|
693
|
+
.ea-review__field textarea:focus-visible {
|
|
694
|
+
outline: 2px solid var(--ea-accent);
|
|
695
|
+
outline-offset: 1px;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.ea-review__count {
|
|
590
699
|
align-self: flex-end;
|
|
591
700
|
color: var(--theme-elevation-500);
|
|
592
701
|
font-size: 11px;
|
|
593
702
|
}
|
|
594
703
|
|
|
704
|
+
.ea-review__current {
|
|
705
|
+
color: var(--theme-elevation-600);
|
|
706
|
+
font-size: 12px;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.ea-review__current > summary {
|
|
710
|
+
cursor: pointer;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.ea-review__current > p,
|
|
714
|
+
.ea-review__value {
|
|
715
|
+
margin: 0;
|
|
716
|
+
white-space: pre-wrap;
|
|
717
|
+
overflow-wrap: anywhere;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.ea-review__current > p,
|
|
721
|
+
.ea-review__current > .ea-review__rich {
|
|
722
|
+
margin-top: 8px;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.ea-review__preview,
|
|
726
|
+
.ea-review__value {
|
|
727
|
+
padding: 12px;
|
|
728
|
+
border: 1px solid var(--theme-elevation-150);
|
|
729
|
+
border-radius: var(--style-radius-s);
|
|
730
|
+
background: var(--theme-elevation-50);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.ea-review__rich {
|
|
734
|
+
color: var(--theme-text);
|
|
735
|
+
line-height: 1.65;
|
|
736
|
+
overflow-wrap: anywhere;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.ea-review__rich > :first-child {
|
|
740
|
+
margin-top: 0;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.ea-review__rich > :last-child {
|
|
744
|
+
margin-bottom: 0;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.ea-review__rich p {
|
|
748
|
+
margin: 0 0 12px;
|
|
749
|
+
white-space: pre-wrap;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.ea-review__rich h1,
|
|
753
|
+
.ea-review__rich h2,
|
|
754
|
+
.ea-review__rich h3,
|
|
755
|
+
.ea-review__rich h4,
|
|
756
|
+
.ea-review__rich h5,
|
|
757
|
+
.ea-review__rich h6 {
|
|
758
|
+
margin: 16px 0 8px;
|
|
759
|
+
font-size: 14px;
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
line-height: 1.4;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.ea-review__rich ul,
|
|
765
|
+
.ea-review__rich ol {
|
|
766
|
+
margin: 0 0 12px;
|
|
767
|
+
padding-inline-start: 20px;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.ea-review__rich li + li {
|
|
771
|
+
margin-top: 4px;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.ea-review__rich blockquote {
|
|
775
|
+
margin: 12px 0;
|
|
776
|
+
padding-inline-start: 12px;
|
|
777
|
+
border-inline-start: 2px solid var(--theme-elevation-250);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.ea-review__link {
|
|
781
|
+
text-decoration: underline;
|
|
782
|
+
text-underline-offset: 2px;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.ea-review__embedded {
|
|
786
|
+
display: block;
|
|
787
|
+
padding: 8px;
|
|
788
|
+
border: 1px dashed var(--theme-elevation-250);
|
|
789
|
+
color: var(--theme-elevation-600);
|
|
790
|
+
font-size: 12px;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.ea-review__change p {
|
|
794
|
+
margin: 0;
|
|
795
|
+
white-space: pre-wrap;
|
|
796
|
+
}
|
|
797
|
+
|
|
595
798
|
.ea-batch__text,
|
|
596
799
|
.ea-batch__hint {
|
|
597
800
|
margin: calc(var(--base) * 0.3) 0 0;
|