listpage_cli 0.0.308 → 0.0.309

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.
@@ -80,6 +80,14 @@ function createNodeFsAdapter() {
80
80
  throw toFsPortError("writeText", targetPath, error);
81
81
  }
82
82
  },
83
+ writeBuffer: (targetPath, content) => {
84
+ try {
85
+ (0, fs_1.writeFileSync)(targetPath, content);
86
+ }
87
+ catch (error) {
88
+ throw toFsPortError("writeBuffer", targetPath, error);
89
+ }
90
+ },
83
91
  };
84
92
  }
85
93
  function toFsPortError(operation, targetPath, error) {
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBlocksToMarkdown = parseBlocksToMarkdown;
4
+ function extractText(elements) {
5
+ if (!elements)
6
+ return "";
7
+ return elements
8
+ .map((el) => {
9
+ if (el.text_run) {
10
+ let text = el.text_run.content || "";
11
+ const style = el.text_run.text_element_style;
12
+ if (style) {
13
+ if (style.bold)
14
+ text = `**${text}**`;
15
+ if (style.italic)
16
+ text = `*${text}*`;
17
+ if (style.strikethrough)
18
+ text = `~~${text}~~`;
19
+ if (style.inline_code)
20
+ text = `\`${text}\``;
21
+ }
22
+ return text;
23
+ }
24
+ return "";
25
+ })
26
+ .join("");
27
+ }
28
+ async function parseBlocksToMarkdown(blocks, options) {
29
+ if (!blocks || blocks.length === 0)
30
+ return "";
31
+ // 1. 构建 block 映射字典
32
+ const blockMap = new Map();
33
+ for (const block of blocks) {
34
+ if (block.block_id) {
35
+ blockMap.set(block.block_id, block);
36
+ }
37
+ }
38
+ // 2. 找到根节点(通常是 parent_id 为空或者 block_type 为 1 的节点)
39
+ const rootBlock = blocks.find((b) => !b.parent_id || b.block_type === 1);
40
+ if (!rootBlock) {
41
+ return "";
42
+ }
43
+ // 3. 异步递归解析
44
+ async function parseBlock(blockId, indent = "") {
45
+ const block = blockMap.get(blockId);
46
+ if (!block)
47
+ return "";
48
+ let content = "";
49
+ let childIndent = indent;
50
+ switch (block.block_type) {
51
+ case 1: // Page
52
+ content += `# ${extractText(block.page?.elements)}\n\n`;
53
+ break;
54
+ case 2: // Text
55
+ const textContent = extractText(block.text?.elements);
56
+ if (textContent.trim()) {
57
+ content += `${indent}${textContent}\n\n`;
58
+ }
59
+ break;
60
+ case 3: // Heading 1
61
+ content += `${indent}# ${extractText(block.heading1?.elements)}\n\n`;
62
+ break;
63
+ case 4: // Heading 2
64
+ content += `${indent}## ${extractText(block.heading2?.elements)}\n\n`;
65
+ break;
66
+ case 5: // Heading 3
67
+ content += `${indent}### ${extractText(block.heading3?.elements)}\n\n`;
68
+ break;
69
+ case 6: // Heading 4
70
+ content += `${indent}#### ${extractText(block.heading4?.elements)}\n\n`;
71
+ break;
72
+ case 7: // Heading 5
73
+ content += `${indent}##### ${extractText(block.heading5?.elements)}\n\n`;
74
+ break;
75
+ case 8: // Heading 6
76
+ content += `${indent}###### ${extractText(block.heading6?.elements)}\n\n`;
77
+ break;
78
+ case 9: // Heading 7
79
+ content += `${indent}####### ${extractText(block.heading7?.elements)}\n\n`;
80
+ break;
81
+ case 10: // Heading 8
82
+ content += `${indent}######## ${extractText(block.heading8?.elements)}\n\n`;
83
+ break;
84
+ case 11: // Heading 9
85
+ content += `${indent}######### ${extractText(block.heading9?.elements)}\n\n`;
86
+ break;
87
+ case 12: // Bullet
88
+ content += `${indent}- ${extractText(block.bullet?.elements)}\n`;
89
+ childIndent = indent + " ";
90
+ break;
91
+ case 13: // Ordered
92
+ let orderIndex = 1;
93
+ if (block.parent_id) {
94
+ const parentBlock = blockMap.get(block.parent_id);
95
+ if (parentBlock && parentBlock.children) {
96
+ const childIndex = parentBlock.children.indexOf(blockId);
97
+ for (let i = childIndex - 1; i >= 0; i--) {
98
+ const siblingId = parentBlock.children[i];
99
+ const sibling = blockMap.get(siblingId);
100
+ if (sibling && sibling.block_type === 13) {
101
+ orderIndex++;
102
+ }
103
+ else {
104
+ break;
105
+ }
106
+ }
107
+ }
108
+ }
109
+ content += `${indent}${orderIndex}. ${extractText(block.ordered?.elements)}\n`;
110
+ childIndent = indent + " ";
111
+ break;
112
+ case 14: // Code
113
+ content += `${indent}\`\`\`\n${extractText(block.code?.elements)}\n${indent}\`\`\`\n\n`;
114
+ break;
115
+ case 15: // Quote
116
+ content += `${indent}> ${extractText(block.quote?.elements)}\n`;
117
+ childIndent = indent + "> ";
118
+ break;
119
+ case 17: // Todo
120
+ const done = block.todo?.style?.done;
121
+ content += `${indent}- [${done ? "x" : " "}] ${extractText(block.todo?.elements)}\n`;
122
+ childIndent = indent + " ";
123
+ break;
124
+ case 19: // Callout
125
+ const emoji = block.callout?.emoji_id || "bulb";
126
+ content += `${indent}> [!NOTE] :${emoji}:\n`;
127
+ childIndent = indent + "> ";
128
+ break;
129
+ case 22: // Divider
130
+ content += `${indent}---\n\n`;
131
+ break;
132
+ case 27: // Image
133
+ const token = block.image?.token;
134
+ if (token) {
135
+ const downloadUrl = `https://internal-api-drive-stream.feishu.cn/space/api/box/stream/download/v2/cover/${token}/?fallback_source=1&mount_node_token=${block.block_id}&mount_point=docx_image&policy=equal`;
136
+ if (options?.fs && options?.outputDir) {
137
+ try {
138
+ const imageFileName = `assets/${token}.png`; // 保存到 assets 目录
139
+ const imagePath = options.fs.resolve(options.outputDir, imageFileName);
140
+ // 确保 assets 目录存在
141
+ const imageDir = options.fs.dirname(imagePath);
142
+ if (!options.fs.exists(imageDir)) {
143
+ options.fs.ensureDir(imageDir);
144
+ }
145
+ // 使用 sdk 提供的下载方法,并直接使用 writeFile 写入
146
+ const fileRes = await options.client.drive.media.download({
147
+ path: { file_token: token },
148
+ });
149
+ // 使用官方推荐的 writeFile 方法直接将文件保存到本地
150
+ await fileRes.writeFile(imagePath);
151
+ content += `${indent}![image](./${imageFileName})\n\n`;
152
+ }
153
+ catch (e) {
154
+ console.warn(`[Lark Parse] Download image failed for block ${block.block_id}:`, e);
155
+ content += `${indent}![image](${downloadUrl})\n\n`;
156
+ }
157
+ }
158
+ else {
159
+ content += `${indent}![image](${downloadUrl})\n\n`;
160
+ }
161
+ }
162
+ else {
163
+ content += `${indent}![image](image)\n\n`;
164
+ }
165
+ break;
166
+ default:
167
+ // 未知类型直接略过或作为普通文本处理
168
+ break;
169
+ }
170
+ // 解析子节点
171
+ if (block.children && block.children.length > 0) {
172
+ for (const childId of block.children) {
173
+ content += await parseBlock(childId, childIndent);
174
+ }
175
+ // 如果当前是列表的最后一项并且有子节点,为美观可以加空行,但为了简单这里暂不处理
176
+ if (block.block_type === 1 || block.block_type === 19) {
177
+ content += "\n";
178
+ }
179
+ }
180
+ return content;
181
+ }
182
+ const finalContent = await parseBlock(rootBlock.block_id);
183
+ return finalContent.trim() + "\n";
184
+ }
@@ -2,30 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleReadCommand = handleReadCommand;
4
4
  const command_result_1 = require("../../domain/command-result");
5
+ const parse_doc_1 = require("./parse-doc");
5
6
  async function handleReadCommand(input, client, fs) {
6
7
  const docToken = input.positionals[1];
7
8
  if (!docToken) {
8
9
  return (0, command_result_1.commandError)("错误: 请提供飞书文档的 doc_token。用法: listpage_cli lark read <doc_token>", "missing_doc_token", 1);
9
10
  }
10
11
  console.log(`正在读取飞书文档内容,doc_token: ${docToken}`);
11
- const response = await client.docs.v1.content.get({
12
+ const blocksRes = await client.docx.documentBlock.list({
13
+ path: { document_id: docToken },
12
14
  params: {
13
- doc_token: docToken,
14
- doc_type: "docx",
15
- content_type: "markdown",
16
- lang: "zh",
15
+ page_size: 500,
16
+ document_revision_id: -1,
17
17
  },
18
18
  });
19
- if (response.code !== 0) {
20
- return (0, command_result_1.commandError)(`读取飞书文档失败,错误码: ${response.code}, 错误信息: ${response.msg}`, "read_doc_failed", 1);
19
+ if (blocksRes.code !== 0 || !blocksRes.data?.items) {
20
+ return (0, command_result_1.commandError)(`获取文档 blocks 失败: ${blocksRes.msg}`, "get_blocks_failed", 1);
21
21
  }
22
- const docContent = response.data?.content || "";
22
+ const blocks = blocksRes.data.items || [];
23
23
  const relativePath = `.listpage/lark/${docToken}/prd.md`;
24
24
  const absolutePath = fs.resolve(fs.cwd(), relativePath);
25
25
  const outputDir = fs.dirname(absolutePath);
26
26
  if (!fs.exists(outputDir)) {
27
27
  fs.ensureDir(outputDir);
28
28
  }
29
+ const docContent = await (0, parse_doc_1.parseBlocksToMarkdown)(blocks, {
30
+ fs,
31
+ outputDir,
32
+ client,
33
+ });
29
34
  fs.writeText(absolutePath, docContent);
30
35
  console.log(`飞书文档读取成功!内容已保存至: ${relativePath}`);
31
36
  return (0, command_result_1.commandOk)();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "listpage_cli",
3
- "version": "0.0.308",
3
+ "version": "0.0.309",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "listpage_cli": "bin/cli.js"
@@ -25,7 +25,7 @@
25
25
  "class-transformer": "^0.5.1",
26
26
  "class-validator": "~0.14.2",
27
27
  "rxjs": "^7.8.1",
28
- "listpage-next-nest": "~0.0.308"
28
+ "listpage-next-nest": "~0.0.309"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@nestjs/schematics": "^11.0.0",
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "react": "^19.2.0",
14
14
  "react-dom": "^19.2.0",
15
- "listpage-next": "~0.0.308",
15
+ "listpage-next": "~0.0.309",
16
16
  "react-router-dom": ">=6.0.0",
17
17
  "@ant-design/v5-patch-for-react-19": "~1.0.3",
18
18
  "ahooks": "^3.9.5",
@@ -23,7 +23,7 @@
23
23
  "styled-components": "^6.1.19",
24
24
  "mobx": "~6.15.0",
25
25
  "@ant-design/icons": "~6.0.2",
26
- "listpage-components": "~0.0.308",
26
+ "listpage-components": "~0.0.309",
27
27
  "lucide-react": "~0.575.0"
28
28
  "mobx-react-lite": "~4.1.1"
29
29
  },