@ptengine/inline-editor 0.1.4 → 0.1.6
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 +54 -0
- package/dist/InlineEditor.d.ts +15 -1
- package/dist/InlineEditor.js +85 -54
- package/dist/canvas-ui/tree/TreePanel.d.ts +2 -1
- package/dist/canvas-ui/tree/TreePanel.js +79 -39
- package/dist/chrome/EditorChrome.d.ts +2 -0
- package/dist/chrome/EditorChrome.js +1 -1
- package/dist/chrome/VersionMenu.d.ts +3 -1
- package/dist/chrome/VersionMenu.js +24 -12
- package/dist/chrome/topbarPrimitives.d.ts +5 -52
- package/dist/chrome/topbarPrimitives.js +13 -31
- package/dist/dialogs/ClickActionDialog.js +4 -5
- package/dist/dialogs/CodeField.d.ts +4 -0
- package/dist/dialogs/CodeField.js +10 -0
- package/dist/dialogs/HtmlEditDialog.js +3 -2
- package/dist/dialogs/ImageEditDialog.js +53 -15
- package/dist/dialogs/ImageSettingDialog.js +3 -3
- package/dist/dialogs/InsertElementDialog.js +6 -6
- package/dist/dialogs/MediaPreview.d.ts +5 -0
- package/dist/dialogs/MediaPreview.js +34 -0
- package/dist/dialogs/ReplaceVideoDialog.js +8 -4
- package/dist/index.d.ts +1 -0
- package/dist/kernel/blocks/BlockOverlay.js +1 -1
- package/dist/kernel/blocks/resolveBlocks.d.ts +16 -0
- package/dist/kernel/blocks/resolveBlocks.js +39 -6
- package/dist/kernel/canvasCommands.d.ts +7 -1
- package/dist/kernel/canvasCommands.js +3 -0
- package/dist/kernel/changelist/ChangeListPanel.js +11 -9
- package/dist/kernel/changelist/useChangeList.d.ts +4 -0
- package/dist/kernel/changelist/useChangeList.js +84 -10
- package/dist/kernel/selection/SelectionOverlay.js +1 -1
- package/dist/kernel/selection/SelectionToolbar.js +26 -8
- package/dist/kernel/selection/buildRootShelf.d.ts +49 -2
- package/dist/kernel/selection/buildRootShelf.js +156 -41
- package/dist/kernel/selection/elementAiContext.d.ts +11 -3
- package/dist/kernel/selection/resolveAnchors.d.ts +17 -0
- package/dist/kernel/selection/resolveAnchors.js +59 -11
- package/dist/locales.d.ts +186 -48
- package/dist/locales.js +10 -9
- package/dist/panels/EditorStatus.d.ts +5 -0
- package/dist/panels/EditorStatus.js +4 -0
- package/dist/panels/ScreenshotCard.d.ts +7 -0
- package/dist/panels/ScreenshotCard.js +57 -0
- package/dist/panels/StylePanel.d.ts +7 -1
- package/dist/panels/StylePanel.js +105 -12
- package/dist/reviewLocales.d.ts +140 -0
- package/dist/reviewLocales.js +134 -0
- package/dist/tree/LayersPanel.js +4 -2
- package/dist/useInlineEditor.d.ts +12 -1
- package/dist/useInlineEditor.js +21 -4
- package/package.json +1 -1
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { buildDescendantShelfWithPaths } from './buildDescendantShelf.js';
|
|
2
2
|
import { AI_DESCENDANT_TEXT_CHARS } from './elementAiContext.js';
|
|
3
|
-
import { rowPositions } from './rowLayout.js';
|
|
3
|
+
import { SHELF_LAYOUT_FIELDS, laysOutInRow, rowPositions } from './rowLayout.js';
|
|
4
|
+
export const ROOT_SHELF_TREE_PARAMS = {
|
|
5
|
+
rootSelector: 'body',
|
|
6
|
+
maxDepth: 16,
|
|
7
|
+
maxChildren: 40,
|
|
8
|
+
maxNodes: 1500,
|
|
9
|
+
visibleOnly: true,
|
|
10
|
+
includeRect: true,
|
|
11
|
+
fields: [...SHELF_LAYOUT_FIELDS]
|
|
12
|
+
};
|
|
4
13
|
const LANDMARK_MIN_HEIGHT = 120;
|
|
5
14
|
const LANDMARK_MIN_WIDTH_RATIO = 0.5;
|
|
6
15
|
const DEFAULT_VIEWPORT_WIDTH = 375;
|
|
7
16
|
const LEAVES_PER_LANDMARK = 6;
|
|
17
|
+
const PAGE_SCALE_RATIO = 0.6;
|
|
18
|
+
const SAME_TOP_PX = 2;
|
|
8
19
|
const CHROME_ROWS_CAP = 5;
|
|
9
|
-
const PREFERRED_LANDMARK_TAGS = new Set(['main', 'section', 'article']);
|
|
10
20
|
const CHROME_TAGS = new Set(['header', 'nav', 'footer']);
|
|
11
21
|
const TEXT_LEAF_TAGS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'button', 'span']);
|
|
12
|
-
|
|
13
|
-
const { limit } = opts;
|
|
22
|
+
function analyzeBlockRoots(root, opts) {
|
|
14
23
|
const viewportWidth = typeof opts.viewportWidth === 'number' && opts.viewportWidth > 0
|
|
15
24
|
? opts.viewportWidth
|
|
16
25
|
: DEFAULT_VIEWPORT_WIDTH;
|
|
@@ -19,7 +28,7 @@ export function buildRootShelf(root, opts) {
|
|
|
19
28
|
const entryOf = new Map();
|
|
20
29
|
let sawRect = root.rect !== undefined;
|
|
21
30
|
const isLandmarkNode = (n) => n.rect !== undefined && n.rect.height >= LANDMARK_MIN_HEIGHT && n.rect.width >= minWidth;
|
|
22
|
-
const visit = (n, ancestors, chromeKey
|
|
31
|
+
const visit = (n, parent, ancestors, chromeKey) => {
|
|
23
32
|
for (const child of n.children) {
|
|
24
33
|
if (child.hidden)
|
|
25
34
|
continue;
|
|
@@ -28,53 +37,154 @@ export function buildRootShelf(root, opts) {
|
|
|
28
37
|
const ownChrome = CHROME_TAGS.has(child.tag) ? child.selector : chromeKey;
|
|
29
38
|
const entry = {
|
|
30
39
|
node: child,
|
|
40
|
+
parent,
|
|
31
41
|
ancestors,
|
|
32
42
|
order: entries.length,
|
|
33
43
|
chromeKey: ownChrome,
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
landmark: isLandmarkNode(child),
|
|
45
|
+
demoted: false
|
|
36
46
|
};
|
|
37
47
|
entries.push(entry);
|
|
38
48
|
entryOf.set(child, entry);
|
|
39
|
-
visit(child, [child.selector, ...ancestors], ownChrome
|
|
49
|
+
visit(child, entry, [child.selector, ...ancestors], ownChrome);
|
|
40
50
|
}
|
|
41
51
|
};
|
|
42
|
-
visit(root, [root.selector], null
|
|
52
|
+
visit(root, null, [root.selector], null);
|
|
43
53
|
if (!sawRect)
|
|
44
|
-
return
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
return null;
|
|
55
|
+
const pageHeight = entries.reduce((h, e) => (e.node.rect ? Math.max(h, e.node.rect.top + e.node.rect.height) : h), root.rect?.height ?? 0);
|
|
56
|
+
const isPageScale = (n) => n.rect !== undefined && pageHeight > 0 && n.rect.height >= PAGE_SCALE_RATIO * pageHeight;
|
|
57
|
+
const isLandmarkRow = (parent) => {
|
|
58
|
+
const items = parent.children.filter(c => !c.hidden && isLandmarkNode(c));
|
|
59
|
+
if (items.length < 2 || items.some(isPageScale))
|
|
60
|
+
return false;
|
|
61
|
+
if (laysOutInRow(parent))
|
|
62
|
+
return true;
|
|
63
|
+
const top0 = items[0].rect.top;
|
|
64
|
+
return items.every(c => Math.abs(c.rect.top - top0) <= SAME_TOP_PX);
|
|
65
|
+
};
|
|
66
|
+
const rowParents = new Map();
|
|
67
|
+
const inLandmarkRow = (parent) => {
|
|
68
|
+
let v = rowParents.get(parent);
|
|
69
|
+
if (v === undefined)
|
|
70
|
+
rowParents.set(parent, (v = isLandmarkRow(parent)));
|
|
71
|
+
return v;
|
|
72
|
+
};
|
|
73
|
+
for (const e of entries)
|
|
74
|
+
e.demoted = (e.parent?.demoted ?? false) || inLandmarkRow(e.parent?.node ?? root);
|
|
75
|
+
if (entries.some(e => e.landmark) && !entries.some(e => e.landmark && !e.demoted)) {
|
|
76
|
+
for (const e of entries)
|
|
77
|
+
e.demoted = false;
|
|
78
|
+
}
|
|
79
|
+
const isCandidate = (e) => e.landmark && !e.demoted;
|
|
80
|
+
const entryBySelector = new Map();
|
|
81
|
+
for (const e of entries)
|
|
82
|
+
if (!entryBySelector.has(e.node.selector))
|
|
83
|
+
entryBySelector.set(e.node.selector, e);
|
|
84
|
+
const claimed = new Map();
|
|
85
|
+
const blocksIn = new Map();
|
|
86
|
+
const addTo = (m, e, id) => {
|
|
87
|
+
let set = m.get(e);
|
|
88
|
+
if (!set)
|
|
89
|
+
m.set(e, (set = new Set()));
|
|
90
|
+
set.add(id);
|
|
91
|
+
};
|
|
92
|
+
for (const b of opts.blocks ?? []) {
|
|
93
|
+
for (const member of b.members) {
|
|
94
|
+
let e = entryBySelector.get(member.selector) ?? null;
|
|
95
|
+
while (e && !isCandidate(e))
|
|
96
|
+
e = e.parent;
|
|
97
|
+
if (!e)
|
|
98
|
+
continue;
|
|
99
|
+
addTo(claimed, e, b.id);
|
|
100
|
+
for (let a = e; a; a = a.parent)
|
|
101
|
+
addTo(blocksIn, a, b.id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const modelBlockCount = (e) => blocksIn.get(e)?.size ?? 0;
|
|
105
|
+
const isAtomic = (e) => claimed.get(e)?.size === 1 && modelBlockCount(e) === 1;
|
|
106
|
+
const nearestCandidateAncestor = (e) => {
|
|
107
|
+
for (let a = e.parent; a; a = a.parent)
|
|
108
|
+
if (isCandidate(a))
|
|
109
|
+
return a;
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
const kids = new Map();
|
|
113
|
+
const tops = [];
|
|
114
|
+
const candidates = entries.filter(isCandidate);
|
|
115
|
+
for (const e of candidates) {
|
|
116
|
+
const p = nearestCandidateAncestor(e);
|
|
117
|
+
if (p) {
|
|
118
|
+
const list = kids.get(p) ?? [];
|
|
119
|
+
list.push(e);
|
|
120
|
+
kids.set(p, list);
|
|
121
|
+
}
|
|
55
122
|
else
|
|
56
|
-
|
|
123
|
+
tops.push(e);
|
|
57
124
|
}
|
|
58
|
-
const
|
|
59
|
-
for (const e of
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
125
|
+
const isUnit = new Map();
|
|
126
|
+
for (const e of [...candidates].reverse()) {
|
|
127
|
+
const ks = kids.get(e) ?? [];
|
|
128
|
+
let unit;
|
|
129
|
+
if (isAtomic(e))
|
|
130
|
+
unit = true;
|
|
131
|
+
else if (modelBlockCount(e) >= 2)
|
|
132
|
+
unit = ks.length === 0;
|
|
133
|
+
else if (ks.length === 0)
|
|
134
|
+
unit = true;
|
|
135
|
+
else if (ks.length >= 2)
|
|
136
|
+
unit = false;
|
|
137
|
+
else
|
|
138
|
+
unit = (isUnit.get(ks[0]) ?? false) && !isPageScale(e.node);
|
|
139
|
+
isUnit.set(e, unit);
|
|
140
|
+
}
|
|
141
|
+
const blockRoots = [];
|
|
142
|
+
const takeRoots = (list) => {
|
|
143
|
+
for (const e of list) {
|
|
144
|
+
if (isUnit.get(e))
|
|
145
|
+
blockRoots.push(e);
|
|
146
|
+
else
|
|
147
|
+
takeRoots(kids.get(e) ?? []);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
takeRoots(tops);
|
|
151
|
+
const blockIdOf = (e) => {
|
|
152
|
+
const ids = blocksIn.get(e);
|
|
153
|
+
return ids && ids.size === 1 ? [...ids][0] : undefined;
|
|
154
|
+
};
|
|
155
|
+
return { entryOf, entryBySelector, blockRoots, blockIdOf };
|
|
156
|
+
}
|
|
157
|
+
export function liftToBlockRoots(root, opts) {
|
|
158
|
+
const out = new Map();
|
|
159
|
+
const analysis = analyzeBlockRoots(root, opts);
|
|
160
|
+
if (!analysis)
|
|
161
|
+
return out;
|
|
162
|
+
const rootSet = new Set(analysis.blockRoots);
|
|
163
|
+
for (const b of opts.blocks) {
|
|
164
|
+
for (const member of b.members) {
|
|
165
|
+
for (let e = analysis.entryBySelector.get(member.selector) ?? null; e; e = e.parent) {
|
|
166
|
+
if (!rootSet.has(e))
|
|
167
|
+
continue;
|
|
168
|
+
if (analysis.blockIdOf(e) === b.id) {
|
|
169
|
+
let byMember = out.get(b.id);
|
|
170
|
+
if (!byMember)
|
|
171
|
+
out.set(b.id, (byMember = new Map()));
|
|
172
|
+
byMember.set(member.selector, e.node);
|
|
69
173
|
}
|
|
70
|
-
|
|
174
|
+
break;
|
|
71
175
|
}
|
|
72
|
-
if (hasSurvivingAncestor)
|
|
73
|
-
continue;
|
|
74
176
|
}
|
|
75
|
-
surviving.add(e);
|
|
76
177
|
}
|
|
77
|
-
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
export function buildRootShelf(root, opts) {
|
|
181
|
+
const { limit } = opts;
|
|
182
|
+
const analysis = analyzeBlockRoots(root, opts);
|
|
183
|
+
if (!analysis)
|
|
184
|
+
return buildDescendantShelfWithPaths(root, limit);
|
|
185
|
+
const { entryOf, blockRoots, blockIdOf } = analysis;
|
|
186
|
+
const rootSet = new Set(blockRoots);
|
|
187
|
+
const landmarks = [...blockRoots].sort((a, b) => {
|
|
78
188
|
const dt = a.node.rect.top - b.node.rect.top;
|
|
79
189
|
return dt !== 0 ? dt : a.order - b.order;
|
|
80
190
|
});
|
|
@@ -83,7 +193,7 @@ export function buildRootShelf(root, opts) {
|
|
|
83
193
|
const ancestorsOf = new Map();
|
|
84
194
|
const emittedSelectors = new Set();
|
|
85
195
|
const chromeRows = new Map();
|
|
86
|
-
const emit = (e) => {
|
|
196
|
+
const emit = (e, blockId) => {
|
|
87
197
|
if (shelf.length >= limit)
|
|
88
198
|
return false;
|
|
89
199
|
if (emittedSelectors.has(e.node.selector))
|
|
@@ -107,6 +217,8 @@ export function buildRootShelf(root, opts) {
|
|
|
107
217
|
const inRow = inRowOf.get(n.selector);
|
|
108
218
|
if (inRow)
|
|
109
219
|
row.inRow = inRow;
|
|
220
|
+
if (blockId)
|
|
221
|
+
row.blockId = blockId;
|
|
110
222
|
shelf.push(row);
|
|
111
223
|
ancestorsOf.set(n.selector, e.ancestors);
|
|
112
224
|
emittedSelectors.add(n.selector);
|
|
@@ -115,21 +227,24 @@ export function buildRootShelf(root, opts) {
|
|
|
115
227
|
return true;
|
|
116
228
|
};
|
|
117
229
|
const isTextLeaf = (n) => n.tag === 'img' || (TEXT_LEAF_TAGS.has(n.tag) && n.textSummary.trim().length > 0);
|
|
230
|
+
const leavesPerBlock = landmarks.length
|
|
231
|
+
? Math.min(LEAVES_PER_LANDMARK, Math.max(1, Math.floor((limit - landmarks.length) / landmarks.length)))
|
|
232
|
+
: LEAVES_PER_LANDMARK;
|
|
118
233
|
for (const lm of landmarks) {
|
|
119
234
|
if (shelf.length >= limit)
|
|
120
235
|
break;
|
|
121
|
-
emit(lm);
|
|
236
|
+
emit(lm, blockIdOf(lm));
|
|
122
237
|
let taken = 0;
|
|
123
238
|
const collect = (n) => {
|
|
124
239
|
for (const child of n.children) {
|
|
125
|
-
if (taken >=
|
|
240
|
+
if (taken >= leavesPerBlock || shelf.length >= limit)
|
|
126
241
|
return;
|
|
127
242
|
if (child.hidden)
|
|
128
243
|
continue;
|
|
129
244
|
const ce = entryOf.get(child);
|
|
130
245
|
if (!ce)
|
|
131
246
|
continue;
|
|
132
|
-
if (
|
|
247
|
+
if (rootSet.has(ce) && ce !== lm)
|
|
133
248
|
continue;
|
|
134
249
|
if (isTextLeaf(child) && emit(ce))
|
|
135
250
|
taken += 1;
|
|
@@ -77,11 +77,19 @@ export interface AiDescendant {
|
|
|
77
77
|
*/
|
|
78
78
|
inRow?: RowPosition;
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
80
|
+
* 区块身份(宿主打的,如 `B2 首屏 h:740`),写成行尾的 `[…]`。
|
|
81
81
|
* 没选中元素时「首屏下面」要有确定的锚:同一 `top`、同一段文字的包装行常有十几个,没有它
|
|
82
|
-
* 「最外层容器」会被选成整页包装层(真机 2026-10-16
|
|
82
|
+
* 「最外层容器」会被选成整页包装层(真机 2026-10-16)。宿主按 `blockId`(新内核)打;
|
|
83
|
+
* 没有 `blockId` 的老内核货架,退回「selector 与页面模型 member 逐字相等」。
|
|
83
84
|
*/
|
|
84
85
|
block?: string;
|
|
86
|
+
/**
|
|
87
|
+
* 页面模型区块 id(内核打的):这一行是**只装着这一个区块的最外层容器**(块根),由
|
|
88
|
+
* `buildRootShelf` 的 `blocks` 认领得出 —— 热图成员是轮播第 1 张时,id 落在整个首屏 section
|
|
89
|
+
* 上,而不是那一张上(真机 2026-10-20)。宿主据此格式化成 `block`。没传 `blocks`、或这一行
|
|
90
|
+
* 不是某个区块的块根时没有这个键。
|
|
91
|
+
*/
|
|
92
|
+
blockId?: string;
|
|
85
93
|
}
|
|
86
94
|
/**
|
|
87
95
|
* 区块级货架取树的口径(`<page-block>`、方案锚点子树、选中元素货架三处共用)。
|
|
@@ -292,7 +300,7 @@ export declare const AI_DESCENDANT_LIMIT = 30;
|
|
|
292
300
|
* 60 → 100:货架改成「区块优先」(`buildRootShelf`)之后,每个区块是 1 行容器 + 最多 6 行
|
|
293
301
|
* 可改点,一个 10 个区块的落地页就是 70 行;60 会把最后几个区块整段切掉,AI 又回到
|
|
294
302
|
* 「找不到要改的那一块」。100 = 约 13 个区块,长落地页也够;header/nav/footer 各封 5 行,
|
|
295
|
-
*
|
|
303
|
+
* 不会被它们吃掉。区块再多时 `buildRootShelf` 按块数压低每块的叶子数,块根行一个不落。
|
|
296
304
|
*
|
|
297
305
|
* 也不能顺手把 `AI_DESCENDANT_LIMIT` 一起调大:那份是**每轮对话**的固定开销,
|
|
298
306
|
* 整页这份一次会话只算一次。
|
|
@@ -35,6 +35,15 @@ export interface InlinePlanAnchor {
|
|
|
35
35
|
op?: string;
|
|
36
36
|
mode?: string;
|
|
37
37
|
placement?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 只给 MOVE_ELEMENT:移到哪(`mode` 相对它:before / after / top / bottom)。移动的落点是**一对**
|
|
40
|
+
* (移谁 + 移到哪),只存 selector 的话首版只知道要挪、不知道挪到哪(2026-10-21 trace 973f6deb:
|
|
41
|
+
* 「新闻区挪到畅销榜后面」)。编辑器打开时与锚点一样在活页面上重新解析,命中的目标并进货架 ——
|
|
42
|
+
* 整页模式下 MOVE 的 target 只认货架行(`pageMoveTargetProblem`)。
|
|
43
|
+
*/
|
|
44
|
+
targetSelector?: string;
|
|
45
|
+
/** 目标那一行的标题 / 首句 —— `targetSelector` 失效时按它找回来(与 `label` 同一套规则)。 */
|
|
46
|
+
targetLabel?: string;
|
|
38
47
|
/**
|
|
39
48
|
* 该区块(B{n})在活页面上的 member 容器 selector,外层优先。**只在内存里**(宿主从页面模型
|
|
40
49
|
* 补进来),不落库、不与 backend 同形。
|
|
@@ -55,6 +64,14 @@ export interface ResolvedAnchor extends InlinePlanAnchor {
|
|
|
55
64
|
resolved: boolean;
|
|
56
65
|
/** 哪条路成功的。`label` = 原 selector 已失效(客户改版),值得让 agent 知道。 */
|
|
57
66
|
via?: 'selector' | 'label';
|
|
67
|
+
/**
|
|
68
|
+
* MOVE 目标(`targetSelector`)的解析结果;锚点没有目标、或锚点自己没解析到时不写。
|
|
69
|
+
* `true` = 目标在活页面上唯一命中、且已作为一行并进货架与祖先表(agent 抄它就过得了 MOVE 的 target 闸)。
|
|
70
|
+
*/
|
|
71
|
+
targetResolved?: boolean;
|
|
72
|
+
targetVia?: 'selector' | 'label';
|
|
73
|
+
/** 目标在活页面上的 selector。`targetVia: 'label'` 时与存下来的 `targetSelector` 不同 —— 宿主打印这一个。 */
|
|
74
|
+
resolvedTargetSelector?: string;
|
|
58
75
|
}
|
|
59
76
|
export interface AnchorShelfResult {
|
|
60
77
|
/** 锚点容器行在前、其子树行在后,按锚点顺序。 */
|
|
@@ -64,6 +64,17 @@ export function withImageRows(shelf, anchorRowCount, images) {
|
|
|
64
64
|
function normalizeLabel(s) {
|
|
65
65
|
return s.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
66
66
|
}
|
|
67
|
+
function indexNodes(root) {
|
|
68
|
+
const map = new Map();
|
|
69
|
+
const walk = (n) => {
|
|
70
|
+
if (!map.has(n.selector))
|
|
71
|
+
map.set(n.selector, n);
|
|
72
|
+
for (const child of n.children)
|
|
73
|
+
walk(child);
|
|
74
|
+
};
|
|
75
|
+
walk(root);
|
|
76
|
+
return map;
|
|
77
|
+
}
|
|
67
78
|
function indexAncestors(root) {
|
|
68
79
|
const map = new Map();
|
|
69
80
|
const walk = (n, ancestors) => {
|
|
@@ -117,6 +128,7 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
117
128
|
const outcomes = [];
|
|
118
129
|
const seen = new Set();
|
|
119
130
|
const rootAncestors = indexAncestors(rootTree);
|
|
131
|
+
const rootNodes = indexNodes(rootTree);
|
|
120
132
|
const rootInRow = rowPositions(rootTree);
|
|
121
133
|
const push = (row, chain) => {
|
|
122
134
|
if (seen.has(row.selector))
|
|
@@ -125,6 +137,37 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
125
137
|
shelf.push(row);
|
|
126
138
|
ancestorsOf.set(row.selector, chain);
|
|
127
139
|
};
|
|
140
|
+
const resolveTarget = async (selector, label) => {
|
|
141
|
+
let found = null;
|
|
142
|
+
let targetVia;
|
|
143
|
+
try {
|
|
144
|
+
const probe = await resolveSelector(selector);
|
|
145
|
+
if (probe.matched && probe.matchCount === 1) {
|
|
146
|
+
found = selector;
|
|
147
|
+
targetVia = 'selector';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
}
|
|
152
|
+
if (!found && label) {
|
|
153
|
+
const byLabel = findUniqueByLabel(rootTree, label);
|
|
154
|
+
if (byLabel) {
|
|
155
|
+
found = byLabel.selector;
|
|
156
|
+
targetVia = 'label';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (!found)
|
|
160
|
+
return { targetResolved: false };
|
|
161
|
+
if (!seen.has(found)) {
|
|
162
|
+
const node = rootNodes.get(found);
|
|
163
|
+
if (!node)
|
|
164
|
+
return { targetResolved: false };
|
|
165
|
+
const inRow = rootInRow.get(found);
|
|
166
|
+
const row = rowOf(node);
|
|
167
|
+
push(inRow ? { ...row, inRow } : row, rootAncestors.get(found) ?? []);
|
|
168
|
+
}
|
|
169
|
+
return { targetResolved: true, targetVia, resolvedTargetSelector: found };
|
|
170
|
+
};
|
|
128
171
|
for (const anchor of anchors) {
|
|
129
172
|
let resolvedSelector = null;
|
|
130
173
|
let via;
|
|
@@ -148,7 +191,8 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
148
191
|
outcomes.push({ ...anchor, resolved: false });
|
|
149
192
|
continue;
|
|
150
193
|
}
|
|
151
|
-
|
|
194
|
+
const outcome = { ...anchor, resolved: true, via };
|
|
195
|
+
outcomes.push(outcome);
|
|
152
196
|
const ownAncestors = rootAncestors.get(resolvedSelector) ?? [];
|
|
153
197
|
const ownInRow = rootInRow.get(resolvedSelector);
|
|
154
198
|
const withOwnInRow = (row) => ownInRow ? { ...row, inRow: ownInRow } : row;
|
|
@@ -160,17 +204,21 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
160
204
|
}
|
|
161
205
|
if (!subtree) {
|
|
162
206
|
push(withOwnInRow({ selector: resolvedSelector, tag: '', text: '', hasChildren: true }), ownAncestors);
|
|
163
|
-
continue;
|
|
164
207
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
208
|
+
else {
|
|
209
|
+
const anchorSelector = subtree.selector || resolvedSelector;
|
|
210
|
+
push(withOwnInRow(rowOf(subtree)), ownAncestors);
|
|
211
|
+
const { shelf: rows, ancestorsOf: within } = buildDescendantShelfWithPaths(subtree, limitPerAnchor);
|
|
212
|
+
for (const row of rows) {
|
|
213
|
+
const chainWithin = within.get(row.selector) ?? [];
|
|
214
|
+
const chain = chainWithin.includes(anchorSelector)
|
|
215
|
+
? [...chainWithin, ...ownAncestors]
|
|
216
|
+
: [...chainWithin, anchorSelector, ...ownAncestors];
|
|
217
|
+
push({ ...row, depth: (row.depth ?? 0) + 1 }, chain);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (anchor.targetSelector) {
|
|
221
|
+
Object.assign(outcome, await resolveTarget(anchor.targetSelector, anchor.targetLabel));
|
|
174
222
|
}
|
|
175
223
|
}
|
|
176
224
|
return { shelf, ancestorsOf, anchors: outcomes };
|