@jacobbubu/md-to-lark 1.4.8 → 1.4.10
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.
|
@@ -2,7 +2,10 @@ import { unified } from 'unified';
|
|
|
2
2
|
import remarkParse from 'remark-parse';
|
|
3
3
|
const CJK_BOLD_TRAILING_PUNCTUATION = new Set([',', '。', ';', ':', '!', '?', '、', ')', '》', '】', '」', '』']);
|
|
4
4
|
const NORMALIZATION_NEXT_CHAR_RE = /[\p{Script=Han}\p{Letter}\p{Number}\[]/u;
|
|
5
|
-
const PROTECTED_NODE_TYPES = new Set(['code', 'inlineCode', '
|
|
5
|
+
const PROTECTED_NODE_TYPES = new Set(['code', 'inlineCode', 'html']);
|
|
6
|
+
function parseMarkdown(markdown) {
|
|
7
|
+
return unified().use(remarkParse).parse(markdown);
|
|
8
|
+
}
|
|
6
9
|
function mergeProtectedRanges(ranges) {
|
|
7
10
|
const sorted = [...ranges].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
8
11
|
const merged = [];
|
|
@@ -19,7 +22,7 @@ function mergeProtectedRanges(ranges) {
|
|
|
19
22
|
return merged;
|
|
20
23
|
}
|
|
21
24
|
function collectProtectedRanges(markdown) {
|
|
22
|
-
const tree =
|
|
25
|
+
const tree = parseMarkdown(markdown);
|
|
23
26
|
const ranges = [];
|
|
24
27
|
const visit = (node) => {
|
|
25
28
|
if (!node || typeof node !== 'object')
|
|
@@ -42,14 +45,84 @@ function collectProtectedRanges(markdown) {
|
|
|
42
45
|
visit(tree);
|
|
43
46
|
return mergeProtectedRanges(ranges);
|
|
44
47
|
}
|
|
45
|
-
function
|
|
48
|
+
function collectParsedStrongRanges(markdown) {
|
|
49
|
+
const tree = parseMarkdown(markdown);
|
|
50
|
+
const ranges = [];
|
|
51
|
+
const visit = (node) => {
|
|
52
|
+
if (!node || typeof node !== 'object')
|
|
53
|
+
return;
|
|
54
|
+
const record = node;
|
|
55
|
+
const start = record.position?.start?.offset;
|
|
56
|
+
const end = record.position?.end?.offset;
|
|
57
|
+
if (record.type === 'strong' && typeof start === 'number' && typeof end === 'number' && end > start) {
|
|
58
|
+
const raw = markdown.slice(start, end);
|
|
59
|
+
if (raw.startsWith('**') && raw.endsWith('**')) {
|
|
60
|
+
ranges.push({ start, end });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const child of record.children ?? []) {
|
|
64
|
+
visit(child);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
visit(tree);
|
|
68
|
+
return ranges.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
69
|
+
}
|
|
70
|
+
function canDelimiterCloseCjkBoldCandidate(markdown, close, blockedOpeningOffsets) {
|
|
71
|
+
const punctuation = markdown[close - 1] ?? '';
|
|
72
|
+
const nextChar = markdown[close + 2] ?? '';
|
|
73
|
+
if (!CJK_BOLD_TRAILING_PUNCTUATION.has(punctuation) || !NORMALIZATION_NEXT_CHAR_RE.test(nextChar)) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
let searchFrom = close - 1;
|
|
77
|
+
while (searchFrom >= 0) {
|
|
78
|
+
const open = markdown.lastIndexOf('**', searchFrom);
|
|
79
|
+
if (open === -1)
|
|
80
|
+
return false;
|
|
81
|
+
if (blockedOpeningOffsets.has(open) || (open > 0 && markdown[open - 1] === '*')) {
|
|
82
|
+
searchFrom = open - 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const rawContent = markdown.slice(open + 2, close);
|
|
86
|
+
if (rawContent.includes('\n')) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (rawContent.includes('**')) {
|
|
90
|
+
searchFrom = open - 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const firstContentChar = markdown[open + 2] ?? '';
|
|
94
|
+
const content = markdown.slice(open + 2, close - 1);
|
|
95
|
+
return Boolean(content &&
|
|
96
|
+
firstContentChar &&
|
|
97
|
+
!/\s/u.test(firstContentChar) &&
|
|
98
|
+
!CJK_BOLD_TRAILING_PUNCTUATION.has(firstContentChar));
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
function collectTrustedStrongClosingDelimiters(markdown) {
|
|
103
|
+
const trustedClosingDelimiters = new Set();
|
|
104
|
+
for (const range of collectParsedStrongRanges(markdown)) {
|
|
105
|
+
if (canDelimiterCloseCjkBoldCandidate(markdown, range.start, trustedClosingDelimiters)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
trustedClosingDelimiters.add(range.end - 2);
|
|
109
|
+
}
|
|
110
|
+
return trustedClosingDelimiters;
|
|
111
|
+
}
|
|
112
|
+
function collectCjkBoldNormalizationEdits(segment, baseOffset, blockedOpeningOffsets) {
|
|
46
113
|
const edits = [];
|
|
47
114
|
let cursor = 0;
|
|
48
115
|
while (cursor < segment.length - 1) {
|
|
49
116
|
const open = segment.indexOf('**', cursor);
|
|
50
117
|
if (open === -1)
|
|
51
118
|
break;
|
|
52
|
-
|
|
119
|
+
const absoluteOpen = baseOffset + open;
|
|
120
|
+
if (blockedOpeningOffsets.has(absoluteOpen) || (open > 0 && segment[open - 1] === '*')) {
|
|
121
|
+
cursor = open + 2;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const firstContentChar = segment[open + 2] ?? '';
|
|
125
|
+
if (!firstContentChar || /\s/u.test(firstContentChar) || CJK_BOLD_TRAILING_PUNCTUATION.has(firstContentChar)) {
|
|
53
126
|
cursor = open + 2;
|
|
54
127
|
continue;
|
|
55
128
|
}
|
|
@@ -63,6 +136,10 @@ function collectCjkBoldNormalizationEdits(segment, baseOffset) {
|
|
|
63
136
|
if (rawContent.includes('\n')) {
|
|
64
137
|
break;
|
|
65
138
|
}
|
|
139
|
+
if (rawContent.includes('**')) {
|
|
140
|
+
closeSearch = close + 2;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
66
143
|
const punctuation = segment[close - 1] ?? '';
|
|
67
144
|
const content = segment.slice(open + 2, close - 1);
|
|
68
145
|
const nextChar = segment[close + 2] ?? '';
|
|
@@ -111,16 +188,17 @@ export function rewriteLeadingFrontmatterAsCodeFence(markdown) {
|
|
|
111
188
|
}
|
|
112
189
|
export function normalizeChineseBoldClosingPunctuation(markdown) {
|
|
113
190
|
const protectedRanges = collectProtectedRanges(markdown);
|
|
191
|
+
const trustedStrongClosingDelimiters = collectTrustedStrongClosingDelimiters(markdown);
|
|
114
192
|
const edits = [];
|
|
115
193
|
let cursor = 0;
|
|
116
194
|
for (const range of protectedRanges) {
|
|
117
195
|
if (cursor < range.start) {
|
|
118
|
-
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor, range.start), cursor));
|
|
196
|
+
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor, range.start), cursor, trustedStrongClosingDelimiters));
|
|
119
197
|
}
|
|
120
198
|
cursor = Math.max(cursor, range.end);
|
|
121
199
|
}
|
|
122
200
|
if (cursor < markdown.length) {
|
|
123
|
-
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor), cursor));
|
|
201
|
+
edits.push(...collectCjkBoldNormalizationEdits(markdown.slice(cursor), cursor, trustedStrongClosingDelimiters));
|
|
124
202
|
}
|
|
125
203
|
return applyTextEdits(markdown, edits);
|
|
126
204
|
}
|