@ptengine/inline-editor 0.1.5 → 0.1.7
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 +69 -0
- package/dist/InlineEditor.d.ts +15 -1
- package/dist/InlineEditor.js +226 -52
- package/dist/canvas-ui/tree/TreePanel.d.ts +2 -1
- package/dist/canvas-ui/tree/TreePanel.js +79 -39
- package/dist/chrome/EditorChrome.d.ts +2 -0
- package/dist/chrome/EditorChrome.js +1 -1
- package/dist/chrome/VersionMenu.d.ts +3 -1
- package/dist/chrome/VersionMenu.js +24 -12
- package/dist/chrome/topbarPrimitives.d.ts +5 -52
- package/dist/chrome/topbarPrimitives.js +13 -31
- package/dist/dialogs/ClickActionDialog.js +4 -5
- package/dist/dialogs/CodeField.d.ts +4 -0
- package/dist/dialogs/CodeField.js +10 -0
- package/dist/dialogs/HtmlEditDialog.js +3 -2
- package/dist/dialogs/ImageEditDialog.js +53 -15
- package/dist/dialogs/ImageSettingDialog.js +3 -3
- package/dist/dialogs/InsertElementDialog.js +10 -6
- package/dist/dialogs/MediaPreview.d.ts +5 -0
- package/dist/dialogs/MediaPreview.js +34 -0
- package/dist/dialogs/ReplaceVideoDialog.js +8 -4
- package/dist/index.d.ts +1 -0
- package/dist/kernel/ai/aiInlineApply.d.ts +4 -3
- package/dist/kernel/blocks/BlockOverlay.js +1 -1
- package/dist/kernel/blocks/resolveBlocks.d.ts +16 -0
- package/dist/kernel/blocks/resolveBlocks.js +39 -6
- package/dist/kernel/changelist/ChangeListPanel.js +11 -9
- package/dist/kernel/changelist/useChangeList.d.ts +5 -0
- package/dist/kernel/changelist/useChangeList.js +85 -10
- package/dist/kernel/selection/SelectionOverlay.js +1 -1
- package/dist/kernel/selection/SelectionToolbar.js +26 -8
- package/dist/kernel/selection/buildRootShelf.d.ts +56 -2
- package/dist/kernel/selection/buildRootShelf.js +189 -41
- package/dist/kernel/selection/elementAiContext.d.ts +11 -3
- package/dist/kernel/selection/resolveAnchors.d.ts +17 -0
- package/dist/kernel/selection/resolveAnchors.js +59 -11
- package/dist/locales.d.ts +201 -48
- package/dist/locales.js +13 -9
- package/dist/panels/EditorStatus.d.ts +5 -0
- package/dist/panels/EditorStatus.js +4 -0
- package/dist/panels/MovePositionControls.d.ts +6 -0
- package/dist/panels/MovePositionControls.js +35 -0
- package/dist/panels/ScreenshotCard.d.ts +7 -0
- package/dist/panels/ScreenshotCard.js +57 -0
- package/dist/panels/StylePanel.d.ts +9 -1
- package/dist/panels/StylePanel.js +108 -12
- package/dist/reviewLocales.d.ts +149 -0
- package/dist/reviewLocales.js +143 -0
- package/dist/tree/LayersPanel.js +4 -2
- package/dist/useInlineEditor.d.ts +1 -0
- package/dist/useInlineEditor.js +40 -1
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
|
4
4
|
import { TREE_CLASS, TREE_ROW_HEIGHT } from './classNames.js';
|
|
5
5
|
import { dropPosition, rangeBetween, visibleOrder } from './order.js';
|
|
6
6
|
const DRAG_MIME = 'text/x-tree-id';
|
|
7
|
-
export function TreePanel({ nodes, rootId, selectedId, hoverId, query, onSelect, onShiftSelect, onHover, onMove, onExpand }) {
|
|
7
|
+
export function TreePanel({ emptyLabel = 'No matching layers', nodes, rootId, selectedId, hoverId, query, onSelect, onShiftSelect, onHover, onMove, onExpand }) {
|
|
8
8
|
const [collapsed, setCollapsed] = useState(new Set());
|
|
9
9
|
const [drop, setDrop] = useState(null);
|
|
10
10
|
const searching = !!query?.trim();
|
|
@@ -88,45 +88,85 @@ export function TreePanel({ nodes, rootId, selectedId, hoverId, query, onSelect,
|
|
|
88
88
|
next.add(id);
|
|
89
89
|
return next;
|
|
90
90
|
});
|
|
91
|
-
return (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const dragId = e.dataTransfer.getData(DRAG_MIME);
|
|
119
|
-
setDrop(null);
|
|
120
|
-
if (!dragId || dragId === id || !onMove)
|
|
121
|
-
return;
|
|
122
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
123
|
-
onMove(dragId, id, dropPosition(e.clientY - rect.top, TREE_ROW_HEIGHT, node.canContain));
|
|
124
|
-
}, children: [hasChildren ? (_jsx("button", { type: "button", "data-testid": `tree-toggle-${id}`, className: TREE_CLASS.toggle, onClick: e => {
|
|
125
|
-
e.stopPropagation();
|
|
126
|
-
if (needsLoad)
|
|
91
|
+
return (_jsxs("div", { ref: scrollRef, role: "tree", "aria-label": "\u56FE\u5C42", className: TREE_CLASS.tree, "data-testid": "tree-panel", onMouseLeave: () => onHover(null), children: [rows.length === 0 && (_jsx("p", { role: "status", className: "p-3 text-xs text-muted-foreground", children: emptyLabel })), rows.map(id => {
|
|
92
|
+
const node = nodes[id];
|
|
93
|
+
if (!node)
|
|
94
|
+
return null;
|
|
95
|
+
const depth = depthOf.get(id) ?? 0;
|
|
96
|
+
const needsLoad = node.children.length === 0 && !!node.truncated;
|
|
97
|
+
const hasChildren = node.children.length > 0 || (needsLoad && !!onExpand);
|
|
98
|
+
const dropHere = drop?.targetId === id ? drop.pos : null;
|
|
99
|
+
return (_jsxs("div", { role: "treeitem", tabIndex: id === selectedId ||
|
|
100
|
+
(!rows.includes(selectedId ?? '') && id === rows[0])
|
|
101
|
+
? 0
|
|
102
|
+
: -1, "aria-selected": id === selectedId, "aria-level": depth + 1, "aria-expanded": hasChildren ? !collapsed.has(id) : undefined, onKeyDown: e => {
|
|
103
|
+
if (e.target !== e.currentTarget)
|
|
104
|
+
return;
|
|
105
|
+
let next;
|
|
106
|
+
if (e.key === 'ArrowDown')
|
|
107
|
+
next = rows[rows.indexOf(id) + 1];
|
|
108
|
+
else if (e.key === 'ArrowUp')
|
|
109
|
+
next = rows[rows.indexOf(id) - 1];
|
|
110
|
+
else if (e.key === 'Home')
|
|
111
|
+
next = rows[0];
|
|
112
|
+
else if (e.key === 'End')
|
|
113
|
+
next = rows[rows.length - 1];
|
|
114
|
+
else if (e.key === 'ArrowRight') {
|
|
115
|
+
if (hasChildren && collapsed.has(id))
|
|
116
|
+
toggle(id);
|
|
117
|
+
else if (needsLoad)
|
|
127
118
|
onExpand?.(id);
|
|
128
119
|
else
|
|
120
|
+
next = node.children[0];
|
|
121
|
+
}
|
|
122
|
+
else if (e.key === 'ArrowLeft') {
|
|
123
|
+
if (hasChildren && !collapsed.has(id))
|
|
129
124
|
toggle(id);
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
else
|
|
126
|
+
next = parentOf.get(id);
|
|
127
|
+
}
|
|
128
|
+
else if (e.key === 'Enter' || e.key === ' ')
|
|
129
|
+
onSelect(id);
|
|
130
|
+
else
|
|
131
|
+
return;
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
if (next) {
|
|
134
|
+
onSelect(next);
|
|
135
|
+
Array.from(scrollRef.current?.querySelectorAll('[role="treeitem"]') ?? [])
|
|
136
|
+
.find(row => row.dataset.id === next)
|
|
137
|
+
?.focus();
|
|
138
|
+
}
|
|
139
|
+
}, "data-testid": "tree-row", "data-row-id": `tree-row-${id}`, "data-id": id, "data-depth": depth, className: `${TREE_CLASS.row}${selectedId === id ? ' is-selected' : ''}${hoverId === id ? ' is-hovered' : ''}${dropHere ? ` drop-${dropHere}` : ''}`, style: { paddingLeft: `${8 + depth * 12}px` }, draggable: onMove ? true : undefined, onMouseEnter: () => onHover(id), onClick: e => {
|
|
140
|
+
if (e.shiftKey && selectedId && onShiftSelect) {
|
|
141
|
+
onShiftSelect(rangeBetween(order, selectedId, id));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
onSelect(id);
|
|
145
|
+
}, onDragStart: e => e.dataTransfer.setData(DRAG_MIME, id), onDragOver: e => {
|
|
146
|
+
if (!onMove)
|
|
147
|
+
return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
150
|
+
const pos = dropPosition(e.clientY - rect.top, TREE_ROW_HEIGHT, node.canContain);
|
|
151
|
+
if (drop?.targetId !== id || drop.pos !== pos) {
|
|
152
|
+
setDrop({ targetId: id, pos });
|
|
153
|
+
}
|
|
154
|
+
}, onDragLeave: () => {
|
|
155
|
+
if (drop?.targetId === id)
|
|
156
|
+
setDrop(null);
|
|
157
|
+
}, onDrop: e => {
|
|
158
|
+
const dragId = e.dataTransfer.getData(DRAG_MIME);
|
|
159
|
+
setDrop(null);
|
|
160
|
+
if (!dragId || dragId === id || !onMove)
|
|
161
|
+
return;
|
|
162
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
163
|
+
onMove(dragId, id, dropPosition(e.clientY - rect.top, TREE_ROW_HEIGHT, node.canContain));
|
|
164
|
+
}, children: [hasChildren ? (_jsx("button", { type: "button", tabIndex: -1, "aria-label": node.label, "aria-expanded": !collapsed.has(id), "data-testid": `tree-toggle-${id}`, className: TREE_CLASS.toggle, onClick: e => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
if (needsLoad)
|
|
167
|
+
onExpand?.(id);
|
|
168
|
+
else
|
|
169
|
+
toggle(id);
|
|
170
|
+
}, children: collapsed.has(id) && !searching ? (_jsx(ChevronRight, { className: "size-3" })) : (_jsx(ChevronDown, { className: "size-3" })) })) : (_jsx("span", { className: "layer-chevron-spacer" })), node.icon && _jsx("span", { className: TREE_CLASS.icon, children: node.icon }), _jsx("span", { className: TREE_CLASS.label, title: node.label, children: node.label })] }, id));
|
|
171
|
+
})] }));
|
|
132
172
|
}
|
|
@@ -50,6 +50,8 @@ export interface EditorChromeProps {
|
|
|
50
50
|
onAddVersion?: () => void;
|
|
51
51
|
/** 版本改名。**不传则下拉行里没有那支铅笔** —— 同上。 */
|
|
52
52
|
onRenameVersion?: (id: string, name: string) => void;
|
|
53
|
+
removableVersionIds?: string[];
|
|
54
|
+
onRemoveVersion?: (id: string) => void;
|
|
53
55
|
changeCount: number;
|
|
54
56
|
onExit: () => void;
|
|
55
57
|
onSave?: () => void;
|
|
@@ -6,5 +6,5 @@ import { ChromeBackButton, ChromeDivider, ChromeIconButton, ChromePrimaryButton,
|
|
|
6
6
|
export function EditorChrome(props) {
|
|
7
7
|
const { t } = useTranslation();
|
|
8
8
|
const full = props.variant === 'full';
|
|
9
|
-
return (_jsxs("header", { "data-testid": full ? 'editor-chrome-full' : 'editor-chrome-minimal', className: "relative flex h-[42px] shrink-0 items-center justify-between gap-2\n border-b border-[#ececef] bg-white px-3", children: [_jsxs("div", { className: "flex min-w-0 items-center", children: [_jsx(ChromeBackButton, { "data-testid": "chrome-exit", label: t(full ? 'inlineCanvas.chrome.exit' : 'inlineCanvas.chrome.close', full ? '退出' : '关闭'), onClick: props.onExit, children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsx(ChromeDivider, {}), _jsx("span", { className: "max-w-[240px] truncate rounded px-2 py-1 text-[13px] font-medium\n text-[#1a1a1a]", children: props.title }), full && (_jsx(VersionMenu, { versions: props.versions, activeVersionId: props.activeVersionId, onVersionChange: props.onVersionChange, onAddVersion: props.onAddVersion, onRenameVersion: props.onRenameVersion, switching: props.versionSwitching, fallbackLabel: props.versionLabel }))] }), _jsxs("div", { className: "flex shrink-0 items-center", children: [full && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(ChromeIconButton, { "data-testid": "chrome-undo", label: t('inlineCanvas.chrome.undo', '撤销'), disabled: !props.canUndo, onClick: props.onUndo, children: _jsx(Undo2, { className: "size-4" }) }), _jsx(ChromeIconButton, { "data-testid": "chrome-redo", label: t('inlineCanvas.chrome.redo', '重做'), disabled: !props.canRedo, onClick: props.onRedo, children: _jsx(Redo2, { className: "size-4" }) })] }), _jsx(ChromeDivider, {})] })), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(ChromeTextButton, { "data-testid": "chrome-changelist", "data-changelist-trigger": true, active: props.changeListOpen, onClick: props.onToggleChangeList, children: [_jsx(List, { className: "size-3.5" }), t('inlineCanvas.chrome.changeList', '改动清单'), props.changeCount > 0 && (_jsx("span", { className: "tabular-nums text-[#8a8f98]", children: props.changeCount }))] }), props.onPreview && (_jsx(ChromeTextButton, { "data-testid": "chrome-preview", onClick: props.onPreview, children: t('inlineCanvas.chrome.preview', '预览') })), full && (_jsx(ChromePrimaryButton, { "data-testid": "chrome-save", disabled: props.versionSwitching, onClick: props.onSave, children: t('inlineCanvas.
|
|
9
|
+
return (_jsxs("header", { "data-testid": full ? 'editor-chrome-full' : 'editor-chrome-minimal', className: "relative flex h-[42px] shrink-0 items-center justify-between gap-2\n border-b border-[#ececef] bg-white px-3", children: [_jsxs("div", { className: "flex min-w-0 items-center", children: [_jsx(ChromeBackButton, { "data-testid": "chrome-exit", label: t(full ? 'inlineCanvas.chrome.exit' : 'inlineCanvas.chrome.close', full ? '退出' : '关闭'), onClick: props.onExit, children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsx(ChromeDivider, {}), _jsx("span", { className: "max-w-[240px] truncate rounded px-2 py-1 text-[13px] font-medium\n text-[#1a1a1a]", children: props.title }), full && (_jsx(VersionMenu, { versions: props.versions, activeVersionId: props.activeVersionId, onVersionChange: props.onVersionChange, onAddVersion: props.onAddVersion, onRenameVersion: props.onRenameVersion, removableVersionIds: props.removableVersionIds, onRemoveVersion: props.onRemoveVersion, switching: props.versionSwitching, fallbackLabel: props.versionLabel }))] }), _jsxs("div", { className: "flex shrink-0 items-center", children: [full && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(ChromeIconButton, { "data-testid": "chrome-undo", label: t('inlineCanvas.chrome.undo', '撤销'), disabled: !props.canUndo, onClick: props.onUndo, children: _jsx(Undo2, { className: "size-4" }) }), _jsx(ChromeIconButton, { "data-testid": "chrome-redo", label: t('inlineCanvas.chrome.redo', '重做'), disabled: !props.canRedo, onClick: props.onRedo, children: _jsx(Redo2, { className: "size-4" }) })] }), _jsx(ChromeDivider, {})] })), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(ChromeTextButton, { "data-testid": "chrome-changelist", "data-changelist-trigger": true, active: props.changeListOpen, onClick: props.onToggleChangeList, children: [_jsx(List, { className: "size-3.5" }), t('inlineCanvas.chrome.changeList', '改动清单'), props.changeCount > 0 && (_jsx("span", { className: "tabular-nums text-[#8a8f98]", children: props.changeCount }))] }), props.onPreview && (_jsx(ChromeTextButton, { "data-testid": "chrome-preview", onClick: props.onPreview, children: t('inlineCanvas.chrome.preview', '预览') })), full && (_jsx(ChromePrimaryButton, { "data-testid": "chrome-save", disabled: props.versionSwitching, onClick: props.onSave, children: t('inlineCanvas.review.saveDraft', '保存草稿') })), props.onToggleAi && (_jsx(ChromeIconButton, { "data-testid": "chrome-ai", label: t('inlineCanvas.chrome.ai', 'AI 助手'), active: props.aiOpen, onClick: props.onToggleAi, children: _jsx(Sparkles, { className: "size-4" }) })), props.slotRight] })] })] }));
|
|
10
10
|
}
|
|
@@ -15,6 +15,8 @@ export interface VersionMenuProps {
|
|
|
15
15
|
* 所以这里只做校验与交互,改不改得成由宿主说了算。
|
|
16
16
|
*/
|
|
17
17
|
onRenameVersion?: (id: string, name: string) => void;
|
|
18
|
+
removableVersionIds?: string[];
|
|
19
|
+
onRemoveVersion?: (id: string) => void;
|
|
18
20
|
/**
|
|
19
21
|
* 正在切换版本(撤光当前版本的改动 + 回放另一版本的)。为真时整个入口禁用 ——
|
|
20
22
|
* 切换是一串跨域命令,中途再选一次会让两个版本的改动交错落在同一个页面上。
|
|
@@ -23,4 +25,4 @@ export interface VersionMenuProps {
|
|
|
23
25
|
/** 没有 `versions` 时展示的静态字符串(旧调用方、单版本入口)。 */
|
|
24
26
|
fallbackLabel: string;
|
|
25
27
|
}
|
|
26
|
-
export declare function VersionMenu({ versions, activeVersionId, onVersionChange, onAddVersion, onRenameVersion, switching, fallbackLabel }: VersionMenuProps): import("react").JSX.Element;
|
|
28
|
+
export declare function VersionMenu({ versions, activeVersionId, onVersionChange, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, switching, fallbackLabel }: VersionMenuProps): import("react").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useRef, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Check, ChevronDown, Pencil, Plus } from 'lucide-react';
|
|
5
|
-
import {
|
|
5
|
+
import { Button, Input, Popover, PopoverTrigger, PopoverContent } from '@ptengine/design-components';
|
|
6
6
|
const MAX_VERSION_NAME = 40;
|
|
7
7
|
function versionLetter(index) {
|
|
8
8
|
return index < 26 ? String.fromCharCode(65 + index) : String(index + 1);
|
|
@@ -12,7 +12,7 @@ function descriptiveName(name, label) {
|
|
|
12
12
|
const trimmed = name.trim();
|
|
13
13
|
return trimmed && trimmed !== label.trim() ? trimmed : null;
|
|
14
14
|
}
|
|
15
|
-
export function VersionMenu({ versions, activeVersionId, onVersionChange, onAddVersion, onRenameVersion, switching = false, fallbackLabel }) {
|
|
15
|
+
export function VersionMenu({ versions, activeVersionId, onVersionChange, onAddVersion, onRenameVersion, removableVersionIds = [], onRemoveVersion, switching = false, fallbackLabel }) {
|
|
16
16
|
const { t } = useTranslation();
|
|
17
17
|
const [open, setOpen] = useState(false);
|
|
18
18
|
const ref = useRef(null);
|
|
@@ -21,7 +21,6 @@ export function VersionMenu({ versions, activeVersionId, onVersionChange, onAddV
|
|
|
21
21
|
setOpen(false);
|
|
22
22
|
setEditing(null);
|
|
23
23
|
}, []);
|
|
24
|
-
useDismissOnOutside(open, ref, close, 'version-trigger');
|
|
25
24
|
if (!versions?.length) {
|
|
26
25
|
return (_jsx("span", { "data-testid": "chrome-version-static", className: "shrink-0 rounded bg-[#f3f4f4] px-2.5 py-1 text-xs text-[#1a1a1a]", children: fallbackLabel }));
|
|
27
26
|
}
|
|
@@ -56,10 +55,18 @@ export function VersionMenu({ versions, activeVersionId, onVersionChange, onAddV
|
|
|
56
55
|
const active = activeIndex >= 0 ? versions[activeIndex] : undefined;
|
|
57
56
|
const label = (i) => t(VARIANT_KEY, { name: versionLetter(i), defaultValue: `版本 ${versionLetter(i)}` });
|
|
58
57
|
const activeLabel = activeIndex >= 0 ? label(activeIndex) : '';
|
|
59
|
-
return (_jsxs(
|
|
58
|
+
return (_jsxs(Popover, { open: open, onOpenChange: v => {
|
|
59
|
+
setOpen(v);
|
|
60
|
+
if (!v)
|
|
61
|
+
setEditing(null);
|
|
62
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", "data-testid": "chrome-version-trigger", "data-version-trigger": true, "aria-haspopup": "dialog", "aria-expanded": open, disabled: switching, variant: "secondary", className: "max-w-[260px]", children: [active ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-[#12262b]", children: activeLabel }), descriptiveName(active.name, activeLabel) && (_jsxs("span", { className: "truncate text-[#12262b]", children: ["\uFF1A", active.name] }))] })) : (_jsx("span", { className: "truncate text-[#52525b]", children: fallbackLabel })), _jsx(ChevronDown, { className: "size-3.5 shrink-0 text-[#8a8f98]" })] }) }), open && (_jsxs(PopoverContent, { align: "start", ref: ref, "data-testid": "chrome-version-menu", role: "dialog", "aria-label": t('inlineCanvas.review.versions', '版本管理'), className: "z-[1050] w-[320px] p-2", children: [versions.map((v, i) => {
|
|
60
63
|
const on = v.id === activeVersionId;
|
|
61
64
|
if (editing?.id === v.id) {
|
|
62
|
-
return (_jsxs("div", { className: "px-2.5 py-1.5", children: [_jsx(
|
|
65
|
+
return (_jsxs("div", { className: "px-2.5 py-1.5", children: [_jsx(Input, { autoFocus: true, "data-testid": `chrome-version-rename-input-${v.id}`, value: editing.draft, onChange: e => setEditing({
|
|
66
|
+
id: v.id,
|
|
67
|
+
draft: e.target.value,
|
|
68
|
+
error: null
|
|
69
|
+
}), onKeyDown: e => {
|
|
63
70
|
if (e.key === 'Escape') {
|
|
64
71
|
e.stopPropagation();
|
|
65
72
|
setEditing(null);
|
|
@@ -69,18 +76,23 @@ export function VersionMenu({ versions, activeVersionId, onVersionChange, onAddV
|
|
|
69
76
|
e.stopPropagation();
|
|
70
77
|
commitRename(v);
|
|
71
78
|
}
|
|
72
|
-
},
|
|
79
|
+
}, "aria-label": t('inlineCanvas.chrome.renameVersion', '重命名') }), _jsxs("div", { className: "mt-2 flex justify-end gap-2", children: [_jsx(Button, { size: "sm", variant: "secondary", onClick: () => setEditing(null), children: t('common.cancel', '取消') }), _jsx(Button, { size: "sm", onClick: () => commitRename(v), children: t('common.apply', '应用') })] }), editing.error && (_jsx("div", { "data-testid": "chrome-version-rename-error", className: "mt-1 px-0.5 text-xs text-[#d4380d]", children: editing.error }))] }, v.id));
|
|
73
80
|
}
|
|
74
|
-
return (_jsxs("div", { className: "group flex w-full items-center gap-1", children: [_jsxs(
|
|
81
|
+
return (_jsxs("div", { className: "group flex w-full items-center gap-1", children: [_jsxs(Button, { type: "button", variant: "ghost", "aria-pressed": on, "data-testid": `chrome-version-option-${v.id}`, onClick: () => {
|
|
75
82
|
setOpen(false);
|
|
76
83
|
if (!on)
|
|
77
84
|
onVersionChange?.(v.id);
|
|
78
|
-
}, className: `flex min-w-0 flex-1 items-center gap-2 rounded-md px-2.5 py-2
|
|
79
|
-
text-left text-[
|
|
85
|
+
}, className: `flex min-w-0 flex-1 items-center justify-start gap-2 rounded-md px-2.5 py-2
|
|
86
|
+
text-left text-[13px] transition-colors
|
|
80
87
|
hover:bg-[rgba(16,28,31,0.06)]
|
|
81
|
-
${on ? 'bg-[rgba(16,28,31,0.06)]' : ''}`, children: [_jsx("span", { className: "shrink-0 text-[#12262b]", children: label(i) }), descriptiveName(v.name, label(i)) && (_jsxs("span", { className: "truncate text-[#12262b]", children: ["\uFF1A", v.name] })), on && _jsx(Check, { className: "ml-auto size-3.5 shrink-0 text-[#12262b]" })] }),
|
|
82
|
-
|
|
88
|
+
${on ? 'bg-[rgba(16,28,31,0.06)]' : ''}`, children: [_jsx("span", { className: "shrink-0 text-[#12262b]", children: label(i) }), descriptiveName(v.name, label(i)) && (_jsxs("span", { className: "truncate text-[#12262b]", children: ["\uFF1A", v.name] })), on && (_jsx(Check, { className: "ml-auto size-3.5 shrink-0 text-[#12262b]" }))] }), removableVersionIds.includes(v.id) && onRemoveVersion && (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": `${v.name} · ${t('inlineCanvas.review.removeEmptyVersion', '移除空版本')}`, onClick: () => {
|
|
89
|
+
onRemoveVersion(v.id);
|
|
90
|
+
close();
|
|
91
|
+
}, children: t('inlineCanvas.review.removeEmptyVersion', '移除空版本') })), onRenameVersion && (_jsx(Button, { type: "button", variant: "link", size: "icon-sm", "data-testid": `chrome-version-rename-${v.id}`, title: t('inlineCanvas.chrome.renameVersion', '重命名'), "aria-label": t('inlineCanvas.chrome.renameVersion', '重命名'), onClick: () => setEditing({ id: v.id, draft: v.name, error: null }), className: "shrink-0 rounded-md p-1.5 text-[#6b7477] opacity-0\n transition-opacity\n focus-visible:opacity-100 group-hover:opacity-100", children: _jsx(Pencil, { className: "size-3.5" }) }))] }, v.id));
|
|
92
|
+
}), onAddVersion && (_jsxs(Button, { type: "button", variant: "ghost", "data-testid": "chrome-add-version", disabled: versions.length >= 4, onClick: () => {
|
|
83
93
|
setOpen(false);
|
|
84
94
|
onAddVersion();
|
|
85
|
-
}, className: "mt-1 flex w-full items-center gap-1.5 border-t\n border-[#f1f2f3] px-2.5 pb-1 pt-2.5 text-left text-[
|
|
95
|
+
}, className: "mt-1 flex w-full items-center justify-start gap-1.5 border-t\n border-[#f1f2f3] px-2.5 pb-1 pt-2.5 text-left text-[13px]\n text-foreground", children: [_jsx(Plus, { className: "size-3.5" }), versions.length >= 4
|
|
96
|
+
? t('inlineCanvas.review.versionLimit', '最多 4 个版本')
|
|
97
|
+
: t('inlineCanvas.chrome.addVersion', '新增版本')] }))] }))] }));
|
|
86
98
|
}
|
|
@@ -1,69 +1,22 @@
|
|
|
1
1
|
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* 顶栏原语 —— 尺寸、间距、色值**逐条对齐三代落地页编辑器的顶栏**
|
|
4
|
-
* (`apps/x/src/components/landing-pages/editor/topbar/topbar.css` 的 `lpx-tb-*`,
|
|
5
|
-
* 以及 `landing-page-editor-v3/styles/editor.css` 的 `.editor-toolbar` 系列)。
|
|
6
|
-
*
|
|
7
|
-
* ## 为什么是抄取值,不是复用组件
|
|
8
|
-
*
|
|
9
|
-
* 那套原语住在 app 里(它依赖 app 的 `SimpleTooltip` 与 `--lpv2-*` token),而本包
|
|
10
|
-
* **不能反向依赖 app** —— 同 `renderNodeIcon` 由宿主注入的理由。
|
|
11
|
-
*
|
|
12
|
-
* ## 色值也跟着换了
|
|
13
|
-
*
|
|
14
|
-
* 早先这里只搬几何、色值留在本包原有的 #091E42 档,理由是「顶栏与它下面的三栏不同色」。
|
|
15
|
-
* 实际做出来两套灰在同一屏上更别扭 —— 顶栏现在整条走 lpx 的中性档(#1a1a1a / #52525b /
|
|
16
|
-
* #ececef / #12262b),下面的图层树、样式面板、清单不动。顶栏是一条独立的横带,
|
|
17
|
-
* 它和落地页编辑器一致比和自己脚下的面板一致更重要。
|
|
18
|
-
*
|
|
19
|
-
* ## 为什么用 `title` 而不是 tooltip 组件
|
|
20
|
-
*
|
|
21
|
-
* 图标按钮没有可见文字,必须有悬浮提示。本包里没有 tooltip 原语,而 Radix 那套在
|
|
22
|
-
* jsdom 里点不开(`useDismissOnOutside` 的头注释有同一条理由)—— 原生 `title`
|
|
23
|
-
* 加 `aria-label` 是这里唯一既能用又守得住的做法。
|
|
24
|
-
*/
|
|
25
|
-
/** 顶栏分组分隔线:1×18 `#e7e8e9`,左右各 8px(= lpx-tb-divider)。 */
|
|
26
2
|
export declare function ChromeDivider(): import("react").JSX.Element;
|
|
27
3
|
export interface ChromeIconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
28
|
-
/** 悬浮提示 + 无障碍名。图标按钮没有可见文字,**必填**。 */
|
|
29
4
|
label: string;
|
|
30
|
-
/**
|
|
31
|
-
* 选中 / 面板展开中。
|
|
32
|
-
*
|
|
33
|
-
* ⚠️ **不传就不写 `aria-pressed`**(不是写 false)。`aria-pressed` 的含义是「这是个
|
|
34
|
-
* 双态开关」—— 撤销、重做、预览都是一次性动作,给它们写上 false 等于告诉读屏软件
|
|
35
|
-
* 「这个开关现在是关的」,那是假的。只有 AI 开关、改动清单这类真开关才带。
|
|
36
|
-
*/
|
|
37
5
|
active?: boolean;
|
|
38
6
|
children: ReactNode;
|
|
39
7
|
}
|
|
40
|
-
|
|
41
|
-
export declare
|
|
42
|
-
|
|
43
|
-
* 退出按钮(= .toolbar-back-btn)——**带边框、radius 6**,与顶栏其它图标按钮刻意不同。
|
|
44
|
-
*
|
|
45
|
-
* 它是这条顶栏上唯一一个「离开当前上下文」的动作,落地页编辑器也是这么区分的:
|
|
46
|
-
* 其余图标按钮是透明底 radius 8,只有它有边框。
|
|
47
|
-
*/
|
|
48
|
-
export declare function ChromeBackButton({ label, className, children, ...rest }: ChromeIconButtonProps): import("react").JSX.Element;
|
|
49
|
-
/** 顶栏文字按钮(= .editor-checklist-btn):h30 / radius 8 / 描边白底。 */
|
|
50
|
-
export declare function ChromeTextButton({ active, className, children, ...rest }: {
|
|
8
|
+
export declare function ChromeIconButton({ label, active, children, ...rest }: ChromeIconButtonProps): import("react").JSX.Element;
|
|
9
|
+
export declare const ChromeBackButton: typeof ChromeIconButton;
|
|
10
|
+
export declare function ChromeTextButton({ active, children, ...rest }: {
|
|
51
11
|
active?: boolean;
|
|
52
12
|
children: ReactNode;
|
|
53
13
|
} & ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
|
|
54
|
-
|
|
55
|
-
export declare function ChromePrimaryButton({ className, children, ...rest }: {
|
|
14
|
+
export declare function ChromePrimaryButton({ children, ...rest }: {
|
|
56
15
|
children: ReactNode;
|
|
57
16
|
} & ButtonHTMLAttributes<HTMLButtonElement>): import("react").JSX.Element;
|
|
58
|
-
/**
|
|
59
|
-
* 分段 tab 的轨道(= lpx-tb-tabs):灰底 + 2px 内边距,当前档是浮起的白卡片。
|
|
60
|
-
*
|
|
61
|
-
* 当前档靠「白卡片」区分而不是靠彩色底 —— 与落地页编辑器一致。
|
|
62
|
-
*/
|
|
63
17
|
export declare function ChromeSegmented({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>): import("react").JSX.Element;
|
|
64
18
|
export interface ChromeSegmentProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
65
19
|
active: boolean;
|
|
66
20
|
children: ReactNode;
|
|
67
21
|
}
|
|
68
|
-
|
|
69
|
-
export declare function ChromeSegment({ active, className, children, ...rest }: ChromeSegmentProps): import("react").JSX.Element;
|
|
22
|
+
export declare function ChromeSegment({ active, children, ...rest }: ChromeSegmentProps): import("react").JSX.Element;
|
|
@@ -1,39 +1,21 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@ptengine/design-components';
|
|
2
3
|
export function ChromeDivider() {
|
|
3
|
-
return _jsx("span", { "aria-hidden": true, className: "mx-2 h-
|
|
4
|
+
return _jsx("span", { "aria-hidden": true, className: "mx-2 h-4 w-px shrink-0 bg-border" });
|
|
4
5
|
}
|
|
5
|
-
export function ChromeIconButton({ label, active,
|
|
6
|
-
return (_jsx(
|
|
7
|
-
text-[rgba(16,28,31,0.7)] transition-colors
|
|
8
|
-
enabled:hover:bg-[rgba(16,28,31,0.08)] enabled:hover:text-[rgba(16,28,31,0.9)]
|
|
9
|
-
disabled:cursor-default disabled:text-[rgba(16,28,31,0.4)]
|
|
10
|
-
${active ? 'bg-[rgba(16,28,31,0.08)] text-[rgba(16,28,31,0.9)]' : ''} ${className}`, ...rest, children: children }));
|
|
6
|
+
export function ChromeIconButton({ label, active, children, ...rest }) {
|
|
7
|
+
return (_jsx(TooltipProvider, { delayDuration: 200, children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "icon", variant: "ghost", "aria-label": label, "aria-pressed": active, ...rest, children: children }) }), _jsx(TooltipContent, { children: label })] }) }));
|
|
11
8
|
}
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
hover:border-[#d0d0d0] hover:bg-[#fafafa] hover:text-[#333] ${className}`, ...rest, children: children }));
|
|
9
|
+
export const ChromeBackButton = ChromeIconButton;
|
|
10
|
+
export function ChromeTextButton({ active, children, ...rest }) {
|
|
11
|
+
return (_jsx(Button, { type: "button", variant: "secondary", "aria-pressed": active, ...rest, children: children }));
|
|
16
12
|
}
|
|
17
|
-
export function
|
|
18
|
-
return (_jsx(
|
|
19
|
-
rounded-lg border border-[#ececef] bg-white px-3 text-xs font-medium
|
|
20
|
-
text-[#52525b] transition-colors
|
|
21
|
-
enabled:hover:border-[rgba(16,28,31,0.2)] enabled:hover:bg-[rgba(16,28,31,0.08)]
|
|
22
|
-
enabled:hover:text-[rgba(16,28,31,0.9)]
|
|
23
|
-
disabled:cursor-default disabled:opacity-50
|
|
24
|
-
${active ? 'border-[rgba(16,28,31,0.2)] bg-[rgba(16,28,31,0.08)] text-[rgba(16,28,31,0.9)]' : ''} ${className}`, ...rest, children: children }));
|
|
25
|
-
}
|
|
26
|
-
export function ChromePrimaryButton({ className = '', children, ...rest }) {
|
|
27
|
-
return (_jsx("button", { type: "button", className: `inline-flex h-[30px] shrink-0 items-center gap-1.5 whitespace-nowrap
|
|
28
|
-
rounded-md bg-[#12262b] px-3 text-xs font-medium text-white transition-colors
|
|
29
|
-
enabled:hover:bg-[#1c3a42] disabled:cursor-default disabled:opacity-50 ${className}`, ...rest, children: children }));
|
|
13
|
+
export function ChromePrimaryButton({ children, ...rest }) {
|
|
14
|
+
return (_jsx(Button, { type: "button", ...rest, children: children }));
|
|
30
15
|
}
|
|
31
16
|
export function ChromeSegmented({ children, className = '', ...rest }) {
|
|
32
|
-
return (_jsx("div", { className: `inline-flex
|
|
17
|
+
return (_jsx("div", { role: "group", className: `inline-flex items-center gap-1 rounded-lg bg-muted p-1 ${className}`, ...rest, children: children }));
|
|
33
18
|
}
|
|
34
|
-
export function ChromeSegment({ active,
|
|
35
|
-
return (_jsx(
|
|
36
|
-
rounded-md px-2.5 text-xs transition-colors ${active
|
|
37
|
-
? 'bg-white font-medium text-[rgba(16,28,31,0.9)] shadow-[0_1px_2px_rgba(16,28,31,0.08)]'
|
|
38
|
-
: 'text-[#707779] enabled:hover:bg-[#e7e8e9] enabled:hover:text-[rgba(16,28,31,0.7)]'} ${className}`, ...rest, children: children }));
|
|
19
|
+
export function ChromeSegment({ active, children, ...rest }) {
|
|
20
|
+
return (_jsx(Button, { type: "button", size: "sm", variant: active ? 'secondary' : 'ghost', "aria-pressed": active, ...rest, children: children }));
|
|
39
21
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CodeField } from './CodeField.js';
|
|
2
3
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Switch,
|
|
5
|
+
import { Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Switch, RadioGroup, RadioGroupItem } from '@ptengine/design-components';
|
|
5
6
|
import { INLINE_ACTION_TYPES, buildActionObject, isActionComplete } from './actionTypes.js';
|
|
6
7
|
function seedToConfigs(seed) {
|
|
7
8
|
const configs = {};
|
|
@@ -25,11 +26,9 @@ export function ClickActionDialog({ open, seed, onOpenChange, onApply }) {
|
|
|
25
26
|
const config = configs[type] ?? {};
|
|
26
27
|
const complete = isActionComplete(type, config);
|
|
27
28
|
const setField = (key, value) => setConfigs(prev => ({ ...prev, [type]: { ...prev[type], [key]: value } }));
|
|
28
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-
|
|
29
|
-
? 'border-[#00A92E] bg-[#E9F9EE] text-[#091E42]'
|
|
30
|
-
: 'border-[#DFE1E6] text-[#6B778C]'}`, children: t(item.labelKey) }, item.key))) })] }), spec?.fields.map(field => (_jsxs("div", { children: [_jsx("p", { className: "mb-1.5 text-[13px] text-[#344563]", children: t(field.labelKey) }), field.kind === 'switch' ? (_jsx(Switch, { "data-testid": `action-field-${field.key}`, checked: Boolean(config[field.key]), onCheckedChange: checked => setField(field.key, checked) })) : field.kind === 'textarea' ? (_jsx(Textarea, { "data-testid": `action-field-${field.key}`, rows: 4, value: String(config[field.key] ?? ''), onChange: e => setField(field.key, e.target.value) })) : (_jsx(Input, { "data-testid": `action-field-${field.key}`, type: field.kind === 'tel' ? 'tel' : 'text', value: String(config[field.key] ?? ''), onChange: e => setField(field.key, e.target.value) }))] }, field.key)))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => onOpenChange(false), children: t('inlineCanvas.chrome.close') }), _jsx(Button, { "data-testid": "action-apply", disabled: !complete, onClick: () => {
|
|
29
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { closeLabel: t('inlineCanvas.chrome.close', '关闭'), className: "max-h-[calc(100dvh-32px)] overflow-y-auto", "data-testid": "click-action-dialog", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('inlineCanvas.action.title') }) }), _jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { children: [_jsx("p", { className: "mb-1.5 text-[13px] text-[#344563]", children: t('inlineCanvas.action.type') }), _jsx(RadioGroup, { value: type, onValueChange: value => setType(value), "aria-label": t('inlineCanvas.action.type'), className: "grid grid-cols-2 gap-x-4 gap-y-3", children: INLINE_ACTION_TYPES.map(item => (_jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm", children: [_jsx(RadioGroupItem, { value: item.key, "data-testid": `action-type-${item.key}`, "aria-label": t(item.labelKey) }), t(item.labelKey)] }, item.key))) })] }), spec?.fields.map(field => (_jsxs("div", { children: [_jsx("label", { htmlFor: `action-${field.key}`, className: "mb-1.5 block text-[13px] text-foreground", children: t(field.labelKey) }), field.kind === 'switch' ? (_jsx(Switch, { id: `action-${field.key}`, "aria-label": t(field.labelKey), "data-testid": `action-field-${field.key}`, checked: Boolean(config[field.key]), onCheckedChange: checked => setField(field.key, checked) })) : field.kind === 'textarea' ? (_jsx(CodeField, { id: `action-${field.key}`, "aria-label": t(field.labelKey), "data-testid": `action-field-${field.key}`, rows: 4, value: String(config[field.key] ?? ''), onChange: e => setField(field.key, e.target.value) })) : (_jsx(Input, { id: `action-${field.key}`, "aria-label": t(field.labelKey), "data-testid": `action-field-${field.key}`, type: field.kind === 'tel' ? 'tel' : 'text', value: String(config[field.key] ?? ''), onChange: e => setField(field.key, e.target.value) }))] }, field.key)))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "secondary", onClick: () => onOpenChange(false), children: t('common.cancel', '取消') }), _jsx(Button, { "data-testid": "action-apply", disabled: !complete, onClick: () => {
|
|
31
30
|
const { [type]: current, ...others } = configs;
|
|
32
31
|
void onApply(buildActionObject({ type, config: current, otherConfigs: others }));
|
|
33
32
|
onOpenChange(false);
|
|
34
|
-
}, children: t('
|
|
33
|
+
}, children: t('common.apply', '应用') })] })] }) }));
|
|
35
34
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react';
|
|
2
|
+
import { Textarea } from '@ptengine/design-components';
|
|
3
|
+
/** Shared lightweight code input. Inline font prevents host typography overriding monospace. */
|
|
4
|
+
export declare function CodeField(props: ComponentProps<typeof Textarea>): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Textarea } from '@ptengine/design-components';
|
|
3
|
+
export function CodeField(props) {
|
|
4
|
+
return (_jsx(Textarea, { ...props, spellCheck: false, autoCapitalize: "off", autoCorrect: "off", style: {
|
|
5
|
+
...props.style,
|
|
6
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
7
|
+
fontSize: 13,
|
|
8
|
+
lineHeight: '20px'
|
|
9
|
+
} }));
|
|
10
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CodeField } from './CodeField.js';
|
|
2
3
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { AlertTriangle } from 'lucide-react';
|
|
5
|
-
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle
|
|
6
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@ptengine/design-components';
|
|
6
7
|
import { validateHtml } from './htmlValidation.js';
|
|
7
8
|
export function HtmlEditDialog({ open, seed, onOpenChange, onApply }) {
|
|
8
9
|
const { t } = useTranslation();
|
|
@@ -13,7 +14,7 @@ export function HtmlEditDialog({ open, seed, onOpenChange, onApply }) {
|
|
|
13
14
|
}, [open, seed]);
|
|
14
15
|
const result = useMemo(() => validateHtml(html), [html]);
|
|
15
16
|
const changed = html.trim() !== seed.trim();
|
|
16
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-
|
|
17
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { closeLabel: t('inlineCanvas.chrome.close', '关闭'), size: "md", className: "max-h-[calc(100dvh-32px)] overflow-y-auto", "data-testid": "html-edit-dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: t('inlineCanvas.html.title', '编辑 HTML') }), _jsx(DialogDescription, { children: t('inlineCanvas.html.tip', '编辑的是整个元素(outerHTML)。保存后 SDK 会用它整体替换原元素。') })] }), _jsx(CodeField, { "aria-label": t('inlineCanvas.html.title'), "data-testid": "html-edit-input", value: html, onChange: e => setHtml(e.target.value), spellCheck: false, className: "h-72 font-mono text-xs leading-5" }), result.strippedScriptCount > 0 && (_jsxs("p", { "data-testid": "html-edit-script-warning", className: "flex items-center gap-1.5 text-xs text-[#8A6D00]", children: [_jsx(AlertTriangle, { className: "size-3.5" }), t('inlineCanvas.html.scriptStripped', {
|
|
17
18
|
count: result.strippedScriptCount,
|
|
18
19
|
defaultValue: '其中 {{count}} 段 script/noscript 会被移除'
|
|
19
20
|
})] })), result.error && (_jsx("p", { "data-testid": "html-edit-error", className: "text-xs text-[#DE350B]", children: t(result.error) })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "secondary", onClick: () => onOpenChange(false), children: t('common.cancel', '取消') }), _jsx(Button, { "data-testid": "html-edit-apply", disabled: !result.ok || !changed, onClick: () => {
|