@xihe-lab/tapd-core 2.0.0-rc.1 → 2.0.0-rc.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/README.md +22 -0
- package/dist/registry/richtext-manifests.generated.d.ts +1 -1
- package/dist/registry/richtext-manifests.generated.d.ts.map +1 -1
- package/dist/registry/richtext-manifests.generated.js +1 -1
- package/dist/registry/richtext-manifests.generated.js.map +1 -1
- package/dist/registry/richtext.d.ts +9 -0
- package/dist/registry/richtext.d.ts.map +1 -1
- package/dist/registry/richtext.js +33 -15
- package/dist/registry/richtext.js.map +1 -1
- package/dist/registry/richtext.test.js +70 -7
- package/dist/registry/richtext.test.js.map +1 -1
- package/dist/richtext/attachment-anchor.d.ts +58 -0
- package/dist/richtext/attachment-anchor.d.ts.map +1 -0
- package/dist/richtext/attachment-anchor.js +221 -0
- package/dist/richtext/attachment-anchor.js.map +1 -0
- package/dist/richtext/attachment-anchor.test.d.ts +2 -0
- package/dist/richtext/attachment-anchor.test.d.ts.map +1 -0
- package/dist/richtext/attachment-anchor.test.js +149 -0
- package/dist/richtext/attachment-anchor.test.js.map +1 -0
- package/dist/richtext/escape.d.ts +7 -0
- package/dist/richtext/escape.d.ts.map +1 -0
- package/dist/richtext/escape.js +60 -0
- package/dist/richtext/escape.js.map +1 -0
- package/dist/richtext/index.d.ts +15 -0
- package/dist/richtext/index.d.ts.map +1 -0
- package/dist/richtext/index.js +35 -0
- package/dist/richtext/index.js.map +1 -0
- package/dist/richtext/mention.d.ts +49 -0
- package/dist/richtext/mention.d.ts.map +1 -0
- package/dist/richtext/mention.js +179 -0
- package/dist/richtext/mention.js.map +1 -0
- package/dist/richtext/mention.test.d.ts +2 -0
- package/dist/richtext/mention.test.d.ts.map +1 -0
- package/dist/richtext/mention.test.js +123 -0
- package/dist/richtext/mention.test.js.map +1 -0
- package/dist/richtext/pipeline.d.ts +5 -0
- package/dist/richtext/pipeline.d.ts.map +1 -0
- package/dist/richtext/pipeline.js +199 -0
- package/dist/richtext/pipeline.js.map +1 -0
- package/dist/richtext/round-trip.test.d.ts +2 -0
- package/dist/richtext/round-trip.test.d.ts.map +1 -0
- package/dist/richtext/round-trip.test.js +105 -0
- package/dist/richtext/round-trip.test.js.map +1 -0
- package/dist/sdk-routes.d.ts.map +1 -1
- package/dist/sdk-routes.js +4 -0
- package/dist/sdk-routes.js.map +1 -1
- package/dist/tapd-client.d.ts +45 -0
- package/dist/tapd-client.d.ts.map +1 -1
- package/dist/tapd-client.js +74 -0
- package/dist/tapd-client.js.map +1 -1
- package/dist/tools/attachment.d.ts +47 -0
- package/dist/tools/attachment.d.ts.map +1 -1
- package/dist/tools/attachment.js +247 -1
- package/dist/tools/attachment.js.map +1 -1
- package/dist/tools/attachment.test.d.ts +2 -0
- package/dist/tools/attachment.test.d.ts.map +1 -0
- package/dist/tools/attachment.test.js +432 -0
- package/dist/tools/attachment.test.js.map +1 -0
- package/dist/tools/bug.d.ts.map +1 -1
- package/dist/tools/bug.js +3 -0
- package/dist/tools/bug.js.map +1 -1
- package/dist/tools/comment.d.ts.map +1 -1
- package/dist/tools/comment.js +3 -0
- package/dist/tools/comment.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/media.d.ts +9 -0
- package/dist/tools/media.d.ts.map +1 -0
- package/dist/tools/media.js +45 -0
- package/dist/tools/media.js.map +1 -0
- package/dist/tools/richtext-wiring.test.d.ts +2 -0
- package/dist/tools/richtext-wiring.test.d.ts.map +1 -0
- package/dist/tools/richtext-wiring.test.js +302 -0
- package/dist/tools/richtext-wiring.test.js.map +1 -0
- package/dist/tools/story.d.ts.map +1 -1
- package/dist/tools/story.js +3 -0
- package/dist/tools/story.js.map +1 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +3 -0
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/utility.d.ts.map +1 -1
- package/dist/tools/utility.js +133 -12
- package/dist/tools/utility.js.map +1 -1
- package/dist/tools/wiki.d.ts.map +1 -1
- package/dist/tools/wiki.js +20 -5
- package/dist/tools/wiki.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -5
- package/dist/tools/zz-acceptance-echo.d.ts +0 -3
- package/dist/tools/zz-acceptance-echo.d.ts.map +0 -1
- package/dist/tools/zz-acceptance-echo.js +0 -9
- package/dist/tools/zz-acceptance-echo.js.map +0 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// mention 语义单测:生成/解析/扫描/边界误伤(FSD §8)/双向镜像。tsx 直跑,不进 package.json test 脚本(接线归集成者)。
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { atWhoNodeToMd, escapeAtLiteralsForMd, findMentions, isAtWhoNode, isAtWhoTag, mentionHtmlToMd, mentionMdToHtml, renderMentionHtml, } from './mention.js';
|
|
4
|
+
const AT_WHO_XUZHAO = '<b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b>';
|
|
5
|
+
const checks = [
|
|
6
|
+
['renderMentionHtml: 逐字段复刻 at-who 模板(e2e 金样)', () => {
|
|
7
|
+
assert.equal(renderMentionHtml('徐昭'), AT_WHO_XUZHAO);
|
|
8
|
+
}],
|
|
9
|
+
['renderMentionHtml: 昵称含 HTML 特殊字符分别转义 attr/文本位', () => {
|
|
10
|
+
const html = renderMentionHtml('a"b<c>');
|
|
11
|
+
assert.ok(html.includes('data-userid="a"b<c>"'));
|
|
12
|
+
assert.ok(html.endsWith('>@a"b<c></b>'));
|
|
13
|
+
assert.ok(html.startsWith('<b class="at-who" contenteditable="false" '));
|
|
14
|
+
}],
|
|
15
|
+
['renderMentionHtml: 空昵称抛 TypeError', () => {
|
|
16
|
+
assert.throws(() => renderMentionHtml(''), TypeError);
|
|
17
|
+
}],
|
|
18
|
+
['findMentions: 基本命中与区间索引', () => {
|
|
19
|
+
assert.deepEqual(findMentions('请 @徐昭 尽快确认'), [{ name: '徐昭', start: 2, end: 5 }]);
|
|
20
|
+
assert.deepEqual(findMentions('no mention here'), []);
|
|
21
|
+
}],
|
|
22
|
+
['findMentions: 右边界贪吃 + 多命中', () => {
|
|
23
|
+
assert.deepEqual(findMentions('@bob_dev.'), [{ name: 'bob_dev', start: 0, end: 8 }]);
|
|
24
|
+
assert.deepEqual(findMentions('@徐昭,你好'), [{ name: '徐昭', start: 0, end: 3 }]);
|
|
25
|
+
assert.deepEqual(findMentions('找 @张三 和 @Alice_1 一起'), [
|
|
26
|
+
{ name: '张三', start: 2, end: 5 },
|
|
27
|
+
{ name: 'Alice_1', start: 8, end: 16 },
|
|
28
|
+
]);
|
|
29
|
+
}],
|
|
30
|
+
['findMentions: FSD §8 误伤对策——邮箱/工单号/紧贴中文不命中', () => {
|
|
31
|
+
assert.deepEqual(findMentions('联系 user@example.com 获取'), []);
|
|
32
|
+
assert.deepEqual(findMentions('工单号 T-100@QA 由 support@test 处理'), []);
|
|
33
|
+
assert.deepEqual(findMentions('联系@徐昭 谢谢'), []); // @ 前紧贴中文(保守侧不标记;R4 服务端仍识别)
|
|
34
|
+
assert.deepEqual(findMentions('版本 v1.2@release'), []);
|
|
35
|
+
}],
|
|
36
|
+
['findMentions: @@ 转义与孤立 @ 不命中', () => {
|
|
37
|
+
assert.deepEqual(findMentions('@@徐昭'), []);
|
|
38
|
+
assert.deepEqual(findMentions('行尾单独 @ 保持'), []);
|
|
39
|
+
assert.deepEqual(findMentions('@'), []);
|
|
40
|
+
assert.deepEqual(findMentions('@ 上路'), []);
|
|
41
|
+
}],
|
|
42
|
+
['findMentions: 数字昵称与起始边界', () => {
|
|
43
|
+
assert.deepEqual(findMentions('@123 上线'), [{ name: '123', start: 0, end: 4 }]);
|
|
44
|
+
assert.deepEqual(findMentions('( @张三 )'), [{ name: '张三', start: 2, end: 5 }]);
|
|
45
|
+
}],
|
|
46
|
+
['mentionMdToHtml: 昵称 → at-who,其余原样', () => {
|
|
47
|
+
assert.equal(mentionMdToHtml('请 @徐昭 今天内确认'), `请 ${AT_WHO_XUZHAO} 今天内确认`);
|
|
48
|
+
}],
|
|
49
|
+
['mentionMdToHtml: @@ → 字面 @ 不触发 mention;@@@ 组合行为', () => {
|
|
50
|
+
assert.equal(mentionMdToHtml('@@徐昭'), '@徐昭');
|
|
51
|
+
assert.equal(mentionMdToHtml('@@@徐昭'), `@${AT_WHO_XUZHAO}`);
|
|
52
|
+
assert.equal(mentionMdToHtml('@@@@'), '@@');
|
|
53
|
+
}],
|
|
54
|
+
['mentionMdToHtml: 疑似 html 标签区域 @ 语义跳过(透传)', () => {
|
|
55
|
+
assert.equal(mentionMdToHtml('a <b>x@y</b> @徐昭'), `a <b>x@y</b> ${AT_WHO_XUZHAO}`);
|
|
56
|
+
}],
|
|
57
|
+
['mentionHtmlToMd: at-who → @昵称,禁止降级 **粗体**(R7 回归)', () => {
|
|
58
|
+
const md = mentionHtmlToMd(`<p>${AT_WHO_XUZHAO} 请查收</p>`);
|
|
59
|
+
assert.equal(md, '<p>@徐昭 请查收</p>');
|
|
60
|
+
assert.ok(!md.includes('**'));
|
|
61
|
+
}],
|
|
62
|
+
['mentionHtmlToMd: 非 at-who 的 <b> 原样(不吞不转)', () => {
|
|
63
|
+
assert.equal(mentionHtmlToMd('<b>user@x</b>'), '<b>user@x</b>');
|
|
64
|
+
}],
|
|
65
|
+
['mentionHtmlToMd: 文本字面 @ 按 mention 规则保护为 @@;不命中者原样', () => {
|
|
66
|
+
assert.equal(mentionHtmlToMd('<p>@x user@y.com @@z</p>'), '<p>@@x user@y.com @@@@z</p>');
|
|
67
|
+
}],
|
|
68
|
+
['escapeAtLiteralsForMd: 与 md 方向规则严格镜像', () => {
|
|
69
|
+
assert.equal(escapeAtLiteralsForMd('@徐昭'), '@@徐昭');
|
|
70
|
+
assert.equal(escapeAtLiteralsForMd('user@example.com'), 'user@example.com');
|
|
71
|
+
assert.equal(escapeAtLiteralsForMd('@@'), '@@@@');
|
|
72
|
+
}],
|
|
73
|
+
['isAtWhoTag / 节点辅助: class 识别与 data-userid 优先', () => {
|
|
74
|
+
assert.equal(isAtWhoTag(AT_WHO_XUZHAO), true);
|
|
75
|
+
assert.equal(isAtWhoTag('<b class="tapd-editor__bold">@徐昭</b>'), false);
|
|
76
|
+
const node = {
|
|
77
|
+
nodeName: 'B',
|
|
78
|
+
getAttribute: (name) => (name === 'class' ? 'at-who' : name === 'data-userid' ? '徐昭' : null),
|
|
79
|
+
};
|
|
80
|
+
assert.equal(isAtWhoNode(node), true);
|
|
81
|
+
assert.equal(atWhoNodeToMd(node), '@徐昭');
|
|
82
|
+
}],
|
|
83
|
+
['节点辅助: data-userid 缺失回退文本', () => {
|
|
84
|
+
const node = {
|
|
85
|
+
nodeName: 'b',
|
|
86
|
+
getAttribute: (name) => (name === 'class' ? 'at-who' : null),
|
|
87
|
+
textContent: '@李四',
|
|
88
|
+
};
|
|
89
|
+
assert.equal(isAtWhoNode(node), true);
|
|
90
|
+
assert.equal(atWhoNodeToMd(node), '@李四');
|
|
91
|
+
}],
|
|
92
|
+
['mention 双向镜像: md→html→md 恒等(多边界样本)', () => {
|
|
93
|
+
const samples = [
|
|
94
|
+
'请 @徐昭 今天内确认,抄送 @bob_dev。',
|
|
95
|
+
'@张三',
|
|
96
|
+
'user@example.com 与 T-100@QA 与 @@李四 与 @',
|
|
97
|
+
'(@王五)结束。',
|
|
98
|
+
];
|
|
99
|
+
for (const md of samples) {
|
|
100
|
+
assert.equal(mentionHtmlToMd(mentionMdToHtml(md)), md);
|
|
101
|
+
}
|
|
102
|
+
}],
|
|
103
|
+
];
|
|
104
|
+
let failed = 0;
|
|
105
|
+
for (const [name, check] of checks) {
|
|
106
|
+
try {
|
|
107
|
+
check();
|
|
108
|
+
console.log(`PASS ${name}`);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
failed += 1;
|
|
112
|
+
console.error(`FAIL ${name}`);
|
|
113
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (failed > 0) {
|
|
117
|
+
console.error(`\n${failed}/${checks.length} failed`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(`\n${checks.length}/${checks.length} passed`);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=mention.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mention.test.js","sourceRoot":"","sources":["../../src/richtext/mention.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** md → TAPD 富文本 html(@昵称/@@转义/attach: 附件引用/图片与普通 md 全兼容)。 */
|
|
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;
|
|
5
|
+
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"AAoLA,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AA+BD,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// TAPD 2.0 rc.2 P3 接线(需求 1139814312001001548)—— 富文本保真转换主管道。
|
|
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
|
+
}
|
|
199
|
+
//# sourceMappingURL=pipeline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/richtext/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round-trip.test.d.ts","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// round-trip 契约测试:六类样本(@/附件/链接/图片/混合/纯文本)md→html→md 与原文等价(空白规约内)。
|
|
2
|
+
// 只覆盖纯函数语义层(本目录 fragment 函数),不经过工具/注册表转换主链路(接线归 Wave 2)。
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { htmlToMdFragment, mdToHtmlFragment } from './index.js';
|
|
5
|
+
const WS = '39814312';
|
|
6
|
+
const AT = (name) => `<b class="at-who" contenteditable="false" data-userid="${name}" data-type="user">@${name}</b>`;
|
|
7
|
+
const ANCHOR = (id, name, fileType) => `<a data-is-tapd-attachment="true" data-can-preview="true" data-file-type="${fileType}" ` +
|
|
8
|
+
`data-name="${name}" target="_blank" rel="noopener" ` +
|
|
9
|
+
`href="/${WS}/attachments/preview_attachments/${id}/story_description_attachment">${name}</a>`;
|
|
10
|
+
// 空白规约:允许转换层的空白差异(本层本不应产生,防御性归一)
|
|
11
|
+
const normalize = (value) => value.replace(/\s+/g, ' ').trim();
|
|
12
|
+
const SAMPLES = [
|
|
13
|
+
{
|
|
14
|
+
name: '样本1 @(中英文昵称、CJK 标点边界)',
|
|
15
|
+
md: '请 @徐昭 今天内确认,备份联系 @bob_dev。',
|
|
16
|
+
html: `请 ${AT('徐昭')} 今天内确认,备份联系 ${AT('bob_dev')}。`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: '样本2 附件(pdf/text/other 映射)',
|
|
20
|
+
md: `评审材料 [📎 接口设计.pdf](attach:${WS}/1139814312001000001),日志 [📎 app.log](attach:${WS}/1139814312001000002)`,
|
|
21
|
+
html: `评审材料 ${ANCHOR('1139814312001000001', '接口设计.pdf', 'pdf')},日志 ${ANCHOR('1139814312001000002', 'app.log', 'other')}`,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: '样本3 链接(语义层零干预透传,基础转换器职责)',
|
|
25
|
+
md: `需求详情见 [TAPD 1139814312001001548](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548)`,
|
|
26
|
+
html: `需求详情见 [TAPD 1139814312001001548](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548)`,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: '样本4 图片(/tfl/ 远端路径,语义层零干预透传)',
|
|
30
|
+
md: '效果图 ',
|
|
31
|
+
html: '效果图 ',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: '样本5 混合(@ + @@转义 + 附件@名 + 图片 + 链接 + 粗体)',
|
|
35
|
+
md: [
|
|
36
|
+
'# rc.2 评审',
|
|
37
|
+
'',
|
|
38
|
+
'负责人 @徐昭,抄送 @@everyone(字面量)。',
|
|
39
|
+
`材料 [📎 报表@v2.txt](attach:${WS}/1139814312001000003),截图 。`,
|
|
40
|
+
`线索 [需求](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548) 与 **加粗**。`,
|
|
41
|
+
].join('\n'),
|
|
42
|
+
html: [
|
|
43
|
+
'# rc.2 评审',
|
|
44
|
+
'',
|
|
45
|
+
`负责人 ${AT('徐昭')},抄送 @everyone(字面量)。`,
|
|
46
|
+
`材料 ${ANCHOR('1139814312001000003', '报表@v2.txt', 'text')},截图 。`,
|
|
47
|
+
`线索 [需求](https://www.tapd.cn/${WS}/prong/stories/view/1139814312001001548) 与 **加粗**。`,
|
|
48
|
+
].join('\n'),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: '样本6 纯文本(邮箱/工单号/孤立 @ 不误伤,@@转义不触发 mention,FSD §8)',
|
|
52
|
+
md: '版本 v1.2 已发布。联系邮箱 user@example.com;工单号 T-100@QA 由 support@test 处理;单独的 @ 保持原样,转义 @@support 也不触发 mention。',
|
|
53
|
+
html: '版本 v1.2 已发布。联系邮箱 user@example.com;工单号 T-100@QA 由 support@test 处理;单独的 @ 保持原样,转义 @support 也不触发 mention。',
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
const checks = [
|
|
57
|
+
...SAMPLES.map((sample) => [
|
|
58
|
+
`md→html: ${sample.name}`,
|
|
59
|
+
() => {
|
|
60
|
+
assert.equal(normalize(mdToHtmlFragment(sample.md)), normalize(sample.html));
|
|
61
|
+
},
|
|
62
|
+
]),
|
|
63
|
+
...SAMPLES.map((sample) => [
|
|
64
|
+
`html→md: ${sample.name}`,
|
|
65
|
+
() => {
|
|
66
|
+
assert.equal(normalize(htmlToMdFragment(sample.html)), normalize(sample.md));
|
|
67
|
+
},
|
|
68
|
+
]),
|
|
69
|
+
...SAMPLES.map((sample) => [
|
|
70
|
+
`round-trip: ${sample.name}`,
|
|
71
|
+
() => {
|
|
72
|
+
assert.equal(normalize(htmlToMdFragment(mdToHtmlFragment(sample.md))), normalize(sample.md));
|
|
73
|
+
// 二次往返幂等(转换稳定点)
|
|
74
|
+
const once = htmlToMdFragment(mdToHtmlFragment(sample.md));
|
|
75
|
+
assert.equal(normalize(htmlToMdFragment(mdToHtmlFragment(once))), normalize(once));
|
|
76
|
+
},
|
|
77
|
+
]),
|
|
78
|
+
['R7 回归: @昵称 产出 at-who 标记而非降级形态', () => {
|
|
79
|
+
const html = mdToHtmlFragment('呼叫 @徐昭');
|
|
80
|
+
assert.ok(html.includes('<b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b>'));
|
|
81
|
+
const md = htmlToMdFragment('<p><b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b></p>');
|
|
82
|
+
assert.equal(md, '<p>@徐昭</p>');
|
|
83
|
+
assert.ok(!md.includes('**'));
|
|
84
|
+
}],
|
|
85
|
+
];
|
|
86
|
+
let failed = 0;
|
|
87
|
+
for (const [name, check] of checks) {
|
|
88
|
+
try {
|
|
89
|
+
check();
|
|
90
|
+
console.log(`PASS ${name}`);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
failed += 1;
|
|
94
|
+
console.error(`FAIL ${name}`);
|
|
95
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (failed > 0) {
|
|
99
|
+
console.error(`\n${failed}/${checks.length} failed`);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(`\n${checks.length}/${checks.length} passed`);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=round-trip.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round-trip.test.js","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":"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"}
|
package/dist/sdk-routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-routes.d.ts","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"sdk-routes.d.ts","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,GAAG,KAAK,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAqLxD,CAAC"}
|
package/dist/sdk-routes.js
CHANGED
|
@@ -8,6 +8,10 @@ export const SDK_ROUTES = {
|
|
|
8
8
|
// attachments
|
|
9
9
|
'GET /attachments': 'getAttachments',
|
|
10
10
|
'GET /attachments/down': 'downloadAttachment',
|
|
11
|
+
'POST /open_app_obj_attachment': 'addOpenAppObjAttachment',
|
|
12
|
+
// files (upload channel — multipart via TapdClient.postFile)
|
|
13
|
+
'POST /files/upload_attachment': 'uploadAttachment',
|
|
14
|
+
'POST /files/upload_image': 'uploadImage',
|
|
11
15
|
// board-cards
|
|
12
16
|
'GET /board_cards': 'getBoardCards',
|
|
13
17
|
// board-columns
|
package/dist/sdk-routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-routes.js","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAsC;IAC3D,cAAc;IACd,kBAAkB,EAAE,gBAAgB;IACpC,uBAAuB,EAAE,oBAAoB;
|
|
1
|
+
{"version":3,"file":"sdk-routes.js","sourceRoot":"","sources":["../src/sdk-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/tapd-client.d.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TAPD API Client
|
|
3
|
+
*
|
|
4
|
+
* Supports dual authentication modes:
|
|
5
|
+
* - Basic Auth: using api_user + api_password
|
|
6
|
+
* - OAuth/Bearer Token: using access_token
|
|
7
|
+
*
|
|
8
|
+
* Routes requests through @opentapd/tapd-node-sdk when the method+path is
|
|
9
|
+
* listed in SDK_ROUTES, otherwise falls back to the custom fetch client.
|
|
10
|
+
* - GET SDK failures fall back to fetch (read-only, idempotent)
|
|
11
|
+
* - POST SDK failures throw directly (never fallback, avoid duplicate writes)
|
|
12
|
+
* - Set TAPD_SDK_DISABLED=1 to bypass the SDK entirely (rollback switch)
|
|
13
|
+
*/
|
|
14
|
+
import { ReadStream } from 'node:fs';
|
|
15
|
+
/**
|
|
16
|
+
* A param value accepted by the multipart upload channel (TapdClient.postFile).
|
|
17
|
+
* The file itself MUST be an fs.ReadStream (i.e. fs.createReadStream):
|
|
18
|
+
* the SDK only switches to multipart/form-data for ReadStream/FILE values
|
|
19
|
+
* (node-sdk/src/sdk.js:159) — Buffers / Readable.from() silently degrade to
|
|
20
|
+
* plain form fields.
|
|
21
|
+
*/
|
|
22
|
+
export type FileUploadParam = string | number | boolean | undefined | ReadStream;
|
|
1
23
|
export declare class TapdClient {
|
|
2
24
|
private authHeader;
|
|
3
25
|
private baseUrl;
|
|
@@ -87,6 +109,29 @@ export declare class TapdClient {
|
|
|
87
109
|
* POST request to TAPD API
|
|
88
110
|
*/
|
|
89
111
|
post<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T>;
|
|
112
|
+
/**
|
|
113
|
+
* POST a multipart (file upload) request — the streaming channel for upload
|
|
114
|
+
* endpoints (`/files/upload_attachment`, `/files/upload_image`, ...).
|
|
115
|
+
*
|
|
116
|
+
* Keeps registry.exec the single execution path (FSD §2 ruling: streaming
|
|
117
|
+
* calls never bypass TapdClient, so audit / timeout / read-only gating and
|
|
118
|
+
* auth stay unified).
|
|
119
|
+
*
|
|
120
|
+
* Routing mirrors route() POST semantics:
|
|
121
|
+
* - When `POST <path>` is mapped in SDK_ROUTES and the SDK is enabled, the
|
|
122
|
+
* request goes through the SDK, which builds multipart/form-data when any
|
|
123
|
+
* param value is an fs.ReadStream (node-sdk/src/sdk.js:159).
|
|
124
|
+
* - POST failures throw directly (never fall back / retry — a flaky network
|
|
125
|
+
* must not duplicate an upload).
|
|
126
|
+
* - Without an SDK route (or with TAPD_SDK_DISABLED=1 rollback switch on),
|
|
127
|
+
* falls back to a fetch-based multipart upload (Node 20+ global FormData).
|
|
128
|
+
*/
|
|
129
|
+
postFile<T>(path: string, params: Record<string, FileUploadParam>): Promise<T>;
|
|
130
|
+
/**
|
|
131
|
+
* fetch-based multipart fallback (no SDK route / SDK disabled). Node 20+
|
|
132
|
+
* global fetch serializes FormData bodies (boundary included) automatically.
|
|
133
|
+
*/
|
|
134
|
+
private postMultipartViaFetch;
|
|
90
135
|
}
|
|
91
136
|
/**
|
|
92
137
|
* TAPD API response structure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tapd-client.d.ts","sourceRoot":"","sources":["../src/tapd-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tapd-client.d.ts","sourceRoot":"","sources":["../src/tapd-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAMrC;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAEjF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,OAAO,CAAC,CAAS;IACzB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAC,CAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwC;IAEhE,OAAO;IAgBP;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAIzE;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU;IAWxF;;;OAGG;IACH,MAAM,CAAC,OAAO,IAAI,UAAU;IAmB5B;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,MAAM,GAAG,SAAS;IAIxC;;OAEG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,GAAG,SAAS;IAKlD;;OAEG;IACH,MAAM,CAAC,6BAA6B,IAAI,MAAM,GAAG,SAAS;IAI1D;;OAEG;IACH,MAAM,CAAC,4BAA4B,IAAI,MAAM,GAAG,SAAS;IAIzD;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,UAAO,GAAG,MAAM;IAc1F;;;;;;;KAOC;IACD;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAe3B,OAAO,CAAC,MAAM;IAQd;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAOd;;;OAGG;YACW,KAAK;IAyBb,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAC7D,OAAO,CAAC,CAAC,CAAC;IAwEb;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAI1F;;OAEG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIvG;;;;;;;;;;;;;;;;OAgBG;IACG,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAiBpF;;;OAGG;YACW,qBAAqB;CAgCpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE,MAAM,CAAC;CACd"}
|