@jacobbubu/md-to-lark 1.7.1 → 1.8.0
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
CHANGED
|
@@ -88,6 +88,18 @@ Progress logs and exceptions are written to stderr.
|
|
|
88
88
|
|
|
89
89
|
Relative local assets such as `./img-001.png` still resolve against the Markdown file directory by default. If your caller generates a temporary Markdown file elsewhere, use `--resource-base-dir` to keep local asset resolution pinned to the original content directory.
|
|
90
90
|
|
|
91
|
+
## Markdown Syntax Notes
|
|
92
|
+
|
|
93
|
+
Inline `<mark>text</mark>` is supported as a controlled HTML shorthand for Feishu yellow text background highlight.
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
|
|
97
|
+
```md
|
|
98
|
+
This is <mark>important text</mark>.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Nested Markdown marks inside `<mark>...</mark>`, such as `**bold**` and links, are preserved. This does not enable arbitrary raw HTML rendering; unsupported HTML keeps the existing parser behavior.
|
|
102
|
+
|
|
91
103
|
## Common Commands
|
|
92
104
|
|
|
93
105
|
Basic publish:
|
|
@@ -182,6 +194,7 @@ Guardrails:
|
|
|
182
194
|
- Mermaid `text-drawing` and `board` output paths
|
|
183
195
|
- Opt-in single-dollar inline math parsing for LaTeX-heavy papers
|
|
184
196
|
- Table width heuristics and numeric-column right alignment
|
|
197
|
+
- Inline `<mark>text</mark>` mapped to Feishu yellow text background
|
|
185
198
|
- Chinese Markdown formatting preset (`zh-format`)
|
|
186
199
|
- Ordered preset composition from CLI and programmatic usage
|
|
187
200
|
- Legacy stage cache output from `00-source` to `05-publish`; protocol mode adds contract and semantic stages through `07-publish`
|
|
@@ -511,6 +511,9 @@ function parseInlineNodes(ctx, nodes, marks = createDefaultMarks()) {
|
|
|
511
511
|
if (node.tagName === 'u') {
|
|
512
512
|
nextMarks.underline = true;
|
|
513
513
|
}
|
|
514
|
+
if (node.tagName === 'mark') {
|
|
515
|
+
nextMarks.backgroundColor = 'light_yellow';
|
|
516
|
+
}
|
|
514
517
|
if (node.tagName === 'a' || node.tagName === 'm2l-footnote-reference') {
|
|
515
518
|
const href = getStringProp(node, 'href');
|
|
516
519
|
nextMarks.link = href ? { url: href } : null;
|
|
@@ -4,12 +4,14 @@ import remarkGfm from 'remark-gfm';
|
|
|
4
4
|
import remarkMath from 'remark-math';
|
|
5
5
|
import remarkRehype from 'remark-rehype';
|
|
6
6
|
import { normalizeMarkdownBeforeParse } from './normalize-markdown.js';
|
|
7
|
+
import { remarkMarkToHast } from './remark-mark.js';
|
|
7
8
|
export async function markdownToHast(markdown, options = {}) {
|
|
8
9
|
const content = normalizeMarkdownBeforeParse(markdown);
|
|
9
10
|
const processor = unified()
|
|
10
11
|
.use(remarkParse)
|
|
11
12
|
.use(remarkGfm)
|
|
12
13
|
.use(remarkMath, { singleDollarTextMath: options.singleDollarTextMath ?? false })
|
|
14
|
+
.use(remarkMarkToHast)
|
|
13
15
|
.use(remarkRehype, { allowDangerousHtml: false });
|
|
14
16
|
const mdast = processor.parse(content);
|
|
15
17
|
const hast = await processor.run(mdast);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const MARK_OPEN_RE = /^<mark(?:\s[^>]*)?>$/i;
|
|
2
|
+
const MARK_CLOSE_RE = /^<\/mark\s*>$/i;
|
|
3
|
+
function isHtmlNode(node) {
|
|
4
|
+
return node?.type === 'html' && typeof node.value === 'string';
|
|
5
|
+
}
|
|
6
|
+
function isMarkOpen(node) {
|
|
7
|
+
return isHtmlNode(node) && MARK_OPEN_RE.test(node.value.trim());
|
|
8
|
+
}
|
|
9
|
+
function isMarkClose(node) {
|
|
10
|
+
return isHtmlNode(node) && MARK_CLOSE_RE.test(node.value.trim());
|
|
11
|
+
}
|
|
12
|
+
function toMarkNode(open, close, children) {
|
|
13
|
+
const markNode = {
|
|
14
|
+
type: 'mark',
|
|
15
|
+
children,
|
|
16
|
+
data: {
|
|
17
|
+
hName: 'mark',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
if (open.position?.start && close.position?.end) {
|
|
21
|
+
markNode.position = {
|
|
22
|
+
start: open.position.start,
|
|
23
|
+
end: close.position.end,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return markNode;
|
|
27
|
+
}
|
|
28
|
+
function rewriteMarkPairs(children) {
|
|
29
|
+
const rewritten = [];
|
|
30
|
+
let cursor = 0;
|
|
31
|
+
while (cursor < children.length) {
|
|
32
|
+
const node = children[cursor];
|
|
33
|
+
if (!node) {
|
|
34
|
+
cursor += 1;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!isMarkOpen(node)) {
|
|
38
|
+
rewritten.push(node);
|
|
39
|
+
cursor += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const markChildren = [];
|
|
43
|
+
let depth = 1;
|
|
44
|
+
let closeIndex = -1;
|
|
45
|
+
for (let index = cursor + 1; index < children.length; index += 1) {
|
|
46
|
+
const child = children[index];
|
|
47
|
+
if (!child)
|
|
48
|
+
continue;
|
|
49
|
+
if (isMarkOpen(child)) {
|
|
50
|
+
depth += 1;
|
|
51
|
+
}
|
|
52
|
+
if (isMarkClose(child)) {
|
|
53
|
+
depth -= 1;
|
|
54
|
+
if (depth === 0) {
|
|
55
|
+
closeIndex = index;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
markChildren.push(child);
|
|
60
|
+
}
|
|
61
|
+
if (closeIndex === -1) {
|
|
62
|
+
rewritten.push(node);
|
|
63
|
+
cursor += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
rewritten.push(toMarkNode(node, children[closeIndex], rewriteMarkPairs(markChildren)));
|
|
67
|
+
cursor = closeIndex + 1;
|
|
68
|
+
}
|
|
69
|
+
return rewritten;
|
|
70
|
+
}
|
|
71
|
+
function visit(node) {
|
|
72
|
+
if (!Array.isArray(node.children))
|
|
73
|
+
return;
|
|
74
|
+
for (const child of node.children) {
|
|
75
|
+
visit(child);
|
|
76
|
+
}
|
|
77
|
+
node.children = rewriteMarkPairs(node.children);
|
|
78
|
+
}
|
|
79
|
+
export const remarkMarkToHast = function remarkMarkToHast() {
|
|
80
|
+
return (tree) => {
|
|
81
|
+
visit(tree);
|
|
82
|
+
};
|
|
83
|
+
};
|