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}\n\n`;
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.warn(`[Lark Parse] Download image failed for block ${block.block_id}:`, e);
|
|
155
|
+
content += `${indent}\n\n`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
content += `${indent}\n\n`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
content += `${indent}\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
|
|
12
|
+
const blocksRes = await client.docx.documentBlock.list({
|
|
13
|
+
path: { document_id: docToken },
|
|
12
14
|
params: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
content_type: "markdown",
|
|
16
|
-
lang: "zh",
|
|
15
|
+
page_size: 500,
|
|
16
|
+
document_revision_id: -1,
|
|
17
17
|
},
|
|
18
18
|
});
|
|
19
|
-
if (
|
|
20
|
-
return (0, command_result_1.commandError)(
|
|
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
|
|
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
|
@@ -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.
|
|
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.
|
|
26
|
+
"listpage-components": "~0.0.309",
|
|
27
27
|
"lucide-react": "~0.575.0"
|
|
28
28
|
"mobx-react-lite": "~4.1.1"
|
|
29
29
|
},
|