@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.
Files changed (169) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +286 -2
  3. package/dist/InlineEditor.d.ts +349 -0
  4. package/dist/InlineEditor.js +886 -0
  5. package/dist/PagePreview.d.ts +83 -0
  6. package/dist/PagePreview.js +111 -0
  7. package/dist/assets/AssetsPanel.d.ts +19 -0
  8. package/dist/assets/AssetsPanel.js +23 -0
  9. package/dist/assets/componentLibrary.d.ts +23 -0
  10. package/dist/assets/componentLibrary.js +12 -0
  11. package/dist/canvas-ui/index.d.ts +17 -0
  12. package/dist/canvas-ui/index.js +7 -0
  13. package/dist/canvas-ui/scope.d.ts +13 -0
  14. package/dist/canvas-ui/scope.js +1 -0
  15. package/dist/canvas-ui/selection/classNames.d.ts +30 -0
  16. package/dist/canvas-ui/selection/classNames.js +15 -0
  17. package/dist/canvas-ui/selection/tokens.css +204 -0
  18. package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
  19. package/dist/canvas-ui/tree/TreePanel.js +132 -0
  20. package/dist/canvas-ui/tree/classNames.d.ts +21 -0
  21. package/dist/canvas-ui/tree/classNames.js +8 -0
  22. package/dist/canvas-ui/tree/order.d.ts +38 -0
  23. package/dist/canvas-ui/tree/order.js +38 -0
  24. package/dist/canvas-ui/tree/tree.css +131 -0
  25. package/dist/chrome/EditorChrome.d.ts +83 -0
  26. package/dist/chrome/EditorChrome.js +10 -0
  27. package/dist/chrome/VersionMenu.d.ts +26 -0
  28. package/dist/chrome/VersionMenu.js +86 -0
  29. package/dist/chrome/topbarPrimitives.d.ts +69 -0
  30. package/dist/chrome/topbarPrimitives.js +39 -0
  31. package/dist/chrome/useDismissOnOutside.d.ts +23 -0
  32. package/dist/chrome/useDismissOnOutside.js +27 -0
  33. package/dist/dialogs/ClickActionDialog.d.ts +24 -0
  34. package/dist/dialogs/ClickActionDialog.js +35 -0
  35. package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
  36. package/dist/dialogs/HtmlEditDialog.js +23 -0
  37. package/dist/dialogs/ImageEditDialog.d.ts +30 -0
  38. package/dist/dialogs/ImageEditDialog.js +57 -0
  39. package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
  40. package/dist/dialogs/ImageSettingDialog.js +17 -0
  41. package/dist/dialogs/InsertElementDialog.d.ts +23 -0
  42. package/dist/dialogs/InsertElementDialog.js +28 -0
  43. package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
  44. package/dist/dialogs/ReplaceVideoDialog.js +16 -0
  45. package/dist/dialogs/actionTypes.d.ts +53 -0
  46. package/dist/dialogs/actionTypes.js +74 -0
  47. package/dist/dialogs/htmlValidation.d.ts +24 -0
  48. package/dist/dialogs/htmlValidation.js +37 -0
  49. package/dist/dialogs/imageEdit.d.ts +49 -0
  50. package/dist/dialogs/imageEdit.js +30 -0
  51. package/dist/index.d.ts +101 -0
  52. package/dist/index.js +53 -0
  53. package/dist/kernel/CanvasContextBar.d.ts +23 -0
  54. package/dist/kernel/CanvasContextBar.js +12 -0
  55. package/dist/kernel/CanvasFrame.d.ts +56 -0
  56. package/dist/kernel/CanvasFrame.js +53 -0
  57. package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
  58. package/dist/kernel/ai/aiInlineApply.js +477 -0
  59. package/dist/kernel/ai/imageSource.d.ts +24 -0
  60. package/dist/kernel/ai/imageSource.js +19 -0
  61. package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
  62. package/dist/kernel/ai/insertHtmlGate.js +123 -0
  63. package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
  64. package/dist/kernel/ai/postApplyCheck.js +74 -0
  65. package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
  66. package/dist/kernel/ai/replaceHtmlGate.js +147 -0
  67. package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
  68. package/dist/kernel/blocks/BlockOverlay.js +66 -0
  69. package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
  70. package/dist/kernel/blocks/annotateAndTile.js +122 -0
  71. package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
  72. package/dist/kernel/blocks/resolveBlocks.js +233 -0
  73. package/dist/kernel/canvasCapabilities.d.ts +31 -0
  74. package/dist/kernel/canvasCapabilities.js +39 -0
  75. package/dist/kernel/canvasCommands.d.ts +126 -0
  76. package/dist/kernel/canvasCommands.js +146 -0
  77. package/dist/kernel/canvasErrors.d.ts +1 -0
  78. package/dist/kernel/canvasErrors.js +14 -0
  79. package/dist/kernel/captureGeometry.d.ts +35 -0
  80. package/dist/kernel/captureGeometry.js +25 -0
  81. package/dist/kernel/captureOrchestration.d.ts +48 -0
  82. package/dist/kernel/captureOrchestration.js +43 -0
  83. package/dist/kernel/captureSlices.d.ts +36 -0
  84. package/dist/kernel/captureSlices.js +20 -0
  85. package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
  86. package/dist/kernel/changelist/ChangeListPanel.js +47 -0
  87. package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
  88. package/dist/kernel/changelist/ChangeListPopover.js +10 -0
  89. package/dist/kernel/changelist/builders.d.ts +89 -0
  90. package/dist/kernel/changelist/builders.js +81 -0
  91. package/dist/kernel/changelist/changeSummary.d.ts +19 -0
  92. package/dist/kernel/changelist/changeSummary.js +85 -0
  93. package/dist/kernel/changelist/clipboard.d.ts +16 -0
  94. package/dist/kernel/changelist/clipboard.js +4 -0
  95. package/dist/kernel/changelist/exportForSave.d.ts +30 -0
  96. package/dist/kernel/changelist/exportForSave.js +11 -0
  97. package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
  98. package/dist/kernel/changelist/mergeForSave.js +40 -0
  99. package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
  100. package/dist/kernel/changelist/moveObserverSafety.js +39 -0
  101. package/dist/kernel/changelist/overrides.d.ts +22 -0
  102. package/dist/kernel/changelist/overrides.js +63 -0
  103. package/dist/kernel/changelist/useChangeList.d.ts +200 -0
  104. package/dist/kernel/changelist/useChangeList.js +282 -0
  105. package/dist/kernel/config.d.ts +29 -0
  106. package/dist/kernel/config.js +10 -0
  107. package/dist/kernel/dataUrlToBlob.d.ts +8 -0
  108. package/dist/kernel/dataUrlToBlob.js +17 -0
  109. package/dist/kernel/linkHref.d.ts +25 -0
  110. package/dist/kernel/linkHref.js +16 -0
  111. package/dist/kernel/mobileFit.d.ts +46 -0
  112. package/dist/kernel/mobileFit.js +44 -0
  113. package/dist/kernel/moveState.d.ts +35 -0
  114. package/dist/kernel/moveState.js +14 -0
  115. package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
  116. package/dist/kernel/selection/SelectionOverlay.js +25 -0
  117. package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
  118. package/dist/kernel/selection/SelectionToolbar.js +82 -0
  119. package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
  120. package/dist/kernel/selection/SelectorPathBar.js +55 -0
  121. package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
  122. package/dist/kernel/selection/buildDescendantShelf.js +50 -0
  123. package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
  124. package/dist/kernel/selection/buildRootShelf.js +149 -0
  125. package/dist/kernel/selection/elementAiContext.d.ts +324 -0
  126. package/dist/kernel/selection/elementAiContext.js +122 -0
  127. package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
  128. package/dist/kernel/selection/elementGeometryForAi.js +15 -0
  129. package/dist/kernel/selection/geometry.d.ts +55 -0
  130. package/dist/kernel/selection/geometry.js +18 -0
  131. package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
  132. package/dist/kernel/selection/resolveAnchors.js +173 -0
  133. package/dist/kernel/selection/shelfRow.d.ts +33 -0
  134. package/dist/kernel/selection/shelfRow.js +21 -0
  135. package/dist/kernel/selection/tagLabels.d.ts +9 -0
  136. package/dist/kernel/selection/tagLabels.js +46 -0
  137. package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
  138. package/dist/kernel/selection/toolbarButtons.js +105 -0
  139. package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
  140. package/dist/kernel/selection/useAiSelectionContext.js +26 -0
  141. package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
  142. package/dist/kernel/selection/useCanvasGeometry.js +47 -0
  143. package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
  144. package/dist/kernel/selection/useSelectionRestore.js +25 -0
  145. package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
  146. package/dist/kernel/shrinkImageForUpload.js +61 -0
  147. package/dist/kernel/useCanvasBridge.d.ts +36 -0
  148. package/dist/kernel/useCanvasBridge.js +41 -0
  149. package/dist/kernel/useCanvasGate.d.ts +22 -0
  150. package/dist/kernel/useCanvasGate.js +35 -0
  151. package/dist/kernel/useCanvasSession.d.ts +44 -0
  152. package/dist/kernel/useCanvasSession.js +182 -0
  153. package/dist/kernel/useCanvasStream.d.ts +39 -0
  154. package/dist/kernel/useCanvasStream.js +45 -0
  155. package/dist/kernel/useMobileFit.d.ts +40 -0
  156. package/dist/kernel/useMobileFit.js +139 -0
  157. package/dist/locales.d.ts +1094 -0
  158. package/dist/locales.js +545 -0
  159. package/dist/panels/StylePanel.d.ts +24 -0
  160. package/dist/panels/StylePanel.js +40 -0
  161. package/dist/panels/styleFields.d.ts +39 -0
  162. package/dist/panels/styleFields.js +91 -0
  163. package/dist/tree/LayersPanel.d.ts +30 -0
  164. package/dist/tree/LayersPanel.js +15 -0
  165. package/dist/tree/domTreeAdapter.d.ts +63 -0
  166. package/dist/tree/domTreeAdapter.js +90 -0
  167. package/dist/useInlineEditor.d.ts +161 -0
  168. package/dist/useInlineEditor.js +346 -0
  169. package/package.json +58 -3
