@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
|
@@ -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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-anchor.test.d.ts","sourceRoot":"","sources":["../../src/richtext/attachment-anchor.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
// attachment-anchor 语义单测:fileTypeFor 映射 / md 扩展语法 / R6 五件套模板 / 解析容错 / 不认识锚点原样保留。
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { ATTACH_SCHEME, DEFAULT_ATTACHMENT_SLUG, FILE_TYPE_BY_EXTENSION, attachmentAnchorHtml, attachmentAnchorMd, attachmentHtmlToMd, attachmentMdToHtml, fileTypeFor, findAttachmentAnchors, isAttachmentAnchorNode, attachmentAnchorNodeToMd, parseAttachmentAnchorHtml, parseAttachmentAnchorMd, } from './attachment-anchor.js';
|
|
4
|
-
const WS = '39814312';
|
|
5
|
-
const ID = '1139814312001000001';
|
|
6
|
-
// 完整编辑器模板(含 inner spans / type / class,e2e 金样 poc-rc2-file-embed.mjs 同构)
|
|
7
|
-
const EDITOR_STYLE_ANCHOR = `<a data-is-tapd-attachment="true" contenteditable="false" target="_blank" rel="noopener" ` +
|
|
8
|
-
`data-can-preview="true" data-file-type="text" type="text" data-name="comment-embed.txt" data-size="54" ` +
|
|
9
|
-
`href="/${WS}/attachments/preview_attachments/1139814312001000004/story_description_attachment" ` +
|
|
10
|
-
`class="tapd-editor__attachment-item"><span class="tapd-editor__attachment-item__left">` +
|
|
11
|
-
`<span class="tapd-editor__attachment-item__text-wrapper">` +
|
|
12
|
-
`<span class="tapd-editor__attachment-item__title">comment-embed.txt</span>` +
|
|
13
|
-
`<span class="tapd-editor__attachment-item__size">0.00MB</span></span></span></a>`;
|
|
14
|
-
const checks = [
|
|
15
|
-
['fileTypeFor: txt→text、png/jpg/jpeg→image、pdf→pdf、其他→other(大小写不敏感)', () => {
|
|
16
|
-
assert.equal(fileTypeFor('说明.txt'), 'text');
|
|
17
|
-
assert.equal(fileTypeFor('pic.PNG'), 'image');
|
|
18
|
-
assert.equal(fileTypeFor('a.jpg'), 'image');
|
|
19
|
-
assert.equal(fileTypeFor('a.jpeg'), 'image');
|
|
20
|
-
assert.equal(fileTypeFor('doc.pdf'), 'pdf');
|
|
21
|
-
assert.equal(fileTypeFor('app.log'), 'other');
|
|
22
|
-
assert.equal(fileTypeFor('noext'), 'other');
|
|
23
|
-
assert.equal(fileTypeFor('报表@v2.pdf'), 'pdf');
|
|
24
|
-
}],
|
|
25
|
-
['FILE_TYPE_BY_EXTENSION: 映射表导出供 Wave 2 复用', () => {
|
|
26
|
-
assert.equal(FILE_TYPE_BY_EXTENSION.txt, 'text');
|
|
27
|
-
assert.equal(FILE_TYPE_BY_EXTENSION.png, 'image');
|
|
28
|
-
assert.equal(FILE_TYPE_BY_EXTENSION.pdf, 'pdf');
|
|
29
|
-
assert.equal(ATTACH_SCHEME, 'attach:');
|
|
30
|
-
assert.equal(DEFAULT_ATTACHMENT_SLUG, 'story_description_attachment');
|
|
31
|
-
}],
|
|
32
|
-
['attachmentAnchorMd: md 扩展语法生成', () => {
|
|
33
|
-
assert.equal(attachmentAnchorMd({ workspaceId: WS, id: ID, name: '需求说明.txt' }), `[📎 需求说明.txt](attach:${WS}/${ID})`);
|
|
34
|
-
}],
|
|
35
|
-
['attachmentAnchorMd: 文件名中 []\\ 转义可逆;非法 ws/id 抛错', () => {
|
|
36
|
-
const md = attachmentAnchorMd({ workspaceId: WS, id: ID, name: 'a]b[ c\\d.txt' });
|
|
37
|
-
assert.equal(md, `[📎 a\\]b\\[ c\\\\d.txt](attach:${WS}/${ID})`);
|
|
38
|
-
const parsed = parseAttachmentAnchorMd(md);
|
|
39
|
-
assert.equal(parsed?.name, 'a]b[ c\\d.txt');
|
|
40
|
-
assert.throws(() => attachmentAnchorMd({ workspaceId: 'ws x', id: ID, name: 'f' }), TypeError);
|
|
41
|
-
assert.throws(() => attachmentAnchorMd({ workspaceId: WS, id: '', name: 'f' }), TypeError);
|
|
42
|
-
}],
|
|
43
|
-
['parseAttachmentAnchorMd: 非 attach: 链接与普通链接 → null', () => {
|
|
44
|
-
assert.equal(parseAttachmentAnchorMd('[标题](https://www.tapd.cn/x)'), null);
|
|
45
|
-
assert.equal(parseAttachmentAnchorMd('[📎 x](https://a/b)'), null);
|
|
46
|
-
assert.equal(parseAttachmentAnchorMd('plain text'), null);
|
|
47
|
-
}],
|
|
48
|
-
['parseAttachmentAnchorMd: 完整解析 ws/id/name', () => {
|
|
49
|
-
const ref = parseAttachmentAnchorMd(`前缀 [📎 需求说明.txt](attach:${WS}/${ID}) 后缀`);
|
|
50
|
-
assert.deepEqual(ref, { workspaceId: WS, id: ID, name: '需求说明.txt' });
|
|
51
|
-
}],
|
|
52
|
-
['attachmentAnchorHtml: R6 定稿最小模板逐字段(含 size)', () => {
|
|
53
|
-
assert.equal(attachmentAnchorHtml({ workspaceId: WS, id: ID, name: '接口设计.pdf', size: 2048 }), `<a data-is-tapd-attachment="true" data-can-preview="true" data-file-type="pdf" ` +
|
|
54
|
-
`data-name="接口设计.pdf" data-size="2048" target="_blank" rel="noopener" ` +
|
|
55
|
-
`href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment">接口设计.pdf</a>`);
|
|
56
|
-
}],
|
|
57
|
-
['attachmentAnchorHtml: size 省略不输出 data-size;fileType 按扩展名默认映射;slug 可覆盖', () => {
|
|
58
|
-
const html = attachmentAnchorHtml({ workspaceId: WS, id: ID, name: 'app.log' });
|
|
59
|
-
assert.ok(html.includes('data-file-type="other"'));
|
|
60
|
-
assert.ok(!html.includes('data-size'));
|
|
61
|
-
assert.ok(html.includes(`href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment"`));
|
|
62
|
-
const custom = attachmentAnchorHtml({ workspaceId: WS, id: ID, name: 'a.txt', fileType: 'text' }, { slug: 'bug_description_attachment' });
|
|
63
|
-
assert.ok(custom.includes('/bug_description_attachment">'));
|
|
64
|
-
assert.ok(custom.endsWith('>a.txt</a>'));
|
|
65
|
-
}],
|
|
66
|
-
['attachmentAnchorHtml: 文件名 HTML 特殊字符转义', () => {
|
|
67
|
-
const html = attachmentAnchorHtml({ workspaceId: WS, id: ID, name: 'a"b&c.txt' });
|
|
68
|
-
assert.ok(html.includes('data-name="a"b&c.txt"'));
|
|
69
|
-
assert.ok(html.endsWith('>a"b&c.txt</a>') === false);
|
|
70
|
-
assert.ok(html.endsWith('>a"b&c.txt</a>'));
|
|
71
|
-
}],
|
|
72
|
-
['parseAttachmentAnchorHtml: 生成结果可逆解析', () => {
|
|
73
|
-
const html = attachmentAnchorHtml({ workspaceId: WS, id: ID, name: '接口设计.pdf', size: 2048 });
|
|
74
|
-
assert.deepEqual(parseAttachmentAnchorHtml(html), {
|
|
75
|
-
workspaceId: WS, id: ID, name: '接口设计.pdf', size: 2048, fileType: 'pdf',
|
|
76
|
-
});
|
|
77
|
-
}],
|
|
78
|
-
['parseAttachmentAnchorHtml: 完整编辑器模板(inner spans/type/class)可识别', () => {
|
|
79
|
-
assert.deepEqual(parseAttachmentAnchorHtml(EDITOR_STYLE_ANCHOR), {
|
|
80
|
-
workspaceId: WS, id: '1139814312001000004', name: 'comment-embed.txt', size: 54, fileType: 'text',
|
|
81
|
-
});
|
|
82
|
-
assert.deepEqual(findAttachmentAnchors(`<p>前</p>${EDITOR_STYLE_ANCHOR}<p>后</p>`).length, 1);
|
|
83
|
-
}],
|
|
84
|
-
['parseAttachmentAnchorHtml: 不认识的锚点 → null(裸 <a>/缺 data-name/缺 flag)', () => {
|
|
85
|
-
assert.equal(parseAttachmentAnchorHtml(`<a href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment">x.txt</a>`), null);
|
|
86
|
-
assert.equal(parseAttachmentAnchorHtml(`<a data-is-tapd-attachment="true" href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment">残缺</a>`), null);
|
|
87
|
-
assert.equal(parseAttachmentAnchorHtml(`<a data-can-preview="true" data-name="x.txt" href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment">无flag</a>`), null);
|
|
88
|
-
assert.equal(parseAttachmentAnchorHtml(`<a data-is-tapd-attachment="true" data-name="x.txt" href="https://elsewhere/x">外部链接</a>`), null);
|
|
89
|
-
}],
|
|
90
|
-
['attachmentHtmlToMd: 锚点 → md 扩展语法;不认识锚点原样保留', () => {
|
|
91
|
-
const md = attachmentHtmlToMd(`<p>材料</p>${EDITOR_STYLE_ANCHOR}`);
|
|
92
|
-
assert.equal(md, `<p>材料</p>[📎 comment-embed.txt](attach:${WS}/1139814312001000004)`);
|
|
93
|
-
const bare = `<a href="/${WS}/attachments/preview_attachments/${ID}/story_description_attachment">残缺</a>`;
|
|
94
|
-
assert.equal(attachmentHtmlToMd(`<p>x</p>${bare}`), `<p>x</p>${bare}`);
|
|
95
|
-
const plain = '<p>普通 <a href="https://e.com">链接</a></p>';
|
|
96
|
-
assert.equal(attachmentHtmlToMd(plain), plain);
|
|
97
|
-
}],
|
|
98
|
-
['attachmentMdToHtml: md → 锚点;非 attach 链接透传', () => {
|
|
99
|
-
const html = attachmentMdToHtml(`见 [📎 需求说明.txt](attach:${WS}/${ID})`);
|
|
100
|
-
assert.ok(html.startsWith('见 <a data-is-tapd-attachment="true"'));
|
|
101
|
-
assert.ok(html.includes('data-name="需求说明.txt"'));
|
|
102
|
-
assert.ok(html.endsWith(`>需求说明.txt</a>`));
|
|
103
|
-
const link = '参考 [标题](https://e.com/a)';
|
|
104
|
-
assert.equal(attachmentMdToHtml(link), link);
|
|
105
|
-
}],
|
|
106
|
-
['attachmentMdToHtml: 多锚点与转义名可逆', () => {
|
|
107
|
-
const md = `[📎 a.txt](attach:${WS}/1139814312001000002) 中间 [📎 b.pdf](attach:${WS}/${ID})`;
|
|
108
|
-
const html = attachmentMdToHtml(md);
|
|
109
|
-
assert.equal(findAttachmentAnchors(html).length, 2);
|
|
110
|
-
assert.equal(attachmentHtmlToMd(html), md);
|
|
111
|
-
}],
|
|
112
|
-
['节点辅助: isAttachmentAnchorNode / attachmentAnchorNodeToMd', () => {
|
|
113
|
-
const node = {
|
|
114
|
-
nodeName: 'A',
|
|
115
|
-
getAttribute: (name) => ({
|
|
116
|
-
'data-is-tapd-attachment': 'true',
|
|
117
|
-
'data-name': 'comment-embed.txt',
|
|
118
|
-
'data-size': '54',
|
|
119
|
-
'data-file-type': 'text',
|
|
120
|
-
href: `/${WS}/attachments/preview_attachments/1139814312001000004/story_description_attachment`,
|
|
121
|
-
})[name] ?? null,
|
|
122
|
-
};
|
|
123
|
-
assert.equal(isAttachmentAnchorNode(node), true);
|
|
124
|
-
assert.equal(attachmentAnchorNodeToMd(node), `[📎 comment-embed.txt](attach:${WS}/1139814312001000004)`);
|
|
125
|
-
const bare = { nodeName: 'A', getAttribute: () => null };
|
|
126
|
-
assert.equal(isAttachmentAnchorNode(bare), false);
|
|
127
|
-
assert.equal(attachmentAnchorNodeToMd(bare), null);
|
|
128
|
-
}],
|
|
129
|
-
];
|
|
130
|
-
let failed = 0;
|
|
131
|
-
for (const [name, check] of checks) {
|
|
132
|
-
try {
|
|
133
|
-
check();
|
|
134
|
-
console.log(`PASS ${name}`);
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
failed += 1;
|
|
138
|
-
console.error(`FAIL ${name}`);
|
|
139
|
-
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (failed > 0) {
|
|
143
|
-
console.error(`\n${failed}/${checks.length} failed`);
|
|
144
|
-
process.exitCode = 1;
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
console.log(`\n${checks.length}/${checks.length} passed`);
|
|
148
|
-
}
|
|
149
|
-
//# sourceMappingURL=attachment-anchor.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-anchor.test.js","sourceRoot":"","sources":["../../src/richtext/attachment-anchor.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/** HTML 文本节点转义(& < >)。 */
|
|
2
|
-
export declare function escapeHtmlText(value: string): string;
|
|
3
|
-
/** HTML 属性值转义(& < > " ')。 */
|
|
4
|
-
export declare function escapeHtmlAttr(value: string): string;
|
|
5
|
-
/** 反解 TAPD 富文本属性值中可能出现的实体(常用命名实体 + 十进制/十六进制数字实体)。未识别的实体原样保留。 */
|
|
6
|
-
export declare function decodeHtmlEntities(value: string): string;
|
|
7
|
-
//# sourceMappingURL=escape.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"escape.d.ts","sourceRoot":"","sources":["../../src/richtext/escape.ts"],"names":[],"mappings":"AAeA,0BAA0B;AAC1B,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,6BAA6B;AAC7B,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAWD,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD"}
|
package/dist/richtext/escape.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// richtext 语义库内部工具:最小 HTML 转义/反解(零依赖、纯函数)。
|
|
2
|
-
// 只覆盖 TAPD 富文本锚点/at-who 生成与解析所需的最小集合,不做完整 HTML 实体表。
|
|
3
|
-
const TEXT_ESCAPES = {
|
|
4
|
-
'&': '&',
|
|
5
|
-
'<': '<',
|
|
6
|
-
'>': '>',
|
|
7
|
-
};
|
|
8
|
-
const ATTR_ESCAPES = {
|
|
9
|
-
...TEXT_ESCAPES,
|
|
10
|
-
'"': '"',
|
|
11
|
-
"'": ''',
|
|
12
|
-
};
|
|
13
|
-
/** HTML 文本节点转义(& < >)。 */
|
|
14
|
-
export function escapeHtmlText(value) {
|
|
15
|
-
return value.replace(/[&<>]/g, (ch) => TEXT_ESCAPES[ch] ?? ch);
|
|
16
|
-
}
|
|
17
|
-
/** HTML 属性值转义(& < > " ')。 */
|
|
18
|
-
export function escapeHtmlAttr(value) {
|
|
19
|
-
return value.replace(/[&<>"']/g, (ch) => ATTR_ESCAPES[ch] ?? ch);
|
|
20
|
-
}
|
|
21
|
-
const NAMED_ENTITIES = {
|
|
22
|
-
amp: '&',
|
|
23
|
-
lt: '<',
|
|
24
|
-
gt: '>',
|
|
25
|
-
quot: '"',
|
|
26
|
-
apos: "'",
|
|
27
|
-
nbsp: ' ',
|
|
28
|
-
};
|
|
29
|
-
/** 反解 TAPD 富文本属性值中可能出现的实体(常用命名实体 + 十进制/十六进制数字实体)。未识别的实体原样保留。 */
|
|
30
|
-
export function decodeHtmlEntities(value) {
|
|
31
|
-
return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (whole, body) => {
|
|
32
|
-
const codePoint = numericEntityCodePoint(body);
|
|
33
|
-
if (codePoint !== null)
|
|
34
|
-
return safeFromCodePoint(codePoint, whole);
|
|
35
|
-
const named = NAMED_ENTITIES[body.toLowerCase()];
|
|
36
|
-
return named ?? whole;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function numericEntityCodePoint(body) {
|
|
40
|
-
if (body.startsWith('#x') || body.startsWith('#X')) {
|
|
41
|
-
const code = Number.parseInt(body.slice(2), 16);
|
|
42
|
-
return Number.isFinite(code) ? code : null;
|
|
43
|
-
}
|
|
44
|
-
if (body.startsWith('#')) {
|
|
45
|
-
const code = Number.parseInt(body.slice(1), 10);
|
|
46
|
-
return Number.isFinite(code) ? code : null;
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
function safeFromCodePoint(code, fallback) {
|
|
51
|
-
if (!Number.isInteger(code) || code < 0 || code > 0x10ffff)
|
|
52
|
-
return fallback;
|
|
53
|
-
try {
|
|
54
|
-
return String.fromCodePoint(code);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return fallback;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=escape.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"escape.js","sourceRoot":"","sources":["../../src/richtext/escape.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,oDAAoD;AAEpD,MAAM,YAAY,GAAqC;IACrD,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;CACZ,CAAC;AAEF,MAAM,YAAY,GAAqC;IACrD,GAAG,YAAY;IACf,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,0BAA0B;AAC1B,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,cAAc,GAAqC;IACvD,GAAG,EAAE,GAAG;IACR,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;CACV,CAAC;AAEF,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,gCAAgC,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC7E,MAAM,SAAS,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACjD,OAAO,KAAK,IAAI,KAAK,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,sBAAsB,CAAC,IAAY;IAC1C,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChD,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY,EAAE,QAAgB;IACvD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC5E,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/** at-who 标记里命中的一次提及。`[start, end)` 覆盖含 `@` 在内的整个原始标记区间。 */
|
|
2
|
-
export interface Mention {
|
|
3
|
-
name: string;
|
|
4
|
-
start: number;
|
|
5
|
-
end: number;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* 最小结构 DOM 节点视图(duck typing)——真实 turndown/DOM 节点天然满足,
|
|
9
|
-
* 测试可直接传字面量对象,保持本库零 DOM 依赖。
|
|
10
|
-
*/
|
|
11
|
-
export interface HtmlNodeLike {
|
|
12
|
-
nodeName: string;
|
|
13
|
-
getAttribute(name: string): string | null;
|
|
14
|
-
readonly textContent?: string | null;
|
|
15
|
-
}
|
|
16
|
-
/** 生成 at-who 标记(昵称含 HTML 特殊字符时按属性/文本位分别转义)。 */
|
|
17
|
-
export declare function renderMentionHtml(name: string): string;
|
|
18
|
-
/** 扫描文本中全部合法 mention(跳过 @@ 转义)。 */
|
|
19
|
-
export declare function findMentions(text: string): Mention[];
|
|
20
|
-
/**
|
|
21
|
-
* md 文本 → html 语义片段:`@@` → 字面 `@`,合法 `@昵称` → at-who 标记;
|
|
22
|
-
* 其余内容(含疑似 html 标签区域)原样透传,交给基础转换器(markdown-it)完成。
|
|
23
|
-
* 注意:本函数不做代码段/链接 URL 等 md 语法识别(见文件头分层约定)。
|
|
24
|
-
*/
|
|
25
|
-
export declare function mentionMdToHtml(md: string): string;
|
|
26
|
-
/**
|
|
27
|
-
* html 文本 → md 文本:会把命中 mention 规则的字面 `@名字` 加倍为 `@@名字`、
|
|
28
|
-
* `@@` 字面对加倍为 `@@@@`,使 md→html 重转换后语义不变(round-trip 稳定);
|
|
29
|
-
* 不命中规则的 `@`(如邮箱、工单号)原样保留,避免无谓转义噪音。
|
|
30
|
-
* 与 md→html 的 scanAtTokens 共用同一套规则,双向严格镜像。
|
|
31
|
-
*/
|
|
32
|
-
export declare function escapeAtLiteralsForMd(text: string): string;
|
|
33
|
-
/** 判断一个标签串是否为 at-who 标记(`<b>` 且 class 含 at-who)。 */
|
|
34
|
-
export declare function isAtWhoTag(tagHtml: string): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* html 片段 → md 语义片段:
|
|
37
|
-
* - at-who 标记 → `@昵称`(data-userid 缺失时回退锚点内文本;禁止降级为 `**粗体**`)
|
|
38
|
-
* - 其余标签原样透传(非 at-who 的 `<b>` 不动,交给基础转换器 turndown 处理)
|
|
39
|
-
* - 标签之间的文本按 escapeAtLiteralsForMd 规则保护字面 @
|
|
40
|
-
*/
|
|
41
|
-
export declare function mentionHtmlToMd(html: string): string;
|
|
42
|
-
/** turndown 规则 filter 用:节点是否为 at-who 标记。 */
|
|
43
|
-
export declare function isAtWhoNode(node: HtmlNodeLike): boolean;
|
|
44
|
-
/**
|
|
45
|
-
* turndown 规则 replacement 用:at-who 节点 → `@昵称`。
|
|
46
|
-
* data-userid 优先;缺失时回退节点文本(去首 @)。永不产出 `**`。
|
|
47
|
-
*/
|
|
48
|
-
export declare function atWhoNodeToMd(node: HtmlNodeLike): string;
|
|
49
|
-
//# sourceMappingURL=mention.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mention.d.ts","sourceRoot":"","sources":["../../src/richtext/mention.ts"],"names":[],"mappings":"AAyBA,4DAA4D;AAC5D,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAQD,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAsCD,mCAAmC;AACnC,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAIpD;AAgBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAalD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS1D;AAYD,oDAAoD;AACpD,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGnD;AASD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcpD;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAGvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAKxD"}
|