@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', 'strong', 'html']);
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 = unified().use(remarkParse).parse(markdown);
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 collectCjkBoldNormalizationEdits(segment, baseOffset) {
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
- if (open > 0 && segment[open - 1] === '*') {
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacobbubu/md-to-lark",
3
- "version": "1.4.8",
3
+ "version": "1.4.10",
4
4
  "description": "Publish Markdown to Feishu docs with a stable pipeline.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",