@@ -0,0 +1,89 @@
1
+ import type { ElementContentModeName, ElementMoveModeName, InlineActionChange, InlineAttributeChange, InlineContentChange, InlineMoveChange, InlineScriptChange, InlineStyleChange, InlineTextChange, StyleChangeFlag } from '@ptengine/inline-core/change-format';
2
+ /**
3
+ * 7 类 change 的 builder —— 产出**明文形态**(DEC-22)。
4
+ *
5
+ * 明文化之后本文件很薄:它只做「默认值 + 条件字段收敛」,值包装与 mode 数值映射
6
+ * 全在 `@ptengine/inline-core/change-format` 的转译器里。
7
+ * **契约风险因此从 7 个 builder 收缩到 1 个转译器** —— 这是 DEC-22 除可读性外的实际收益。
8
+ *
9
+ * 字段语义的权威来源仍是 engage-editor 的 7 个 `$create`
10
+ * (见设计稿 §2.6 的「7 类 × 全字段对照表」),只是形态换成了明文。
11
+ */
12
+ interface CommonInput {
13
+ identifier: string;
14
+ selector: string;
15
+ /** 默认 false。undo 分组语义,7 类的 isValidTagData 都不校验它。 */
16
+ continueUndo?: boolean;
17
+ /**
18
+ * 省略 = **不写这个键**,交给 SDK 按类型的默认表(存量数据就是这个形态)。
19
+ * 只在需要**显式关闭**时才传 false —— 见 `InlineChangeBase.enableObserver`。
20
+ */
21
+ enableObserver?: boolean;
22
+ /**
23
+ * 作用元素的祖先 selector。**只给编辑器内存用**(落盘收敛,见 `mergeForSave`),
24
+ * builder 不会把它写进 change 本身。
25
+ */
26
+ ancestors?: string[];
27
+ }
28
+ export interface StyleChangeInput extends CommonInput {
29
+ property: string;
30
+ value: string;
31
+ cssRule?: boolean;
32
+ important?: boolean;
33
+ cssConditional?: string;
34
+ }
35
+ export declare function buildStyleChange(input: StyleChangeInput): InlineStyleChange;
36
+ export interface ContentChangeInput extends CommonInput {
37
+ html: string;
38
+ mode: ElementContentModeName;
39
+ isFromAddPtElement?: boolean;
40
+ isTextChange?: boolean;
41
+ isStyleChange?: StyleChangeFlag;
42
+ }
43
+ export declare function buildContentChange(input: ContentChangeInput): InlineContentChange;
44
+ export interface AttributeChangeInput extends CommonInput {
45
+ attribute: string;
46
+ /** 省略 = 删除该属性。空串 = 设为空值。 */
47
+ value?: string;
48
+ }
49
+ export declare function buildAttributeChange(input: AttributeChangeInput): InlineAttributeChange;
50
+ export interface ActionChangeInput extends CommonInput {
51
+ action: unknown;
52
+ }
53
+ export declare function buildActionChange(input: ActionChangeInput): InlineActionChange;
54
+ export interface MoveChangeInput extends CommonInput {
55
+ targetIdentifier: string;
56
+ targetSelector: string;
57
+ /**
58
+ * ⚠️ move 自己的词表(`top`/`bottom`/`before`/`after`),**不是** content 那套。
59
+ * 两套都有 before/after 但映射到不同数值(content before=3、move before=2)。
60
+ */
61
+ mode: ElementMoveModeName;
62
+ }
63
+ export declare function buildMoveChange(input: MoveChangeInput): InlineMoveChange;
64
+ export interface TextChangeInput extends CommonInput {
65
+ text: string;
66
+ /** **childNode 下标(含文本节点!)**,不是元素下标。 */
67
+ childIndex: number;
68
+ mode: ElementContentModeName;
69
+ }
70
+ /**
71
+ * DEC-17:生成侧**不使用**本函数 —— 选中态的「编辑文字」走
72
+ * `elementContent` + `isTextChange: true`(现役 `inlineText.service.js:24-31`
73
+ * 里 `changeText` 的调用已被注释掉,改走 content)。
74
+ *
75
+ * 它存在的理由是反序列化路径需要能重建 57,以及「手工新增自定义 change」的历史入口
76
+ * (`EditTextDialog.vue:72` / `SidErrorDialog.vue:60` 仍在调 `TextChange$create`)。
77
+ */
78
+ export declare function buildTextChange(input: TextChangeInput): InlineTextChange;
79
+ export interface ScriptChangeInput {
80
+ identifier: string;
81
+ /** 空串 = 全局 JS。 */
82
+ selector: string;
83
+ continueUndo?: boolean;
84
+ script: string;
85
+ requiresContent: boolean;
86
+ }
87
+ /** DEC-7:画布不支持 script 编辑(现役也没有选中态入口)。本函数只服务反序列化。 */
88
+ export declare function buildScriptChange(input: ScriptChangeInput): InlineScriptChange;
89
+ export {};
@@ -0,0 +1,81 @@
1
+ function common(input) {
2
+ return {
3
+ identifier: input.identifier,
4
+ selector: input.selector,
5
+ continueUndo: input.continueUndo ?? false,
6
+ enableObserver: input.enableObserver
7
+ };
8
+ }
9
+ export function buildStyleChange(input) {
10
+ const change = {
11
+ type: 'elementStyle',
12
+ ...common(input),
13
+ property: input.property,
14
+ value: input.value
15
+ };
16
+ if (input.cssRule != null)
17
+ change.cssRule = input.cssRule;
18
+ if (input.important != null)
19
+ change.important = input.important;
20
+ if (input.cssConditional != null)
21
+ change.cssConditional = input.cssConditional;
22
+ return change;
23
+ }
24
+ export function buildContentChange(input) {
25
+ const change = {
26
+ type: 'elementContent',
27
+ ...common(input),
28
+ html: input.html,
29
+ mode: input.mode
30
+ };
31
+ if (input.isFromAddPtElement !== undefined) {
32
+ change.isFromAddPtElement = input.isFromAddPtElement;
33
+ }
34
+ if (input.isTextChange !== undefined)
35
+ change.isTextChange = input.isTextChange;
36
+ if (input.isStyleChange !== undefined)
37
+ change.isStyleChange = input.isStyleChange;
38
+ return change;
39
+ }
40
+ export function buildAttributeChange(input) {
41
+ const change = {
42
+ type: 'changeElementAttribute',
43
+ ...common(input),
44
+ attribute: input.attribute
45
+ };
46
+ if (input.value != null)
47
+ change.value = input.value;
48
+ return change;
49
+ }
50
+ export function buildActionChange(input) {
51
+ return { type: 'changeElementAction', ...common(input), action: input.action };
52
+ }
53
+ export function buildMoveChange(input) {
54
+ return {
55
+ type: 'elementMove',
56
+ ...common(input),
57
+ path: '',
58
+ targetIdentifier: input.targetIdentifier,
59
+ targetSelector: input.targetSelector,
60
+ mode: input.mode
61
+ };
62
+ }
63
+ export function buildTextChange(input) {
64
+ return {
65
+ type: 'elementText',
66
+ ...common(input),
67
+ text: input.text,
68
+ childIndex: input.childIndex,
69
+ mode: input.mode
70
+ };
71
+ }
72
+ export function buildScriptChange(input) {
73
+ return {
74
+ type: 'elementScript',
75
+ identifier: input.identifier,
76
+ selector: input.selector,
77
+ continueUndo: input.continueUndo ?? false,
78
+ script: input.script,
79
+ requiresContent: input.requiresContent
80
+ };
81
+ }
@@ -0,0 +1,19 @@
1
+ import type { InlineChange } from '@ptengine/inline-core/change-format';
2
+ /**
3
+ * 把一条 change 翻译成人能读的摘要。
4
+ *
5
+ * ⚠️ **不是给机器看的**:JSON 那一份仍然保留(切「数值」形态),那是与存量数据逐字段
6
+ * 比对用的。这里解决的是另一个问题 —— 列表里一眼看不出「我到底改了什么」。
7
+ * `elementStyle · div.RectangleCopy3` 这种展示,改了 5 处之后完全分不清哪条是哪条。
8
+ */
9
+ export interface ChangeSummary {
10
+ /** 动作名,如「改样式」「删除」「编辑文字」。 */
11
+ action: string;
12
+ /** 改动详情,如 `font-size: 18px`。删除类没有详情。 */
13
+ detail: string;
14
+ /** 用于定位元素。 */
15
+ selector: string;
16
+ /** 图标标识,渲染层自行映射。 */
17
+ icon: 'style' | 'delete' | 'text' | 'code' | 'image' | 'link' | 'move' | 'insert';
18
+ }
19
+ export declare function summarizeChange(change: InlineChange): ChangeSummary;
@@ -0,0 +1,85 @@
1
+ function isDeleteChange(change) {
2
+ return (change.type === 'elementStyle' &&
3
+ change.property === 'display' &&
4
+ change.value === 'none');
5
+ }
6
+ export function summarizeChange(change) {
7
+ const selector = 'selector' in change ? change.selector : '';
8
+ switch (change.type) {
9
+ case 'elementStyle':
10
+ if (isDeleteChange(change)) {
11
+ return { action: '删除元素', detail: '', selector, icon: 'delete' };
12
+ }
13
+ return {
14
+ action: '改样式',
15
+ detail: `${change.property}: ${shorten(change.value)}`,
16
+ selector,
17
+ icon: 'style'
18
+ };
19
+ case 'elementContent': {
20
+ if (change.isTextChange) {
21
+ return {
22
+ action: '编辑文字',
23
+ detail: textPreview(change.html),
24
+ selector,
25
+ icon: 'text'
26
+ };
27
+ }
28
+ if (change.mode === 'replace') {
29
+ return { action: '编辑 HTML', detail: htmlPreview(change.html), selector, icon: 'code' };
30
+ }
31
+ return {
32
+ action: '插入元素',
33
+ detail: `${modeLabel(change.mode)} · ${htmlPreview(change.html)}`,
34
+ selector,
35
+ icon: 'insert'
36
+ };
37
+ }
38
+ case 'changeElementAttribute':
39
+ if (change.attribute === 'src') {
40
+ return { action: '替换图片', detail: shorten(change.value ?? ''), selector, icon: 'image' };
41
+ }
42
+ return {
43
+ action: '改属性',
44
+ detail: change.value == null
45
+ ? `移除 ${change.attribute}`
46
+ : `${change.attribute}: ${shorten(change.value)}`,
47
+ selector,
48
+ icon: 'style'
49
+ };
50
+ case 'changeElementAction':
51
+ return { action: '点击动作', detail: actionDetail(change.action), selector, icon: 'link' };
52
+ case 'elementMove':
53
+ return {
54
+ action: '移动元素',
55
+ detail: `${moveModeLabel(change.mode)} ${change.targetSelector}`,
56
+ selector,
57
+ icon: 'move'
58
+ };
59
+ case 'elementText':
60
+ return { action: '改文本', detail: textPreview(change.text), selector, icon: 'text' };
61
+ case 'elementScript':
62
+ return { action: '自定义脚本', detail: '', selector, icon: 'code' };
63
+ }
64
+ }
65
+ function modeLabel(mode) {
66
+ return ({ insert: '内部开头', append: '内部末尾', before: '之前', after: '之后' }[mode] ?? mode);
67
+ }
68
+ function moveModeLabel(mode) {
69
+ return { top: '移入开头', bottom: '移入末尾', before: '移到前面', after: '移到后面' }[mode] ?? mode;
70
+ }
71
+ function textPreview(html) {
72
+ const text = html.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
73
+ return shorten(text);
74
+ }
75
+ function htmlPreview(html) {
76
+ const tag = html.trim().match(/^<\s*([a-zA-Z][\w-]*)/)?.[1];
77
+ return tag ? `<${tag.toLowerCase()}> …` : shorten(html);
78
+ }
79
+ function actionDetail(action) {
80
+ const type = action?.clickActionType;
81
+ return type ? String(type) : '';
82
+ }
83
+ function shorten(text, max = 42) {
84
+ return text.length > max ? `${text.slice(0, max)}…` : text;
85
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 复制粘贴的剪贴板内容处理。
3
+ *
4
+ * ⚠️ **粘贴前必须重写防撞号**(`pt-element-XXXXXXXX`)。
5
+ *
6
+ * 那个 id 会被选择器生成**优先当锚点**用(`#id`)。原样粘贴的话页面上就有两个同 id
7
+ * 元素 —— identifier 不再唯一,第二份的所有后续 change 会全部错打到第一份上。
8
+ * 而且这件事在粘贴当下**看不出任何异常**:两个元素长得一模一样,直到用户去改第二份
9
+ * 才发现改的是第一份。
10
+ */
11
+ export interface PasteSource {
12
+ /** 复制下来的 outerHTML(已剥 script/noscript —— `getOuterHtml` 做的)。 */
13
+ html: string;
14
+ }
15
+ /** 粘贴用的 HTML:重写全部防撞号。 */
16
+ export declare function pasteHtml(source: PasteSource, randomId?: () => string): string;
@@ -0,0 +1,4 @@
1
+ import { rewritePtElementIds } from '../../dialogs/imageEdit.js';
2
+ export function pasteHtml(source, randomId) {
3
+ return rewritePtElementIds(source.html, randomId);
4
+ }
@@ -0,0 +1,30 @@
1
+ import { type InlineChange } from '@ptengine/inline-core/change-format';
2
+ import { type MergeOutcome } from './mergeForSave.js';
3
+ import type { ChangeRecord } from './useChangeList.js';
4
+ /**
5
+ * 把编辑器里的**明文** change list 转成落盘形态。
6
+ *
7
+ * change list 跟随 experience 配置一起存库,包本身不碰存储 —— 宿主在保存 experience
8
+ * 时调这个函数,把结果放进配置里。
9
+ *
10
+ * ⚠️ **不要直接存 `editor.changeList.changes`**(那是明文形态,DEC-22)。直接存不会
11
+ * 报任何错,要等发布后 SDK 读到 `type: 'elementStyle'` 而不是 `53` 才暴露 ——
12
+ * 那时候排查要从「实验不生效」一路倒推回保存那一刻。
13
+ *
14
+ * 单独给一个具名函数而不是让宿主自己 `changes.map(toWireFormat)`:这一步太容易被漏,
15
+ * 而漏了没有任何信号。有了它,README 那句「落盘前必经 toWireFormat」才有着落。
16
+ */
17
+ export declare function exportChangesForSave(changes: InlineChange[]): unknown[];
18
+ /**
19
+ * 从 change list 的**记录**落盘 —— 比上面那个多做一步:按祖先关系收敛
20
+ * (见 `mergeForSave`:祖先后来被隐藏 / 整体替换时,子树里的改动不必存)。
21
+ *
22
+ * 为什么另给一个函数而不是改上面那个:上面那个只吃 `InlineChange[]`,没有祖先信息,
23
+ * 而祖先只活在 `ChangeRecord` 上。宿主手里就是记录,用这个。
24
+ *
25
+ * 返回 `dropped` 而不是静默筛掉:界面上要能回答「我明明改了 C 的文字,为什么没存」。
26
+ */
27
+ export declare function exportRecordsForSave(records: ChangeRecord[]): {
28
+ tagData: unknown[];
29
+ dropped: MergeOutcome['dropped'];
30
+ };
@@ -0,0 +1,11 @@
1
+ import { toWireFormat } from '@ptengine/inline-core/change-format';
2
+ import { mergeForSave } from './mergeForSave.js';
3
+ import { disableObserverAroundMoves } from './moveObserverSafety.js';
4
+ export function exportChangesForSave(changes) {
5
+ return changes.map(toWireFormat);
6
+ }
7
+ export function exportRecordsForSave(records) {
8
+ const guarded = disableObserverAroundMoves(records.map(r => ({ change: r.change, ancestors: r.ancestors })));
9
+ const { kept, dropped } = mergeForSave(guarded);
10
+ return { tagData: kept.map(toWireFormat), dropped };
11
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * 落盘前的收敛 —— 只做**祖先关系**那两条,不建树、不读活 DOM。
3
+ *
4
+ * ## 与现役 `useInline.ts:getInlineRenderList` 的关系
5
+ *
6
+ * 那份实现做七件事,我们只需要其中两件。差别的根源是**它必须从活 DOM 反推最终状态**:
7
+ *
8
+ * - 它的 `undo/redo` 会让 `insertedNodes` 变游离(它自己的注释写的),所以只能在保存时
9
+ * `updateContentChangeNewHtml` 从活 DOM 重新序列化;一旦这么做,后代的 content change
10
+ * 就必须丢(否则重复应用,且刷新后的 html 里那个子元素可能已不存在)。
11
+ * 「丢后代」是那个选择的**后果**,不是独立需求。
12
+ * - 我们的 `revert` 是**把那一行从清单里删掉**,所以清单永远是一条干净的、只往前的序列;
13
+ * 顺序重放即得最终状态,不需要反推。
14
+ * - 它建树的另一半价值是算 `__enableObserver`(尽量开成 true,只对 move 参与的子树关掉)。
15
+ * 我们改成「默认不写、交给 SDK 那张按类型的默认表」,所以这一半也不需要建树。
16
+ *
17
+ * 剩下这两条是真有价值的,而且都只需要**祖先 selector 列表**(采集时就知道,见
18
+ * `ChangeRecord.ancestors`),不需要 DOM:
19
+ *
20
+ * 1. 祖先被隐藏(`elementStyle display:none`)→ 子树内的改动全丢。它们在一个不可见的
21
+ * 子树里,视觉上等于不存在,但 SDK 会为每条**空转轮询最长 3 分 20 秒**
22
+ * (`_observeTextChange` 在「已是目标值」时返回 false,早停要靠成功计数推进,
23
+ * 所以它一直轮到 1000 次上限)。
24
+ * 2. 祖先的内容被整体替换(`elementContent` 的 `replace`,含 `isTextChange`)→
25
+ * 子树内**内容类**改动丢掉,`elementStyle` / `changeElementAction` 留着。
26
+ * 现役同一条规则;理由是那两类不写在 html 串里(action 靠运行时绑定,style 走样式表
27
+ * 规则或内联+找元素重试),而 content / attribute 会被序列化进祖先的 html。
28
+ *
29
+ * ## ⚠️ 必须带顺序,这是与现役的实质差异
30
+ *
31
+ * 只有当祖先的替换**在后**时才丢后代 —— 因为我们存的祖先 html 是「打开 HTML 弹框那一刻
32
+ * 的活 DOM 快照」,已经含了先前的后代改动。反过来(先改祖先、后改后代)后代才是更晚的
33
+ * 意图,**必须留**。现役不需要区分顺序,因为它读活 DOM 拿最终态;我们不读,所以要区分。
34
+ *
35
+ * ## 刻意不抄的一条
36
+ *
37
+ * 现役的 `isParentAdded`(新增元素的后代也丢)对我们不成立:我们存的 insert html 是当时
38
+ * 给的那份,后来在新元素里改的文字**没被烤进去**,丢了就真丢了。所以 `insert` / `append`
39
+ * 不触发规则 2,只有 `replace` 触发。
40
+ *
41
+ * ## ⚠️ 输出顺序绝不能变
42
+ *
43
+ * SDK 的 `_applyContentBefore` 只能清掉**在它之前注册**的监听(它在每次 content replace
44
+ * 前把子树里的监听收走)。我们的时间顺序正是让这套清理落在该清的那条上的原因。
45
+ * 为了「整洁」重排会让先后关系错位,症状是「偶尔某条被还原回旧值」。
46
+ */
47
+ import type { InlineChange } from '@ptengine/inline-core/change-format';
48
+ /** 一条改动 + 它的祖先 selector(采集时记下,不进落盘 payload)。 */
49
+ export interface ChangeWithAncestors {
50
+ change: InlineChange;
51
+ /** 由近及远或反序都行,只做集合判断。空 = 不知道祖先(则本条不参与筛除)。 */
52
+ ancestors: string[];
53
+ }
54
+ export interface MergeOutcome {
55
+ kept: InlineChange[];
56
+ /** 被筛掉的,连原因 —— 界面上要能说清「为什么这条没进存库」。 */
57
+ dropped: Array<{
58
+ change: InlineChange;
59
+ reason: 'ancestor-hidden' | 'ancestor-replaced';
60
+ /** 那个祖先的 selector。 */
61
+ by: string;
62
+ }>;
63
+ }
64
+ /** 一条被压制的判定:为什么、被哪条压的。`null` = 不被压制。 */
65
+ export interface Suppression {
66
+ reason: 'ancestor-hidden' | 'ancestor-replaced';
67
+ /** 压制它的那条改动的 selector。 */
68
+ by: string;
69
+ }
70
+ /**
71
+ * 判断 `entries[index]` 是否被后面某条改动压制。
72
+ *
73
+ * ⚠️ **这是唯一的判定处,清单徽章与落盘收敛都必须调它。**
74
+ *
75
+ * 分成两份实现的后果不是「代码重复」,是**徽章会撒谎** —— 用户看到「会提交」结果
76
+ * 没存进去,比压根没有徽章更糟:他会以为自己改的东西丢了,而清单明明写着没问题。
77
+ */
78
+ export declare function classifySuppression(entries: ChangeWithAncestors[], index: number): Suppression | null;
79
+ export declare function mergeForSave(entries: ChangeWithAncestors[]): MergeOutcome;
@@ -0,0 +1,40 @@
1
+ function isHide(change) {
2
+ return (change.type === 'elementStyle' &&
3
+ change.property === 'display' &&
4
+ change.value === 'none');
5
+ }
6
+ function isReplace(change) {
7
+ return change.type === 'elementContent' && change.mode === 'replace';
8
+ }
9
+ function isBakedIntoHtml(change) {
10
+ return change.type === 'elementContent' || change.type === 'changeElementAttribute';
11
+ }
12
+ export function classifySuppression(entries, index) {
13
+ const entry = entries[index];
14
+ if (!entry)
15
+ return null;
16
+ const ancestors = new Set(entry.ancestors);
17
+ for (let later = index + 1; later < entries.length; later++) {
18
+ const other = entries[later].change;
19
+ if (!ancestors.has(other.selector))
20
+ continue;
21
+ if (isHide(other))
22
+ return { reason: 'ancestor-hidden', by: other.selector };
23
+ if (isReplace(other) && isBakedIntoHtml(entry.change)) {
24
+ return { reason: 'ancestor-replaced', by: other.selector };
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ export function mergeForSave(entries) {
30
+ const kept = [];
31
+ const dropped = [];
32
+ entries.forEach((entry, index) => {
33
+ const verdict = classifySuppression(entries, index);
34
+ if (verdict)
35
+ dropped.push({ change: entry.change, ...verdict });
36
+ else
37
+ kept.push(entry.change);
38
+ });
39
+ return { kept, dropped };
40
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * move 与 observer 的互锁 —— 列表里有移动时,把**位置可能失效**的那些改动的
3
+ * `enableObserver` 显式设成 `false`。
4
+ *
5
+ * ## 为什么必须关
6
+ *
7
+ * SDK 的 observer 是「隔一会儿按 selector 重新找元素、再把改动套上去」
8
+ * (`ptengine-sdk/src/modules/experience/inline/`,200ms 轮询)。而我们的 selector 是
9
+ * 位置型的(`nth-of-type` / 子序号),移动元素会让**同一条 selector 指向另一个元素** ——
10
+ * 观察者于是把改动套到错的元素上。
11
+ *
12
+ * 两种错的方向代价完全不对等:
13
+ * - 不该关而关了 → 客户端重渲染后这条改动不再被写回,可能被页面自己覆盖掉;
14
+ * - 该关而没关 → 改动落到**错误的元素**上,页面当场是花的。
15
+ *
16
+ * 所以宁可多关,但也不能一刀切全关(见下)。
17
+ *
18
+ * ## 关谁:位置失效的真实范围
19
+ *
20
+ * `nth-of-type` / 子序号只在**元素离开的那个容器**和**它进入的那个容器**里发生偏移。
21
+ * 于是受影响的恰好是这四类:
22
+ *
23
+ * 1. 被移动的元素自己;
24
+ * 2. 被移动元素**内部**的改动(子树整体换了位置,路径前缀变了);
25
+ * 3. 被移动元素的**祖先**(子树内容变了,祖先的 html 序列化随之变);
26
+ * 4. **源容器与目标容器子树内的一切** —— 任何路径穿过这两个容器的 selector 都会
27
+ * 因为兄弟序号偏移而指错。
28
+ *
29
+ * 第 4 条是自适应的:如果移动发生在顶层(源/目标容器就是 `body`),那么 `body` 出现在
30
+ * 所有改动的祖先链里,于是全部关闭 —— 正确,因为此时 `body > :nth-child(n)` 这类
31
+ * 路径确实全都偏了。
32
+ *
33
+ * ## ⚠️ 与现役的差异(刻意)
34
+ *
35
+ * 现役 `useInline.ts:checkMoveOperation` 的判据是「节点自身或任一后代有 move →
36
+ * 该节点整个子树关闭」,而 traverse 在 `body` 根节点直接跳过判定(`:445`),净效果是
37
+ * **「与某个 move 共享同一个顶层容器的改动全部关闭」**。那是树遍历形状的副产物,不是
38
+ * 位置失效的实际边界:同一顶层容器里另一条分支上的改动其实没受影响,却被关掉了。
39
+ *
40
+ * 另外现役那半「move 之前的路径」(`movePaths`)**是死代码**:`movePaths.push` 前有
41
+ * `change.elementMove?.path?.string &&` 守卫,而 `path` 全文件零赋值(DEC-19 的依据
42
+ * 正是这个),所以那个数组恒为空、`originPathHasMoveOperation` 恒 false。没有照抄。
43
+ *
44
+ * ## ⚠️ 为什么在**落盘时**算,不在采集时
45
+ *
46
+ * 移动可能是**后**加的:先改了 C 的文字,再把 C 的祖先移走 —— 那条文字改动在采集时
47
+ * 完全正常,是后来的移动让它的 selector 失效的。采集时决定就必然漏掉这种,
48
+ * 而漏掉的表现是「线上偶尔某个改动出现在错误的元素上」。
49
+ */
50
+ import type { ChangeWithAncestors } from './mergeForSave.js';
51
+ /**
52
+ * 返回一份新的 entries:受影响的改动 `enableObserver` 被显式设成 `false`。
53
+ *
54
+ * ⚠️ **写显式 `false`,不是留 undefined。** `undefined` 的含义是「不写这个 key、交给
55
+ * SDK 按类型的默认表决定」,而那张表里 attribute / textChange 是「缺省即监听」——
56
+ * 正是我们要关掉的两类。
57
+ *
58
+ * 列表里没有移动时原样返回(不产生新对象,调用方可以按引用判断有没有动过)。
59
+ *
60
+ * ## ⚠️ 已知降级:回显的存量改动没有祖先信息
61
+ *
62
+ * `hydrate` 填进来的记录 `ancestors: []`(存库是扁平数组、没有层级)。对这些改动:
63
+ * - 它们**自己**若正是被移动的元素 / 源容器 / 目标容器,仍会被关掉(按 selector 命中);
64
+ * - 但「在被移动子树内部」这一条判不出来 —— 那需要它的祖先链。
65
+ *
66
+ * 也就是说「上次保存的改动 + 本次新加一个移动」这种组合下,保护是不完整的。
67
+ * 不用 selector 字符串前缀去猜层级:v2 selector 不保证是可截断的路径串,猜错的方向是
68
+ * **误关**(丢掉本该有的 observer),而那种丢失在编辑器里完全看不出来。
69
+ */
70
+ export declare function disableObserverAroundMoves(entries: ChangeWithAncestors[]): ChangeWithAncestors[];
@@ -0,0 +1,39 @@
1
+ function collectRegions(entries) {
2
+ const subtree = new Set();
3
+ const selfOnly = new Set();
4
+ for (const entry of entries) {
5
+ const change = entry.change;
6
+ if (change.type !== 'elementMove')
7
+ continue;
8
+ subtree.add(change.selector);
9
+ if (change.targetSelector)
10
+ subtree.add(change.targetSelector);
11
+ const sourceParent = entry.ancestors[0];
12
+ if (sourceParent)
13
+ subtree.add(sourceParent);
14
+ for (const ancestor of entry.ancestors)
15
+ selfOnly.add(ancestor);
16
+ }
17
+ return { subtree, selfOnly };
18
+ }
19
+ function isAffected(entry, regions) {
20
+ const selector = entry.change.selector;
21
+ if (regions.subtree.has(selector) || regions.selfOnly.has(selector))
22
+ return true;
23
+ return entry.ancestors.some(a => regions.subtree.has(a));
24
+ }
25
+ export function disableObserverAroundMoves(entries) {
26
+ const regions = collectRegions(entries);
27
+ if (regions.subtree.size === 0 && regions.selfOnly.size === 0)
28
+ return entries;
29
+ return entries.map(entry => {
30
+ const change = entry.change;
31
+ if (change.type === 'elementScript')
32
+ return entry;
33
+ if (!isAffected(entry, regions))
34
+ return entry;
35
+ if (change.enableObserver === false)
36
+ return entry;
37
+ return { ...entry, change: { ...change, enableObserver: false } };
38
+ });
39
+ }
@@ -0,0 +1,22 @@
1
+ import type { InlineChange } from '@ptengine/inline-core/change-format';
2
+ /**
3
+ * 返回可被 `incoming` 覆盖的下标,无则 -1。
4
+ *
5
+ * ⚠️ `affectsSelectors` 的 break 不可省,且**必须在 `overrides` 判断之前**:
6
+ * 顺序反了会让 attribute 之类「既有覆盖键又改选择器」的 change 被错误覆盖。
7
+ */
8
+ export declare function findOverrideIndex(list: InlineChange[], incoming: InlineChange): number;
9
+ /**
10
+ * 加入 `incoming` 的结果,**同时报出被丢弃的是哪些**。
11
+ *
12
+ * ⚠️ `dropped` 不是可选信息,是正确性所必需的:被覆盖的旧 change 只是从**列表**里
13
+ * 消失了,它在客户页 DOM 上**仍然生效**、agent 侧的撤销闭包也还挂着没人调。
14
+ * 调用方必须先把它们撤销掉再应用新的 —— 否则新 change 捕获到的「原始现场」其实是
15
+ * 上一条改过之后的状态,撤销它只会回退一步,用户看到的就是「撤销了但没恢复」。
16
+ */
17
+ export declare function overrideOutcome(list: InlineChange[], incoming: InlineChange): {
18
+ next: InlineChange[];
19
+ dropped: InlineChange[];
20
+ };
21
+ /** 只要结果数组的薄封装。新代码请用 `overrideOutcome` —— 它同时报出被丢弃的条目。 */
22
+ export declare function applyOverride(list: InlineChange[], incoming: InlineChange): InlineChange[];
@@ -0,0 +1,63 @@
1
+ function affectsSelectors(change) {
2
+ return change.type !== 'elementStyle' && change.type !== 'elementText';
3
+ }
4
+ function selectorOf(change) {
5
+ return change.selector;
6
+ }
7
+ function isAction(change) {
8
+ return change.type === 'changeElementAction';
9
+ }
10
+ function overrides(incoming, existing) {
11
+ if (incoming.type !== existing.type)
12
+ return false;
13
+ if (selectorOf(incoming) !== selectorOf(existing))
14
+ return false;
15
+ if (incoming.type === 'elementStyle' && existing.type === 'elementStyle') {
16
+ return (incoming.property === existing.property &&
17
+ incoming.cssConditional === existing.cssConditional);
18
+ }
19
+ if (incoming.type === 'elementText' && existing.type === 'elementText') {
20
+ return (incoming.mode === 'replace' &&
21
+ existing.mode === 'replace' &&
22
+ incoming.childIndex === existing.childIndex);
23
+ }
24
+ if (incoming.type === 'changeElementAttribute' &&
25
+ existing.type === 'changeElementAttribute') {
26
+ return incoming.attribute === existing.attribute;
27
+ }
28
+ return false;
29
+ }
30
+ export function findOverrideIndex(list, incoming) {
31
+ for (let i = list.length - 1; i >= 0; i--) {
32
+ const existing = list[i];
33
+ if (affectsSelectors(existing))
34
+ break;
35
+ if (overrides(incoming, existing))
36
+ return i;
37
+ }
38
+ return -1;
39
+ }
40
+ export function overrideOutcome(list, incoming) {
41
+ if (isAction(incoming)) {
42
+ const selector = selectorOf(incoming);
43
+ const dropped = list.filter(c => isAction(c) && selectorOf(c) === selector);
44
+ return { next: [...list.filter(c => !dropped.includes(c)), incoming], dropped };
45
+ }
46
+ const index = findOverrideIndex(list, incoming);
47
+ if (index < 0)
48
+ return { next: [...list, incoming], dropped: [] };
49
+ return {
50
+ next: [...list.slice(0, index), ...list.slice(index + 1), incoming],
51
+ dropped: [list[index]]
52
+ };
53
+ }
54
+ export function applyOverride(list, incoming) {
55
+ if (isAction(incoming)) {
56
+ const selector = selectorOf(incoming);
57
+ return [...list.filter(c => !(isAction(c) && selectorOf(c) === selector)), incoming];
58
+ }
59
+ const index = findOverrideIndex(list, incoming);
60
+ if (index < 0)
61
+ return [...list, incoming];
62
+ return [...list.slice(0, index), ...list.slice(index + 1), incoming];
63
+ }