@xihe-lab/tapd-core 2.0.0-rc.3 → 2.0.0-rc.4
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/dist/richtext/index.d.ts +1 -14
- package/dist/richtext/index.d.ts.map +1 -1
- package/dist/richtext/index.js +3 -34
- package/dist/richtext/index.js.map +1 -1
- package/dist/richtext/pipeline.d.ts +1 -4
- package/dist/richtext/pipeline.d.ts.map +1 -1
- package/dist/richtext/pipeline.js +2 -198
- package/dist/richtext/pipeline.js.map +1 -1
- package/package.json +3 -8
- package/dist/richtext/attachment-anchor.d.ts +0 -58
- package/dist/richtext/attachment-anchor.d.ts.map +0 -1
- package/dist/richtext/attachment-anchor.js +0 -221
- package/dist/richtext/attachment-anchor.js.map +0 -1
- package/dist/richtext/attachment-anchor.test.d.ts +0 -2
- package/dist/richtext/attachment-anchor.test.d.ts.map +0 -1
- package/dist/richtext/attachment-anchor.test.js +0 -149
- package/dist/richtext/attachment-anchor.test.js.map +0 -1
- package/dist/richtext/escape.d.ts +0 -7
- package/dist/richtext/escape.d.ts.map +0 -1
- package/dist/richtext/escape.js +0 -60
- package/dist/richtext/escape.js.map +0 -1
- package/dist/richtext/mention.d.ts +0 -49
- package/dist/richtext/mention.d.ts.map +0 -1
- package/dist/richtext/mention.js +0 -179
- package/dist/richtext/mention.js.map +0 -1
- package/dist/richtext/mention.test.d.ts +0 -2
- package/dist/richtext/mention.test.d.ts.map +0 -1
- package/dist/richtext/mention.test.js +0 -123
- package/dist/richtext/mention.test.js.map +0 -1
- package/dist/richtext/round-trip.test.d.ts +0 -2
- package/dist/richtext/round-trip.test.d.ts.map +0 -1
- package/dist/richtext/round-trip.test.js +0 -105
- package/dist/richtext/round-trip.test.js.map +0 -1
package/dist/richtext/index.d.ts
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
export * from '
|
|
2
|
-
export * from './mention.js';
|
|
3
|
-
export * from './attachment-anchor.js';
|
|
4
|
-
/**
|
|
5
|
-
* md 语义片段 → html 语义片段(round-trip 参考实现):
|
|
6
|
-
* 先替换附件引用(其 link-text 可能含 @),再做 @ 语义替换(at-who/@@ 转义),
|
|
7
|
-
* 两步均对其余内容原样透传。注意 fragment 层不感知 md 代码段/链接 URL(见 mention.ts 分层约定)。
|
|
8
|
-
*/
|
|
9
|
-
export declare function mdToHtmlFragment(md: string): string;
|
|
10
|
-
/**
|
|
11
|
-
* html 语义片段 → md 语义片段(round-trip 参考实现):
|
|
12
|
-
* 先还原 at-who 并保护字面 @(此时附件锚点仍是标签、不会被误转义),再把可识别附件锚点还原为 md 扩展语法。
|
|
13
|
-
*/
|
|
14
|
-
export declare function htmlToMdFragment(html: string): string;
|
|
1
|
+
export * from '@xihe-lab/tapd-markdown';
|
|
15
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/richtext/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/richtext/index.ts"],"names":[],"mappings":"AAEA,cAAc,yBAAyB,CAAC"}
|
package/dist/richtext/index.js
CHANGED
|
@@ -1,35 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
// Wave 2 接线指引(与现有转换器风格对齐):
|
|
6
|
-
// md→html(markdown-it 侧,text/inline token 级):
|
|
7
|
-
// - findMentions(text) + renderMentionHtml(name) —— @昵称 → at-who 标记(@@ 转义由 findMentions 语义内建)
|
|
8
|
-
// - parseAttachmentAnchorMd(md) + attachmentAnchorHtml(ref) —— attach: 链接 → data 五件套锚点
|
|
9
|
-
// - 或直接用 fragment 级 mdToHtmlFragment / 各模块 fragment 函数做参考实现
|
|
10
|
-
// html→md(turndown 侧,规则注册):
|
|
11
|
-
// - at-who:<b> 规则,filter=isAtWhoNode、replacement=atWhoNodeToMd(永不产出 **粗体**)
|
|
12
|
-
// - 附件锚点:<a> 规则,filter=isAttachmentAnchorNode、replacement=attachmentAnchorNodeToMd
|
|
13
|
-
// - 文本节点 @ 字面量保护:escapeAtLiteralsForMd(turndown text escape 钩子内调用)
|
|
14
|
-
// 保真规则须先于 GFM 通用规则注册(FSD §8 规则优先级要求)。
|
|
15
|
-
export * from './escape.js';
|
|
16
|
-
export * from './mention.js';
|
|
17
|
-
export * from './attachment-anchor.js';
|
|
18
|
-
import { mentionMdToHtml, mentionHtmlToMd } from './mention.js';
|
|
19
|
-
import { attachmentMdToHtml, attachmentHtmlToMd } from './attachment-anchor.js';
|
|
20
|
-
/**
|
|
21
|
-
* md 语义片段 → html 语义片段(round-trip 参考实现):
|
|
22
|
-
* 先替换附件引用(其 link-text 可能含 @),再做 @ 语义替换(at-who/@@ 转义),
|
|
23
|
-
* 两步均对其余内容原样透传。注意 fragment 层不感知 md 代码段/链接 URL(见 mention.ts 分层约定)。
|
|
24
|
-
*/
|
|
25
|
-
export function mdToHtmlFragment(md) {
|
|
26
|
-
return mentionMdToHtml(attachmentMdToHtml(md));
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* html 语义片段 → md 语义片段(round-trip 参考实现):
|
|
30
|
-
* 先还原 at-who 并保护字面 @(此时附件锚点仍是标签、不会被误转义),再把可识别附件锚点还原为 md 扩展语法。
|
|
31
|
-
*/
|
|
32
|
-
export function htmlToMdFragment(html) {
|
|
33
|
-
return attachmentHtmlToMd(mentionHtmlToMd(html));
|
|
34
|
-
}
|
|
1
|
+
// 兼容垫片:richtext 语义库与转换引擎已迁出为独立包 @xihe-lab/tapd-markdown(tapd-node/markdown)。
|
|
2
|
+
// 本文件保持 core 内既有 import 路径('../richtext/index.js')零改动;新代码请直接依赖包。
|
|
3
|
+
export * from '@xihe-lab/tapd-markdown';
|
|
35
4
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/richtext/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/richtext/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,iEAAiE;AACjE,cAAc,yBAAyB,CAAC"}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function mdToHtml(md: string): string;
|
|
3
|
-
/** TAPD 富文本 html → md(at-who → @昵称、附件锚点 → attach: 引用、/tfl/ 图片走 GFM 内建规则)。 */
|
|
4
|
-
export declare function htmlToMd(html: string): string;
|
|
1
|
+
export { mdToHtml, htmlToMd } from '@xihe-lab/tapd-markdown';
|
|
5
2
|
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -1,199 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// B 侧语义库(core/richtext,纯函数)在转换器「token / 规则级」接入(B 的接线警告):
|
|
4
|
-
// - md→html(markdown-it core 规则,token 级):
|
|
5
|
-
// * `attach:` 链接(link_open..link_close 纯文本运行)→ 整段替换为 R6 data 五件套锚点
|
|
6
|
-
// (html_inline token;html:false 只影响解析不影响程序化 token 的渲染);
|
|
7
|
-
// * `@昵称` / `@@` 语义只在 inline children 的 **text token** 内替换(mention/escape 各自成 token);
|
|
8
|
-
// * code_inline / fence / html_* / image(含 alt)/ 链接内部(含 URL)一律跳过;
|
|
9
|
-
// 裸 URL(https://.../@name)内的 @ 由「连续非空白段含 ://」护栏跳过。
|
|
10
|
-
// * 禁止对整段输入直接调 fragment 级函数上线(会误伤代码段与 URL)——本模块即正确接线层。
|
|
11
|
-
// - html→md(turndown 规则级):
|
|
12
|
-
// * at-who <b> → `@昵称`(永不降级 **粗体**,R7);附件锚点 <a> → `[📎 name](attach:ws/id)`;
|
|
13
|
-
// 识别失败(href 缺失等)保留原节点 HTML,绝不猜测改写。
|
|
14
|
-
// * 保真规则先于 GFM 注册(FSD §8):turndown addRule 为 unshift 语义(后注册者先匹配),
|
|
15
|
-
// 故在 use(gfm) 之后再 addRule 即获得最高匹配优先级,并有规则优先级测试固化。
|
|
16
|
-
// * 文本节点字面 @ 保护走 turndown 实例 escape 钩子(escapeAtLiteralsForMd;
|
|
17
|
-
// CODE 内文本 isCode 天然绕过 escape,代码段 @ 不被加倍)。
|
|
18
|
-
//
|
|
19
|
-
// 单一实例共享给 md_to_html/html_to_md 工具(tools/utility.ts)与写管道
|
|
20
|
-
// (registry/richtext.ts 的 transformWriteArgs/transformReadData)——FSD §3.7:
|
|
21
|
-
// 所有富文本写工具与 pm 层场景共用同一条管道,免费获得 @/附件/图片能力。
|
|
22
|
-
import MarkdownIt from 'markdown-it';
|
|
23
|
-
import TurndownService from 'turndown';
|
|
24
|
-
import { gfm } from 'turndown-plugin-gfm';
|
|
25
|
-
import { attachmentAnchorHtml, attachmentAnchorNodeToMd, atWhoNodeToMd, escapeAtLiteralsForMd, findMentions, isAttachmentAnchorNode, isAtWhoNode, renderMentionHtml, } from './index.js';
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// md → html(markdown-it,token 级)
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
/** `attach:<ws>/<id>`(与 richtext md 扩展语法的 ws/id 文法一致:排除空白、()、/)。 */
|
|
30
|
-
const ATTACH_HREF_RE = /^attach:([^()\s/]+)\/([^()\s/]+)$/i;
|
|
31
|
-
/**
|
|
32
|
-
* 裸 URL 防误伤:mention 候选往前回溯同一连续非空白段,段内含 `://` 视为 URL 的一部分
|
|
33
|
-
* (如 `https://e.com/@zhang`)。md 链接语法 `[t](url)` 的 URL 在 token attrs 里天然不进本层。
|
|
34
|
-
*/
|
|
35
|
-
function insideBareUrl(content, atIndex) {
|
|
36
|
-
let start = atIndex;
|
|
37
|
-
while (start > 0 && !/\s/.test(content[start - 1]))
|
|
38
|
-
start -= 1;
|
|
39
|
-
return content.slice(start, atIndex).includes('://');
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* 把一个 text token 的内容按 @ 语义拆分为 [text | html_inline] 串
|
|
43
|
-
* (镜像 richtext scanAtTokens 规则:@@ 优先、findMentions 已内建左边界判定)。
|
|
44
|
-
* 无任何语义变化时返回 null(调用方保留原 token,避免无谓重排)。
|
|
45
|
-
*/
|
|
46
|
-
function splitTextTokenAtSemantics(TokenCtor, token) {
|
|
47
|
-
const content = token.content;
|
|
48
|
-
const mentions = findMentions(content);
|
|
49
|
-
if (mentions.length === 0 && !content.includes('@@'))
|
|
50
|
-
return null;
|
|
51
|
-
const mentionByStart = new Map(mentions.map(m => [m.start, m]));
|
|
52
|
-
const out = [];
|
|
53
|
-
let plain = '';
|
|
54
|
-
const flush = () => {
|
|
55
|
-
if (plain === '')
|
|
56
|
-
return;
|
|
57
|
-
const text = new TokenCtor('text', '', 0);
|
|
58
|
-
text.content = plain;
|
|
59
|
-
out.push(text);
|
|
60
|
-
plain = '';
|
|
61
|
-
};
|
|
62
|
-
let i = 0;
|
|
63
|
-
let changed = false;
|
|
64
|
-
while (i < content.length) {
|
|
65
|
-
if (content[i] === '@') {
|
|
66
|
-
if (content[i + 1] === '@') {
|
|
67
|
-
plain += '@'; // md 转义 @@ → 字面 @
|
|
68
|
-
changed = true;
|
|
69
|
-
i += 2;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const mention = mentionByStart.get(i);
|
|
73
|
-
if (mention && !insideBareUrl(content, i)) {
|
|
74
|
-
flush();
|
|
75
|
-
const html = new TokenCtor('html_inline', '', 0);
|
|
76
|
-
html.content = renderMentionHtml(mention.name);
|
|
77
|
-
out.push(html);
|
|
78
|
-
changed = true;
|
|
79
|
-
i = mention.end;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
plain += content[i];
|
|
84
|
-
i += 1;
|
|
85
|
-
}
|
|
86
|
-
flush();
|
|
87
|
-
if (!changed || (out.length === 1 && out[0].type === 'text' && out[0].content === content))
|
|
88
|
-
return null;
|
|
89
|
-
return out;
|
|
90
|
-
}
|
|
91
|
-
/** inline children 内的 `attach:` 链接 → R6 锚点(整段替换为单个 html_inline token)。 */
|
|
92
|
-
function convertAttachmentLinks(TokenCtor, tokens) {
|
|
93
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
94
|
-
const open = tokens[i];
|
|
95
|
-
if (open.type !== 'link_open')
|
|
96
|
-
continue;
|
|
97
|
-
const href = String(open.attrGet('href') ?? '');
|
|
98
|
-
const refMatch = ATTACH_HREF_RE.exec(href.trim());
|
|
99
|
-
if (!refMatch)
|
|
100
|
-
continue;
|
|
101
|
-
// link_open..link_close 之间必须是纯文本运行(含嵌套/换行则保守跳过,保留原链接)
|
|
102
|
-
let text = '';
|
|
103
|
-
let closeIdx = -1;
|
|
104
|
-
for (let j = i + 1; j < tokens.length; j++) {
|
|
105
|
-
const t = tokens[j];
|
|
106
|
-
if (t.type === 'link_close') {
|
|
107
|
-
closeIdx = j;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
if (t.type !== 'text')
|
|
111
|
-
break;
|
|
112
|
-
text += t.content;
|
|
113
|
-
}
|
|
114
|
-
if (closeIdx < 0)
|
|
115
|
-
continue;
|
|
116
|
-
const name = text.replace(/^\s*📎\s*/, '');
|
|
117
|
-
if (name === '')
|
|
118
|
-
continue;
|
|
119
|
-
let anchor;
|
|
120
|
-
try {
|
|
121
|
-
anchor = attachmentAnchorHtml({ workspaceId: refMatch[1], id: refMatch[2], name });
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
continue; // ws/id 含空白或非法字符 → 原样保留(绝不猜测改写)
|
|
125
|
-
}
|
|
126
|
-
const htmlToken = new TokenCtor('html_inline', '', 0);
|
|
127
|
-
htmlToken.content = anchor;
|
|
128
|
-
tokens.splice(i, closeIdx - i + 1, htmlToken);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/** inline children 内 text token 的 @ 语义替换(链接内部跳过;code/html/image token 天然跳过)。 */
|
|
132
|
-
function convertMentions(TokenCtor, tokens) {
|
|
133
|
-
let linkDepth = 0;
|
|
134
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
135
|
-
const token = tokens[i];
|
|
136
|
-
if (token.type === 'link_open') {
|
|
137
|
-
linkDepth += 1;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (token.type === 'link_close') {
|
|
141
|
-
linkDepth = Math.max(0, linkDepth - 1);
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (linkDepth > 0)
|
|
145
|
-
continue; // 链接文本内 @ 不转换(URL 已天然不在 text token)
|
|
146
|
-
if (token.type !== 'text')
|
|
147
|
-
continue;
|
|
148
|
-
const replacement = splitTextTokenAtSemantics(TokenCtor, token);
|
|
149
|
-
if (replacement) {
|
|
150
|
-
tokens.splice(i, 1, ...replacement);
|
|
151
|
-
i += replacement.length - 1;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function tapdRichtextCoreRule(state) {
|
|
156
|
-
const TokenCtor = state.Token;
|
|
157
|
-
for (const token of state.tokens) {
|
|
158
|
-
// fence/code_block/html_block 等 block token 内容不经过 inline 层,天然跳过
|
|
159
|
-
if (token.type !== 'inline' || !token.children)
|
|
160
|
-
continue;
|
|
161
|
-
convertAttachmentLinks(TokenCtor, token.children);
|
|
162
|
-
convertMentions(TokenCtor, token.children);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const markdownIt = new MarkdownIt({ html: false, breaks: true });
|
|
166
|
-
// push 到 core 链尾(text_join 之后):不再有规则会合并/重排我们的 token 切分
|
|
167
|
-
markdownIt.core.ruler.push('tapd_richtext_semantics', tapdRichtextCoreRule);
|
|
168
|
-
/** md → TAPD 富文本 html(@昵称/@@转义/attach: 附件引用/图片与普通 md 全兼容)。 */
|
|
169
|
-
export function mdToHtml(md) {
|
|
170
|
-
return markdownIt.render(md);
|
|
171
|
-
}
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
// html → md(turndown,规则级)
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
|
|
176
|
-
turndown.use(gfm);
|
|
177
|
-
// 保真规则先于 GFM/内建规则匹配:addRule 是 unshift 语义(后注册者先匹配),
|
|
178
|
-
// 在 use(gfm) 之后注册即拿到最高优先级(FSD §8,规则优先级由测试固化)。
|
|
179
|
-
turndown.addRule('tapdAtWho', {
|
|
180
|
-
filter: node => isAtWhoNode(node),
|
|
181
|
-
replacement: (_content, node) => atWhoNodeToMd(node),
|
|
182
|
-
});
|
|
183
|
-
turndown.addRule('tapdAttachmentAnchor', {
|
|
184
|
-
filter: node => isAttachmentAnchorNode(node),
|
|
185
|
-
replacement: (_content, node) => {
|
|
186
|
-
const md = attachmentAnchorNodeToMd(node);
|
|
187
|
-
// 识别条件不满足(缺 href 等)→ 保留原节点 HTML,绝不猜测改写
|
|
188
|
-
return md ?? node.outerHTML ?? '';
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
// 文本节点字面 @ 保护(round-trip 稳定:命中 mention 规则的 @名字 加倍为 @@名字)。
|
|
192
|
-
// CODE 内文本 isCode 天然绕过 escape 钩子(turndown process 分支),代码段 @ 不被加倍。
|
|
193
|
-
const baseEscape = TurndownService.prototype.escape.bind(TurndownService.prototype);
|
|
194
|
-
turndown.escape = (text) => baseEscape(escapeAtLiteralsForMd(text));
|
|
195
|
-
/** TAPD 富文本 html → md(at-who → @昵称、附件锚点 → attach: 引用、/tfl/ 图片走 GFM 内建规则)。 */
|
|
196
|
-
export function htmlToMd(html) {
|
|
197
|
-
return turndown.turndown(html);
|
|
198
|
-
}
|
|
1
|
+
// 兼容垫片:转换引擎(mdToHtml/htmlToMd)已迁出为 @xihe-lab/tapd-markdown。
|
|
2
|
+
export { mdToHtml, htmlToMd } from '@xihe-lab/tapd-markdown';
|
|
199
3
|
//# sourceMappingURL=pipeline.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xihe-lab/tapd-core",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.4",
|
|
4
4
|
"description": "Shared kernel for TAPD tools: tool definitions, TapdClient and registry, shared by CLI and MCP entries",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"type-check": "tsc --noEmit",
|
|
11
|
-
"test": "tsx src/registry/long-id-guard.test.ts && tsx src/registry/registry.test.ts && tsx src/config/config.test.ts && tsx src/schema/export.test.ts && tsx src/advisor/advisor.test.ts && tsx src/tools/md-to-html.test.ts && tsx src/registry/richtext.test.ts && tsx src/tools/attachment.test.ts && tsx src/
|
|
11
|
+
"test": "tsx src/registry/long-id-guard.test.ts && tsx src/registry/registry.test.ts && tsx src/config/config.test.ts && tsx src/schema/export.test.ts && tsx src/advisor/advisor.test.ts && tsx src/tools/md-to-html.test.ts && tsx src/registry/richtext.test.ts && tsx src/tools/attachment.test.ts && tsx src/tools/richtext-wiring.test.ts",
|
|
12
12
|
"debug:commands": "tsx src/registry/debug-commands.ts"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
@@ -39,15 +39,10 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@opentapd/tapd-node-sdk": "^1.67.0",
|
|
42
|
-
"markdown
|
|
43
|
-
"turndown": "^7.2.4",
|
|
44
|
-
"turndown-plugin-gfm": "^1.0.2",
|
|
42
|
+
"@xihe-lab/tapd-markdown": "^1.0.0-rc.1",
|
|
45
43
|
"zod": "^3.25.76",
|
|
46
44
|
"zod-to-json-schema": "^3.25.2"
|
|
47
45
|
},
|
|
48
|
-
"devDependencies": {
|
|
49
|
-
"@types/turndown": "^5.0.6"
|
|
50
|
-
},
|
|
51
46
|
"homepage": "https://xihe-lab.com",
|
|
52
47
|
"packageManager": "pnpm@12.3.4"
|
|
53
48
|
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { HtmlNodeLike } from './mention.js';
|
|
2
|
-
/** TAPD 前端附件组件的 file-type 图标分类。 */
|
|
3
|
-
export type FileType = 'text' | 'image' | 'pdf' | 'other';
|
|
4
|
-
/** 扩展名 → file-type 映射表(键为小写扩展名;导出供 Wave 2 上传工具复用)。 */
|
|
5
|
-
export declare const FILE_TYPE_BY_EXTENSION: Readonly<Record<string, FileType>>;
|
|
6
|
-
/** 由文件名(任意大小写扩展名)映射 file-type;无扩展名或未收录 → other。 */
|
|
7
|
-
export declare function fileTypeFor(filename: string): FileType;
|
|
8
|
-
export declare const ATTACH_SCHEME = "attach:";
|
|
9
|
-
export declare const DEFAULT_ATTACHMENT_SLUG = "story_description_attachment";
|
|
10
|
-
/** 附件引用。size/fileType 可省略:省略 fileType 时按 name 扩展名映射。 */
|
|
11
|
-
export interface AttachmentRef {
|
|
12
|
-
workspaceId: string;
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
size?: number;
|
|
16
|
-
fileType?: FileType;
|
|
17
|
-
}
|
|
18
|
-
export interface AttachmentAnchorHtmlOptions {
|
|
19
|
-
/** href 末段 slug(随实体类型变化的场景可覆盖;默认评论/需求描述通用值)。 */
|
|
20
|
-
slug?: string;
|
|
21
|
-
/** data-can-preview,默认 true(R6 五件套定稿值)。 */
|
|
22
|
-
canPreview?: boolean;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* 生成附件引用 md:`[📎 <文件名>](attach:<ws>/<id>)`。
|
|
26
|
-
* 文件名中的 `[` `]` `\` 按 md 规则转义(parseAttachmentAnchorMd 可逆)。
|
|
27
|
-
*/
|
|
28
|
-
export declare function attachmentAnchorMd(ref: Pick<AttachmentRef, 'workspaceId' | 'id' | 'name'>): string;
|
|
29
|
-
/**
|
|
30
|
-
* 解析单个附件引用 md 片段(非附件链接 / scheme 不符 → null,调用方原样保留)。
|
|
31
|
-
* 返回的 ref 不含 size/fileType(md 形态不承载,html 侧按文件名重新映射)。
|
|
32
|
-
*/
|
|
33
|
-
export declare function parseAttachmentAnchorMd(md: string): AttachmentRef | null;
|
|
34
|
-
/** 生成 R6 定稿最小锚点(data 五件套 + 零 spans)。size 省略时不输出 data-size。 */
|
|
35
|
-
export declare function attachmentAnchorHtml(ref: AttachmentRef, options?: AttachmentAnchorHtmlOptions): string;
|
|
36
|
-
/**
|
|
37
|
-
* 解析单个锚点标签串。识别条件(缺一即 null,调用方原样保留该锚点):
|
|
38
|
-
* data-is-tapd-attachment="true" + data-name + href 形如
|
|
39
|
-
* `/<ws>/attachments/preview_attachments/<id>/<slug>`。兼容完整编辑器模板(含 inner spans / type / class)。
|
|
40
|
-
*/
|
|
41
|
-
export declare function parseAttachmentAnchorHtml(anchorHtml: string): AttachmentRef | null;
|
|
42
|
-
/** 扫描 html 片段中全部可识别的附件锚点。 */
|
|
43
|
-
export declare function findAttachmentAnchors(html: string): AttachmentRef[];
|
|
44
|
-
/**
|
|
45
|
-
* html 片段 → md 语义片段:可识别锚点 → `[📎 name](attach:ws/id)`;
|
|
46
|
-
* 不认识的锚点与其余内容原样透传(交给基础转换器 turndown 处理)。
|
|
47
|
-
*/
|
|
48
|
-
export declare function attachmentHtmlToMd(html: string): string;
|
|
49
|
-
/**
|
|
50
|
-
* md 片段 → html 语义片段:附件引用 → R6 锚点;
|
|
51
|
-
* 非 `attach:` scheme 的链接与其余内容原样透传(交给基础转换器 markdown-it 处理)。
|
|
52
|
-
*/
|
|
53
|
-
export declare function attachmentMdToHtml(md: string): string;
|
|
54
|
-
/** turndown 规则 filter 用:节点是否为可识别附件锚点。 */
|
|
55
|
-
export declare function isAttachmentAnchorNode(node: HtmlNodeLike): boolean;
|
|
56
|
-
/** turndown 规则 replacement 用:锚点节点 → 附件引用 md;不满足识别条件返回 null(调用方保留原样)。 */
|
|
57
|
-
export declare function attachmentAnchorNodeToMd(node: HtmlNodeLike): string | null;
|
|
58
|
-
//# sourceMappingURL=attachment-anchor.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-anchor.d.ts","sourceRoot":"","sources":["../../src/richtext/attachment-anchor.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,mCAAmC;AACnC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;AAE1D,sDAAsD;AACtD,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAMrE,CAAC;AAEF,mDAAmD;AACnD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAItD;AAED,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,uBAAuB,iCAAiC,CAAC;AAEtE,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAiBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAAC,GAAG,MAAM,CAKlG;AAOD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAQxE;AAED,8DAA8D;AAC9D,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,GAAE,2BAAgC,GAAG,MAAM,CAoB1G;AAoCD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAGlF;AAED,6BAA6B;AAC7B,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CASnE;AAID;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAavD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAWrD;AAED,yCAAyC;AACzC,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAGlE;AAED,wEAAwE;AACxE,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAc1E"}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
// TAPD 2.0 rc.2 P1/P3(需求 1139814312001001548)—— 附件引用 md 扩展 ↔ data 五件套锚点 HTML
|
|
2
|
-
// 双向语义(纯函数层)。设计依据:FSD §3.1(embed_md/embed_html)、R6 定稿模板;
|
|
3
|
-
// 实证:rc2-painpoints-research.md(评论 367/368/369 严格作用域)+ e2e 金样 poc-rc2-file-embed.mjs。
|
|
4
|
-
//
|
|
5
|
-
// md 扩展语法(BNF):
|
|
6
|
-
// attachment-md ::= "[📎 " link-text "](attach:" ws "/" id ")"
|
|
7
|
-
// link-text ::= ( md-escape | 非 "\" "]" 换行 字符 )*
|
|
8
|
-
// md-escape ::= "\" 任一非换行字符 (仅 \[ \] \\ 被 unescape 语义消费)
|
|
9
|
-
// ws / id ::= 1*( 排除空白、")(" 、"/" 的字符 )(实际为 TAPD 数字 ID)
|
|
10
|
-
//
|
|
11
|
-
// HTML 定稿最小模板(前端重建组件只读 data 五件套,inner spans 全可省——评论 369 实证完美渲染):
|
|
12
|
-
// <a data-is-tapd-attachment="true" data-can-preview="true" data-file-type="text"
|
|
13
|
-
// data-name="<文件名>" data-size="<字节数>" target="_blank" rel="noopener"
|
|
14
|
-
// href="/<ws>/attachments/preview_attachments/<附件ID>/story_description_attachment"><文件名></a>
|
|
15
|
-
//
|
|
16
|
-
// 语义约定:
|
|
17
|
-
// - data-file-type 由文件扩展名映射(fileTypeFor / FILE_TYPE_BY_EXTENSION,导出供 Wave 2 上传工具复用)。
|
|
18
|
-
// - html→md 方向丢 size/slug/type 细节:md 形态是纯引用(by design,round-trip 允许该损)。
|
|
19
|
-
// - 不认识的锚点(缺 data 五件套关键属性、裸 <a href> 等)原样保留,绝不猜测改写。
|
|
20
|
-
// - 与 mention.ts 相同的分层约定:fragment 级函数只做语义 token 替换 + 其余原样透传,
|
|
21
|
-
// Wave 2 在 markdown-it/turndown token 级接线(本文件提供节点级 duck-typing 辅助)。
|
|
22
|
-
import { decodeHtmlEntities, escapeHtmlAttr, escapeHtmlText } from './escape.js';
|
|
23
|
-
/** 扩展名 → file-type 映射表(键为小写扩展名;导出供 Wave 2 上传工具复用)。 */
|
|
24
|
-
export const FILE_TYPE_BY_EXTENSION = {
|
|
25
|
-
txt: 'text',
|
|
26
|
-
png: 'image',
|
|
27
|
-
jpg: 'image',
|
|
28
|
-
jpeg: 'image',
|
|
29
|
-
pdf: 'pdf',
|
|
30
|
-
};
|
|
31
|
-
/** 由文件名(任意大小写扩展名)映射 file-type;无扩展名或未收录 → other。 */
|
|
32
|
-
export function fileTypeFor(filename) {
|
|
33
|
-
const match = /\.([A-Za-z0-9]+)$/.exec(filename.trim());
|
|
34
|
-
if (!match)
|
|
35
|
-
return 'other';
|
|
36
|
-
return FILE_TYPE_BY_EXTENSION[match[1].toLowerCase()] ?? 'other';
|
|
37
|
-
}
|
|
38
|
-
export const ATTACH_SCHEME = 'attach:';
|
|
39
|
-
export const DEFAULT_ATTACHMENT_SLUG = 'story_description_attachment';
|
|
40
|
-
function assertRefToken(value, field) {
|
|
41
|
-
if (!value)
|
|
42
|
-
throw new TypeError(`attachment ${field} must be a non-empty string`);
|
|
43
|
-
if (/[\s()/]/.test(value)) {
|
|
44
|
-
throw new TypeError(`attachment ${field} must not contain whitespace, "/", "(" or ")": ${JSON.stringify(value)}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function escapeMdLinkText(name) {
|
|
48
|
-
return name.replace(/[\\\][]/g, (ch) => `\\${ch}`);
|
|
49
|
-
}
|
|
50
|
-
function unescapeMdLinkText(text) {
|
|
51
|
-
return text.replace(/\\([\\\][])/g, '$1');
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* 生成附件引用 md:`[📎 <文件名>](attach:<ws>/<id>)`。
|
|
55
|
-
* 文件名中的 `[` `]` `\` 按 md 规则转义(parseAttachmentAnchorMd 可逆)。
|
|
56
|
-
*/
|
|
57
|
-
export function attachmentAnchorMd(ref) {
|
|
58
|
-
assertRefToken(ref.workspaceId, 'workspaceId');
|
|
59
|
-
assertRefToken(ref.id, 'id');
|
|
60
|
-
if (!ref.name)
|
|
61
|
-
throw new TypeError('attachment name must be a non-empty string');
|
|
62
|
-
return `[📎 ${escapeMdLinkText(ref.name)}](${ATTACH_SCHEME}${ref.workspaceId}/${ref.id})`;
|
|
63
|
-
}
|
|
64
|
-
// `\\.` 允许任意转义字符;其余字符排除 `\`、`]`、换行(`[` 无需排除但转义统一处理)。
|
|
65
|
-
const ATTACH_MD_PATTERN = '\\[📎 ((?:\\\\.|[^\\\\\\]\\n])*)\\]\\(attach:([^()\\s]+)/([^()\\s]+)\\)';
|
|
66
|
-
const ATTACH_MD_RE = new RegExp(ATTACH_MD_PATTERN, 'g');
|
|
67
|
-
const ATTACH_MD_RE_ONE = new RegExp(ATTACH_MD_PATTERN);
|
|
68
|
-
/**
|
|
69
|
-
* 解析单个附件引用 md 片段(非附件链接 / scheme 不符 → null,调用方原样保留)。
|
|
70
|
-
* 返回的 ref 不含 size/fileType(md 形态不承载,html 侧按文件名重新映射)。
|
|
71
|
-
*/
|
|
72
|
-
export function parseAttachmentAnchorMd(md) {
|
|
73
|
-
const match = ATTACH_MD_RE_ONE.exec(md);
|
|
74
|
-
if (!match)
|
|
75
|
-
return null;
|
|
76
|
-
return {
|
|
77
|
-
workspaceId: match[2],
|
|
78
|
-
id: match[3],
|
|
79
|
-
name: unescapeMdLinkText(match[1]),
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/** 生成 R6 定稿最小锚点(data 五件套 + 零 spans)。size 省略时不输出 data-size。 */
|
|
83
|
-
export function attachmentAnchorHtml(ref, options = {}) {
|
|
84
|
-
assertRefToken(ref.workspaceId, 'workspaceId');
|
|
85
|
-
assertRefToken(ref.id, 'id');
|
|
86
|
-
if (!ref.name)
|
|
87
|
-
throw new TypeError('attachment name must be a non-empty string');
|
|
88
|
-
const slug = options.slug ?? DEFAULT_ATTACHMENT_SLUG;
|
|
89
|
-
const canPreview = options.canPreview ?? true;
|
|
90
|
-
const fileType = ref.fileType ?? fileTypeFor(ref.name);
|
|
91
|
-
const attrs = [
|
|
92
|
-
'data-is-tapd-attachment="true"',
|
|
93
|
-
`data-can-preview="${canPreview}"`,
|
|
94
|
-
`data-file-type="${fileType}"`,
|
|
95
|
-
`data-name="${escapeHtmlAttr(ref.name)}"`,
|
|
96
|
-
];
|
|
97
|
-
if (ref.size !== undefined)
|
|
98
|
-
attrs.push(`data-size="${ref.size}"`);
|
|
99
|
-
attrs.push('target="_blank"', 'rel="noopener"', `href="/${ref.workspaceId}/attachments/preview_attachments/${ref.id}/${slug}"`);
|
|
100
|
-
return `<a ${attrs.join(' ')}>${escapeHtmlText(ref.name)}</a>`;
|
|
101
|
-
}
|
|
102
|
-
const A_OPEN_RE = /<a\b[^>]*>/gi;
|
|
103
|
-
const FLAG_ATTR_RE = /\bdata-is-tapd-attachment\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
104
|
-
const NAME_ATTR_RE = /\bdata-name\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
105
|
-
const SIZE_ATTR_RE = /\bdata-size\s*=\s*(?:"(\d+)"|'(\d+)')/i;
|
|
106
|
-
const FILE_TYPE_ATTR_RE = /\bdata-file-type\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
107
|
-
const HREF_ATTR_RE = /\bhref\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
108
|
-
const PREVIEW_HREF_RE = /^\/([^/\s]+)\/attachments\/preview_attachments\/([^/\s]+)\/([^/\s]*)$/i;
|
|
109
|
-
const FILE_TYPES = new Set(['text', 'image', 'pdf', 'other']);
|
|
110
|
-
function attrValue(match) {
|
|
111
|
-
if (!match)
|
|
112
|
-
return null;
|
|
113
|
-
return decodeHtmlEntities(match[1] ?? match[2] ?? match[3] ?? '');
|
|
114
|
-
}
|
|
115
|
-
function refFromOpenTag(openTag) {
|
|
116
|
-
const flag = attrValue(FLAG_ATTR_RE.exec(openTag));
|
|
117
|
-
if (flag?.toLowerCase() !== 'true')
|
|
118
|
-
return null;
|
|
119
|
-
const name = attrValue(NAME_ATTR_RE.exec(openTag));
|
|
120
|
-
const href = attrValue(HREF_ATTR_RE.exec(openTag));
|
|
121
|
-
if (!name || !href)
|
|
122
|
-
return null;
|
|
123
|
-
const hrefMatch = PREVIEW_HREF_RE.exec(href);
|
|
124
|
-
if (!hrefMatch)
|
|
125
|
-
return null;
|
|
126
|
-
const ref = { workspaceId: hrefMatch[1], id: hrefMatch[2], name };
|
|
127
|
-
const size = attrValue(SIZE_ATTR_RE.exec(openTag));
|
|
128
|
-
if (size !== null) {
|
|
129
|
-
const parsed = Number.parseInt(size, 10);
|
|
130
|
-
if (Number.isFinite(parsed))
|
|
131
|
-
ref.size = parsed;
|
|
132
|
-
}
|
|
133
|
-
const fileType = attrValue(FILE_TYPE_ATTR_RE.exec(openTag));
|
|
134
|
-
if (fileType !== null && FILE_TYPES.has(fileType))
|
|
135
|
-
ref.fileType = fileType;
|
|
136
|
-
return ref;
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* 解析单个锚点标签串。识别条件(缺一即 null,调用方原样保留该锚点):
|
|
140
|
-
* data-is-tapd-attachment="true" + data-name + href 形如
|
|
141
|
-
* `/<ws>/attachments/preview_attachments/<id>/<slug>`。兼容完整编辑器模板(含 inner spans / type / class)。
|
|
142
|
-
*/
|
|
143
|
-
export function parseAttachmentAnchorHtml(anchorHtml) {
|
|
144
|
-
const openMatch = /^<a\b[^>]*>/i.exec(anchorHtml);
|
|
145
|
-
return openMatch ? refFromOpenTag(openMatch[0]) : null;
|
|
146
|
-
}
|
|
147
|
-
/** 扫描 html 片段中全部可识别的附件锚点。 */
|
|
148
|
-
export function findAttachmentAnchors(html) {
|
|
149
|
-
const refs = [];
|
|
150
|
-
A_OPEN_RE.lastIndex = 0;
|
|
151
|
-
let match;
|
|
152
|
-
while ((match = A_OPEN_RE.exec(html)) !== null) {
|
|
153
|
-
const ref = refFromOpenTag(match[0]);
|
|
154
|
-
if (ref)
|
|
155
|
-
refs.push(ref);
|
|
156
|
-
}
|
|
157
|
-
return refs;
|
|
158
|
-
}
|
|
159
|
-
const A_TAG_RE = /<a\b[^>]*>[\s\S]*?<\/a\s*>|<a\b[^>]*\/?>/gi;
|
|
160
|
-
/**
|
|
161
|
-
* html 片段 → md 语义片段:可识别锚点 → `[📎 name](attach:ws/id)`;
|
|
162
|
-
* 不认识的锚点与其余内容原样透传(交给基础转换器 turndown 处理)。
|
|
163
|
-
*/
|
|
164
|
-
export function attachmentHtmlToMd(html) {
|
|
165
|
-
let out = '';
|
|
166
|
-
let pos = 0;
|
|
167
|
-
let match;
|
|
168
|
-
A_TAG_RE.lastIndex = 0;
|
|
169
|
-
while ((match = A_TAG_RE.exec(html)) !== null) {
|
|
170
|
-
const openMatch = /^<a\b[^>]*>/i.exec(match[0]);
|
|
171
|
-
const ref = openMatch ? refFromOpenTag(openMatch[0]) : null;
|
|
172
|
-
out += html.slice(pos, match.index);
|
|
173
|
-
out += ref ? attachmentAnchorMd(ref) : match[0];
|
|
174
|
-
pos = match.index + match[0].length;
|
|
175
|
-
}
|
|
176
|
-
return out + html.slice(pos);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* md 片段 → html 语义片段:附件引用 → R6 锚点;
|
|
180
|
-
* 非 `attach:` scheme 的链接与其余内容原样透传(交给基础转换器 markdown-it 处理)。
|
|
181
|
-
*/
|
|
182
|
-
export function attachmentMdToHtml(md) {
|
|
183
|
-
let out = '';
|
|
184
|
-
let pos = 0;
|
|
185
|
-
let match;
|
|
186
|
-
ATTACH_MD_RE.lastIndex = 0;
|
|
187
|
-
while ((match = ATTACH_MD_RE.exec(md)) !== null) {
|
|
188
|
-
out += md.slice(pos, match.index);
|
|
189
|
-
out += attachmentAnchorHtml({ workspaceId: match[2], id: match[3], name: unescapeMdLinkText(match[1]) });
|
|
190
|
-
pos = match.index + match[0].length;
|
|
191
|
-
}
|
|
192
|
-
return out + md.slice(pos);
|
|
193
|
-
}
|
|
194
|
-
/** turndown 规则 filter 用:节点是否为可识别附件锚点。 */
|
|
195
|
-
export function isAttachmentAnchorNode(node) {
|
|
196
|
-
if (node.nodeName.toUpperCase() !== 'A')
|
|
197
|
-
return false;
|
|
198
|
-
return (node.getAttribute('data-is-tapd-attachment') ?? '').toLowerCase() === 'true';
|
|
199
|
-
}
|
|
200
|
-
/** turndown 规则 replacement 用:锚点节点 → 附件引用 md;不满足识别条件返回 null(调用方保留原样)。 */
|
|
201
|
-
export function attachmentAnchorNodeToMd(node) {
|
|
202
|
-
if (!isAttachmentAnchorNode(node))
|
|
203
|
-
return null;
|
|
204
|
-
const name = node.getAttribute('data-name');
|
|
205
|
-
const href = node.getAttribute('href');
|
|
206
|
-
if (!name || !href)
|
|
207
|
-
return null;
|
|
208
|
-
const hrefMatch = PREVIEW_HREF_RE.exec(decodeHtmlEntities(href));
|
|
209
|
-
if (!hrefMatch)
|
|
210
|
-
return null;
|
|
211
|
-
const ref = { workspaceId: hrefMatch[1], id: hrefMatch[2], name: decodeHtmlEntities(name) };
|
|
212
|
-
const size = node.getAttribute('data-size');
|
|
213
|
-
const parsedSize = size === null ? Number.NaN : Number.parseInt(size, 10);
|
|
214
|
-
if (Number.isFinite(parsedSize))
|
|
215
|
-
ref.size = parsedSize;
|
|
216
|
-
const fileType = node.getAttribute('data-file-type');
|
|
217
|
-
if (fileType !== null && FILE_TYPES.has(fileType))
|
|
218
|
-
ref.fileType = fileType;
|
|
219
|
-
return attachmentAnchorMd(ref);
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=attachment-anchor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-anchor.js","sourceRoot":"","sources":["../../src/richtext/attachment-anchor.ts"],"names":[],"mappings":"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"}
|