@ptengine/inline-editor 0.0.0-stage → 0.1.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 +28 -0
- package/README.md +286 -2
- package/dist/InlineEditor.d.ts +342 -0
- package/dist/InlineEditor.js +867 -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 +326 -0
- package/dist/kernel/ai/aiInlineApply.js +461 -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 +185 -0
- package/dist/kernel/changelist/useChangeList.js +259 -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 +313 -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 +19 -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 +158 -0
- package/dist/useInlineEditor.js +343 -0
- package/package.json +58 -3
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { InlineChange } from '@ptengine/inline-core/change-format';
|
|
2
|
+
import { type ActionChangeInput, type AttributeChangeInput, type ContentChangeInput, type MoveChangeInput, type StyleChangeInput } from './builders.js';
|
|
3
|
+
/**
|
|
4
|
+
* change list 状态 + 「应用到画布」的编排。
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ **顺序是硬要求:先 `applyChange` 命令、成功后才入列。**
|
|
7
|
+
* 反过来会出现「列表里有、页面上没有」—— 用户看到 change 已记录,但画布毫无变化,
|
|
8
|
+
* 且没有任何报错。这是最难排查的一类不一致。
|
|
9
|
+
*
|
|
10
|
+
* 每条 change 带一个产品页分配的 `changeId`(**不进落盘 payload**),
|
|
11
|
+
* agent 侧靠它定位撤销闭包。
|
|
12
|
+
*/
|
|
13
|
+
export interface ChangeRecord {
|
|
14
|
+
changeId: string;
|
|
15
|
+
change: InlineChange;
|
|
16
|
+
/**
|
|
17
|
+
* 这条改动作用元素的**祖先 selector**。落盘前按祖先关系收敛要用它
|
|
18
|
+
* (见 `mergeForSave`)—— 例如「祖先后来被隐藏/整体替换了,本条就不必存」。
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ **不进落盘 payload**,只活在编辑器内存里。
|
|
21
|
+
*
|
|
22
|
+
* 为什么在这里记:祖先关系在**加入 change 那一刻**是已知的(选中态里就有
|
|
23
|
+
* `selection.chain`)。现役是在保存时从 `renderPath` 逐段截字符串反推
|
|
24
|
+
* (`useInline.ts:getParentNode`),既要活 DOM 也更容易错。
|
|
25
|
+
*
|
|
26
|
+
* 缺省空数组 = 不知道祖先(程序化加入、回显的存量数据)→ 该条不参与收敛。
|
|
27
|
+
*/
|
|
28
|
+
ancestors: string[];
|
|
29
|
+
/**
|
|
30
|
+
* 这条改动插入的新元素 selector(扩展 `applyChange` 回报的 `insertedSelectors`:INSERT 的根、
|
|
31
|
+
* EDIT_HTML 的新根)。**只活在内存里、不进落盘 payload**,AI 落地后的几何自检要量它们,
|
|
32
|
+
* 宿主也要把它们并进授权范围。没有插入(样式 / 文案 / 回显数据)就没有这个键。
|
|
33
|
+
*/
|
|
34
|
+
insertedSelectors?: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface UseChangeListResult {
|
|
37
|
+
list: ChangeRecord[];
|
|
38
|
+
/** 明文 change 数组(落盘前经 toWireFormat 转数值形态)。 */
|
|
39
|
+
changes: InlineChange[];
|
|
40
|
+
addStyle: (input: StyleChangeInput) => Promise<string>;
|
|
41
|
+
addContent: (input: ContentChangeInput) => Promise<string>;
|
|
42
|
+
addAttribute: (input: AttributeChangeInput) => Promise<string>;
|
|
43
|
+
addAction: (input: ActionChangeInput) => Promise<string>;
|
|
44
|
+
addMove: (input: MoveChangeInput) => Promise<string>;
|
|
45
|
+
revert: (changeId: string) => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* **同步**读当前清单(`listRef`),不是渲染期那份 `list`。
|
|
48
|
+
*
|
|
49
|
+
* 为什么必须有:`list` 是 state,而写入是异步的 —— 「刚 `await` 完一次 apply 就要知道
|
|
50
|
+
* 清单里有什么」这种调用(AI 应用完立刻撤销、改完最后一条立刻切版本)发生在 React
|
|
51
|
+
* flush 之前,读 `list` 拿到的是**上一次渲染**的值。少一条的后果是静默的:
|
|
52
|
+
* 切版本时交还给宿主的清单漏掉那条改动(它就此消失),或者刚铸出来的 changeId
|
|
53
|
+
* 被判成「不属于当前版本」而一条都撤不掉。
|
|
54
|
+
*/
|
|
55
|
+
peekList: () => ChangeRecord[];
|
|
56
|
+
/** 全部撤销。**倒序**撤 —— 顺序撤会让后面的 change 基于已被撤掉的现场还原。 */
|
|
57
|
+
revertAll: () => Promise<void>;
|
|
58
|
+
clear: () => void;
|
|
59
|
+
/**
|
|
60
|
+
* 把**已存在的**改动载入本次会话:逐条回放到画布,并填进清单(撤销/覆盖照常可用)。
|
|
61
|
+
*
|
|
62
|
+
* 用在「打开已有实验的编辑器」——清单和画布都得回到上次保存的样子。
|
|
63
|
+
*/
|
|
64
|
+
hydrate: (changes: InlineChange[]) => Promise<HydrateOutcome>;
|
|
65
|
+
/**
|
|
66
|
+
* 把清单**整体换成另一批**改动:撤光当前的,再回放新的。用在**切换实验版本**
|
|
67
|
+
* (每个版本一份清单,宿主持有数据、包只负责换)。
|
|
68
|
+
*
|
|
69
|
+
* = 快照 → `revertAll()` → `hydrate(next)`。拆成两步调用方自己做也行,但那样每个
|
|
70
|
+
* 调用点都要重复「先快照、必须倒序撤、撤完才能 hydrate」这三件事,漏一件都不报错:
|
|
71
|
+
* 不快照就把上一个版本的清单丢了(用户的改动凭空消失),不撤就把两个版本的改动
|
|
72
|
+
* 叠在同一个页面上。
|
|
73
|
+
*
|
|
74
|
+
* @param prevIds 与 `next` **等长且同序**的旧 changeId(宿主上次从
|
|
75
|
+
* `onVersionLeave` 拿到的那份 `records` 的 id)。给了就能拿到 `idMap`
|
|
76
|
+
* (旧 id → 本次新铸的 id)—— `hydrate` 每次都铸新 id(撤销闭包随 iframe 重建而丢,
|
|
77
|
+
* 见它的注释),而 AI 卡片上记的是**旧** id,没有这张别名表,切回来之后
|
|
78
|
+
* 「撤销这一批」会找不到任何一条。不给则 `idMap` 为空。
|
|
79
|
+
*/
|
|
80
|
+
replaceAll: (next: InlineChange[], prevIds?: string[]) => Promise<{
|
|
81
|
+
previous: ChangeRecord[];
|
|
82
|
+
outcome: HydrateOutcome;
|
|
83
|
+
idMap: Map<string, string>;
|
|
84
|
+
}>;
|
|
85
|
+
lastError: string | null;
|
|
86
|
+
}
|
|
87
|
+
export interface HydrateOutcome {
|
|
88
|
+
/** 成功回放到画布的条数。 */
|
|
89
|
+
applied: number;
|
|
90
|
+
/**
|
|
91
|
+
* 应用失败的条目(selector 在当前页面上不再命中、元素已被改版删掉等)。
|
|
92
|
+
*
|
|
93
|
+
* ⚠️ **它们仍然留在清单里**,不会被丢掉。三种做法里只有这种是诚实的:
|
|
94
|
+
* · 静默丢掉 → 用户上次保存的改动凭空消失,再一保存就真没了(数据丢失);
|
|
95
|
+
* · 静默留着不报 → 清单说有 5 项改动,页面上只看得见 2 项,用户以为编辑器坏了;
|
|
96
|
+
* · 留着 + 报出来 → 用户能看到「这 3 项在当前页面上失效了」,自己决定删还是留。
|
|
97
|
+
* 客户页是活的、会改版,这条路径不是异常而是常态。
|
|
98
|
+
*/
|
|
99
|
+
failed: Array<{
|
|
100
|
+
change: InlineChange;
|
|
101
|
+
reason: string;
|
|
102
|
+
}>;
|
|
103
|
+
}
|
|
104
|
+
export declare function useChangeList(sessionId: string | undefined): UseChangeListResult;
|
|
105
|
+
export interface ButtonActionDeps {
|
|
106
|
+
identifier: string;
|
|
107
|
+
selector: string;
|
|
108
|
+
/**
|
|
109
|
+
* 作用元素的祖先 selector —— 落盘收敛要用(`mergeForSave`)。
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ **每个工具函数都必须把它转发进 `add*` 的 input**。漏了不会报错,
|
|
112
|
+
* 只是那条 change 的祖先链是空的、永远不参与收敛 —— 症状是「按理该被收掉的条目
|
|
113
|
+
* 还留在存库数据里」。加进来时 tsc 抓到了 delete / insert 两处漏转发。
|
|
114
|
+
*/
|
|
115
|
+
ancestors?: string[];
|
|
116
|
+
addStyle: (input: StyleChangeInput) => Promise<string>;
|
|
117
|
+
addContent: (input: ContentChangeInput) => Promise<string>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 「删除」—— ⚠️ **不是真删**,是 `display: none`
|
|
121
|
+
* (`edit.service.js:332-339` 第一行就是 `changeStyle('display','none')`)。
|
|
122
|
+
*
|
|
123
|
+
* 现役随后还会把选中态上移到父元素;那一步由调用方做(它才拿得到祖先链)。
|
|
124
|
+
*/
|
|
125
|
+
export declare function deleteElement(deps: ButtonActionDeps): Promise<string>;
|
|
126
|
+
/** 「编辑样式」—— 一条 `elementStyle`。 */
|
|
127
|
+
export declare function editStyle(deps: ButtonActionDeps, property: string, value: string, opts?: {
|
|
128
|
+
cssRule?: boolean;
|
|
129
|
+
important?: boolean;
|
|
130
|
+
cssConditional?: string;
|
|
131
|
+
}): Promise<string>;
|
|
132
|
+
/**
|
|
133
|
+
* 「`</>` 编辑 HTML」—— `elementContent` + `mode: 'replace'`,**不传 `isTextChange`**。
|
|
134
|
+
* 编辑的是整个元素(outerHTML),seed 来自 `getOuterHtml`。
|
|
135
|
+
*/
|
|
136
|
+
export declare function editHtml(deps: ButtonActionDeps, html: string): Promise<string>;
|
|
137
|
+
/**
|
|
138
|
+
* 「编辑文字」—— `elementContent` + `mode: 'replace'` + **`isTextChange: true`**。
|
|
139
|
+
*
|
|
140
|
+
* ⚠️ `isTextChange` 是与 `</>` 唯一的区别(两者共用 `mode: 'replace'`):
|
|
141
|
+
* 它让 agent 走「换 innerHTML、外壳保留」,而 `</>` 走「换元素本身」。
|
|
142
|
+
* 传错的后果是 `#target` 这个锚点一侧存在一侧不存在,后续 selector 全部错位。
|
|
143
|
+
*/
|
|
144
|
+
export declare function editText(deps: ButtonActionDeps, html: string): Promise<string>;
|
|
145
|
+
/**
|
|
146
|
+
* 插入位置。**刻意不用 `ElementContentModeName`** —— 那个类型含 `'replace'`,
|
|
147
|
+
* 而「插一张新图」永远不该是 replace(那会把目标元素整个换掉)。tsc 已经拦过一次。
|
|
148
|
+
*/
|
|
149
|
+
export type InsertMode = 'insert' | 'append' | 'before' | 'after';
|
|
150
|
+
export interface ImageActionDeps extends ButtonActionDeps {
|
|
151
|
+
addAttribute: (input: AttributeChangeInput) => Promise<string>;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 「替换图片」—— **同一个按钮,三种完全不同的 change**(分支判定见 `dialogs/imageEdit.ts`)。
|
|
155
|
+
*
|
|
156
|
+
* 判错不报错,只是「点了替换图片、图没变」:比如对背景图 div 产出
|
|
157
|
+
* `changeElementAttribute src`,属性确实设上了,但 div 根本不吃 src。
|
|
158
|
+
*/
|
|
159
|
+
export declare function replaceImage(deps: ImageActionDeps, input: {
|
|
160
|
+
branch: 'img-src' | 'background' | 'insert';
|
|
161
|
+
url: string;
|
|
162
|
+
mode?: InsertMode;
|
|
163
|
+
}): Promise<string>;
|
|
164
|
+
/** 「图片设置」—— 本期只有 alt。**空串 = 移除属性**(`value` 省略)。 */
|
|
165
|
+
export declare function setImageAlt(deps: ImageActionDeps, alt: string): Promise<string>;
|
|
166
|
+
/** 「点击动作」—— action 对象由 `dialogs/actionTypes.ts` 的 `buildActionObject` 组装。 */
|
|
167
|
+
export declare function setClickAction(deps: ButtonActionDeps & {
|
|
168
|
+
addAction: (input: ActionChangeInput) => Promise<string>;
|
|
169
|
+
}, action: Record<string, unknown>): Promise<string>;
|
|
170
|
+
/**
|
|
171
|
+
* 「替换视频」—— `<video>` 与 YouTube 内嵌 iframe 都是改 `src`,只有一条分支
|
|
172
|
+
* (不像替换图片要分 img / 背景图 / 插入三种)。
|
|
173
|
+
*/
|
|
174
|
+
export declare function replaceVideo(deps: ImageActionDeps, url: string): Promise<string>;
|
|
175
|
+
/**
|
|
176
|
+
* 「插入元素」—— 用户手写的 HTML **原样插入,不加包装**。
|
|
177
|
+
*
|
|
178
|
+
* 与 `replaceImage` 的 insert 分支刻意不同:那里插的是我们生成的 `<img>`,包一层带防撞号
|
|
179
|
+
* 的 `div` 是为了给它稳定的选择器锚点;用户手写的 HTML 该落成什么样就是什么样,
|
|
180
|
+
* 凭空多一层 `div` 会把他的布局改坏,而且他不知道那层是哪来的。
|
|
181
|
+
*/
|
|
182
|
+
export declare function insertElement(deps: ButtonActionDeps, input: {
|
|
183
|
+
html: string;
|
|
184
|
+
mode: InsertMode;
|
|
185
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { applyChange, revertChange } from '../canvasCommands.js';
|
|
3
|
+
import { overrideOutcome } from './overrides.js';
|
|
4
|
+
import { backgroundImageValue, buildInsertImageHtml, randomPtElementId } from '../../dialogs/imageEdit.js';
|
|
5
|
+
import { buildActionChange, buildAttributeChange, buildContentChange, buildMoveChange, buildStyleChange } from './builders.js';
|
|
6
|
+
function newChangeId() {
|
|
7
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
8
|
+
return crypto.randomUUID();
|
|
9
|
+
}
|
|
10
|
+
return `cid-${Math.random().toString(36).slice(2)}-${performance.now()}`;
|
|
11
|
+
}
|
|
12
|
+
function withResolvedStyleMode(change, applied) {
|
|
13
|
+
return change.type === 'elementStyle' && applied?.resolvedStyleMode
|
|
14
|
+
? { ...change, ...applied.resolvedStyleMode }
|
|
15
|
+
: change;
|
|
16
|
+
}
|
|
17
|
+
export function useChangeList(sessionId) {
|
|
18
|
+
const [list, setList] = useState([]);
|
|
19
|
+
const [lastError, setLastError] = useState(null);
|
|
20
|
+
const listRef = useRef([]);
|
|
21
|
+
listRef.current = list;
|
|
22
|
+
const push = useCallback(async (change, ancestors = []) => {
|
|
23
|
+
if (!sessionId)
|
|
24
|
+
throw new Error('no canvas session');
|
|
25
|
+
const changeId = newChangeId();
|
|
26
|
+
const prev = listRef.current;
|
|
27
|
+
const { next: nextChanges, dropped } = overrideOutcome(prev.map(r => r.change), change);
|
|
28
|
+
const byChange = new Map(prev.map(r => [r.change, r.changeId]));
|
|
29
|
+
for (const old of dropped) {
|
|
30
|
+
const oldId = byChange.get(old);
|
|
31
|
+
if (!oldId)
|
|
32
|
+
continue;
|
|
33
|
+
await revertChange(sessionId, oldId).catch(() => { });
|
|
34
|
+
}
|
|
35
|
+
const applied = await applyChange(sessionId, changeId, change);
|
|
36
|
+
const stored = withResolvedStyleMode(change, applied);
|
|
37
|
+
const ancestorsByChange = new Map(prev.map(r => [r.change, r.ancestors]));
|
|
38
|
+
const insertedByChange = new Map(prev.map(r => [r.change, r.insertedSelectors]));
|
|
39
|
+
const insertedNow = applied.insertedSelectors?.length ? applied.insertedSelectors : undefined;
|
|
40
|
+
const nextRecords = nextChanges.map(c => {
|
|
41
|
+
const inserted = c === change ? insertedNow : insertedByChange.get(c);
|
|
42
|
+
return {
|
|
43
|
+
change: c === change ? stored : c,
|
|
44
|
+
changeId: byChange.get(c) ?? changeId,
|
|
45
|
+
ancestors: ancestorsByChange.get(c) ?? ancestors,
|
|
46
|
+
...(inserted?.length ? { insertedSelectors: inserted } : {})
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
listRef.current = nextRecords;
|
|
50
|
+
setList(nextRecords);
|
|
51
|
+
setLastError(null);
|
|
52
|
+
return changeId;
|
|
53
|
+
}, [sessionId]);
|
|
54
|
+
const hydrate = useCallback(async (changes) => {
|
|
55
|
+
if (!sessionId)
|
|
56
|
+
throw new Error('no canvas session');
|
|
57
|
+
if (listRef.current.length > 0) {
|
|
58
|
+
return { applied: 0, failed: [] };
|
|
59
|
+
}
|
|
60
|
+
const records = [];
|
|
61
|
+
const failed = [];
|
|
62
|
+
for (const change of changes) {
|
|
63
|
+
const changeId = newChangeId();
|
|
64
|
+
let applied;
|
|
65
|
+
try {
|
|
66
|
+
applied = await applyChange(sessionId, changeId, change);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
failed.push({ change, reason: String(e?.message ?? e) });
|
|
70
|
+
records.push({ changeId, change, ancestors: [] });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
records.push({
|
|
74
|
+
changeId,
|
|
75
|
+
change: withResolvedStyleMode(change, applied),
|
|
76
|
+
ancestors: []
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
listRef.current = records;
|
|
80
|
+
setList(records);
|
|
81
|
+
setLastError(null);
|
|
82
|
+
return { applied: records.length - failed.length, failed };
|
|
83
|
+
}, [sessionId]);
|
|
84
|
+
const guard = useCallback(async (run) => {
|
|
85
|
+
try {
|
|
86
|
+
return await run();
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
setLastError(String(e?.message ?? e));
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
const addStyle = useCallback((input) => guard(() => push(buildStyleChange(input), input.ancestors)), [guard, push]);
|
|
94
|
+
const addContent = useCallback((input) => guard(() => push(buildContentChange(input), input.ancestors)), [guard, push]);
|
|
95
|
+
const addAttribute = useCallback((input) => guard(() => push(buildAttributeChange(input), input.ancestors)), [guard, push]);
|
|
96
|
+
const addAction = useCallback((input) => guard(() => push(buildActionChange(input), input.ancestors)), [guard, push]);
|
|
97
|
+
const addMove = useCallback((input) => guard(() => push(buildMoveChange(input), input.ancestors)), [guard, push]);
|
|
98
|
+
const revert = useCallback((changeId) => guard(async () => {
|
|
99
|
+
if (!sessionId)
|
|
100
|
+
throw new Error('no canvas session');
|
|
101
|
+
await revertChange(sessionId, changeId);
|
|
102
|
+
const next = listRef.current.filter(r => r.changeId !== changeId);
|
|
103
|
+
listRef.current = next;
|
|
104
|
+
setList(next);
|
|
105
|
+
}), [guard, sessionId]);
|
|
106
|
+
const revertAll = useCallback(() => guard(async () => {
|
|
107
|
+
if (!sessionId)
|
|
108
|
+
throw new Error('no canvas session');
|
|
109
|
+
for (const record of [...listRef.current].reverse()) {
|
|
110
|
+
await revertChange(sessionId, record.changeId).catch(() => { });
|
|
111
|
+
}
|
|
112
|
+
listRef.current = [];
|
|
113
|
+
setList([]);
|
|
114
|
+
}), [guard, sessionId]);
|
|
115
|
+
const replaceAll = useCallback(async (next, prevIds) => {
|
|
116
|
+
if (!sessionId)
|
|
117
|
+
throw new Error('no canvas session');
|
|
118
|
+
const previous = listRef.current;
|
|
119
|
+
await revertAll();
|
|
120
|
+
let outcome;
|
|
121
|
+
try {
|
|
122
|
+
outcome = await hydrate(next);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
listRef.current = previous;
|
|
126
|
+
setList(previous);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
const nextRecords = listRef.current;
|
|
130
|
+
const idMap = new Map();
|
|
131
|
+
if (prevIds) {
|
|
132
|
+
for (let i = 0; i < nextRecords.length; i++) {
|
|
133
|
+
const old = prevIds[i];
|
|
134
|
+
if (old)
|
|
135
|
+
idMap.set(old, nextRecords[i].changeId);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { previous, outcome, idMap };
|
|
139
|
+
}, [hydrate, revertAll, sessionId]);
|
|
140
|
+
const peekList = useCallback(() => listRef.current, []);
|
|
141
|
+
const clear = useCallback(() => {
|
|
142
|
+
listRef.current = [];
|
|
143
|
+
setList([]);
|
|
144
|
+
setLastError(null);
|
|
145
|
+
}, []);
|
|
146
|
+
return {
|
|
147
|
+
list,
|
|
148
|
+
changes: list.map(r => r.change),
|
|
149
|
+
addStyle,
|
|
150
|
+
addContent,
|
|
151
|
+
addAttribute,
|
|
152
|
+
addAction,
|
|
153
|
+
addMove,
|
|
154
|
+
revert,
|
|
155
|
+
revertAll,
|
|
156
|
+
clear,
|
|
157
|
+
hydrate,
|
|
158
|
+
replaceAll,
|
|
159
|
+
peekList,
|
|
160
|
+
lastError
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export function deleteElement(deps) {
|
|
164
|
+
return deps.addStyle({
|
|
165
|
+
identifier: deps.identifier,
|
|
166
|
+
selector: deps.selector,
|
|
167
|
+
ancestors: deps.ancestors,
|
|
168
|
+
property: 'display',
|
|
169
|
+
value: 'none'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
export function editStyle(deps, property, value, opts = {}) {
|
|
173
|
+
return deps.addStyle({
|
|
174
|
+
identifier: deps.identifier,
|
|
175
|
+
selector: deps.selector,
|
|
176
|
+
ancestors: deps.ancestors,
|
|
177
|
+
property,
|
|
178
|
+
value,
|
|
179
|
+
...opts
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export function editHtml(deps, html) {
|
|
183
|
+
return deps.addContent({
|
|
184
|
+
identifier: deps.identifier,
|
|
185
|
+
selector: deps.selector,
|
|
186
|
+
ancestors: deps.ancestors,
|
|
187
|
+
html,
|
|
188
|
+
mode: 'replace'
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
export function editText(deps, html) {
|
|
192
|
+
return deps.addContent({
|
|
193
|
+
identifier: deps.identifier,
|
|
194
|
+
selector: deps.selector,
|
|
195
|
+
ancestors: deps.ancestors,
|
|
196
|
+
html,
|
|
197
|
+
mode: 'replace',
|
|
198
|
+
isTextChange: true
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export function replaceImage(deps, input) {
|
|
202
|
+
if (input.branch === 'img-src') {
|
|
203
|
+
return deps.addAttribute({
|
|
204
|
+
identifier: deps.identifier,
|
|
205
|
+
selector: deps.selector,
|
|
206
|
+
attribute: 'src',
|
|
207
|
+
value: input.url
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (input.branch === 'background') {
|
|
211
|
+
return deps.addStyle({
|
|
212
|
+
identifier: deps.identifier,
|
|
213
|
+
selector: deps.selector,
|
|
214
|
+
property: 'background-image',
|
|
215
|
+
value: backgroundImageValue(input.url)
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return deps.addContent({
|
|
219
|
+
identifier: deps.identifier,
|
|
220
|
+
selector: deps.selector,
|
|
221
|
+
html: buildInsertImageHtml(input.url, input.mode ?? 'append', randomPtElementId),
|
|
222
|
+
mode: input.mode ?? 'append'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
export function setImageAlt(deps, alt) {
|
|
226
|
+
return deps.addAttribute({
|
|
227
|
+
identifier: deps.identifier,
|
|
228
|
+
selector: deps.selector,
|
|
229
|
+
ancestors: deps.ancestors,
|
|
230
|
+
attribute: 'alt',
|
|
231
|
+
value: alt.length > 0 ? alt : undefined
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
export function setClickAction(deps, action) {
|
|
235
|
+
return deps.addAction({
|
|
236
|
+
identifier: deps.identifier,
|
|
237
|
+
selector: deps.selector,
|
|
238
|
+
ancestors: deps.ancestors,
|
|
239
|
+
action
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
export function replaceVideo(deps, url) {
|
|
243
|
+
return deps.addAttribute({
|
|
244
|
+
identifier: deps.identifier,
|
|
245
|
+
selector: deps.selector,
|
|
246
|
+
ancestors: deps.ancestors,
|
|
247
|
+
attribute: 'src',
|
|
248
|
+
value: url
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
export function insertElement(deps, input) {
|
|
252
|
+
return deps.addContent({
|
|
253
|
+
identifier: deps.identifier,
|
|
254
|
+
selector: deps.selector,
|
|
255
|
+
ancestors: deps.ancestors,
|
|
256
|
+
html: input.html,
|
|
257
|
+
mode: input.mode
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inline-editor 的显式运行时配置。
|
|
3
|
+
*
|
|
4
|
+
* 本包以**预构建 ESM** 发布,产物里不能再依赖消费方 Vite 替换构建期环境变量
|
|
5
|
+
* (预构建后那些表达式要么原样留在产物里读不到值,要么被消费方的构建误处理)。
|
|
6
|
+
* 所以两个原本读构建期环境变量的配置项,改成由消费方在应用启动时显式传入。
|
|
7
|
+
* (为此有测试扫描源码、禁止再出现对 Vite 环境变量对象的读取,注释里也不要写那个字面量。)
|
|
8
|
+
*
|
|
9
|
+
* Vite 消费方在入口调用一次 `configureInlineEditor`,把自己的两个 `VITE_*` 变量传进来,
|
|
10
|
+
* 具体写法见包 README「配置扩展 ID」。
|
|
11
|
+
*/
|
|
12
|
+
export interface InlineEditorConfig {
|
|
13
|
+
/**
|
|
14
|
+
* 扩展 id。**非空 = 严格模式**:只认这一个 id(正式上线填商店 id)。
|
|
15
|
+
* 不配(或空串)= 内测模式:id 改读插件在 `<html data-ptengine-kit-id>` 上报的值。
|
|
16
|
+
*/
|
|
17
|
+
extensionId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* 即使插件支持也不启用的能力:逗号分隔字符串,或字符串数组。
|
|
20
|
+
* 两种形式走同一套规范化(trim、去空)。
|
|
21
|
+
*/
|
|
22
|
+
disabledCapabilities?: string | readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/** 浅合并进模块级配置,可多次调用;后调用的字段覆盖先前的同名字段。 */
|
|
25
|
+
export declare function configureInlineEditor(config: InlineEditorConfig): void;
|
|
26
|
+
/** 当前生效的配置(只读快照:返回的是副本,改它不会影响模块级配置)。 */
|
|
27
|
+
export declare function getInlineEditorConfig(): Readonly<InlineEditorConfig>;
|
|
28
|
+
/** 清空配置。仅测试用(也从包入口导出,方便消费方的测试隔离)。 */
|
|
29
|
+
export declare function resetInlineEditorConfig(): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `data:` URL → Blob,不经 `fetch`。
|
|
3
|
+
*
|
|
4
|
+
* `captureElement` 回的是 PNG dataURL(图像数据全程留在 SW 里,只有成品一张过通道)。
|
|
5
|
+
* 现役 `captureAiScreenshot` 用 `fetch(dataUrl).blob()` 转,那依赖宿主的网络栈对 `data:`
|
|
6
|
+
* 方案的支持(jsdom / 某些 CSP 下不可用);这里纯解码,哪儿都能跑。
|
|
7
|
+
*/
|
|
8
|
+
export declare function dataUrlToBlob(dataUrl: string): Blob;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function dataUrlToBlob(dataUrl) {
|
|
2
|
+
const comma = dataUrl.indexOf(',');
|
|
3
|
+
if (!dataUrl.startsWith('data:') || comma < 0)
|
|
4
|
+
throw new Error('not a data URL');
|
|
5
|
+
const meta = dataUrl.slice(5, comma);
|
|
6
|
+
const payload = dataUrl.slice(comma + 1);
|
|
7
|
+
const [mime = 'application/octet-stream', ...params] = meta.split(';');
|
|
8
|
+
const isBase64 = params.includes('base64');
|
|
9
|
+
if (isBase64) {
|
|
10
|
+
const bin = atob(payload);
|
|
11
|
+
const bytes = new Uint8Array(bin.length);
|
|
12
|
+
for (let i = 0; i < bin.length; i++)
|
|
13
|
+
bytes[i] = bin.charCodeAt(i);
|
|
14
|
+
return new Blob([bytes], { type: mime });
|
|
15
|
+
}
|
|
16
|
+
return new Blob([decodeURIComponent(payload)], { type: mime });
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 树节点上的链接(`DomTreeNode.href`)—— 类型补丁 + 给 AI 看的去向写法。
|
|
3
|
+
*
|
|
4
|
+
* 扩展从 `<a href>` 与 PageFly 这类构建器的 `data-href`(图片链接 / 商品卡:外面没有 `<a>`,JS 跳转)
|
|
5
|
+
* 读出这个字段:同站是 `pathname + search + hash`(以 `/` 开头),外站是完整地址。
|
|
6
|
+
* 真机 2026-10-15(vivaia allday):整页 41 个 `data-href` 图片,树里没有链接信息时 agent 把
|
|
7
|
+
* 「page-model 没列 cta」读成了「全页没有购买导线」。
|
|
8
|
+
*/
|
|
9
|
+
import '@ptengine/inline-core/protocol';
|
|
10
|
+
declare module '@ptengine/inline-core/protocol' {
|
|
11
|
+
interface DomTreeNode {
|
|
12
|
+
/**
|
|
13
|
+
* 仅 `includeRect: true` 时出现:这个节点**自己**点下去会去哪。同站给路径(含查询串),
|
|
14
|
+
* 外站给完整地址,≤200 字符。不是链接时不发;老扩展永远不发。
|
|
15
|
+
*/
|
|
16
|
+
href?: string;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** 去向写法的长度上限:够认出是哪个页面就行。 */
|
|
20
|
+
export declare const LINK_PATH_MAX = 60;
|
|
21
|
+
/**
|
|
22
|
+
* 给 AI 看的去向:查询串去掉(`?variant=46629727863040` 是噪音,同一个商品不同变体应算一个去向),
|
|
23
|
+
* hash 留着(页内锚点 `#buy` 本身就是导线);外站去掉协议、留域名;`mailto:` 这类原样。截到上限。
|
|
24
|
+
*/
|
|
25
|
+
export declare function linkPath(href: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import '@ptengine/inline-core/protocol';
|
|
2
|
+
export const LINK_PATH_MAX = 60;
|
|
3
|
+
export function linkPath(href) {
|
|
4
|
+
const raw = href.trim();
|
|
5
|
+
let out = raw;
|
|
6
|
+
const hashAt = raw.indexOf('#');
|
|
7
|
+
const hash = hashAt >= 0 ? raw.slice(hashAt) : '';
|
|
8
|
+
const beforeHash = hashAt >= 0 ? raw.slice(0, hashAt) : raw;
|
|
9
|
+
const queryAt = beforeHash.indexOf('?');
|
|
10
|
+
const path = queryAt >= 0 ? beforeHash.slice(0, queryAt) : beforeHash;
|
|
11
|
+
if (raw.startsWith('/'))
|
|
12
|
+
out = `${path}${hash}`;
|
|
13
|
+
else if (/^https?:\/\//i.test(raw))
|
|
14
|
+
out = `${path.replace(/^https?:\/\//i, '')}${hash}`;
|
|
15
|
+
return out.length > LINK_PATH_MAX ? out.slice(0, LINK_PATH_MAX) : out;
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MeasureDocumentResult } from '@ptengine/inline-core/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* 移动端超宽缩小(契约 v11 设计稿 §8)—— **纯计算**,不碰画布。
|
|
4
|
+
*
|
|
5
|
+
* 客户页写死 `<meta name="viewport" content="width=750">` 之类时,375 宽的画布里页面会横向溢出。
|
|
6
|
+
* 算法照搬 engage-editor 线上版(`page.store.ts` 的 `updateFrameSize` + `OriginalPageIframe.vue`
|
|
7
|
+
* 的 `setFrameScaleStyle`):量出自然宽度 W,`s = min(1, 视口宽 / W)`,把 `transform: scale(s)`
|
|
8
|
+
* 加在 `<html>` 上,宽度钉成 W、高度钉成 H·s。
|
|
9
|
+
*
|
|
10
|
+
* 为什么是**产品页**算而不是插件:缩多少、要不要缩、加哪几条渲染修正,都是会随客户页反馈
|
|
11
|
+
* 反复调的策略;插件只提供 `measureDocument` / `setStyleLayer` / `observeDocumentSize` 三个原语,
|
|
12
|
+
* 这里改阈值不用发插件。
|
|
13
|
+
*/
|
|
14
|
+
/** 缩放层。`measureDocument` / `observeDocumentSize` 都要把它摘掉再量,否则量到的是缩放后的尺寸。 */
|
|
15
|
+
export declare const MOBILE_FIT_LAYER = "mobile-fit";
|
|
16
|
+
/** 渲染修正层(滚动行为 / 防闪烁隐藏 / body 高度写死)。不参与排除 —— 修正后的布局才是我们要量的。 */
|
|
17
|
+
export declare const RENDER_FIXES_LAYER = "render-fixes";
|
|
18
|
+
/** 两层的叠放顺序:修正在前、缩放在后(数字大的后插入、优先级高)。 */
|
|
19
|
+
export declare const RENDER_FIXES_ORDER = 10;
|
|
20
|
+
export declare const MOBILE_FIT_ORDER = 20;
|
|
21
|
+
export interface NaturalSize {
|
|
22
|
+
/** 自然宽度 W:body 横向 overflow hidden 时取 body.clientWidth(溢出部分本来就看不见,不该按它缩)。 */
|
|
23
|
+
width: number;
|
|
24
|
+
/** 自然高度 H = max(html.scrollHeight, body.scrollHeight)。 */
|
|
25
|
+
height: number;
|
|
26
|
+
/** 未经 overflow 修正的原始宽度 max(html.scrollWidth, body.scrollWidth)。`documentSize` 事件只有这个口径,用它判「宽度变了没」。 */
|
|
27
|
+
rawWidth: number;
|
|
28
|
+
}
|
|
29
|
+
export interface MobileFitPlan extends NaturalSize {
|
|
30
|
+
/** `min(1, innerWidth / W)`;W 为 0(文档还没布局)时为 1。 */
|
|
31
|
+
scale: number;
|
|
32
|
+
/** 写入 `mobile-fit` 层的 CSS;不用缩时为空串(= 删层)。 */
|
|
33
|
+
css: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function naturalSize(m: MeasureDocumentResult): NaturalSize;
|
|
36
|
+
export declare function computeScale(viewportWidth: number, naturalWidth: number): number;
|
|
37
|
+
/** 缩放层 CSS。`transform-origin: 0 0` 与 engage-editor 的 `left top` 等价。 */
|
|
38
|
+
export declare function buildFitCss(naturalWidth: number, naturalHeight: number, scale: number): string;
|
|
39
|
+
export declare function planMobileFit(m: MeasureDocumentResult): MobileFitPlan;
|
|
40
|
+
/**
|
|
41
|
+
* 渲染修正层 CSS,只在命中条件时加(engage-editor `frameStateChange` 里的三条):
|
|
42
|
+
* - `scroll-behavior: smooth` 会吞掉程序化滚动(定位、截图分片)→ 强制 auto;
|
|
43
|
+
* - 防闪烁脚本把 html/body 隐藏了 → 强制显示,否则量到的宽高全是 0;
|
|
44
|
+
* - body 高度被写死且内容更高 → 解开,避免 html/body 各一条滚动条。
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildRenderFixesCss(m: MeasureDocumentResult): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const MOBILE_FIT_LAYER = 'mobile-fit';
|
|
2
|
+
export const RENDER_FIXES_LAYER = 'render-fixes';
|
|
3
|
+
export const RENDER_FIXES_ORDER = 10;
|
|
4
|
+
export const MOBILE_FIT_ORDER = 20;
|
|
5
|
+
export function naturalSize(m) {
|
|
6
|
+
const rawWidth = Math.max(m.html.scrollWidth, m.body?.scrollWidth ?? 0);
|
|
7
|
+
const width = m.body && m.body.computed.overflowX === 'hidden' ? m.body.clientWidth : rawWidth;
|
|
8
|
+
const height = Math.max(m.html.scrollHeight, m.body?.scrollHeight ?? 0);
|
|
9
|
+
return { width, height, rawWidth };
|
|
10
|
+
}
|
|
11
|
+
export function computeScale(viewportWidth, naturalWidth) {
|
|
12
|
+
if (naturalWidth <= 0 || viewportWidth <= 0)
|
|
13
|
+
return 1;
|
|
14
|
+
return Math.min(1, viewportWidth / naturalWidth);
|
|
15
|
+
}
|
|
16
|
+
export function buildFitCss(naturalWidth, naturalHeight, scale) {
|
|
17
|
+
if (scale >= 1)
|
|
18
|
+
return '';
|
|
19
|
+
const scaledHeight = Math.round(naturalHeight * scale);
|
|
20
|
+
return (`html{transform:scale(${scale}) !important;transform-origin:0 0 !important;` +
|
|
21
|
+
`width:${naturalWidth}px !important;height:${scaledHeight}px !important;}`);
|
|
22
|
+
}
|
|
23
|
+
export function planMobileFit(m) {
|
|
24
|
+
const size = naturalSize(m);
|
|
25
|
+
const scale = computeScale(m.innerWidth, size.width);
|
|
26
|
+
return { ...size, scale, css: buildFitCss(size.width, size.height, scale) };
|
|
27
|
+
}
|
|
28
|
+
export function buildRenderFixesCss(m) {
|
|
29
|
+
const rules = [];
|
|
30
|
+
if (m.html.computed.scrollBehavior && m.html.computed.scrollBehavior !== 'auto') {
|
|
31
|
+
rules.push('html{scroll-behavior:auto !important;}');
|
|
32
|
+
}
|
|
33
|
+
if (m.html.computed.display === 'none' || m.body?.computed.display === 'none') {
|
|
34
|
+
rules.push('html,body{display:block !important;}');
|
|
35
|
+
}
|
|
36
|
+
if (m.body) {
|
|
37
|
+
const h = m.body.computed.height;
|
|
38
|
+
const fixed = h !== '' && h !== 'auto' && h !== 'unset';
|
|
39
|
+
if (fixed && m.body.scrollHeight > m.body.offsetHeight) {
|
|
40
|
+
rules.push('body{height:unset !important;}');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return rules.join('');
|
|
44
|
+
}
|