feishu-docs-cli 0.1.0-beta.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.
Files changed (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +403 -0
  3. package/README.zh.md +402 -0
  4. package/bin/feishu-docs.js +8 -0
  5. package/dist/auth.d.ts +76 -0
  6. package/dist/auth.js +512 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/cli.d.ts +5 -0
  9. package/dist/cli.js +197 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/client.d.ts +28 -0
  12. package/dist/client.js +256 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/commands/authorize.d.ts +12 -0
  15. package/dist/commands/authorize.js +73 -0
  16. package/dist/commands/authorize.js.map +1 -0
  17. package/dist/commands/cat.d.ts +6 -0
  18. package/dist/commands/cat.js +159 -0
  19. package/dist/commands/cat.js.map +1 -0
  20. package/dist/commands/cp.d.ts +9 -0
  21. package/dist/commands/cp.js +70 -0
  22. package/dist/commands/cp.js.map +1 -0
  23. package/dist/commands/create.d.ts +6 -0
  24. package/dist/commands/create.js +120 -0
  25. package/dist/commands/create.js.map +1 -0
  26. package/dist/commands/delete.d.ts +6 -0
  27. package/dist/commands/delete.js +91 -0
  28. package/dist/commands/delete.js.map +1 -0
  29. package/dist/commands/info.d.ts +6 -0
  30. package/dist/commands/info.js +69 -0
  31. package/dist/commands/info.js.map +1 -0
  32. package/dist/commands/install-skill.d.ts +5 -0
  33. package/dist/commands/install-skill.js +28 -0
  34. package/dist/commands/install-skill.js.map +1 -0
  35. package/dist/commands/login.d.ts +10 -0
  36. package/dist/commands/login.js +93 -0
  37. package/dist/commands/login.js.map +1 -0
  38. package/dist/commands/ls.d.ts +6 -0
  39. package/dist/commands/ls.js +79 -0
  40. package/dist/commands/ls.js.map +1 -0
  41. package/dist/commands/mkdir.d.ts +6 -0
  42. package/dist/commands/mkdir.js +49 -0
  43. package/dist/commands/mkdir.js.map +1 -0
  44. package/dist/commands/mv.d.ts +9 -0
  45. package/dist/commands/mv.js +72 -0
  46. package/dist/commands/mv.js.map +1 -0
  47. package/dist/commands/read.d.ts +6 -0
  48. package/dist/commands/read.js +439 -0
  49. package/dist/commands/read.js.map +1 -0
  50. package/dist/commands/search.d.ts +7 -0
  51. package/dist/commands/search.js +92 -0
  52. package/dist/commands/search.js.map +1 -0
  53. package/dist/commands/share.d.ts +13 -0
  54. package/dist/commands/share.js +266 -0
  55. package/dist/commands/share.js.map +1 -0
  56. package/dist/commands/spaces.d.ts +6 -0
  57. package/dist/commands/spaces.js +43 -0
  58. package/dist/commands/spaces.js.map +1 -0
  59. package/dist/commands/tree.d.ts +6 -0
  60. package/dist/commands/tree.js +101 -0
  61. package/dist/commands/tree.js.map +1 -0
  62. package/dist/commands/update.d.ts +9 -0
  63. package/dist/commands/update.js +217 -0
  64. package/dist/commands/update.js.map +1 -0
  65. package/dist/commands/wiki.d.ts +6 -0
  66. package/dist/commands/wiki.js +286 -0
  67. package/dist/commands/wiki.js.map +1 -0
  68. package/dist/parser/block-types.d.ts +141 -0
  69. package/dist/parser/block-types.js +167 -0
  70. package/dist/parser/block-types.js.map +1 -0
  71. package/dist/parser/blocks-to-md.d.ts +26 -0
  72. package/dist/parser/blocks-to-md.js +666 -0
  73. package/dist/parser/blocks-to-md.js.map +1 -0
  74. package/dist/parser/text-elements.d.ts +13 -0
  75. package/dist/parser/text-elements.js +91 -0
  76. package/dist/parser/text-elements.js.map +1 -0
  77. package/dist/scopes.d.ts +21 -0
  78. package/dist/scopes.js +48 -0
  79. package/dist/scopes.js.map +1 -0
  80. package/dist/services/block-writer.d.ts +29 -0
  81. package/dist/services/block-writer.js +131 -0
  82. package/dist/services/block-writer.js.map +1 -0
  83. package/dist/services/doc-blocks.d.ts +8 -0
  84. package/dist/services/doc-blocks.js +26 -0
  85. package/dist/services/doc-blocks.js.map +1 -0
  86. package/dist/services/markdown-convert.d.ts +68 -0
  87. package/dist/services/markdown-convert.js +217 -0
  88. package/dist/services/markdown-convert.js.map +1 -0
  89. package/dist/services/wiki-nodes.d.ts +20 -0
  90. package/dist/services/wiki-nodes.js +46 -0
  91. package/dist/services/wiki-nodes.js.map +1 -0
  92. package/dist/types/index.d.ts +236 -0
  93. package/dist/types/index.js +5 -0
  94. package/dist/types/index.js.map +1 -0
  95. package/dist/utils/document-resolver.d.ts +26 -0
  96. package/dist/utils/document-resolver.js +46 -0
  97. package/dist/utils/document-resolver.js.map +1 -0
  98. package/dist/utils/drive-types.d.ts +4 -0
  99. package/dist/utils/drive-types.js +16 -0
  100. package/dist/utils/drive-types.js.map +1 -0
  101. package/dist/utils/errors.d.ts +23 -0
  102. package/dist/utils/errors.js +114 -0
  103. package/dist/utils/errors.js.map +1 -0
  104. package/dist/utils/member.d.ts +11 -0
  105. package/dist/utils/member.js +30 -0
  106. package/dist/utils/member.js.map +1 -0
  107. package/dist/utils/scope-prompt.d.ts +39 -0
  108. package/dist/utils/scope-prompt.js +134 -0
  109. package/dist/utils/scope-prompt.js.map +1 -0
  110. package/dist/utils/url-parser.d.ts +5 -0
  111. package/dist/utils/url-parser.js +55 -0
  112. package/dist/utils/url-parser.js.map +1 -0
  113. package/dist/utils/validate.d.ts +8 -0
  114. package/dist/utils/validate.js +15 -0
  115. package/dist/utils/validate.js.map +1 -0
  116. package/dist/utils/version.d.ts +12 -0
  117. package/dist/utils/version.js +128 -0
  118. package/dist/utils/version.js.map +1 -0
  119. package/package.json +53 -0
  120. package/skills/feishu-docs/SKILL.md +194 -0
@@ -0,0 +1,666 @@
1
+ /**
2
+ * Convert Feishu document blocks to Markdown.
3
+ *
4
+ * Input: Block[] (flat array with parent_id + children)
5
+ * Output: Markdown string
6
+ */
7
+ import { BlockType, isHeading, headingLevel, CODE_LANGUAGES, } from "./block-types.js";
8
+ import { elementsToMarkdown } from "./text-elements.js";
9
+ /**
10
+ * Emoji ID to Unicode mapping for common callout emojis.
11
+ */
12
+ const EMOJI_MAP = {
13
+ round_pushpin: "\u{1F4CD}",
14
+ bulb: "\u{1F4A1}",
15
+ warning: "\u26A0\uFE0F",
16
+ star: "\u2B50",
17
+ fire: "\u{1F525}",
18
+ check_mark: "\u2705",
19
+ cross_mark: "\u274C",
20
+ info: "\u2139\uFE0F",
21
+ question: "\u2753",
22
+ exclamation: "\u2757",
23
+ memo: "\u{1F4DD}",
24
+ pencil2: "\u270F\uFE0F",
25
+ rocket: "\u{1F680}",
26
+ tada: "\u{1F389}",
27
+ thumbsup: "\u{1F44D}",
28
+ eyes: "\u{1F440}",
29
+ heart: "\u2764\uFE0F",
30
+ zap: "\u26A1",
31
+ bookmark: "\u{1F516}",
32
+ link: "\u{1F517}",
33
+ umbrella_on_ground: "\u26F1\uFE0F",
34
+ umbrella: "\u2602\uFE0F",
35
+ sunny: "\u2600\uFE0F",
36
+ cloud: "\u2601\uFE0F",
37
+ snowflake: "\u2744\uFE0F",
38
+ rainbow: "\u{1F308}",
39
+ bell: "\u{1F514}",
40
+ key: "\u{1F511}",
41
+ lock: "\u{1F512}",
42
+ unlock: "\u{1F513}",
43
+ gear: "\u2699\uFE0F",
44
+ wrench: "\u{1F527}",
45
+ hammer: "\u{1F528}",
46
+ shield: "\u{1F6E1}\uFE0F",
47
+ trophy: "\u{1F3C6}",
48
+ clipboard: "\u{1F4CB}",
49
+ chart_with_upwards_trend: "\u{1F4C8}",
50
+ light_bulb: "\u{1F4A1}",
51
+ magnifying_glass: "\u{1F50D}",
52
+ alarm_clock: "\u23F0",
53
+ hourglass: "\u231B",
54
+ calendar: "\u{1F4C5}",
55
+ inbox_tray: "\u{1F4E5}",
56
+ outbox_tray: "\u{1F4E4}",
57
+ package: "\u{1F4E6}",
58
+ loudspeaker: "\u{1F4E2}",
59
+ thought_balloon: "\u{1F4AD}",
60
+ speech_balloon: "\u{1F4AC}",
61
+ construction: "\u{1F6A7}",
62
+ white_check_mark: "\u2705",
63
+ x: "\u274C",
64
+ bangbang: "\u203C\uFE0F",
65
+ interrobang: "\u2049\uFE0F",
66
+ pushpin: "\u{1F4CC}",
67
+ triangular_flag_on_post: "\u{1F6A9}",
68
+ bomb: "\u{1F4A3}",
69
+ seedling: "\u{1F331}",
70
+ four_leaf_clover: "\u{1F340}",
71
+ dart: "\u{1F3AF}",
72
+ 100: "\u{1F4AF}",
73
+ muscle: "\u{1F4AA}",
74
+ clap: "\u{1F44F}",
75
+ wave: "\u{1F44B}",
76
+ point_right: "\u{1F449}",
77
+ point_up: "\u261D\uFE0F",
78
+ pray: "\u{1F64F}",
79
+ };
80
+ function emojiIdToUnicode(emojiId) {
81
+ return EMOJI_MAP[emojiId] || `:${emojiId}:`;
82
+ }
83
+ /**
84
+ * Build a tree from flat block array.
85
+ * Each block gains a `_children` array of child block objects.
86
+ */
87
+ function buildTree(blocks) {
88
+ const map = new Map();
89
+ for (const block of blocks) {
90
+ map.set(block.block_id, { ...block, _children: [] });
91
+ }
92
+ let root = null;
93
+ for (const block of blocks) {
94
+ const node = map.get(block.block_id);
95
+ if (block.parent_id && map.has(block.parent_id)) {
96
+ map.get(block.parent_id)._children.push(node);
97
+ }
98
+ else {
99
+ root = node;
100
+ }
101
+ }
102
+ // Sort children according to `children` order if available
103
+ for (const node of map.values()) {
104
+ if (node.children && node.children.length > 0) {
105
+ const order = new Map(node.children.map((id, i) => [id, i]));
106
+ node._children.sort((a, b) => {
107
+ const ai = order.get(a.block_id) ?? Infinity;
108
+ const bi = order.get(b.block_id) ?? Infinity;
109
+ return ai - bi;
110
+ });
111
+ }
112
+ }
113
+ return root;
114
+ }
115
+ /**
116
+ * Main entry: convert blocks to markdown string.
117
+ */
118
+ export function blocksToMarkdown(blocks, options = {}) {
119
+ if (!blocks || blocks.length === 0)
120
+ return "";
121
+ const root = buildTree(blocks);
122
+ if (!root)
123
+ return "";
124
+ const lines = [];
125
+ const ctx = {
126
+ imageUrlMap: options.imageUrlMap || new Map(),
127
+ userNameMap: options.userNameMap || new Map(),
128
+ bitableDataMap: options.bitableDataMap || new Map(),
129
+ boardImageMap: options.boardImageMap || new Map(),
130
+ sheetDataMap: options.sheetDataMap || new Map(),
131
+ warnings: [],
132
+ };
133
+ // Extract document title from root PAGE block
134
+ if (root.block_type === BlockType.PAGE) {
135
+ const titleText = getElements(root, "page", ctx);
136
+ if (titleText) {
137
+ lines.push(`# ${titleText}`);
138
+ lines.push("");
139
+ }
140
+ }
141
+ const state = { orderedIndex: 0 };
142
+ for (const child of root._children) {
143
+ if (child.block_type === BlockType.ORDERED) {
144
+ renderNode(child, lines, ctx, 0, state);
145
+ state.orderedIndex = (state.orderedIndex ?? 0) + 1;
146
+ }
147
+ else {
148
+ state.orderedIndex = 0;
149
+ renderNode(child, lines, ctx, 0, {});
150
+ }
151
+ }
152
+ // Emit warnings to stderr
153
+ for (const w of ctx.warnings) {
154
+ process.stderr.write(`feishu-docs: warning: ${w}\n`);
155
+ }
156
+ return (lines
157
+ .join("\n")
158
+ .replace(/\n{3,}/g, "\n\n")
159
+ .trim() + "\n");
160
+ }
161
+ /**
162
+ * Render children with ordered list index tracking.
163
+ */
164
+ function renderChildren(children, lines, ctx, depth) {
165
+ const childState = { orderedIndex: 0 };
166
+ for (const child of children) {
167
+ if (child.block_type === BlockType.ORDERED) {
168
+ renderNode(child, lines, ctx, depth, childState);
169
+ childState.orderedIndex = (childState.orderedIndex ?? 0) + 1;
170
+ }
171
+ else {
172
+ childState.orderedIndex = 0;
173
+ renderNode(child, lines, ctx, depth, {});
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Render a single node and its children.
179
+ */
180
+ function renderNode(node, lines, ctx, depth, state) {
181
+ const type = node.block_type;
182
+ const indent = " ".repeat(depth);
183
+ if (type === BlockType.PAGE) {
184
+ renderChildren(node._children, lines, ctx, depth);
185
+ return;
186
+ }
187
+ if (type === BlockType.TEXT) {
188
+ const text = getElements(node, "text", ctx);
189
+ lines.push(indent + text);
190
+ lines.push("");
191
+ return;
192
+ }
193
+ if (isHeading(type)) {
194
+ const level = headingLevel(type);
195
+ const key = `heading${level}`;
196
+ const text = getElements(node, key, ctx);
197
+ const hashes = "#".repeat(Math.min(level, 6));
198
+ lines.push(`${hashes} ${text}`);
199
+ lines.push("");
200
+ // Headings in Feishu can have children (folded/collapsible content)
201
+ renderChildren(node._children, lines, ctx, depth);
202
+ return;
203
+ }
204
+ if (type === BlockType.BULLET) {
205
+ const text = getElements(node, "bullet", ctx);
206
+ lines.push(`${indent}- ${text}`);
207
+ renderChildren(node._children, lines, ctx, depth + 1);
208
+ if (depth === 0)
209
+ lines.push("");
210
+ return;
211
+ }
212
+ if (type === BlockType.ORDERED) {
213
+ const idx = (state.orderedIndex || 0) + 1;
214
+ const text = getElements(node, "ordered", ctx);
215
+ lines.push(`${indent}${idx}. ${text}`);
216
+ renderChildren(node._children, lines, ctx, depth + 1);
217
+ if (depth === 0)
218
+ lines.push("");
219
+ return;
220
+ }
221
+ if (type === BlockType.TODO) {
222
+ const todo = (node.todo || {});
223
+ const text = getElements(node, "todo", ctx);
224
+ const check = todo.done ? "x" : " ";
225
+ lines.push(`${indent}- [${check}] ${text}`);
226
+ renderChildren(node._children, lines, ctx, depth + 1);
227
+ if (depth === 0)
228
+ lines.push("");
229
+ return;
230
+ }
231
+ if (type === BlockType.CODE) {
232
+ const codeData = (node.code || {});
233
+ const langNum = codeData.style?.language;
234
+ const lang = langNum !== undefined
235
+ ? CODE_LANGUAGES[langNum] || ""
236
+ : "";
237
+ const text = getElements(node, "code", ctx);
238
+ lines.push(`\`\`\`${lang}`);
239
+ lines.push(text);
240
+ lines.push("```");
241
+ lines.push("");
242
+ return;
243
+ }
244
+ if (type === BlockType.QUOTE) {
245
+ const text = getElements(node, "quote", ctx);
246
+ const quotedLines = text.split("\n").map((l) => `> ${l}`);
247
+ lines.push(...quotedLines);
248
+ for (const child of node._children) {
249
+ const childLines = [];
250
+ renderNode(child, childLines, ctx, 0, {});
251
+ lines.push(...childLines.map((l) => (l ? `> ${l}` : ">")));
252
+ }
253
+ lines.push("");
254
+ return;
255
+ }
256
+ if (type === BlockType.QUOTE_CONTAINER) {
257
+ for (const child of node._children) {
258
+ const childLines = [];
259
+ renderNode(child, childLines, ctx, 0, {});
260
+ lines.push(...childLines.map((l) => (l ? `> ${l}` : ">")));
261
+ }
262
+ lines.push("");
263
+ return;
264
+ }
265
+ if (type === BlockType.EQUATION) {
266
+ const eq = (node.equation || {});
267
+ const content = (eq.content || "").trim();
268
+ lines.push(`$$`);
269
+ lines.push(content);
270
+ lines.push(`$$`);
271
+ lines.push("");
272
+ return;
273
+ }
274
+ if (type === BlockType.DIVIDER) {
275
+ lines.push("---");
276
+ lines.push("");
277
+ return;
278
+ }
279
+ if (type === BlockType.IMAGE) {
280
+ const imageData = (node.image || {});
281
+ const fileToken = imageData.token;
282
+ const url = fileToken ? ctx.imageUrlMap.get(fileToken) || "" : "";
283
+ const alt = imageData.alt || "";
284
+ if (url) {
285
+ lines.push(`![${alt}](${url})`);
286
+ }
287
+ else {
288
+ lines.push(`![${alt}](${fileToken || ""})`);
289
+ }
290
+ lines.push("");
291
+ return;
292
+ }
293
+ if (type === BlockType.TABLE) {
294
+ renderTable(node, lines, ctx);
295
+ lines.push("");
296
+ return;
297
+ }
298
+ if (type === BlockType.CALLOUT) {
299
+ const callout = (node.callout || {});
300
+ const emoji = callout.emoji_id
301
+ ? emojiIdToUnicode(callout.emoji_id) + " "
302
+ : "";
303
+ let isFirst = true;
304
+ for (const child of node._children) {
305
+ const childLines = [];
306
+ renderNode(child, childLines, ctx, 0, {});
307
+ const first = childLines.shift() || "";
308
+ // Emoji only on the very first line of the callout
309
+ const prefix = isFirst ? emoji : "";
310
+ isFirst = false;
311
+ lines.push(`> ${prefix}${first}`);
312
+ for (const cl of childLines) {
313
+ lines.push(cl ? `> ${cl}` : ">");
314
+ }
315
+ }
316
+ lines.push("");
317
+ return;
318
+ }
319
+ if (type === BlockType.DIAGRAM) {
320
+ const text = getElements(node, "diagram", ctx);
321
+ lines.push("```mermaid");
322
+ lines.push(sanitizeMermaid(text));
323
+ lines.push("```");
324
+ lines.push("");
325
+ return;
326
+ }
327
+ if (type === BlockType.IFRAME) {
328
+ const iframe = (node.iframe || {});
329
+ const url = iframe.component?.url || "";
330
+ lines.push(`[嵌入](${url})`);
331
+ lines.push("");
332
+ return;
333
+ }
334
+ if (type === BlockType.GRID) {
335
+ for (const child of node._children) {
336
+ for (const grandchild of child._children) {
337
+ renderNode(grandchild, lines, ctx, depth, {});
338
+ }
339
+ }
340
+ return;
341
+ }
342
+ if (type === BlockType.GRID_COLUMN) {
343
+ for (const child of node._children) {
344
+ renderNode(child, lines, ctx, depth, {});
345
+ }
346
+ return;
347
+ }
348
+ if (type === BlockType.TABLE_CELL) {
349
+ // Handled by renderTable
350
+ return;
351
+ }
352
+ if (type === BlockType.FILE) {
353
+ const fileData = (node.file || {});
354
+ const name = fileData.name || "文件";
355
+ const token = fileData.token || "";
356
+ const url = ctx.imageUrlMap.get(token) || "";
357
+ lines.push(`[${name}](${url || token})`);
358
+ lines.push("");
359
+ return;
360
+ }
361
+ if (type === BlockType.ADDONS) {
362
+ const addOns = node["add_ons"] ||
363
+ {};
364
+ // Try to extract Mermaid diagram from record
365
+ try {
366
+ const record = JSON.parse(addOns.record || "{}");
367
+ if ((record.data &&
368
+ typeof record.data === "string" &&
369
+ record.data.includes("graph")) ||
370
+ record.data?.includes("flowchart") ||
371
+ record.data?.includes("sequenceDiagram") ||
372
+ record.data?.includes("classDiagram") ||
373
+ record.data?.includes("gantt") ||
374
+ record.data?.includes("pie") ||
375
+ record.data?.includes("erDiagram")) {
376
+ lines.push("```mermaid");
377
+ lines.push(sanitizeMermaid(record.data.trim()));
378
+ lines.push("```");
379
+ lines.push("");
380
+ return;
381
+ }
382
+ }
383
+ catch {
384
+ // not parseable, skip
385
+ }
386
+ // TOC, Jira, OKR etc. — skip gracefully
387
+ return;
388
+ }
389
+ // Bitable — render as Markdown table if data available
390
+ if (type === BlockType.BITABLE) {
391
+ const token = node.bitable?.token || "";
392
+ const data = ctx.bitableDataMap.get(token);
393
+ if (data && data.fields.length > 0) {
394
+ lines.push("| " +
395
+ data.fields.map((f) => f.replace(/\|/g, "\\|")).join(" | ") +
396
+ " |");
397
+ lines.push("| " + data.fields.map(() => "---").join(" | ") + " |");
398
+ for (const row of data.records) {
399
+ lines.push("| " +
400
+ row
401
+ .map((c) => String(c).replace(/\|/g, "\\|").replace(/\n/g, " "))
402
+ .join(" | ") +
403
+ " |");
404
+ }
405
+ }
406
+ else {
407
+ lines.push(`[多维表格: ${token}]`);
408
+ }
409
+ lines.push("");
410
+ return;
411
+ }
412
+ // Board — whiteboard/flowchart, rendered as image if available
413
+ if (type === BlockType.BOARD) {
414
+ const token = node.board
415
+ ?.token || "";
416
+ const imagePath = ctx.boardImageMap.get(token);
417
+ if (imagePath) {
418
+ lines.push(`![画板](${imagePath})`);
419
+ }
420
+ else {
421
+ lines.push(`[画板: ${token}]`);
422
+ }
423
+ lines.push("");
424
+ return;
425
+ }
426
+ // Sheet — render as Markdown table if data available
427
+ if (type === BlockType.SHEET) {
428
+ const token = node.sheet?.token || "";
429
+ const data = ctx.sheetDataMap.get(token);
430
+ if (data && data.fields.length > 0) {
431
+ if (data.title) {
432
+ lines.push(`**${data.title}**`);
433
+ lines.push("");
434
+ }
435
+ lines.push("| " +
436
+ data.fields.map((f) => f.replace(/\|/g, "\\|")).join(" | ") +
437
+ " |");
438
+ lines.push("| " + data.fields.map(() => "---").join(" | ") + " |");
439
+ for (const row of data.records) {
440
+ lines.push("| " +
441
+ row
442
+ .map((c) => String(c).replace(/\|/g, "\\|").replace(/\n/g, " "))
443
+ .join(" | ") +
444
+ " |");
445
+ }
446
+ }
447
+ else {
448
+ lines.push(`[电子表格: ${token}]`);
449
+ }
450
+ lines.push("");
451
+ return;
452
+ }
453
+ // Task — render as TODO with metadata
454
+ if (type === BlockType.TASK) {
455
+ const task = (node.task || {});
456
+ const taskId = task.task_id || "";
457
+ const text = getElements(node, "task", ctx);
458
+ const summary = text || task.summary || taskId || "未命名任务";
459
+ const parts = [];
460
+ if (task.assignees && task.assignees.length > 0) {
461
+ const names = task.assignees.map((a) => `@${a.name || a.id || "?"}`);
462
+ parts.push(names.join(", "));
463
+ }
464
+ if (task.due) {
465
+ parts.push(`截止: ${task.due}`);
466
+ }
467
+ const meta = parts.length > 0 ? ` (${parts.join(", ")})` : "";
468
+ const check = task.completed ? "x" : " ";
469
+ lines.push(`${indent}- [${check}] ${summary}${meta}`);
470
+ if (depth === 0)
471
+ lines.push("");
472
+ return;
473
+ }
474
+ // LinkPreview — degrade to hyperlink
475
+ if (type === BlockType.LINK_PREVIEW) {
476
+ const preview = node["link_preview"] || {};
477
+ const url = preview.url || "";
478
+ const title = preview.title || url || "链接";
479
+ lines.push(`[${title}](${url})`);
480
+ lines.push("");
481
+ return;
482
+ }
483
+ // JiraIssue — render as JIRA link
484
+ if (type === BlockType.JIRA_ISSUE) {
485
+ const jira = node["jira_issue"] || {};
486
+ const key = jira.key || "";
487
+ const url = jira.url || "";
488
+ const summary = jira.summary || key || "JIRA Issue";
489
+ if (url) {
490
+ lines.push(`[JIRA: ${key || summary}](${url})`);
491
+ }
492
+ else {
493
+ lines.push(`[JIRA: ${key || summary}]`);
494
+ }
495
+ lines.push("");
496
+ return;
497
+ }
498
+ // WikiCatalog — render children or placeholder
499
+ if (type === BlockType.WIKI_CATALOG) {
500
+ if (node._children.length > 0) {
501
+ renderChildren(node._children, lines, ctx, depth);
502
+ }
503
+ else {
504
+ lines.push("[知识库目录]");
505
+ lines.push("");
506
+ }
507
+ return;
508
+ }
509
+ // SubPageList — render children or placeholder
510
+ if (type === BlockType.SUB_PAGE_LIST) {
511
+ if (node._children.length > 0) {
512
+ renderChildren(node._children, lines, ctx, depth);
513
+ }
514
+ else {
515
+ lines.push("[子页面列表]");
516
+ lines.push("");
517
+ }
518
+ return;
519
+ }
520
+ // Agenda blocks — meeting agenda rendering
521
+ if (type === BlockType.AGENDA || type === BlockType.AGENDA_ITEM) {
522
+ renderChildren(node._children, lines, ctx, depth);
523
+ return;
524
+ }
525
+ if (type === BlockType.AGENDA_ITEM_TITLE) {
526
+ const text = getElements(node, "agenda_item_title", ctx);
527
+ lines.push(`${indent}**${text}**`);
528
+ lines.push("");
529
+ return;
530
+ }
531
+ if (type === BlockType.AGENDA_ITEM_CONTENT) {
532
+ renderChildren(node._children, lines, ctx, depth);
533
+ return;
534
+ }
535
+ // OKR blocks — complex business data, skip silently
536
+ if (type === BlockType.OKR ||
537
+ type === BlockType.OKR_OBJECTIVE ||
538
+ type === BlockType.OKR_KEY_RESULT ||
539
+ type === BlockType.OKR_PROGRESS) {
540
+ return;
541
+ }
542
+ // Synced blocks and AI template — no extractable content, skip silently
543
+ if (type === BlockType.SOURCE_SYNCED ||
544
+ type === BlockType.REFERENCE_SYNCED ||
545
+ type === BlockType.AI_TEMPLATE) {
546
+ return;
547
+ }
548
+ // Reference types — show as label with token
549
+ const refTypes = {
550
+ [BlockType.MINDNOTE]: "思维笔记",
551
+ [BlockType.VIEW]: "视图",
552
+ [BlockType.CHAT_CARD]: "群消息卡片",
553
+ [BlockType.ISV]: "三方块",
554
+ };
555
+ if (refTypes[type]) {
556
+ const data = node[Object.keys(node).find((k) => typeof node[k] === "object" &&
557
+ node[k] !== null &&
558
+ node[k]
559
+ ?.token) || ""] || {};
560
+ const token = data.token || "";
561
+ lines.push(`[${refTypes[type]}: ${token}]`);
562
+ lines.push("");
563
+ return;
564
+ }
565
+ // Unknown type
566
+ ctx.warnings.push(`不支持的内容类型: ${type}`);
567
+ lines.push(`[不支持的内容类型: ${type}]`);
568
+ lines.push("");
569
+ }
570
+ /**
571
+ * Get inline text from a block's elements.
572
+ */
573
+ function getElements(node, key, ctx) {
574
+ const data = node[key] || {};
575
+ return elementsToMarkdown(data.elements, ctx);
576
+ }
577
+ /**
578
+ * Render a table block as Markdown table.
579
+ */
580
+ function renderTable(node, lines, ctx) {
581
+ const tableData = (node.table || {});
582
+ const property = tableData.property || {};
583
+ const rowSize = property.row_size || 0;
584
+ const colSize = property.column_size || 0;
585
+ if (rowSize === 0 || colSize === 0)
586
+ return;
587
+ // Build 2D grid from table_cell children
588
+ const cells = node._children;
589
+ const grid = [];
590
+ for (let r = 0; r < rowSize; r++) {
591
+ const row = [];
592
+ for (let c = 0; c < colSize; c++) {
593
+ const cellIndex = r * colSize + c;
594
+ const cell = cells[cellIndex];
595
+ if (cell) {
596
+ const text = cellToText(cell, ctx);
597
+ row.push(text);
598
+ }
599
+ else {
600
+ row.push("");
601
+ }
602
+ }
603
+ grid.push(row);
604
+ }
605
+ if (grid.length === 0)
606
+ return;
607
+ // Header row
608
+ lines.push("| " + grid[0].map((c) => c.replace(/\|/g, "\\|")).join(" | ") + " |");
609
+ // Separator
610
+ lines.push("| " + grid[0].map(() => "---").join(" | ") + " |");
611
+ // Data rows
612
+ for (let r = 1; r < grid.length; r++) {
613
+ lines.push("| " + grid[r].map((c) => c.replace(/\|/g, "\\|")).join(" | ") + " |");
614
+ }
615
+ }
616
+ /**
617
+ * Render a table_cell block content as inline text.
618
+ */
619
+ function cellToText(cell, ctx) {
620
+ const parts = [];
621
+ for (const child of cell._children) {
622
+ const key = Object.keys(child).find((k) => typeof child[k] === "object" &&
623
+ child[k]
624
+ ?.elements);
625
+ if (key) {
626
+ const data = child[key];
627
+ parts.push(elementsToMarkdown(data.elements, ctx));
628
+ }
629
+ }
630
+ return parts.join(" ").replace(/\n/g, " ");
631
+ }
632
+ /**
633
+ * Sanitize Feishu mermaid content for standard mermaid compatibility.
634
+ *
635
+ * Feishu's mermaid renderer is more lenient than standard mermaid.
636
+ * This function fixes common incompatibilities:
637
+ * - Block labels (alt, else, loop, opt, rect, par, critical, break, note)
638
+ * don't support <br/> in standard mermaid — replace with comma-space
639
+ * - Arrow messages missing space after ':' — add the space
640
+ */
641
+ function sanitizeMermaid(content) {
642
+ // Block-level keywords whose label text doesn't support <br/> in standard mermaid
643
+ const blockKeywords = /^(\s*(?:alt|else|loop|opt|par|critical|break)\s+)(.+)$/;
644
+ return content
645
+ .split("\n")
646
+ .map((line) => {
647
+ // Fix block labels: replace <br/> and <br> with comma-space,
648
+ // and replace parentheses with full-width versions to avoid parser confusion
649
+ const blockMatch = line.match(blockKeywords);
650
+ if (blockMatch) {
651
+ const cleaned = blockMatch[2]
652
+ .replace(/<br\s*\/?>/gi, ", ")
653
+ .replace(/\(/g, "\uFF08")
654
+ .replace(/\)/g, "\uFF09");
655
+ return blockMatch[1] + cleaned;
656
+ }
657
+ // Fix missing space after ':' in arrow messages (e.g., A->>B:text → A->>B: text)
658
+ const arrowMatch = line.match(/^(\s*\S+\s*-[-.)>x]+[+-]?\s*\S+\s*):(\S)/);
659
+ if (arrowMatch) {
660
+ return line.replace(/^(\s*\S+\s*-[-.)>x]+[+-]?\s*\S+\s*):(\S)/, "$1: $2");
661
+ }
662
+ return line;
663
+ })
664
+ .join("\n");
665
+ }
666
+ //# sourceMappingURL=blocks-to-md.js.map