@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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacobbubu/md-to-lark",
3
- "version": "1.7.1",
3
+ "version": "1.8.0",
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",