@jacobbubu/md-to-lark 1.4.7 → 1.4.9
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.
|
@@ -1,32 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { unified } from 'unified';
|
|
2
|
+
import remarkParse from 'remark-parse';
|
|
3
|
+
const CJK_BOLD_TRAILING_PUNCTUATION = new Set([',', '。', ';', ':', '!', '?', '、', ')', '》', '】', '」', '』']);
|
|
4
|
+
const NORMALIZATION_NEXT_CHAR_RE = /[\p{Script=Han}\p{Letter}\p{Number}\[]/u;
|
|
5
|
+
const PROTECTED_NODE_TYPES = new Set(['code', 'inlineCode', 'html']);
|
|
6
|
+
function mergeProtectedRanges(ranges) {
|
|
7
|
+
const sorted = [...ranges].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
8
|
+
const merged = [];
|
|
9
|
+
for (const range of sorted) {
|
|
10
|
+
const last = merged[merged.length - 1];
|
|
11
|
+
if (!last || range.start > last.end) {
|
|
12
|
+
merged.push({ ...range });
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (range.end > last.end) {
|
|
16
|
+
last.end = range.end;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return merged;
|
|
20
|
+
}
|
|
21
|
+
function collectProtectedRanges(markdown) {
|
|
22
|
+
const tree = unified().use(remarkParse).parse(markdown);
|
|
23
|
+
const ranges = [];
|
|
24
|
+
const visit = (node) => {
|
|
25
|
+
if (!node || typeof node !== 'object')
|
|
26
|
+
return;
|
|
27
|
+
const record = node;
|
|
28
|
+
const start = record.position?.start?.offset;
|
|
29
|
+
const end = record.position?.end?.offset;
|
|
30
|
+
if (record.type &&
|
|
31
|
+
PROTECTED_NODE_TYPES.has(record.type) &&
|
|
32
|
+
typeof start === 'number' &&
|
|
33
|
+
typeof end === 'number' &&
|
|
34
|
+
end > start) {
|
|
35
|
+
ranges.push({ start, end });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const child of record.children ?? []) {
|
|
39
|
+
visit(child);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
visit(tree);
|
|
43
|
+
return mergeProtectedRanges(ranges);
|
|
7
44
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
45
|
+
function collectCjkBoldNormalizationEdits(segment, baseOffset) {
|
|
46
|
+
const edits = [];
|
|
10
47
|
let cursor = 0;
|
|
11
|
-
while (cursor <
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
output += transformOutsideInlineCode(line.slice(cursor));
|
|
48
|
+
while (cursor < segment.length - 1) {
|
|
49
|
+
const open = segment.indexOf('**', cursor);
|
|
50
|
+
if (open === -1)
|
|
15
51
|
break;
|
|
52
|
+
if (open > 0 && segment[open - 1] === '*') {
|
|
53
|
+
cursor = open + 2;
|
|
54
|
+
continue;
|
|
16
55
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
56
|
+
const firstContentChar = segment[open + 2] ?? '';
|
|
57
|
+
if (!firstContentChar || /\s/u.test(firstContentChar)) {
|
|
58
|
+
cursor = open + 2;
|
|
59
|
+
continue;
|
|
21
60
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
let closeSearch = open + 2;
|
|
62
|
+
let matched = false;
|
|
63
|
+
while (closeSearch < segment.length - 1) {
|
|
64
|
+
const close = segment.indexOf('**', closeSearch);
|
|
65
|
+
if (close === -1)
|
|
66
|
+
break;
|
|
67
|
+
const rawContent = segment.slice(open + 2, close);
|
|
68
|
+
if (rawContent.includes('\n')) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (rawContent.includes('**')) {
|
|
72
|
+
closeSearch = close + 2;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const punctuation = segment[close - 1] ?? '';
|
|
76
|
+
const content = segment.slice(open + 2, close - 1);
|
|
77
|
+
const nextChar = segment[close + 2] ?? '';
|
|
78
|
+
if (content && CJK_BOLD_TRAILING_PUNCTUATION.has(punctuation) && NORMALIZATION_NEXT_CHAR_RE.test(nextChar)) {
|
|
79
|
+
edits.push({
|
|
80
|
+
start: baseOffset + open,
|
|
81
|
+
end: baseOffset + close + 2,
|
|
82
|
+
replacement: `**${content}**${punctuation}`,
|
|
83
|
+
});
|
|
84
|
+
cursor = close + 2;
|
|
85
|
+
matched = true;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
closeSearch = close + 2;
|
|
89
|
+
}
|
|
90
|
+
if (!matched) {
|
|
91
|
+
cursor = open + 2;
|
|
27
92
|
}
|
|
28
|
-
|
|
29
|
-
|
|
93
|
+
}
|
|
94
|
+
return edits;
|
|
95
|
+
}
|
|
96
|
+
function applyTextEdits(source, edits) {
|
|
97
|
+
if (edits.length === 0)
|
|
98
|
+
return source;
|
|
99
|
+
let output = source;
|
|
100
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start || b.end - a.end)) {
|
|
101
|
+
output = `${output.slice(0, edit.start)}${edit.replacement}${output.slice(edit.end)}`;
|
|
30
102
|
}
|
|
31
103
|
return output;
|
|
32
104
|
}
|
|
@@ -47,41 +119,19 @@ export function rewriteLeadingFrontmatterAsCodeFence(markdown) {
|
|
|
47
119
|
return hasBom ? `\uFEFF${rewritten}` : rewritten;
|
|
48
120
|
}
|
|
49
121
|
export function normalizeChineseBoldClosingPunctuation(markdown) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
let
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (!chunk)
|
|
57
|
-
continue;
|
|
58
|
-
const lineBreakMatch = chunk.match(/(\r?\n)$/);
|
|
59
|
-
const lineBreak = lineBreakMatch?.[1] ?? '';
|
|
60
|
-
const line = lineBreak ? chunk.slice(0, -lineBreak.length) : chunk;
|
|
61
|
-
const fenceMatch = line.match(/^[ \t]{0,3}([`~]{3,})/);
|
|
62
|
-
if (inFence) {
|
|
63
|
-
output += chunk;
|
|
64
|
-
if (fenceMatch) {
|
|
65
|
-
const fence = fenceMatch[1] ?? '';
|
|
66
|
-
if (fence && fence[0] === activeFenceChar && fence.length >= activeFenceLength) {
|
|
67
|
-
inFence = false;
|
|
68
|
-
activeFenceChar = '';
|
|
69
|
-
activeFenceLength = 0;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (fenceMatch) {
|
|
75
|
-
const fence = fenceMatch[1] ?? '';
|
|
76
|
-
inFence = true;
|
|
77
|
-
activeFenceChar = fence[0] ?? '';
|
|
78
|
-
activeFenceLength = fence.length;
|
|
79
|
-
output += chunk;
|
|
80
|
-
continue;
|
|
122
|
+
const protectedRanges = collectProtectedRanges(markdown);
|
|
123
|
+
const edits = [];
|
|
124
|
+
let cursor = 0;
|
|
125
|
+
for (const range of protectedRanges) {
|
|
126
|
+
if (cursor < range.start) {
|
|
127
|
+
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor, range.start), cursor));
|
|
81
128
|
}
|
|
82
|
-
|
|
129
|
+
cursor = Math.max(cursor, range.end);
|
|
83
130
|
}
|
|
84
|
-
|
|
131
|
+
if (cursor < markdown.length) {
|
|
132
|
+
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor), cursor));
|
|
133
|
+
}
|
|
134
|
+
return applyTextEdits(markdown, edits);
|
|
85
135
|
}
|
|
86
136
|
export function normalizeMarkdownBeforeParse(markdown) {
|
|
87
137
|
return normalizeChineseBoldClosingPunctuation(rewriteLeadingFrontmatterAsCodeFence(markdown));
|