@ptengine/inline-editor 0.1.1 → 0.1.2
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 +13 -0
- package/dist/InlineEditor.js +7 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/kernel/selection/buildDescendantShelf.js +4 -0
- package/dist/kernel/selection/buildRootShelf.js +5 -0
- package/dist/kernel/selection/elementAiContext.d.ts +7 -0
- package/dist/kernel/selection/resolveAnchors.js +6 -2
- package/dist/kernel/selection/rowLayout.d.ts +31 -0
- package/dist/kernel/selection/rowLayout.js +41 -0
- package/dist/kernel/selection/shelfRow.d.ts +8 -1
- package/dist/kernel/selection/shelfRow.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
全部是新增:扩展不支持契约 v11 `feature:node-fields`(取树不回 `facts`)时,货架行与原来逐字一致。
|
|
6
|
+
|
|
7
|
+
**新增**
|
|
8
|
+
- AI 货架行标出父级横排:`AiDescendant.inRow`(`{ index, count }`),`formatShelfRow` 在引用文本之后、区块身份之前写
|
|
9
|
+
` [横排 i/n]`(与 frontend-base `<page-block>` 的行尾逐字一致)。父级是 flex 行(不换行;换行的要孩子同一行)或多列 grid
|
|
10
|
+
时才写 —— `after` 这一行会排到它旁边而不是下方。对整棵树判:父级常是货架跳过的包装层(PageFly `.pf-c`)。
|
|
11
|
+
`buildDescendantShelf(WithPaths)` / `buildRootShelf` / `resolveInlinePlanAnchors` 都会写。
|
|
12
|
+
- 编辑器给 AI 取树的三处(选中元素子树、整页、锚点子树)请求 `SHELF_LAYOUT_FIELDS`(display / flexDirection / flexWrap /
|
|
13
|
+
gridTemplateColumns / gridAutoFlow 的计算样式);图层面板取树不变。
|
|
14
|
+
- 导出 `SHELF_LAYOUT_FIELDS` / `laysOutInRow` / `rowPositions` / `rowTag` 与类型 `RowPosition`,宿主自己拼货架时可同口径调用。
|
|
15
|
+
|
|
3
16
|
## 0.1.1
|
|
4
17
|
|
|
5
18
|
自 frontend-base `feat/dp/inline-v2` 搬来编辑器包的改动(源仓 develop 已删 `packages/inline-editor`,此后只在本仓改)。
|
package/dist/InlineEditor.js
CHANGED
|
@@ -17,6 +17,7 @@ import { getAncestorChain, getDomTree, getElementCapabilities, getElementImages,
|
|
|
17
17
|
import { useAiSelectionContext } from './kernel/selection/useAiSelectionContext.js';
|
|
18
18
|
import { buildDescendantShelf } from './kernel/selection/buildDescendantShelf.js';
|
|
19
19
|
import { buildRootShelf } from './kernel/selection/buildRootShelf.js';
|
|
20
|
+
import { SHELF_LAYOUT_FIELDS } from './kernel/selection/rowLayout.js';
|
|
20
21
|
import { backgroundImageAncestorFor, blockContainerFor, resolveInlinePlanAnchors, withImageRows } from './kernel/selection/resolveAnchors.js';
|
|
21
22
|
import { elementGeometryForAi } from './kernel/selection/elementGeometryForAi.js';
|
|
22
23
|
import { applyAiChanges } from './kernel/ai/aiInlineApply.js';
|
|
@@ -371,7 +372,8 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
371
372
|
maxChildren: 20,
|
|
372
373
|
maxNodes: SHELF_TREE_MAX_NODES,
|
|
373
374
|
visibleOnly: true,
|
|
374
|
-
includeRect: true
|
|
375
|
+
includeRect: true,
|
|
376
|
+
fields: [...SHELF_LAYOUT_FIELDS]
|
|
375
377
|
})
|
|
376
378
|
.then(root => {
|
|
377
379
|
if (!stale)
|
|
@@ -447,7 +449,8 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
447
449
|
maxChildren: 40,
|
|
448
450
|
maxNodes: 1500,
|
|
449
451
|
visibleOnly: true,
|
|
450
|
-
includeRect: true
|
|
452
|
+
includeRect: true,
|
|
453
|
+
fields: [...SHELF_LAYOUT_FIELDS]
|
|
451
454
|
});
|
|
452
455
|
const { html } = await getOuterHtml(sessionIdForAiContext, 'body');
|
|
453
456
|
if (stale)
|
|
@@ -471,7 +474,8 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
|
|
|
471
474
|
maxChildren: 20,
|
|
472
475
|
maxNodes: SHELF_TREE_MAX_NODES,
|
|
473
476
|
visibleOnly: true,
|
|
474
|
-
includeRect: true
|
|
477
|
+
includeRect: true,
|
|
478
|
+
fields: [...SHELF_LAYOUT_FIELDS]
|
|
475
479
|
})
|
|
476
480
|
})
|
|
477
481
|
: null;
|
package/dist/index.d.ts
CHANGED
|
@@ -74,7 +74,9 @@ export type { ElementAiContext, AiDescendant, AiHtmlFragment, AiPageImage, Ancho
|
|
|
74
74
|
export { resolveInlinePlanAnchors } from './kernel/selection/resolveAnchors.js';
|
|
75
75
|
export type { InlinePlanAnchor, ResolvedAnchor, AnchorShelfResult } from './kernel/selection/resolveAnchors.js';
|
|
76
76
|
export { buildDescendantShelf } from './kernel/selection/buildDescendantShelf.js';
|
|
77
|
-
export { formatShelfRow, imageTag, linkTag, SHELF_TEXT_MAX } from './kernel/selection/shelfRow.js';
|
|
77
|
+
export { formatShelfRow, imageTag, linkTag, rowTag, SHELF_TEXT_MAX } from './kernel/selection/shelfRow.js';
|
|
78
|
+
export { SHELF_LAYOUT_FIELDS, laysOutInRow, rowPositions } from './kernel/selection/rowLayout.js';
|
|
79
|
+
export type { RowPosition } from './kernel/selection/rowLayout.js';
|
|
78
80
|
export { linkPath } from './kernel/linkHref.js';
|
|
79
81
|
export { buildRootShelf } from './kernel/selection/buildRootShelf.js';
|
|
80
82
|
export { LayersPanel } from './tree/LayersPanel.js';
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,8 @@ export { exportChangesForSave } from './kernel/changelist/exportForSave.js';
|
|
|
36
36
|
export { buildElementAiContext, truncateHtml, AI_HTML_BUDGET_BYTES, AI_DESCENDANT_LIMIT, SHELF_TREE_MAX_DEPTH, SHELF_TREE_MAX_NODES, ROOT_SHELF_LIMIT, composeAnchorHtml, extractPageLinks, linksFragment } from './kernel/selection/elementAiContext.js';
|
|
37
37
|
export { resolveInlinePlanAnchors } from './kernel/selection/resolveAnchors.js';
|
|
38
38
|
export { buildDescendantShelf } from './kernel/selection/buildDescendantShelf.js';
|
|
39
|
-
export { formatShelfRow, imageTag, linkTag, SHELF_TEXT_MAX } from './kernel/selection/shelfRow.js';
|
|
39
|
+
export { formatShelfRow, imageTag, linkTag, rowTag, SHELF_TEXT_MAX } from './kernel/selection/shelfRow.js';
|
|
40
|
+
export { SHELF_LAYOUT_FIELDS, laysOutInRow, rowPositions } from './kernel/selection/rowLayout.js';
|
|
40
41
|
export { linkPath } from './kernel/linkHref.js';
|
|
41
42
|
export { buildRootShelf } from './kernel/selection/buildRootShelf.js';
|
|
42
43
|
export { LayersPanel } from './tree/LayersPanel.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import '../linkHref.js';
|
|
2
2
|
import { AI_DESCENDANT_LIMIT, AI_DESCENDANT_TEXT_CHARS } from './elementAiContext.js';
|
|
3
|
+
import { rowPositions } from './rowLayout.js';
|
|
3
4
|
const NOISE_TAGS = new Set(['br', 'wbr', 'hr', 'script', 'style', 'noscript', 'template', 'link', 'meta']);
|
|
4
5
|
const INTERACTIVE_TAGS = new Set(['a', 'button', 'input', 'select', 'textarea', 'label', 'summary', 'details', 'form']);
|
|
5
6
|
function hasChildrenOf(node) {
|
|
@@ -23,6 +24,7 @@ export function buildDescendantShelf(root, limit = AI_DESCENDANT_LIMIT) {
|
|
|
23
24
|
export function buildDescendantShelfWithPaths(root, limit = AI_DESCENDANT_LIMIT) {
|
|
24
25
|
const shelf = [];
|
|
25
26
|
const ancestorsOf = new Map();
|
|
27
|
+
const inRowOf = rowPositions(root);
|
|
26
28
|
const queue = root.children.map(node => ({ node, ancestors: [root.selector], depth: 0 }));
|
|
27
29
|
while (queue.length && shelf.length < limit) {
|
|
28
30
|
const { node, ancestors, depth } = queue.shift();
|
|
@@ -34,6 +36,7 @@ export function buildDescendantShelfWithPaths(root, limit = AI_DESCENDANT_LIMIT)
|
|
|
34
36
|
continue;
|
|
35
37
|
}
|
|
36
38
|
const text = node.textSummary.trim().slice(0, AI_DESCENDANT_TEXT_CHARS);
|
|
39
|
+
const inRow = inRowOf.get(node.selector);
|
|
37
40
|
shelf.push({
|
|
38
41
|
selector: node.selector,
|
|
39
42
|
tag: node.tag,
|
|
@@ -41,6 +44,7 @@ export function buildDescendantShelfWithPaths(root, limit = AI_DESCENDANT_LIMIT)
|
|
|
41
44
|
hasChildren: hasChildrenOf(node),
|
|
42
45
|
...(node.image ? { image: node.image } : {}),
|
|
43
46
|
...(node.href ? { href: node.href } : {}),
|
|
47
|
+
...(inRow ? { inRow } : {}),
|
|
44
48
|
...(depth > 0 ? { depth } : {})
|
|
45
49
|
});
|
|
46
50
|
ancestorsOf.set(node.selector, ancestors);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDescendantShelfWithPaths } from './buildDescendantShelf.js';
|
|
2
2
|
import { AI_DESCENDANT_TEXT_CHARS } from './elementAiContext.js';
|
|
3
|
+
import { rowPositions } from './rowLayout.js';
|
|
3
4
|
const LANDMARK_MIN_HEIGHT = 120;
|
|
4
5
|
const LANDMARK_MIN_WIDTH_RATIO = 0.5;
|
|
5
6
|
const DEFAULT_VIEWPORT_WIDTH = 375;
|
|
@@ -77,6 +78,7 @@ export function buildRootShelf(root, opts) {
|
|
|
77
78
|
const dt = a.node.rect.top - b.node.rect.top;
|
|
78
79
|
return dt !== 0 ? dt : a.order - b.order;
|
|
79
80
|
});
|
|
81
|
+
const inRowOf = rowPositions(root);
|
|
80
82
|
const shelf = [];
|
|
81
83
|
const ancestorsOf = new Map();
|
|
82
84
|
const emittedSelectors = new Set();
|
|
@@ -102,6 +104,9 @@ export function buildRootShelf(root, opts) {
|
|
|
102
104
|
row.top = n.rect.top;
|
|
103
105
|
if (n.image)
|
|
104
106
|
row.image = n.image;
|
|
107
|
+
const inRow = inRowOf.get(n.selector);
|
|
108
|
+
if (inRow)
|
|
109
|
+
row.inRow = inRow;
|
|
105
110
|
shelf.push(row);
|
|
106
111
|
ancestorsOf.set(n.selector, e.ancestors);
|
|
107
112
|
emittedSelectors.add(n.selector);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { CanvasDevice } from '@ptengine/inline-core/protocol';
|
|
8
8
|
import type { ResolvedAnchor } from './resolveAnchors.js';
|
|
9
|
+
import type { RowPosition } from './rowLayout.js';
|
|
9
10
|
/** outerHTML 进 prompt 的预算。 */
|
|
10
11
|
export declare const AI_HTML_BUDGET_BYTES = 4096;
|
|
11
12
|
/**
|
|
@@ -69,6 +70,12 @@ export interface AiDescendant {
|
|
|
69
70
|
* 信息,至少让模型知道它是新的;用户撤销后宿主会把它从清单里拿掉。写成行尾的 `[ai-inserted]`。
|
|
70
71
|
*/
|
|
71
72
|
aiInserted?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* 这一行在**父级横排**里的位次(父级是 flex 行或多列 grid,见 `rowPositions`):`after` 它会排到
|
|
75
|
+
* 它旁边,不是下方。来自树节点的排法事实(取树时请求 `SHELF_LAYOUT_FIELDS`);父级竖排、或老扩展
|
|
76
|
+
* 没给事实时没有这个键。写成引用文本之后的 `[横排 i/n]`。
|
|
77
|
+
*/
|
|
78
|
+
inRow?: RowPosition;
|
|
72
79
|
/**
|
|
73
80
|
* 区块身份(宿主按页面模型的 member 容器打的,如 `B2 首屏 h:740`),写成行尾的 `[…]`。
|
|
74
81
|
* 没选中元素时「首屏下面」要有确定的锚:同一 `top`、同一段文字的包装行常有十几个,没有它
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildDescendantShelfWithPaths } from './buildDescendantShelf.js';
|
|
2
|
+
import { rowPositions } from './rowLayout.js';
|
|
2
3
|
import { AI_DESCENDANT_LIMIT, AI_DESCENDANT_TEXT_CHARS } from './elementAiContext.js';
|
|
3
4
|
export function blockContainerFor(anchor, shelf, ancestorsOf) {
|
|
4
5
|
const isBlockRow = (selector) => {
|
|
@@ -116,6 +117,7 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
116
117
|
const outcomes = [];
|
|
117
118
|
const seen = new Set();
|
|
118
119
|
const rootAncestors = indexAncestors(rootTree);
|
|
120
|
+
const rootInRow = rowPositions(rootTree);
|
|
119
121
|
const push = (row, chain) => {
|
|
120
122
|
if (seen.has(row.selector))
|
|
121
123
|
return;
|
|
@@ -148,6 +150,8 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
148
150
|
}
|
|
149
151
|
outcomes.push({ ...anchor, resolved: true, via });
|
|
150
152
|
const ownAncestors = rootAncestors.get(resolvedSelector) ?? [];
|
|
153
|
+
const ownInRow = rootInRow.get(resolvedSelector);
|
|
154
|
+
const withOwnInRow = (row) => ownInRow ? { ...row, inRow: ownInRow } : row;
|
|
151
155
|
let subtree = null;
|
|
152
156
|
try {
|
|
153
157
|
subtree = await getSubtree(resolvedSelector);
|
|
@@ -155,11 +159,11 @@ export async function resolveInlinePlanAnchors(input) {
|
|
|
155
159
|
catch {
|
|
156
160
|
}
|
|
157
161
|
if (!subtree) {
|
|
158
|
-
push({ selector: resolvedSelector, tag: '', text: '', hasChildren: true }, ownAncestors);
|
|
162
|
+
push(withOwnInRow({ selector: resolvedSelector, tag: '', text: '', hasChildren: true }), ownAncestors);
|
|
159
163
|
continue;
|
|
160
164
|
}
|
|
161
165
|
const anchorSelector = subtree.selector || resolvedSelector;
|
|
162
|
-
push(rowOf(subtree), ownAncestors);
|
|
166
|
+
push(withOwnInRow(rowOf(subtree)), ownAncestors);
|
|
163
167
|
const { shelf: rows, ancestorsOf: within } = buildDescendantShelfWithPaths(subtree, limitPerAnchor);
|
|
164
168
|
for (const row of rows) {
|
|
165
169
|
const chainWithin = within.get(row.selector) ?? [];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「父级横着摆孩子吗」—— 货架行上的 ` [横排 i/n]`(`rowTag`)就从这里来。
|
|
3
|
+
*
|
|
4
|
+
* `after` X 落在 X 的**父级**怎么摆孩子的地方:父级横排就排到 X 右边。货架此前只有缩进,看不出
|
|
5
|
+
* 父级怎么排 —— 真机 2026-09-30 / 10-16 / 10-19 三次,模型锚了 PageFly `.pf-c`(flex 不换行)里
|
|
6
|
+
* 唯一的 Column,「下方加一行」落到文案右边、把文案挤窄;同一页另一次碰巧锚了 Column 里的 h2 就对了。
|
|
7
|
+
*
|
|
8
|
+
* 口径与 frontend-base `apps/x/src/stores/pageModel.ts` 的 `laysOutInRow` / `rowMarkIndex`
|
|
9
|
+
* (63112d497a,`<page-block>` 那份)逐字一致:同一个元素在 `<page-block>` 与 `<page-shelf>` /
|
|
10
|
+
* `<page-element>` 里必须打出同一个标记。那份以后可改成调用这里。
|
|
11
|
+
*/
|
|
12
|
+
import type { DomTreeNode, NodeField } from '@ptengine/inline-core/protocol';
|
|
13
|
+
/**
|
|
14
|
+
* 判排法要的计算样式(契约 v11 `feature:node-fields`,挂在节点的 `facts.computed` 上)。货架取树时
|
|
15
|
+
* 随 `fields` 一起请求;老扩展不认 `fields`、静默忽略 —— 树上没有 facts,行不打标记,与原来逐字一致。
|
|
16
|
+
*/
|
|
17
|
+
export declare const SHELF_LAYOUT_FIELDS: readonly NodeField[];
|
|
18
|
+
/** 在父级横排里的位次:第 `index` 项(从 1 起)/ 共 `count` 项。 */
|
|
19
|
+
export interface RowPosition {
|
|
20
|
+
index: number;
|
|
21
|
+
count: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 树上每个节点 → 它在父级横排里的位次;父级竖排、或树上没有排法事实的不进表。
|
|
25
|
+
*
|
|
26
|
+
* 父级可能是货架上跳过的包装层(`.pf-c` 正是),所以对着**整棵树**判,不对着货架行判。
|
|
27
|
+
* 根自己的父级不在树里,判不了横竖,不进表。
|
|
28
|
+
*/
|
|
29
|
+
export declare function rowPositions(root: DomTreeNode): Map<string, RowPosition>;
|
|
30
|
+
/** 这个节点把孩子横着摆吗:flex 行(不换行;换行的要孩子真排在同一行)或多列 grid。 */
|
|
31
|
+
export declare function laysOutInRow(n: DomTreeNode): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const SHELF_LAYOUT_FIELDS = [
|
|
2
|
+
'computed:display',
|
|
3
|
+
'computed:flexDirection',
|
|
4
|
+
'computed:flexWrap',
|
|
5
|
+
'computed:gridTemplateColumns',
|
|
6
|
+
'computed:gridAutoFlow'
|
|
7
|
+
];
|
|
8
|
+
export function rowPositions(root) {
|
|
9
|
+
const positions = new Map();
|
|
10
|
+
const walk = (n) => {
|
|
11
|
+
if (laysOutInRow(n)) {
|
|
12
|
+
n.children.forEach((c, i) => positions.set(c.selector, { index: i + 1, count: n.children.length }));
|
|
13
|
+
}
|
|
14
|
+
n.children.forEach(walk);
|
|
15
|
+
};
|
|
16
|
+
walk(root);
|
|
17
|
+
return positions;
|
|
18
|
+
}
|
|
19
|
+
export function laysOutInRow(n) {
|
|
20
|
+
const c = n.facts?.computed;
|
|
21
|
+
if (!c)
|
|
22
|
+
return false;
|
|
23
|
+
if (c.display === 'flex' || c.display === 'inline-flex') {
|
|
24
|
+
if (c.flexDirection && !c.flexDirection.startsWith('row'))
|
|
25
|
+
return false;
|
|
26
|
+
if (c.flexWrap !== 'wrap' && c.flexWrap !== 'wrap-reverse')
|
|
27
|
+
return true;
|
|
28
|
+
const tops = n.children.flatMap(ch => (ch.rect ? [ch.rect.top] : []));
|
|
29
|
+
return tops.some((t, i) => tops.some((u, j) => j !== i && Math.abs(t - u) <= 1));
|
|
30
|
+
}
|
|
31
|
+
if (c.display === 'grid' || c.display === 'inline-grid') {
|
|
32
|
+
if (c.gridAutoFlow?.startsWith('column'))
|
|
33
|
+
return true;
|
|
34
|
+
const tracks = (c.gridTemplateColumns ?? '')
|
|
35
|
+
.replace(/\[[^\]]*\]/g, ' ')
|
|
36
|
+
.split(/\s+/)
|
|
37
|
+
.filter(t => t && t !== 'none');
|
|
38
|
+
return tracks.length >= 2;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 一漂移就是「同一个元素在 `<page-block>` 与 `<page-element>` 里长得不一样」,而它会按其中
|
|
7
7
|
* 一种去抄 selector。
|
|
8
8
|
*
|
|
9
|
-
* 行形:` - selector <tag> [container] [image: …] [link→…] top:N "text"`,`depth` 每加一层在 `- ` 前
|
|
9
|
+
* 行形:` - selector <tag> [container] [image: …] [link→…] top:N "text" [横排 i/n]`,`depth` 每加一层在 `- ` 前
|
|
10
10
|
* 多两个空格。缩进是 2026-09-30 加的:平铺的货架看不出谁是谁的父级,agent 分不清
|
|
11
11
|
* 「after 容器」与「after 容器里的标题」落在哪 —— 真机里它 after 了一个 flex 横排里的 Column,
|
|
12
12
|
* 新加的一行跑到了标题右边。
|
|
@@ -29,5 +29,12 @@ export declare function imageTag(d: Pick<AiDescendant, 'image'>): string;
|
|
|
29
29
|
* 方案卡据此断言「全页没有购买导线」,而那页几乎每张图都链到商品页。
|
|
30
30
|
*/
|
|
31
31
|
export declare function linkTag(d: Pick<AiDescendant, 'href'>): string;
|
|
32
|
+
/**
|
|
33
|
+
* 货架行上的横排位:` [横排 1/2]`,父级不是横排(或树上没有排法事实)时空串。
|
|
34
|
+
*
|
|
35
|
+
* `after` 这一行落在它的父级怎么摆孩子的地方:父级横排就排到它旁边、把它挤窄(真机 2026-09-30 /
|
|
36
|
+
* 10-16 / 10-19 三次)。写法与 `<page-block>`(frontend-base 63112d497a)逐字一致。
|
|
37
|
+
*/
|
|
38
|
+
export declare function rowTag(d: Pick<AiDescendant, 'inRow'>): string;
|
|
32
39
|
/** 叶子行优先写自己的直接文本;容器行(自己没有文本)才写 heading。 */
|
|
33
40
|
export declare function formatShelfRow(d: AiDescendant): string;
|
|
@@ -6,6 +6,9 @@ export function imageTag(d) {
|
|
|
6
6
|
export function linkTag(d) {
|
|
7
7
|
return d.href ? ` [link→${linkPath(d.href)}]` : '';
|
|
8
8
|
}
|
|
9
|
+
export function rowTag(d) {
|
|
10
|
+
return d.inRow ? ` [横排 ${d.inRow.index}/${d.inRow.count}]` : '';
|
|
11
|
+
}
|
|
9
12
|
function quote(text, max) {
|
|
10
13
|
const clean = text.replace(/\s+/g, ' ').replace(/"/g, "'").trim();
|
|
11
14
|
return `"${clean.length > max ? clean.slice(0, max) : clean}"`;
|
|
@@ -17,5 +20,5 @@ export function formatShelfRow(d) {
|
|
|
17
20
|
const label = d.text || d.heading;
|
|
18
21
|
const inserted = d.aiInserted ? ' [ai-inserted]' : '';
|
|
19
22
|
const block = d.block ? ` [${d.block}]` : '';
|
|
20
|
-
return ` ${indent}- ${d.selector}${tag}${d.hasChildren ? ' [container]' : ''}${imageTag(d)}${linkTag(d)}${inserted}${pos}${label ? ` ${quote(label, SHELF_TEXT_MAX)}` : ''}${block}`;
|
|
23
|
+
return ` ${indent}- ${d.selector}${tag}${d.hasChildren ? ' [container]' : ''}${imageTag(d)}${linkTag(d)}${inserted}${pos}${label ? ` ${quote(label, SHELF_TEXT_MAX)}` : ''}${rowTag(d)}${block}`;
|
|
21
24
|
}
|