@xihe-lab/tapd-core 2.0.0-rc.2 → 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.
Files changed (119) hide show
  1. package/README.md +1 -0
  2. package/dist/advisor/index.d.ts.map +1 -1
  3. package/dist/advisor/index.js +2 -0
  4. package/dist/advisor/index.js.map +1 -1
  5. package/dist/cache/read-cache.d.ts +136 -0
  6. package/dist/cache/read-cache.d.ts.map +1 -0
  7. package/dist/cache/read-cache.js +322 -0
  8. package/dist/cache/read-cache.js.map +1 -0
  9. package/dist/cache/read-cache.test.d.ts +2 -0
  10. package/dist/cache/read-cache.test.d.ts.map +1 -0
  11. package/dist/cache/read-cache.test.js +429 -0
  12. package/dist/cache/read-cache.test.js.map +1 -0
  13. package/dist/errors/index.d.ts +120 -0
  14. package/dist/errors/index.d.ts.map +1 -0
  15. package/dist/errors/index.js +429 -0
  16. package/dist/errors/index.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/registry/registry.d.ts +7 -0
  22. package/dist/registry/registry.d.ts.map +1 -1
  23. package/dist/registry/registry.js +9 -3
  24. package/dist/registry/registry.js.map +1 -1
  25. package/dist/registry/registry.test.js +293 -2
  26. package/dist/registry/registry.test.js.map +1 -1
  27. package/dist/richtext/index.d.ts +1 -14
  28. package/dist/richtext/index.d.ts.map +1 -1
  29. package/dist/richtext/index.js +3 -34
  30. package/dist/richtext/index.js.map +1 -1
  31. package/dist/richtext/pipeline.d.ts +1 -4
  32. package/dist/richtext/pipeline.d.ts.map +1 -1
  33. package/dist/richtext/pipeline.js +2 -198
  34. package/dist/richtext/pipeline.js.map +1 -1
  35. package/dist/tapd-client.d.ts +34 -2
  36. package/dist/tapd-client.d.ts.map +1 -1
  37. package/dist/tapd-client.js +121 -42
  38. package/dist/tapd-client.js.map +1 -1
  39. package/dist/tools/attachment.js +40 -40
  40. package/dist/tools/attachment.js.map +1 -1
  41. package/dist/tools/board.js +22 -22
  42. package/dist/tools/board.js.map +1 -1
  43. package/dist/tools/bug.js +259 -259
  44. package/dist/tools/bug.js.map +1 -1
  45. package/dist/tools/changes.js +20 -20
  46. package/dist/tools/changes.js.map +1 -1
  47. package/dist/tools/comment.js +23 -23
  48. package/dist/tools/comment.js.map +1 -1
  49. package/dist/tools/custom-fields.js +16 -16
  50. package/dist/tools/custom-fields.js.map +1 -1
  51. package/dist/tools/image.js +2 -2
  52. package/dist/tools/image.js.map +1 -1
  53. package/dist/tools/iteration.js +50 -50
  54. package/dist/tools/iteration.js.map +1 -1
  55. package/dist/tools/media.js +2 -2
  56. package/dist/tools/media.js.map +1 -1
  57. package/dist/tools/mini-comment.js +27 -27
  58. package/dist/tools/mini-comment.js.map +1 -1
  59. package/dist/tools/mini-item.js +83 -83
  60. package/dist/tools/mini-item.js.map +1 -1
  61. package/dist/tools/mini-workspace.js +5 -5
  62. package/dist/tools/mini-workspace.js.map +1 -1
  63. package/dist/tools/program.js +7 -7
  64. package/dist/tools/program.js.map +1 -1
  65. package/dist/tools/relations.js +14 -14
  66. package/dist/tools/relations.js.map +1 -1
  67. package/dist/tools/release.js +53 -53
  68. package/dist/tools/release.js.map +1 -1
  69. package/dist/tools/report.js +8 -8
  70. package/dist/tools/report.js.map +1 -1
  71. package/dist/tools/settings.js +77 -77
  72. package/dist/tools/settings.js.map +1 -1
  73. package/dist/tools/source.js +14 -14
  74. package/dist/tools/source.js.map +1 -1
  75. package/dist/tools/story.js +120 -120
  76. package/dist/tools/story.js.map +1 -1
  77. package/dist/tools/task.js +123 -123
  78. package/dist/tools/task.js.map +1 -1
  79. package/dist/tools/test.js +117 -117
  80. package/dist/tools/test.js.map +1 -1
  81. package/dist/tools/timesheet.js +23 -23
  82. package/dist/tools/timesheet.js.map +1 -1
  83. package/dist/tools/user.js +8 -8
  84. package/dist/tools/user.js.map +1 -1
  85. package/dist/tools/utility.d.ts +17 -0
  86. package/dist/tools/utility.d.ts.map +1 -1
  87. package/dist/tools/utility.js +73 -78
  88. package/dist/tools/utility.js.map +1 -1
  89. package/dist/tools/wiki.js +51 -51
  90. package/dist/tools/wiki.js.map +1 -1
  91. package/dist/tools/workflow.js +26 -26
  92. package/dist/tools/workflow.js.map +1 -1
  93. package/dist/tools/workspace.js +16 -16
  94. package/dist/tools/workspace.js.map +1 -1
  95. package/package.json +10 -14
  96. package/dist/richtext/attachment-anchor.d.ts +0 -58
  97. package/dist/richtext/attachment-anchor.d.ts.map +0 -1
  98. package/dist/richtext/attachment-anchor.js +0 -221
  99. package/dist/richtext/attachment-anchor.js.map +0 -1
  100. package/dist/richtext/attachment-anchor.test.d.ts +0 -2
  101. package/dist/richtext/attachment-anchor.test.d.ts.map +0 -1
  102. package/dist/richtext/attachment-anchor.test.js +0 -149
  103. package/dist/richtext/attachment-anchor.test.js.map +0 -1
  104. package/dist/richtext/escape.d.ts +0 -7
  105. package/dist/richtext/escape.d.ts.map +0 -1
  106. package/dist/richtext/escape.js +0 -60
  107. package/dist/richtext/escape.js.map +0 -1
  108. package/dist/richtext/mention.d.ts +0 -49
  109. package/dist/richtext/mention.d.ts.map +0 -1
  110. package/dist/richtext/mention.js +0 -179
  111. package/dist/richtext/mention.js.map +0 -1
  112. package/dist/richtext/mention.test.d.ts +0 -2
  113. package/dist/richtext/mention.test.d.ts.map +0 -1
  114. package/dist/richtext/mention.test.js +0 -123
  115. package/dist/richtext/mention.test.js.map +0 -1
  116. package/dist/richtext/round-trip.test.d.ts +0 -2
  117. package/dist/richtext/round-trip.test.d.ts.map +0 -1
  118. package/dist/richtext/round-trip.test.js +0 -105
  119. package/dist/richtext/round-trip.test.js.map +0 -1
