@ptengine/inline-editor 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +286 -2
- package/dist/InlineEditor.d.ts +349 -0
- package/dist/InlineEditor.js +886 -0
- package/dist/PagePreview.d.ts +83 -0
- package/dist/PagePreview.js +111 -0
- package/dist/assets/AssetsPanel.d.ts +19 -0
- package/dist/assets/AssetsPanel.js +23 -0
- package/dist/assets/componentLibrary.d.ts +23 -0
- package/dist/assets/componentLibrary.js +12 -0
- package/dist/canvas-ui/index.d.ts +17 -0
- package/dist/canvas-ui/index.js +7 -0
- package/dist/canvas-ui/scope.d.ts +13 -0
- package/dist/canvas-ui/scope.js +1 -0
- package/dist/canvas-ui/selection/classNames.d.ts +30 -0
- package/dist/canvas-ui/selection/classNames.js +15 -0
- package/dist/canvas-ui/selection/tokens.css +204 -0
- package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
- package/dist/canvas-ui/tree/TreePanel.js +132 -0
- package/dist/canvas-ui/tree/classNames.d.ts +21 -0
- package/dist/canvas-ui/tree/classNames.js +8 -0
- package/dist/canvas-ui/tree/order.d.ts +38 -0
- package/dist/canvas-ui/tree/order.js +38 -0
- package/dist/canvas-ui/tree/tree.css +131 -0
- package/dist/chrome/EditorChrome.d.ts +83 -0
- package/dist/chrome/EditorChrome.js +10 -0
- package/dist/chrome/VersionMenu.d.ts +26 -0
- package/dist/chrome/VersionMenu.js +86 -0
- package/dist/chrome/topbarPrimitives.d.ts +69 -0
- package/dist/chrome/topbarPrimitives.js +39 -0
- package/dist/chrome/useDismissOnOutside.d.ts +23 -0
- package/dist/chrome/useDismissOnOutside.js +27 -0
- package/dist/dialogs/ClickActionDialog.d.ts +24 -0
- package/dist/dialogs/ClickActionDialog.js +35 -0
- package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
- package/dist/dialogs/HtmlEditDialog.js +23 -0
- package/dist/dialogs/ImageEditDialog.d.ts +30 -0
- package/dist/dialogs/ImageEditDialog.js +57 -0
- package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
- package/dist/dialogs/ImageSettingDialog.js +17 -0
- package/dist/dialogs/InsertElementDialog.d.ts +23 -0
- package/dist/dialogs/InsertElementDialog.js +28 -0
- package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
- package/dist/dialogs/ReplaceVideoDialog.js +16 -0
- package/dist/dialogs/actionTypes.d.ts +53 -0
- package/dist/dialogs/actionTypes.js +74 -0
- package/dist/dialogs/htmlValidation.d.ts +24 -0
- package/dist/dialogs/htmlValidation.js +37 -0
- package/dist/dialogs/imageEdit.d.ts +49 -0
- package/dist/dialogs/imageEdit.js +30 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +53 -0
- package/dist/kernel/CanvasContextBar.d.ts +23 -0
- package/dist/kernel/CanvasContextBar.js +12 -0
- package/dist/kernel/CanvasFrame.d.ts +56 -0
- package/dist/kernel/CanvasFrame.js +53 -0
- package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
- package/dist/kernel/ai/aiInlineApply.js +477 -0
- package/dist/kernel/ai/imageSource.d.ts +24 -0
- package/dist/kernel/ai/imageSource.js +19 -0
- package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
- package/dist/kernel/ai/insertHtmlGate.js +123 -0
- package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
- package/dist/kernel/ai/postApplyCheck.js +74 -0
- package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
- package/dist/kernel/ai/replaceHtmlGate.js +147 -0
- package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
- package/dist/kernel/blocks/BlockOverlay.js +66 -0
- package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
- package/dist/kernel/blocks/annotateAndTile.js +122 -0
- package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
- package/dist/kernel/blocks/resolveBlocks.js +233 -0
- package/dist/kernel/canvasCapabilities.d.ts +31 -0
- package/dist/kernel/canvasCapabilities.js +39 -0
- package/dist/kernel/canvasCommands.d.ts +126 -0
- package/dist/kernel/canvasCommands.js +146 -0
- package/dist/kernel/canvasErrors.d.ts +1 -0
- package/dist/kernel/canvasErrors.js +14 -0
- package/dist/kernel/captureGeometry.d.ts +35 -0
- package/dist/kernel/captureGeometry.js +25 -0
- package/dist/kernel/captureOrchestration.d.ts +48 -0
- package/dist/kernel/captureOrchestration.js +43 -0
- package/dist/kernel/captureSlices.d.ts +36 -0
- package/dist/kernel/captureSlices.js +20 -0
- package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
- package/dist/kernel/changelist/ChangeListPanel.js +47 -0
- package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
- package/dist/kernel/changelist/ChangeListPopover.js +10 -0
- package/dist/kernel/changelist/builders.d.ts +89 -0
- package/dist/kernel/changelist/builders.js +81 -0
- package/dist/kernel/changelist/changeSummary.d.ts +19 -0
- package/dist/kernel/changelist/changeSummary.js +85 -0
- package/dist/kernel/changelist/clipboard.d.ts +16 -0
- package/dist/kernel/changelist/clipboard.js +4 -0
- package/dist/kernel/changelist/exportForSave.d.ts +30 -0
- package/dist/kernel/changelist/exportForSave.js +11 -0
- package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
- package/dist/kernel/changelist/mergeForSave.js +40 -0
- package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
- package/dist/kernel/changelist/moveObserverSafety.js +39 -0
- package/dist/kernel/changelist/overrides.d.ts +22 -0
- package/dist/kernel/changelist/overrides.js +63 -0
- package/dist/kernel/changelist/useChangeList.d.ts +200 -0
- package/dist/kernel/changelist/useChangeList.js +282 -0
- package/dist/kernel/config.d.ts +29 -0
- package/dist/kernel/config.js +10 -0
- package/dist/kernel/dataUrlToBlob.d.ts +8 -0
- package/dist/kernel/dataUrlToBlob.js +17 -0
- package/dist/kernel/linkHref.d.ts +25 -0
- package/dist/kernel/linkHref.js +16 -0
- package/dist/kernel/mobileFit.d.ts +46 -0
- package/dist/kernel/mobileFit.js +44 -0
- package/dist/kernel/moveState.d.ts +35 -0
- package/dist/kernel/moveState.js +14 -0
- package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
- package/dist/kernel/selection/SelectionOverlay.js +25 -0
- package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
- package/dist/kernel/selection/SelectionToolbar.js +82 -0
- package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
- package/dist/kernel/selection/SelectorPathBar.js +55 -0
- package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
- package/dist/kernel/selection/buildDescendantShelf.js +50 -0
- package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
- package/dist/kernel/selection/buildRootShelf.js +149 -0
- package/dist/kernel/selection/elementAiContext.d.ts +324 -0
- package/dist/kernel/selection/elementAiContext.js +122 -0
- package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
- package/dist/kernel/selection/elementGeometryForAi.js +15 -0
- package/dist/kernel/selection/geometry.d.ts +55 -0
- package/dist/kernel/selection/geometry.js +18 -0
- package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
- package/dist/kernel/selection/resolveAnchors.js +173 -0
- package/dist/kernel/selection/shelfRow.d.ts +33 -0
- package/dist/kernel/selection/shelfRow.js +21 -0
- package/dist/kernel/selection/tagLabels.d.ts +9 -0
- package/dist/kernel/selection/tagLabels.js +46 -0
- package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
- package/dist/kernel/selection/toolbarButtons.js +105 -0
- package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
- package/dist/kernel/selection/useAiSelectionContext.js +26 -0
- package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
- package/dist/kernel/selection/useCanvasGeometry.js +47 -0
- package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
- package/dist/kernel/selection/useSelectionRestore.js +25 -0
- package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
- package/dist/kernel/shrinkImageForUpload.js +61 -0
- package/dist/kernel/useCanvasBridge.d.ts +36 -0
- package/dist/kernel/useCanvasBridge.js +41 -0
- package/dist/kernel/useCanvasGate.d.ts +22 -0
- package/dist/kernel/useCanvasGate.js +35 -0
- package/dist/kernel/useCanvasSession.d.ts +44 -0
- package/dist/kernel/useCanvasSession.js +182 -0
- package/dist/kernel/useCanvasStream.d.ts +39 -0
- package/dist/kernel/useCanvasStream.js +45 -0
- package/dist/kernel/useMobileFit.d.ts +40 -0
- package/dist/kernel/useMobileFit.js +139 -0
- package/dist/locales.d.ts +1094 -0
- package/dist/locales.js +545 -0
- package/dist/panels/StylePanel.d.ts +24 -0
- package/dist/panels/StylePanel.js +40 -0
- package/dist/panels/styleFields.d.ts +39 -0
- package/dist/panels/styleFields.js +91 -0
- package/dist/tree/LayersPanel.d.ts +30 -0
- package/dist/tree/LayersPanel.js +15 -0
- package/dist/tree/domTreeAdapter.d.ts +63 -0
- package/dist/tree/domTreeAdapter.js +90 -0
- package/dist/useInlineEditor.d.ts +161 -0
- package/dist/useInlineEditor.js +346 -0
- package/package.json +58 -3
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export const STYLE_SECTIONS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'size',
|
|
4
|
+
labelKey: 'inlineCanvas.style.size',
|
|
5
|
+
fields: [
|
|
6
|
+
{ property: 'width', labelKey: 'inlineCanvas.style.width', kind: 'length', unit: 'px' },
|
|
7
|
+
{ property: 'height', labelKey: 'inlineCanvas.style.height', kind: 'length', unit: 'px' }
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: 'typography',
|
|
12
|
+
labelKey: 'inlineCanvas.style.typography',
|
|
13
|
+
visible: caps => caps.hasText,
|
|
14
|
+
fields: [
|
|
15
|
+
{ property: 'font-family', labelKey: 'inlineCanvas.style.fontFamily', kind: 'text' },
|
|
16
|
+
{
|
|
17
|
+
property: 'font-size',
|
|
18
|
+
labelKey: 'inlineCanvas.style.fontSize',
|
|
19
|
+
kind: 'length',
|
|
20
|
+
unit: 'px'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
property: 'font-weight',
|
|
24
|
+
labelKey: 'inlineCanvas.style.fontWeight',
|
|
25
|
+
kind: 'select',
|
|
26
|
+
options: ['300', '400', '500', '600', '700', '800', '900']
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
property: 'line-height',
|
|
30
|
+
labelKey: 'inlineCanvas.style.lineHeight',
|
|
31
|
+
kind: 'length',
|
|
32
|
+
unit: 'px'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
property: 'text-align',
|
|
36
|
+
labelKey: 'inlineCanvas.style.textAlign',
|
|
37
|
+
kind: 'select',
|
|
38
|
+
options: ['left', 'center', 'right', 'justify']
|
|
39
|
+
},
|
|
40
|
+
{ property: 'color', labelKey: 'inlineCanvas.style.color', kind: 'color' }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'background',
|
|
45
|
+
labelKey: 'inlineCanvas.style.background',
|
|
46
|
+
fields: [
|
|
47
|
+
{
|
|
48
|
+
property: 'background-color',
|
|
49
|
+
labelKey: 'inlineCanvas.style.backgroundColor',
|
|
50
|
+
kind: 'color'
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'spacing',
|
|
56
|
+
labelKey: 'inlineCanvas.style.spacing',
|
|
57
|
+
fields: [
|
|
58
|
+
{ property: 'padding', labelKey: 'inlineCanvas.style.padding', kind: 'text' },
|
|
59
|
+
{ property: 'margin', labelKey: 'inlineCanvas.style.margin', kind: 'text' }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'border',
|
|
64
|
+
labelKey: 'inlineCanvas.style.border',
|
|
65
|
+
fields: [
|
|
66
|
+
{ property: 'border', labelKey: 'inlineCanvas.style.borderAll', kind: 'text' },
|
|
67
|
+
{
|
|
68
|
+
property: 'border-radius',
|
|
69
|
+
labelKey: 'inlineCanvas.style.borderRadius',
|
|
70
|
+
kind: 'length',
|
|
71
|
+
unit: 'px'
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
export function visibleSections(caps) {
|
|
77
|
+
if (!caps)
|
|
78
|
+
return [];
|
|
79
|
+
return STYLE_SECTIONS.filter(s => (s.visible ? s.visible(caps) : true));
|
|
80
|
+
}
|
|
81
|
+
const HAS_UNIT_OR_KEYWORD = /[a-z%)]$/i;
|
|
82
|
+
export function composeStyleValue(field, raw) {
|
|
83
|
+
const value = raw.trim();
|
|
84
|
+
if (!value)
|
|
85
|
+
return value;
|
|
86
|
+
if (field.kind !== 'length' || !field.unit)
|
|
87
|
+
return value;
|
|
88
|
+
if (HAS_UNIT_OR_KEYWORD.test(value))
|
|
89
|
+
return value;
|
|
90
|
+
return `${value}${field.unit}`;
|
|
91
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type DropPos } from '../canvas-ui/index.js';
|
|
2
|
+
import { type AdaptedTree } from './domTreeAdapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* inline editor 的图层面板。
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ **对客户页只读**:点=选中、悬浮=高亮、拖=移动。**没有重命名** ——
|
|
7
|
+
* 客户页元素没有 `data-pt-name` 可写,写了就是在改客户页的属性。
|
|
8
|
+
*
|
|
9
|
+
* id 与 selector 的转换只在这一层做:`TreePanel` 只认 id,画布命令只认 selector,
|
|
10
|
+
* 中间那层撞号后缀(`#dup2`)绝不能漏到命令上去(见 `domTreeAdapter`)。
|
|
11
|
+
*/
|
|
12
|
+
export interface LayersPanelProps {
|
|
13
|
+
tree: AdaptedTree | null;
|
|
14
|
+
selectedSelector: string | null;
|
|
15
|
+
hoverSelector?: string | null;
|
|
16
|
+
onSelect: (selector: string) => void;
|
|
17
|
+
/** 空串 = 清除高亮。 */
|
|
18
|
+
onHover: (selector: string) => void;
|
|
19
|
+
/** 拖拽重排 → `elementMove`。省略则不可拖。 */
|
|
20
|
+
onMove?: (dragSelector: string, targetSelector: string, pos: DropPos) => void;
|
|
21
|
+
/**
|
|
22
|
+
* 展开一个被截断的节点(去把它的子树拉回来)。省略则截断节点不出箭头。
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ **唯一交出内部 id 而不是 selector 的回调,而且必须如此**:回填的目标是
|
|
25
|
+
* 「树上这一行」,撞号时多行共享同一个 selector —— 交 selector 出去,宿主只能
|
|
26
|
+
* 回查到第一行,于是点第 2 个同名节点会把子树接到第 1 个身上,**且不报错**。
|
|
27
|
+
*/
|
|
28
|
+
onExpand?: (id: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export declare function LayersPanel({ tree, selectedSelector, hoverSelector, onSelect, onHover, onMove, onExpand }: LayersPanelProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Input } from '@ptengine/design-components';
|
|
4
|
+
import { TreePanel } from '../canvas-ui/index.js';
|
|
5
|
+
import { findIdBySelector } from './domTreeAdapter.js';
|
|
6
|
+
export function LayersPanel({ tree, selectedSelector, hoverSelector, onSelect, onHover, onMove, onExpand }) {
|
|
7
|
+
const [query, setQuery] = useState('');
|
|
8
|
+
if (!tree) {
|
|
9
|
+
return (_jsx("p", { "data-testid": "layers-empty", className: "px-3 py-6 text-center text-xs text-[#8993A4]", children: "\u8FD8\u6CA1\u6709\u62FF\u5230\u9875\u9762\u7ED3\u6784" }));
|
|
10
|
+
}
|
|
11
|
+
const selectorOf = (id) => tree.nodes[id]?.selector ?? id;
|
|
12
|
+
const idOf = (selector) => selector ? findIdBySelector(tree, selector) : null;
|
|
13
|
+
return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "layers-panel", children: [_jsx("div", { className: "p-2", children: _jsx(Input, { "data-testid": "layers-search", placeholder: "\u641C\u7D22\u56FE\u5C42", value: query, onChange: e => setQuery(e.target.value) }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: _jsx(TreePanel, { nodes: tree.nodes, rootId: tree.rootId, selectedId: idOf(selectedSelector), hoverId: idOf(hoverSelector ?? null), query: query, onSelect: id => onSelect(selectorOf(id)), onHover: id => onHover(id ? selectorOf(id) : ''), onMove: onMove &&
|
|
14
|
+
((dragId, targetId, pos) => onMove(selectorOf(dragId), selectorOf(targetId), pos)), onExpand: onExpand }) })] }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { DomTreeNode } from '@ptengine/inline-core/protocol';
|
|
3
|
+
import type { ElementMoveModeName } from '@ptengine/inline-core/change-format';
|
|
4
|
+
import type { TreePanelNode } from '../canvas-ui/index.js';
|
|
5
|
+
export interface AdaptedTreeNode extends TreePanelNode {
|
|
6
|
+
/** 发给画布命令用的**原始** selector(不带撞号后缀)。 */
|
|
7
|
+
selector: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AdaptedTree {
|
|
10
|
+
nodes: Record<string, AdaptedTreeNode>;
|
|
11
|
+
rootId: string;
|
|
12
|
+
}
|
|
13
|
+
/** 宿主渲染行首图标要的信息。**只给事实,不给结论** —— 图标怎么挑是宿主的事。 */
|
|
14
|
+
export interface TreeNodeIconInput {
|
|
15
|
+
tag: string;
|
|
16
|
+
/**
|
|
17
|
+
* 有直接子元素。
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ 用 `childCount` 而不是 `children.length`:DOM 树命令对超大页面会**截断**子节点,
|
|
20
|
+
* 截断处 `children` 是空数组而 `childCount > 0` —— 只看数组会把一个容器画成叶子。
|
|
21
|
+
*/
|
|
22
|
+
hasChildren: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface AdaptDomTreeOptions {
|
|
25
|
+
/**
|
|
26
|
+
* 行首图标渲染器。省略则树里不出图标。
|
|
27
|
+
*
|
|
28
|
+
* 为什么要注入:glyph 真值表(三代编辑器唯一那份)在 `apps/x` 里,而本包在
|
|
29
|
+
* `packages/` —— 包不能反向依赖 app。同 `uploadImage` / `libraryComponents` 的分工。
|
|
30
|
+
*/
|
|
31
|
+
renderIcon?: (input: TreeNodeIconInput) => React.ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export declare function adaptDomTree(root: DomTreeNode, options?: AdaptDomTreeOptions): AdaptedTree;
|
|
34
|
+
/**
|
|
35
|
+
* 把按需拉回来的子树接回原树 —— `getDomTree({ rootSelector })` 的返回,其**根就是**
|
|
36
|
+
* `atId` 这个节点自己。
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ **原地替换那一个节点的子树,不重建整棵树。** 重建会换掉所有 id,于是折叠态、
|
|
39
|
+
* 选中态、滚动定位全部对不上 —— 而且是静默的:树看着没问题,点哪一行都不对。
|
|
40
|
+
* 同理 `atId` 这一行的 id 也保持原样,只更新它的 label / truncated / children。
|
|
41
|
+
*
|
|
42
|
+
* `atId` 已经不在树里(切过设备、树重拉过)时**原样返回同一个对象**,让调用方的
|
|
43
|
+
* `===` 比较能直接判出「什么都没变」。
|
|
44
|
+
*/
|
|
45
|
+
export declare function graftSubtree(tree: AdaptedTree, atId: string, subtree: DomTreeNode, options?: AdaptDomTreeOptions): AdaptedTree;
|
|
46
|
+
/**
|
|
47
|
+
* selector → 行 id。撞号时多行共享同一个 selector,取第一行 —— 与「点第一行选中它」
|
|
48
|
+
* 自洽。查不到返回 `null`,**调用方必须把它当「这个元素还没加载进树」来处理**,
|
|
49
|
+
* 不能当成「没有这个元素」。
|
|
50
|
+
*/
|
|
51
|
+
export declare function findIdBySelector(tree: AdaptedTree, selector: string): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* 树的落点档位 → `elementMove` 的 mode。
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ **两套词表不一样**,直接传会静默错位:
|
|
56
|
+
* 树只有 `before` / `after` / `inside`,而 `elementMove` 是
|
|
57
|
+
* `before` / `after` / `top`(移入开头)/ `bottom`(移入末尾)。
|
|
58
|
+
*
|
|
59
|
+
* `inside` 映射到 `bottom`(追加到末尾)—— 与「把一行拖进容器」的直觉一致:
|
|
60
|
+
* 用户看到的是它落在容器里已有内容的下面。映射到 `top` 会让它插到最前面,
|
|
61
|
+
* 现象是「拖进去了但位置不对」,而且用户完全无法预期。
|
|
62
|
+
*/
|
|
63
|
+
export declare function moveModeFromDropPos(pos: 'before' | 'after' | 'inside'): ElementMoveModeName;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const VOID_TAGS = new Set([
|
|
2
|
+
'img',
|
|
3
|
+
'br',
|
|
4
|
+
'hr',
|
|
5
|
+
'input',
|
|
6
|
+
'source',
|
|
7
|
+
'track',
|
|
8
|
+
'area',
|
|
9
|
+
'base',
|
|
10
|
+
'col',
|
|
11
|
+
'embed',
|
|
12
|
+
'link',
|
|
13
|
+
'meta',
|
|
14
|
+
'param',
|
|
15
|
+
'wbr'
|
|
16
|
+
]);
|
|
17
|
+
function labelOf(node) {
|
|
18
|
+
const text = node.textSummary.trim();
|
|
19
|
+
const suffix = node.truncated ? ` …(${node.childCount})` : '';
|
|
20
|
+
return text ? `${node.tag} · ${text}${suffix}` : `${node.tag}${suffix}`;
|
|
21
|
+
}
|
|
22
|
+
function makeUniqueId(used) {
|
|
23
|
+
return (selector) => {
|
|
24
|
+
if (!used.has(selector)) {
|
|
25
|
+
used.add(selector);
|
|
26
|
+
return selector;
|
|
27
|
+
}
|
|
28
|
+
let n = 2;
|
|
29
|
+
while (used.has(`${selector}#dup${n}`))
|
|
30
|
+
n += 1;
|
|
31
|
+
const id = `${selector}#dup${n}`;
|
|
32
|
+
used.add(id);
|
|
33
|
+
return id;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function rowOf(node, id, childIds, options) {
|
|
37
|
+
const hasChildren = node.childCount > 0 || childIds.length > 0;
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
selector: node.selector,
|
|
41
|
+
label: labelOf(node),
|
|
42
|
+
canContain: !VOID_TAGS.has(node.tag.toLowerCase()),
|
|
43
|
+
truncated: node.truncated,
|
|
44
|
+
children: childIds,
|
|
45
|
+
icon: options.renderIcon?.({ tag: node.tag, hasChildren })
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function writeSubtree(node, nodes, uniqueId, options) {
|
|
49
|
+
const id = uniqueId(node.selector);
|
|
50
|
+
const childIds = node.children.map(child => writeSubtree(child, nodes, uniqueId, options));
|
|
51
|
+
nodes[id] = rowOf(node, id, childIds, options);
|
|
52
|
+
return id;
|
|
53
|
+
}
|
|
54
|
+
export function adaptDomTree(root, options = {}) {
|
|
55
|
+
const nodes = {};
|
|
56
|
+
const rootId = writeSubtree(root, nodes, makeUniqueId(new Set()), options);
|
|
57
|
+
return { nodes, rootId };
|
|
58
|
+
}
|
|
59
|
+
export function graftSubtree(tree, atId, subtree, options = {}) {
|
|
60
|
+
if (!tree.nodes[atId])
|
|
61
|
+
return tree;
|
|
62
|
+
const nodes = { ...tree.nodes };
|
|
63
|
+
const seen = new Set([atId]);
|
|
64
|
+
const drop = (id) => {
|
|
65
|
+
for (const child of nodes[id]?.children ?? []) {
|
|
66
|
+
if (seen.has(child))
|
|
67
|
+
continue;
|
|
68
|
+
seen.add(child);
|
|
69
|
+
drop(child);
|
|
70
|
+
delete nodes[child];
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
drop(atId);
|
|
74
|
+
const uniqueId = makeUniqueId(new Set(Object.keys(nodes)));
|
|
75
|
+
const childIds = subtree.children.map(child => writeSubtree(child, nodes, uniqueId, options));
|
|
76
|
+
nodes[atId] = rowOf(subtree, atId, childIds, options);
|
|
77
|
+
return { nodes, rootId: tree.rootId };
|
|
78
|
+
}
|
|
79
|
+
export function findIdBySelector(tree, selector) {
|
|
80
|
+
if (!selector)
|
|
81
|
+
return null;
|
|
82
|
+
if (tree.nodes[selector])
|
|
83
|
+
return selector;
|
|
84
|
+
return Object.keys(tree.nodes).find(id => tree.nodes[id].selector === selector) ?? null;
|
|
85
|
+
}
|
|
86
|
+
export function moveModeFromDropPos(pos) {
|
|
87
|
+
if (pos === 'inside')
|
|
88
|
+
return 'bottom';
|
|
89
|
+
return pos;
|
|
90
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { AncestorNode, CanvasDevice, ElementCapabilities } from '@ptengine/inline-core/protocol';
|
|
2
|
+
import type { InlineChange } from '@ptengine/inline-core/change-format';
|
|
3
|
+
import { useCanvasSession } from './kernel/useCanvasSession.js';
|
|
4
|
+
import { type MobileFitState } from './kernel/useMobileFit.js';
|
|
5
|
+
import { useCanvasGeometry } from './kernel/selection/useCanvasGeometry.js';
|
|
6
|
+
import { useChangeList } from './kernel/changelist/useChangeList.js';
|
|
7
|
+
import { type CaptureOutcome } from './kernel/captureOrchestration.js';
|
|
8
|
+
import { type AdaptDomTreeOptions, type AdaptedTree } from './tree/domTreeAdapter.js';
|
|
9
|
+
import type { InsertMode } from './kernel/changelist/useChangeList.js';
|
|
10
|
+
import { type MovePosition, type MoveState } from './kernel/moveState.js';
|
|
11
|
+
import { getOuterHtml } from './kernel/canvasCommands.js';
|
|
12
|
+
/**
|
|
13
|
+
* 内联编辑器的 **headless 层**。
|
|
14
|
+
*
|
|
15
|
+
* 它把「会话编排 + 选中态 + 几何 + change list」收成一个对象,**不含任何 UI**。
|
|
16
|
+
*
|
|
17
|
+
* 为什么必须有这一层,而不是只提供成品 `<InlineEditor>`:
|
|
18
|
+
* 编辑器在不同入口下**外壳差异极大** —— experience 创建流程需要版本管理、设备切换、
|
|
19
|
+
* 保存/预览的双层顶栏;从 AI 对话里打开则只要一条极简顶栏。只封装成品组件的话,
|
|
20
|
+
* 后者要么被迫塞进它不需要的 UI,要么就得给组件加一堆 `showXxx` 布尔开关
|
|
21
|
+
* —— 那是组件复杂度失控的典型开端。
|
|
22
|
+
*
|
|
23
|
+
* 所以:**外壳是宿主的事,内核在这里**。`<InlineEditor>` 只是本 hook 的一个默认外壳。
|
|
24
|
+
*/
|
|
25
|
+
export interface InlineEditorSelection {
|
|
26
|
+
/** 匹配用 selector(可能命中多个,SDK 只作用第一个)。 */
|
|
27
|
+
selector: string;
|
|
28
|
+
/** 唯一定位串(强制 tagName + nth-of-type)。 */
|
|
29
|
+
identifier: string;
|
|
30
|
+
tag: string;
|
|
31
|
+
/** `[自己, 父, …, body]`,index 0 是自己。路径选择器用它。 */
|
|
32
|
+
chain: AncestorNode[];
|
|
33
|
+
/** DEC-14:一次往返批量拿到的能力位,决定工具条显示哪些按钮。 */
|
|
34
|
+
capabilities: ElementCapabilities | null;
|
|
35
|
+
/**
|
|
36
|
+
* 选中元素的 innerHTML(来自 `elementPicked` 的 `originalValue.html`)。
|
|
37
|
+
* 「编辑文字」的 seed 用它 —— 用 textContent 会把 `<br>`/`<strong>` 这类内联标记
|
|
38
|
+
* 悄悄抹掉。
|
|
39
|
+
*/
|
|
40
|
+
declaredHtml: string;
|
|
41
|
+
/** author 层声明的样式,属性面板拿它作输入框初值。 */
|
|
42
|
+
declaredStyles: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
export interface UseInlineEditorInput {
|
|
45
|
+
url: string;
|
|
46
|
+
device: CanvasDevice;
|
|
47
|
+
/** change list 变化时回调 —— 宿主自己决定存哪(本 hook 不做持久化)。 */
|
|
48
|
+
onChangesChange?: (changes: InlineChange[]) => void;
|
|
49
|
+
/**
|
|
50
|
+
* 画布 iframe 的 ref。元素截图要靠它拿 iframe 在产品页里的位置与 zoom ——
|
|
51
|
+
* 没有这两个值就没法把「元素在客户页视口里的坐标」换算成「屏幕位图里的坐标」。
|
|
52
|
+
*/
|
|
53
|
+
iframeRef?: React.MutableRefObject<HTMLIFrameElement | null>;
|
|
54
|
+
/** 图层树行首图标渲染器(宿主注入,见 `adaptDomTree`)。 */
|
|
55
|
+
renderNodeIcon?: AdaptDomTreeOptions['renderIcon'];
|
|
56
|
+
/** 撤销拿掉了插入类 change → 回报它插入过的元素(见 `UseChangeListOptions.onInsertedRemoved`)。 */
|
|
57
|
+
onInsertedRemoved?: (selectors: string[]) => void;
|
|
58
|
+
onInsertedRestored?: (selectors: string[]) => void;
|
|
59
|
+
/**
|
|
60
|
+
* 是否在 `domReady` 后拉取页面结构树(图层面板的数据源)。默认 `true`。
|
|
61
|
+
*
|
|
62
|
+
* 给**不渲染图层面板**的外壳用(分析阶段的 `<PagePreview>`):那一趟是跨三跳的
|
|
63
|
+
* 往返 + 整棵结构的序列化(现在是 depth 16 / maxNodes 1500),对一个从头到尾都不
|
|
64
|
+
* 显示树的壳纯属白花。关掉后 `tree` 恒为 `null`。
|
|
65
|
+
*
|
|
66
|
+
* ⚠️ **这不是「关掉一个功能」的开关模板**。包的 API 主张是「外壳差异靠 headless 层
|
|
67
|
+
* 自己拼」(见 `index.ts` 头部),所以这里只收**纯数据获取**的开关;任何「显示/不显示
|
|
68
|
+
* 某个部件」的诉求都该在外壳里解决,不要往这个 input 上加 `showXxx`。
|
|
69
|
+
*
|
|
70
|
+
* ⚠️ 尤其**不要**照这个样子再加一个「关掉拾取」的开关:`picker.ts` 的
|
|
71
|
+
* `onPointerDown`/`onClick` 正是在客户页上 `preventDefault`/`stopPropagation` 的地方,
|
|
72
|
+
* 关掉拾取等于让嵌进来的客户页**活过来** —— 点一个链接画布就导航走了,而 sessionId
|
|
73
|
+
* 不变,于是选中、几何、区块坐标全部对着一个已经不存在的页面,且零报错。
|
|
74
|
+
* 拾取开着才是这块板子「惰性」的成因;`elementPicked` 没人消费是无害的。
|
|
75
|
+
*/
|
|
76
|
+
tree?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface UseInlineEditorResult {
|
|
79
|
+
/** 会话状态:gating / opening / ready / failed + 失败原因。 */
|
|
80
|
+
session: ReturnType<typeof useCanvasSession>;
|
|
81
|
+
/** 供 `<CanvasFrame>` 直接展开的 props;`null` = 还没到可渲染画布的阶段。 */
|
|
82
|
+
frameProps: ReturnType<typeof useCanvasSession>['frameProps'];
|
|
83
|
+
/** DOM 解析完即为真 —— 不等 iframe 原生 load(那要等全部子资源,真实落地页好几秒)。 */
|
|
84
|
+
domReady: boolean;
|
|
85
|
+
selection: InlineEditorSelection | null;
|
|
86
|
+
/** 页面结构树(图层面板用);`null` = 还没拿到。 */
|
|
87
|
+
tree: AdaptedTree | null;
|
|
88
|
+
/** 剪贴板里复制下来的 outerHTML;`null` = 空。 */
|
|
89
|
+
clipboard: string | null;
|
|
90
|
+
/** 「移动元素」的两步拾取状态;`null` = 没在移动。 */
|
|
91
|
+
move: MoveState | null;
|
|
92
|
+
/**
|
|
93
|
+
* 正在截图。为真时宿主**必须隐藏所有浮层**(高亮框 / 工具条 / 提示条)——
|
|
94
|
+
* `captureVisibleTab` 抓的是整屏,不隐藏就会把编辑器 UI 截进客户页的图里。
|
|
95
|
+
*/
|
|
96
|
+
capturing: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* 正在页面内原位改文字的元素 selector;`null` = 没在改。
|
|
99
|
+
*
|
|
100
|
+
* 必须有这个状态,不然编辑态**完全不可见** —— contentEditable 本身不带任何视觉
|
|
101
|
+
* 提示,用户点了「编辑文字」看到的和没点一模一样,分不清是没生效还是生效了。
|
|
102
|
+
*/
|
|
103
|
+
textEditing: string | null;
|
|
104
|
+
geometry: ReturnType<typeof useCanvasGeometry>;
|
|
105
|
+
/**
|
|
106
|
+
* 移动端超宽缩小(契约 v11 §8)的当前状态。`scale < 1` 时 `<html>` 被整体缩放,
|
|
107
|
+
* agent 回报的几何(选中框、区块框)已经是缩放后的值,宿主**不用**换算;
|
|
108
|
+
* 只有拿外部数据(热图坐标)往画布上对时才需要除以 `scale`。
|
|
109
|
+
*/
|
|
110
|
+
mobileFit: MobileFitState;
|
|
111
|
+
changeList: ReturnType<typeof useChangeList>;
|
|
112
|
+
commands: {
|
|
113
|
+
/** 选中某个元素(点路径选择器的某一级、或程序化选中)。空串 = 清空。 */
|
|
114
|
+
select: (selector: string) => Promise<void>;
|
|
115
|
+
/** 悬浮预览高亮。⚠️ **不改选中态**;空串 = 清除。 */
|
|
116
|
+
hover: (selector: string) => Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* 「定位」:把元素滚进视野。**与 `select` 分开**,不合并进去 ——
|
|
119
|
+
* 画布内点选、恢复选中态、AI 应用改动都会产生选中,那些场景滚动是意外行为
|
|
120
|
+
* (尤其恢复:刷新后页面在顶部,自己跳走会让用户以为页面没加载完)。
|
|
121
|
+
*
|
|
122
|
+
* 未命中不抛错(客户改版后老 selector 失效是正常分支)。
|
|
123
|
+
*/
|
|
124
|
+
reveal: (selector: string) => Promise<void>;
|
|
125
|
+
/** `</>` 的 seed:**outerHTML**,已剥 script/noscript。 */
|
|
126
|
+
readOuterHtml: (selector?: string) => ReturnType<typeof getOuterHtml>;
|
|
127
|
+
/**
|
|
128
|
+
* 在客户页**原位**开始改文字(问题 3)。
|
|
129
|
+
*
|
|
130
|
+
* 没有对应的「结束」命令给宿主调:用户按 Esc、按 Enter、点到别处都算结束,
|
|
131
|
+
* 由 agent 的 `textEditCommit` 事件回报,本 hook 收到后自动落一条 change。
|
|
132
|
+
*/
|
|
133
|
+
startTextEdit: (selector?: string) => Promise<void>;
|
|
134
|
+
/** 截选中元素并返回图像字节。**不上传** —— 上传是宿主的事(包里不碰后端端点)。 */
|
|
135
|
+
captureSelectedElement: (selector: string) => Promise<CaptureOutcome>;
|
|
136
|
+
/** 复制元素(存 outerHTML 到剪贴板,不产生 change)。 */
|
|
137
|
+
copyElement: (selector: string) => Promise<void>;
|
|
138
|
+
/** 粘贴到某个元素的指定位置。**粘贴前会重写防撞号**。 */
|
|
139
|
+
pasteInto: (selector: string, mode: InsertMode) => Promise<void>;
|
|
140
|
+
/** 开始移动:记下源,等用户在画布里点目标。 */
|
|
141
|
+
startMove: (selector: string) => void;
|
|
142
|
+
/** 确认移动。目标未选时是 no-op。 */
|
|
143
|
+
confirmMove: (position: MovePosition) => Promise<void>;
|
|
144
|
+
cancelMove: () => void;
|
|
145
|
+
/**
|
|
146
|
+
* 展开图层树里某个被截断的节点:拉它的子树接回原树。
|
|
147
|
+
*
|
|
148
|
+
* 目标行不在树里(切过设备、树重拉过)时是 no-op。失败不抛 —— 树少一段,
|
|
149
|
+
* 但画布点选与路径选择器都还在。
|
|
150
|
+
*/
|
|
151
|
+
expandTreeNode: (id: string) => Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* 把清单整体换成另一批改动(切换实验版本)。转发 `changeList.replaceAll`。
|
|
154
|
+
*
|
|
155
|
+
* 也放在 `commands` 上,是因为自己拼外壳的宿主(headless 层)拿到的就是这一组
|
|
156
|
+
* 动作入口 —— 版本切换是「一个动作」,不该要求调用方先绕到 `changeList` 上去找。
|
|
157
|
+
*/
|
|
158
|
+
replaceAll: ReturnType<typeof useChangeList>['replaceAll'];
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export declare function useInlineEditor(input: UseInlineEditorInput): UseInlineEditorResult;
|