@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.
- package/README.md +1 -0
- package/dist/advisor/index.d.ts.map +1 -1
- package/dist/advisor/index.js +2 -0
- package/dist/advisor/index.js.map +1 -1
- package/dist/cache/read-cache.d.ts +136 -0
- package/dist/cache/read-cache.d.ts.map +1 -0
- package/dist/cache/read-cache.js +322 -0
- package/dist/cache/read-cache.js.map +1 -0
- package/dist/cache/read-cache.test.d.ts +2 -0
- package/dist/cache/read-cache.test.d.ts.map +1 -0
- package/dist/cache/read-cache.test.js +429 -0
- package/dist/cache/read-cache.test.js.map +1 -0
- package/dist/errors/index.d.ts +120 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +429 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/registry/registry.d.ts +7 -0
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +9 -3
- package/dist/registry/registry.js.map +1 -1
- package/dist/registry/registry.test.js +293 -2
- package/dist/registry/registry.test.js.map +1 -1
- 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/dist/tapd-client.d.ts +34 -2
- package/dist/tapd-client.d.ts.map +1 -1
- package/dist/tapd-client.js +121 -42
- package/dist/tapd-client.js.map +1 -1
- package/dist/tools/attachment.js +40 -40
- package/dist/tools/attachment.js.map +1 -1
- package/dist/tools/board.js +22 -22
- package/dist/tools/board.js.map +1 -1
- package/dist/tools/bug.js +259 -259
- package/dist/tools/bug.js.map +1 -1
- package/dist/tools/changes.js +20 -20
- package/dist/tools/changes.js.map +1 -1
- package/dist/tools/comment.js +23 -23
- package/dist/tools/comment.js.map +1 -1
- package/dist/tools/custom-fields.js +16 -16
- package/dist/tools/custom-fields.js.map +1 -1
- package/dist/tools/image.js +2 -2
- package/dist/tools/image.js.map +1 -1
- package/dist/tools/iteration.js +50 -50
- package/dist/tools/iteration.js.map +1 -1
- package/dist/tools/media.js +2 -2
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/mini-comment.js +27 -27
- package/dist/tools/mini-comment.js.map +1 -1
- package/dist/tools/mini-item.js +83 -83
- package/dist/tools/mini-item.js.map +1 -1
- package/dist/tools/mini-workspace.js +5 -5
- package/dist/tools/mini-workspace.js.map +1 -1
- package/dist/tools/program.js +7 -7
- package/dist/tools/program.js.map +1 -1
- package/dist/tools/relations.js +14 -14
- package/dist/tools/relations.js.map +1 -1
- package/dist/tools/release.js +53 -53
- package/dist/tools/release.js.map +1 -1
- package/dist/tools/report.js +8 -8
- package/dist/tools/report.js.map +1 -1
- package/dist/tools/settings.js +77 -77
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/source.js +14 -14
- package/dist/tools/source.js.map +1 -1
- package/dist/tools/story.js +120 -120
- package/dist/tools/story.js.map +1 -1
- package/dist/tools/task.js +123 -123
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/test.js +117 -117
- package/dist/tools/test.js.map +1 -1
- package/dist/tools/timesheet.js +23 -23
- package/dist/tools/timesheet.js.map +1 -1
- package/dist/tools/user.js +8 -8
- package/dist/tools/user.js.map +1 -1
- package/dist/tools/utility.d.ts +17 -0
- package/dist/tools/utility.d.ts.map +1 -1
- package/dist/tools/utility.js +73 -78
- package/dist/tools/utility.js.map +1 -1
- package/dist/tools/wiki.js +51 -51
- package/dist/tools/wiki.js.map +1 -1
- package/dist/tools/workflow.js +26 -26
- package/dist/tools/workflow.js.map +1 -1
- package/dist/tools/workspace.js +16 -16
- package/dist/tools/workspace.js.map +1 -1
- package/package.json +10 -14
- 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/mention.js
DELETED
|
@@ -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 +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"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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mention.test.js","sourceRoot":"","sources":["../../src/richtext/mention.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -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: '效果图 ',
|
|
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
|
|
@@ -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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"}
|