@@ -1,179 +0,0 @@
1
- // TAPD 2.0 rc.2 P3(需求 1139814312001001548)—— @昵称 ↔ at-who 标记双向语义(纯函数层)。
2
- // 设计依据:FSD §3.5 / §8;e2e 金样 test/e2e-rc2/poc-rc2-mention.mjs(保真实证)。
3
- //
4
- // HTML 形态(逐字段定稿,属性顺序勿改——data-userid 存昵称而非数字 uid):
5
- // <b class="at-who" contenteditable="false" data-userid="徐昭" data-type="user">@徐昭</b>
6
- //
7
- // md 形态:`@昵称`;转义语法:`@@` → 字面 `@`(不触发 mention)。
8
- //
9
- // 昵称文法(FSD §8 误伤对策:仅匹配「@ + 中文名/英文名 + 边界」):
10
- // name-char ::= Han 字符 | [A-Za-z0-9_]
11
- // mention ::= left-boundary '@' name-char{1,}
12
- // left-boundary ::= 文本起始 | 非 name-char
13
- // 名字 greedily 吃满 name-char,天然获得右边界(空白/标点/行尾)。
14
- // 左边界要求 @ 前不能紧跟 name-char —— 邮箱 user@example.com、工单号 T-100@QA 因此不命中;
15
- // 代价是中文紧贴写法(联系@徐昭)不生成 at-who,但 R4 实证服务端对纯文本 @昵称 仍智能识别,欠标记无害,
16
- // 过标记(把邮箱拆成 at-who)才有害,故取保守侧。
17
- //
18
- // 分层约定(Wave 2 接线必读):
19
- // - 本文件是纯函数语义层,零 IO、零 SDK 依赖,不感知 md 代码段/链接 URL 等 md 语法;
20
- // `@` 出现在代码段或 URL 内的场景必须在 markdown-it/turndown 的 token 级接线时规避
21
- // (本层提供的 fragment 级函数只做语义 token 替换,其余内容原样透传)。
22
- // - html→md 方向:at-who 一律还原为 `@昵称`,禁止降级为 `**粗体**`(R7 实测破坏点)。
23
- import { decodeHtmlEntities, escapeHtmlAttr, escapeHtmlText } from './escape.js';
24
- const NAME_CHAR_RE = /[\p{Script=Han}A-Za-z0-9_]/u;
25
- function isNameChar(ch) {
26
- return ch !== undefined && NAME_CHAR_RE.test(ch);
27
- }
28
- /** 生成 at-who 标记(昵称含 HTML 特殊字符时按属性/文本位分别转义)。 */
29
- export function renderMentionHtml(name) {
30
- if (!name)
31
- throw new TypeError('mention name must be a non-empty string');
32
- return (`<b class="at-who" contenteditable="false" data-userid="${escapeHtmlAttr(name)}" data-type="user">` +
33
- `@${escapeHtmlText(name)}</b>`);
34
- }
35
- /**
36
- * 单遍扫描文本中的 @ 语义 token:
37
- * - `@@` 相邻对 → escape(md 侧表示一个字面 @)
38
- * - 通过左边界 + 名字规则的 `@名字` → mention
39
- * - 其余 `@` 不产出 token(由调用方原样处理)
40
- * 供 md→html 与 html→md 两个方向共用,保证双向规则严格镜像。
41
- */
42
- function scanAtTokens(text) {
43
- const tokens = [];
44
- let i = 0;
45
- while (i < text.length) {
46
- if (text[i] !== '@') {
47
- i += 1;
48
- continue;
49
- }
50
- if (text[i + 1] === '@') {
51
- tokens.push({ kind: 'escape', start: i, end: i + 2 });
52
- i += 2;
53
- continue;
54
- }
55
- if (i > 0 && isNameChar(text[i - 1])) {
56
- i += 1;
57
- continue;
58
- }
59
- let j = i + 1;
60
- while (j < text.length && isNameChar(text[j]))
61
- j += 1;
62
- if (j > i + 1)
63
- tokens.push({ kind: 'mention', name: text.slice(i + 1, j), start: i, end: j });
64
- i = Math.max(j, i + 1);
65
- }
66
- return tokens;
67
- }
68
- /** 扫描文本中全部合法 mention(跳过 @@ 转义)。 */
69
- export function findMentions(text) {
70
- return scanAtTokens(text).flatMap((token) => token.kind === 'mention' ? [{ name: token.name, start: token.start, end: token.end }] : []);
71
- }
72
- /** md 文本片段中「疑似 html 标签」的区域:@ 语义跳过、原样透传(fragment 层不做 md 语法解析的边界)。 */
73
- const TAG_RE = /<\/?[a-zA-Z][^>]*>/g;
74
- function replaceAtSemantics(segment) {
75
- let out = '';
76
- let pos = 0;
77
- for (const token of scanAtTokens(segment)) {
78
- out += segment.slice(pos, token.start);
79
- out += token.kind === 'escape' ? '@' : renderMentionHtml(token.name);
80
- pos = token.end;
81
- }
82
- return out + segment.slice(pos);
83
- }
84
- /**
85
- * md 文本 → html 语义片段:`@@` → 字面 `@`,合法 `@昵称` → at-who 标记;
86
- * 其余内容(含疑似 html 标签区域)原样透传,交给基础转换器(markdown-it)完成。
87
- * 注意:本函数不做代码段/链接 URL 等 md 语法识别(见文件头分层约定)。
88
- */
89
- export function mentionMdToHtml(md) {
90
- let out = '';
91
- let pos = 0;
92
- TAG_RE.lastIndex = 0;
93
- for (;;) {
94
- const match = TAG_RE.exec(md);
95
- const segmentEnd = match ? match.index : md.length;
96
- out += replaceAtSemantics(md.slice(pos, segmentEnd));
97
- if (!match)
98
- break;
99
- out += match[0];
100
- pos = segmentEnd + match[0].length;
101
- }
102
- return out;
103
- }
104
- /**
105
- * html 文本 → md 文本:会把命中 mention 规则的字面 `@名字` 加倍为 `@@名字`、
106
- * `@@` 字面对加倍为 `@@@@`,使 md→html 重转换后语义不变(round-trip 稳定);
107
- * 不命中规则的 `@`(如邮箱、工单号)原样保留,避免无谓转义噪音。
108
- * 与 md→html 的 scanAtTokens 共用同一套规则,双向严格镜像。
109
- */
110
- export function escapeAtLiteralsForMd(text) {
111
- let out = '';
112
- let pos = 0;
113
- for (const token of scanAtTokens(text)) {
114
- out += text.slice(pos, token.start);
115
- out += token.kind === 'escape' ? '@@@@' : `@@${token.name}`;
116
- pos = token.end;
117
- }
118
- return out + text.slice(pos);
119
- }
120
- const B_TAG_RE = /<b\b[^>]*>([\s\S]*?)<\/b\s*>|<\/?[a-zA-Z][^>]*>/gi;
121
- const CLASS_ATTR_RE = /\bclass\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
122
- const USERID_ATTR_RE = /\bdata-userid\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
123
- function classListOf(openTag) {
124
- const match = CLASS_ATTR_RE.exec(openTag);
125
- if (!match)
126
- return [];
127
- return (match[1] ?? match[2] ?? '').split(/\s+/);
128
- }
129
- /** 判断一个标签串是否为 at-who 标记(`<b>` 且 class 含 at-who)。 */
130
- export function isAtWhoTag(tagHtml) {
131
- const openMatch = /^<b\b[^>]*>/i.exec(tagHtml);
132
- return openMatch !== null && classListOf(openMatch[0]).includes('at-who');
133
- }
134
- function nicknameFromTag(tagHtml, innerHtml) {
135
- const useridMatch = USERID_ATTR_RE.exec(tagHtml);
136
- if (useridMatch)
137
- return decodeHtmlEntities(useridMatch[1] ?? useridMatch[2] ?? '');
138
- const text = decodeHtmlEntities((innerHtml ?? '').replace(/<[^>]+>/g, '')).trim();
139
- return text.startsWith('@') ? text.slice(1) : text;
140
- }
141
- /**
142
- * html 片段 → md 语义片段:
143
- * - at-who 标记 → `@昵称`(data-userid 缺失时回退锚点内文本;禁止降级为 `**粗体**`)
144
- * - 其余标签原样透传(非 at-who 的 `<b>` 不动,交给基础转换器 turndown 处理)
145
- * - 标签之间的文本按 escapeAtLiteralsForMd 规则保护字面 @
146
- */
147
- export function mentionHtmlToMd(html) {
148
- let out = '';
149
- let pos = 0;
150
- let match;
151
- B_TAG_RE.lastIndex = 0;
152
- while ((match = B_TAG_RE.exec(html)) !== null) {
153
- out += escapeAtLiteralsForMd(html.slice(pos, match.index));
154
- const isAtWho = isAtWhoTag(match[0]);
155
- const nickname = isAtWho ? nicknameFromTag(match[0], match[1]) : '';
156
- out += nickname ? `@${nickname}` : match[0];
157
- pos = match.index + match[0].length;
158
- }
159
- out += escapeAtLiteralsForMd(html.slice(pos));
160
- return out;
161
- }
162
- /** turndown 规则 filter 用:节点是否为 at-who 标记。 */
163
- export function isAtWhoNode(node) {
164
- if (node.nodeName.toUpperCase() !== 'B')
165
- return false;
166
- return (node.getAttribute('class') ?? '').split(/\s+/).includes('at-who');
167
- }
168
- /**
169
- * turndown 规则 replacement 用:at-who 节点 → `@昵称`。
170
- * data-userid 优先;缺失时回退节点文本(去首 @)。永不产出 `**`。
171
- */
172
- export function atWhoNodeToMd(node) {
173
- const userid = node.getAttribute('data-userid');
174
- if (userid)
175
- return `@${userid}`;
176
- const text = (node.textContent ?? '').trim();
177
- return text.startsWith('@') ? text : `@${text}`;
178
- }
179
- //# sourceMappingURL=mention.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mention.js","sourceRoot":"","sources":["../../src/richtext/mention.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=mention.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mention.test.d.ts","sourceRoot":"","sources":["../../src/richtext/mention.test.ts"],"names":[],"mappings":""}
@@ -1,123 +0,0 @@
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&quot;b&lt;c&gt;"'));
12
- assert.ok(html.endsWith('>@a"b&lt;c&gt;</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
@@ -1 +0,0 @@
1
- {"version":3,"file":"mention.test.js","sourceRoot":"","sources":["../../src/richtext/mention.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=round-trip.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"round-trip.test.d.ts","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":""}
@@ -1,105 +0,0 @@
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: '效果图 ![登录页](/tfl/captures/2026/09/login.png)',
31
- html: '效果图 ![登录页](/tfl/captures/2026/09/login.png)',
32
- },
33
- {
34
- name: '样本5 混合(@ + @@转义 + 附件@名 + 图片 + 链接 + 粗体)',
35
- md: [
36
- '# rc.2 评审',
37
- '',
38
- '负责人 @徐昭,抄送 @@everyone(字面量)。',
39
- `材料 [📎 报表@v2.txt](attach:${WS}/1139814312001000003),截图 ![首页](/tfl/a.png)。`,
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')},截图 ![首页](/tfl/a.png)。`,
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"round-trip.test.js","sourceRoot":"","sources":["../../src/richtext/round-trip.test.ts"],"names":[],"mappings":"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"}