@max1874/feishu 0.2.11 → 0.2.13
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/package.json +1 -1
- package/src/docx.ts +424 -98
package/package.json
CHANGED
package/src/docx.ts
CHANGED
|
@@ -129,12 +129,63 @@ interface TableData {
|
|
|
129
129
|
cells: string[][]; // 2D array of cell text content
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
/** A content item that can be either a regular block or a table */
|
|
133
|
+
type ContentItem =
|
|
134
|
+
| { type: "block"; block: any }
|
|
135
|
+
| { type: "table"; table: TableData };
|
|
136
|
+
|
|
137
|
+
/** Extract table data from a table block */
|
|
138
|
+
function extractTableData(tableBlock: any, blockMap: Map<string, any>): { tableData: TableData; relatedIds: Set<string> } {
|
|
139
|
+
const relatedIds = new Set<string>();
|
|
140
|
+
relatedIds.add(tableBlock.block_id);
|
|
141
|
+
|
|
142
|
+
const tableInfo = tableBlock.table;
|
|
143
|
+
const rowSize = tableInfo?.property?.row_size ?? 0;
|
|
144
|
+
const colSize = tableInfo?.property?.column_size ?? 0;
|
|
145
|
+
|
|
146
|
+
const cells: string[][] = Array.from({ length: rowSize }, () =>
|
|
147
|
+
Array.from({ length: colSize }, () => ""),
|
|
148
|
+
);
|
|
136
149
|
|
|
137
|
-
|
|
150
|
+
const cellIds: string[] = tableInfo?.cells ?? [];
|
|
151
|
+
let cellIndex = 0;
|
|
152
|
+
|
|
153
|
+
for (let row = 0; row < rowSize; row++) {
|
|
154
|
+
for (let col = 0; col < colSize; col++) {
|
|
155
|
+
if (cellIndex >= cellIds.length) break;
|
|
156
|
+
|
|
157
|
+
const cellId = cellIds[cellIndex];
|
|
158
|
+
relatedIds.add(cellId);
|
|
159
|
+
|
|
160
|
+
const cellBlock = blockMap.get(cellId);
|
|
161
|
+
if (cellBlock?.block_type === TABLE_CELL_BLOCK_TYPE) {
|
|
162
|
+
const childIds: string[] = cellBlock.children ?? [];
|
|
163
|
+
const textParts: string[] = [];
|
|
164
|
+
|
|
165
|
+
for (const childId of childIds) {
|
|
166
|
+
relatedIds.add(childId);
|
|
167
|
+
const textBlock = blockMap.get(childId);
|
|
168
|
+
if (textBlock?.block_type === 2 && textBlock.text?.elements) {
|
|
169
|
+
const text = textBlock.text.elements
|
|
170
|
+
.filter((e: any) => e.text_run)
|
|
171
|
+
.map((e: any) => e.text_run.content)
|
|
172
|
+
.join("");
|
|
173
|
+
textParts.push(text);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
cells[row][col] = textParts.join("\n");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
cellIndex++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { tableData: { rowSize, colSize, cells }, relatedIds };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Convert blocks to content items, preserving order with tables in correct positions */
|
|
188
|
+
function convertBlocksToContentItems(blocks: any[]): ContentItem[] {
|
|
138
189
|
const blockMap = new Map<string, any>();
|
|
139
190
|
for (const block of blocks) {
|
|
140
191
|
if (block.block_id) {
|
|
@@ -142,69 +193,52 @@ function extractTableFromBlocks(blocks: any[]): { tables: TableData[]; otherBloc
|
|
|
142
193
|
}
|
|
143
194
|
}
|
|
144
195
|
|
|
145
|
-
// Track which blocks are part of tables
|
|
196
|
+
// Track which blocks are part of tables
|
|
146
197
|
const tableRelatedIds = new Set<string>();
|
|
198
|
+
const tableDataMap = new Map<string, TableData>();
|
|
147
199
|
|
|
200
|
+
// First pass: extract all table data and mark related IDs
|
|
148
201
|
for (const block of blocks) {
|
|
149
202
|
if (block.block_type === TABLE_BLOCK_TYPE) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
const rowSize = tableInfo.property?.row_size ?? 0;
|
|
155
|
-
const colSize = tableInfo.property?.column_size ?? 0;
|
|
156
|
-
|
|
157
|
-
// Initialize cells array
|
|
158
|
-
const cells: string[][] = Array.from({ length: rowSize }, () =>
|
|
159
|
-
Array.from({ length: colSize }, () => ""),
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
// table.cells is a flat array of cell block IDs in row-major order
|
|
163
|
-
const cellIds: string[] = tableInfo.cells ?? [];
|
|
164
|
-
|
|
165
|
-
let cellIndex = 0;
|
|
166
|
-
for (let row = 0; row < rowSize; row++) {
|
|
167
|
-
for (let col = 0; col < colSize; col++) {
|
|
168
|
-
if (cellIndex >= cellIds.length) break;
|
|
169
|
-
|
|
170
|
-
const cellId = cellIds[cellIndex];
|
|
171
|
-
tableRelatedIds.add(cellId);
|
|
172
|
-
|
|
173
|
-
// Find the TableCell block
|
|
174
|
-
const cellBlock = blockMap.get(cellId);
|
|
175
|
-
if (cellBlock?.block_type === TABLE_CELL_BLOCK_TYPE) {
|
|
176
|
-
// Get text content from cell's children (Text blocks)
|
|
177
|
-
const childIds: string[] = cellBlock.children ?? [];
|
|
178
|
-
const textParts: string[] = [];
|
|
179
|
-
|
|
180
|
-
for (const childId of childIds) {
|
|
181
|
-
tableRelatedIds.add(childId);
|
|
182
|
-
const textBlock = blockMap.get(childId);
|
|
183
|
-
if (textBlock?.block_type === 2 && textBlock.text?.elements) {
|
|
184
|
-
const text = textBlock.text.elements
|
|
185
|
-
.filter((e: any) => e.text_run)
|
|
186
|
-
.map((e: any) => e.text_run.content)
|
|
187
|
-
.join("");
|
|
188
|
-
textParts.push(text);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
cells[row][col] = textParts.join("\n");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
cellIndex++;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
tables.push({ rowSize, colSize, cells });
|
|
203
|
+
const { tableData, relatedIds } = extractTableData(block, blockMap);
|
|
204
|
+
tableDataMap.set(block.block_id, tableData);
|
|
205
|
+
for (const id of relatedIds) {
|
|
206
|
+
tableRelatedIds.add(id);
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
}
|
|
203
210
|
|
|
204
|
-
//
|
|
211
|
+
// Second pass: build content items in order
|
|
212
|
+
const items: ContentItem[] = [];
|
|
205
213
|
for (const block of blocks) {
|
|
206
|
-
if (
|
|
207
|
-
|
|
214
|
+
if (tableRelatedIds.has(block.block_id)) {
|
|
215
|
+
// If this is a table block itself, add the table item
|
|
216
|
+
if (block.block_type === TABLE_BLOCK_TYPE) {
|
|
217
|
+
const tableData = tableDataMap.get(block.block_id);
|
|
218
|
+
if (tableData) {
|
|
219
|
+
items.push({ type: "table", table: tableData });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Skip table-related blocks (cells, cell contents)
|
|
223
|
+
} else {
|
|
224
|
+
items.push({ type: "block", block });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return items;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Extract table data from converted blocks (flat list with ID references) - legacy function */
|
|
232
|
+
function extractTableFromBlocks(blocks: any[]): { tables: TableData[]; otherBlocks: any[] } {
|
|
233
|
+
const items = convertBlocksToContentItems(blocks);
|
|
234
|
+
const tables: TableData[] = [];
|
|
235
|
+
const otherBlocks: any[] = [];
|
|
236
|
+
|
|
237
|
+
for (const item of items) {
|
|
238
|
+
if (item.type === "table") {
|
|
239
|
+
tables.push(item.table);
|
|
240
|
+
} else {
|
|
241
|
+
otherBlocks.push(item.block);
|
|
208
242
|
}
|
|
209
243
|
}
|
|
210
244
|
|
|
@@ -229,6 +263,231 @@ function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[
|
|
|
229
263
|
return { cleaned, skipped };
|
|
230
264
|
}
|
|
231
265
|
|
|
266
|
+
// ============ Helpers for Recursive Insert ============
|
|
267
|
+
|
|
268
|
+
/** Delay helper for rate limiting */
|
|
269
|
+
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
270
|
+
|
|
271
|
+
/** API call with retry for rate limiting */
|
|
272
|
+
async function createChildrenWithRetry(
|
|
273
|
+
client: Lark.Client,
|
|
274
|
+
docToken: string,
|
|
275
|
+
parentBlockId: string,
|
|
276
|
+
children: any[],
|
|
277
|
+
index?: number,
|
|
278
|
+
retries = 3
|
|
279
|
+
): Promise<any> {
|
|
280
|
+
for (let i = 0; i < retries; i++) {
|
|
281
|
+
try {
|
|
282
|
+
const res = await client.docx.documentBlockChildren.create({
|
|
283
|
+
path: { document_id: docToken, block_id: parentBlockId },
|
|
284
|
+
data: {
|
|
285
|
+
children,
|
|
286
|
+
...(index !== undefined && { index })
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
if (res.code === 0) return res;
|
|
290
|
+
// Rate limit error
|
|
291
|
+
if (res.code === 99991400 || res.msg?.includes('rate')) {
|
|
292
|
+
await delay((i + 1) * 2000);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
return res;
|
|
296
|
+
} catch (e: any) {
|
|
297
|
+
if (e.message?.includes('429') || e.response?.status === 429) {
|
|
298
|
+
await delay((i + 1) * 2000);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
throw e;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { code: -1, msg: 'Max retries exceeded' };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Context for recursive block insertion */
|
|
308
|
+
interface InsertContext {
|
|
309
|
+
client: Lark.Client;
|
|
310
|
+
docToken: string;
|
|
311
|
+
blockMap: Map<string, any>;
|
|
312
|
+
tableRelatedIds: Set<string>;
|
|
313
|
+
tableDataMap: Map<string, TableData>;
|
|
314
|
+
insertedCount: number;
|
|
315
|
+
tablesCreated: number;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Recursively insert a block and its children */
|
|
319
|
+
async function insertBlockWithChildren(
|
|
320
|
+
ctx: InsertContext,
|
|
321
|
+
block: any,
|
|
322
|
+
parentBlockId: string,
|
|
323
|
+
index?: number
|
|
324
|
+
): Promise<string | null> {
|
|
325
|
+
// Skip table-related blocks (handled separately)
|
|
326
|
+
if (ctx.tableRelatedIds.has(block.block_id)) {
|
|
327
|
+
if (block.block_type === TABLE_BLOCK_TYPE) {
|
|
328
|
+
const tableData = ctx.tableDataMap.get(block.block_id);
|
|
329
|
+
if (tableData) {
|
|
330
|
+
return await createTableWithRetry(ctx, parentBlockId, tableData, index);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Clean block (remove read-only fields but preserve children reference)
|
|
337
|
+
const { block_id, parent_id, children, ...cleanBlock } = block;
|
|
338
|
+
|
|
339
|
+
// Add delay to avoid rate limiting
|
|
340
|
+
await delay(100);
|
|
341
|
+
|
|
342
|
+
// Insert current block
|
|
343
|
+
const insertRes = await createChildrenWithRetry(
|
|
344
|
+
ctx.client,
|
|
345
|
+
ctx.docToken,
|
|
346
|
+
parentBlockId,
|
|
347
|
+
[cleanBlock],
|
|
348
|
+
index
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (insertRes.code !== 0) {
|
|
352
|
+
console.error(`Failed to insert block: ${insertRes.msg}`);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const newBlockId = insertRes.data?.children?.[0]?.block_id;
|
|
357
|
+
if (!newBlockId) return null;
|
|
358
|
+
|
|
359
|
+
ctx.insertedCount++;
|
|
360
|
+
|
|
361
|
+
// Recursively insert children
|
|
362
|
+
if (children && children.length > 0) {
|
|
363
|
+
for (let i = 0; i < children.length; i++) {
|
|
364
|
+
const childId = children[i];
|
|
365
|
+
const childBlock = ctx.blockMap.get(childId);
|
|
366
|
+
if (childBlock) {
|
|
367
|
+
await insertBlockWithChildren(ctx, childBlock, newBlockId, i);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return newBlockId;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Create and fill a table with retry */
|
|
376
|
+
async function createTableWithRetry(
|
|
377
|
+
ctx: InsertContext,
|
|
378
|
+
parentBlockId: string,
|
|
379
|
+
tableData: TableData,
|
|
380
|
+
index?: number
|
|
381
|
+
): Promise<string | null> {
|
|
382
|
+
await delay(100);
|
|
383
|
+
|
|
384
|
+
const createRes = await createChildrenWithRetry(
|
|
385
|
+
ctx.client,
|
|
386
|
+
ctx.docToken,
|
|
387
|
+
parentBlockId,
|
|
388
|
+
[{
|
|
389
|
+
block_type: TABLE_BLOCK_TYPE,
|
|
390
|
+
table: {
|
|
391
|
+
property: { row_size: tableData.rowSize, column_size: tableData.colSize, header_row: true },
|
|
392
|
+
cells: []
|
|
393
|
+
}
|
|
394
|
+
}],
|
|
395
|
+
index
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
if (createRes.code !== 0) {
|
|
399
|
+
console.error(`Failed to create table: ${createRes.msg}`);
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const tableBlockId = createRes.data?.children?.[0]?.block_id;
|
|
404
|
+
if (!tableBlockId) return null;
|
|
405
|
+
|
|
406
|
+
// Fill table content
|
|
407
|
+
const cellBlocksRes = await ctx.client.docx.documentBlockChildren.get({
|
|
408
|
+
path: { document_id: ctx.docToken, block_id: tableBlockId },
|
|
409
|
+
});
|
|
410
|
+
const cellBlocks = cellBlocksRes.data?.items ?? [];
|
|
411
|
+
|
|
412
|
+
let cellIndex = 0;
|
|
413
|
+
for (let row = 0; row < tableData.rowSize; row++) {
|
|
414
|
+
for (let col = 0; col < tableData.colSize; col++) {
|
|
415
|
+
if (cellIndex >= cellBlocks.length) break;
|
|
416
|
+
const cellBlockId = cellBlocks[cellIndex]?.block_id;
|
|
417
|
+
const cellText = tableData.cells[row]?.[col] ?? "";
|
|
418
|
+
if (cellBlockId && cellText) {
|
|
419
|
+
await delay(50);
|
|
420
|
+
const cellChildrenRes = await ctx.client.docx.documentBlockChildren.get({
|
|
421
|
+
path: { document_id: ctx.docToken, block_id: cellBlockId },
|
|
422
|
+
});
|
|
423
|
+
const textBlockId = cellChildrenRes.data?.items?.[0]?.block_id;
|
|
424
|
+
if (textBlockId) {
|
|
425
|
+
await ctx.client.docx.documentBlock.patch({
|
|
426
|
+
path: { document_id: ctx.docToken, block_id: textBlockId },
|
|
427
|
+
data: { update_text_elements: { elements: [{ text_run: { content: cellText } }] } },
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
cellIndex++;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
ctx.tablesCreated++;
|
|
436
|
+
return tableBlockId;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Insert blocks recursively preserving nested structure */
|
|
440
|
+
async function insertBlocksRecursively(
|
|
441
|
+
client: Lark.Client,
|
|
442
|
+
docToken: string,
|
|
443
|
+
blocks: any[],
|
|
444
|
+
firstLevelBlockIds: string[],
|
|
445
|
+
startIndex: number = 0
|
|
446
|
+
): Promise<{ blocksInserted: number; tablesCreated: number }> {
|
|
447
|
+
// Build block map
|
|
448
|
+
const blockMap = new Map<string, any>();
|
|
449
|
+
for (const block of blocks) {
|
|
450
|
+
if (block.block_id) {
|
|
451
|
+
blockMap.set(block.block_id, block);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Extract table data and related IDs
|
|
456
|
+
const tableRelatedIds = new Set<string>();
|
|
457
|
+
const tableDataMap = new Map<string, TableData>();
|
|
458
|
+
|
|
459
|
+
for (const block of blocks) {
|
|
460
|
+
if (block.block_type === TABLE_BLOCK_TYPE) {
|
|
461
|
+
const { tableData, relatedIds } = extractTableData(block, blockMap);
|
|
462
|
+
tableDataMap.set(block.block_id, tableData);
|
|
463
|
+
for (const id of relatedIds) {
|
|
464
|
+
tableRelatedIds.add(id);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ctx: InsertContext = {
|
|
470
|
+
client,
|
|
471
|
+
docToken,
|
|
472
|
+
blockMap,
|
|
473
|
+
tableRelatedIds,
|
|
474
|
+
tableDataMap,
|
|
475
|
+
insertedCount: 0,
|
|
476
|
+
tablesCreated: 0,
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
// Insert blocks in order of first_level_block_ids
|
|
480
|
+
for (let i = 0; i < firstLevelBlockIds.length; i++) {
|
|
481
|
+
const blockId = firstLevelBlockIds[i];
|
|
482
|
+
const block = blockMap.get(blockId);
|
|
483
|
+
if (block) {
|
|
484
|
+
await insertBlockWithChildren(ctx, block, docToken, startIndex + i);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { blocksInserted: ctx.insertedCount, tablesCreated: ctx.tablesCreated };
|
|
489
|
+
}
|
|
490
|
+
|
|
232
491
|
// ============ Core Functions ============
|
|
233
492
|
|
|
234
493
|
/** Convert markdown to Feishu blocks using the Convert API */
|
|
@@ -289,6 +548,40 @@ async function insertBlocks(
|
|
|
289
548
|
return { children: allChildren, skipped };
|
|
290
549
|
}
|
|
291
550
|
|
|
551
|
+
/** Insert blocks at a specific index (with batching for >50 blocks) */
|
|
552
|
+
async function insertBlocksAtIndex(
|
|
553
|
+
client: Lark.Client,
|
|
554
|
+
docToken: string,
|
|
555
|
+
blocks: any[],
|
|
556
|
+
startIndex: number,
|
|
557
|
+
parentBlockId?: string,
|
|
558
|
+
): Promise<{ children: any[]; skipped: string[] }> {
|
|
559
|
+
const { cleaned, skipped } = cleanBlocksForInsert(blocks);
|
|
560
|
+
const blockId = parentBlockId ?? docToken;
|
|
561
|
+
|
|
562
|
+
if (cleaned.length === 0) {
|
|
563
|
+
return { children: [], skipped };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const BATCH_SIZE = 50;
|
|
567
|
+
const allChildren: any[] = [];
|
|
568
|
+
let insertIndex = startIndex;
|
|
569
|
+
|
|
570
|
+
for (let i = 0; i < cleaned.length; i += BATCH_SIZE) {
|
|
571
|
+
const batch = cleaned.slice(i, i + BATCH_SIZE);
|
|
572
|
+
const res = await client.docx.documentBlockChildren.create({
|
|
573
|
+
path: { document_id: docToken, block_id: blockId },
|
|
574
|
+
data: { children: batch, index: insertIndex },
|
|
575
|
+
});
|
|
576
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
577
|
+
const inserted = res.data?.children ?? [];
|
|
578
|
+
allChildren.push(...inserted);
|
|
579
|
+
insertIndex += inserted.length;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { children: allChildren, skipped };
|
|
583
|
+
}
|
|
584
|
+
|
|
292
585
|
/** Delete all child blocks from a parent */
|
|
293
586
|
async function clearDocumentContent(client: Lark.Client, docToken: string) {
|
|
294
587
|
const existing = await client.docx.documentBlock.list({
|
|
@@ -352,6 +645,7 @@ async function createEmptyTable(
|
|
|
352
645
|
parentBlockId: string,
|
|
353
646
|
rowSize: number,
|
|
354
647
|
colSize: number,
|
|
648
|
+
index?: number,
|
|
355
649
|
): Promise<string | null> {
|
|
356
650
|
const tableBlock = {
|
|
357
651
|
block_type: TABLE_BLOCK_TYPE,
|
|
@@ -367,7 +661,7 @@ async function createEmptyTable(
|
|
|
367
661
|
|
|
368
662
|
const res = await client.docx.documentBlockChildren.create({
|
|
369
663
|
path: { document_id: docToken, block_id: parentBlockId },
|
|
370
|
-
data: { children: [tableBlock] },
|
|
664
|
+
data: { children: [tableBlock], ...(index !== undefined && { index }) },
|
|
371
665
|
});
|
|
372
666
|
|
|
373
667
|
if (res.code !== 0) {
|
|
@@ -479,6 +773,34 @@ async function createAndFillTable(
|
|
|
479
773
|
return true;
|
|
480
774
|
}
|
|
481
775
|
|
|
776
|
+
/** Create and fill a table at a specific index */
|
|
777
|
+
async function createAndFillTableAtIndex(
|
|
778
|
+
client: Lark.Client,
|
|
779
|
+
docToken: string,
|
|
780
|
+
parentBlockId: string,
|
|
781
|
+
tableData: TableData,
|
|
782
|
+
index: number,
|
|
783
|
+
): Promise<boolean> {
|
|
784
|
+
// 1. Create empty table at specific index
|
|
785
|
+
const tableBlockId = await createEmptyTable(
|
|
786
|
+
client,
|
|
787
|
+
docToken,
|
|
788
|
+
parentBlockId,
|
|
789
|
+
tableData.rowSize,
|
|
790
|
+
tableData.colSize,
|
|
791
|
+
index,
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
if (!tableBlockId) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// 2. Fill table content
|
|
799
|
+
await fillTableContent(client, docToken, tableBlockId, tableData.cells);
|
|
800
|
+
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
|
|
482
804
|
/** Process images in markdown: download from URL, upload to Feishu, update blocks */
|
|
483
805
|
async function processImages(
|
|
484
806
|
client: Lark.Client,
|
|
@@ -714,71 +1036,75 @@ async function writeDoc(client: Lark.Client, docToken: string, markdown: string)
|
|
|
714
1036
|
const deleted = await clearDocumentContent(client, docToken);
|
|
715
1037
|
|
|
716
1038
|
// 2. Convert markdown to blocks
|
|
717
|
-
const { blocks } = await convertMarkdown(client, markdown);
|
|
1039
|
+
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
|
718
1040
|
if (blocks.length === 0) {
|
|
719
1041
|
return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0, tables_created: 0 };
|
|
720
1042
|
}
|
|
721
1043
|
|
|
722
|
-
// 3.
|
|
723
|
-
const {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
for (const tableData of tables) {
|
|
731
|
-
const success = await createAndFillTable(client, docToken, docToken, tableData);
|
|
732
|
-
if (success) tablesCreated++;
|
|
733
|
-
}
|
|
1044
|
+
// 3. Insert blocks recursively (preserves nested structure)
|
|
1045
|
+
const { blocksInserted, tablesCreated } = await insertBlocksRecursively(
|
|
1046
|
+
client,
|
|
1047
|
+
docToken,
|
|
1048
|
+
blocks,
|
|
1049
|
+
firstLevelBlockIds,
|
|
1050
|
+
0
|
|
1051
|
+
);
|
|
734
1052
|
|
|
735
|
-
//
|
|
736
|
-
const
|
|
1053
|
+
// 4. Process images (get inserted blocks for image processing)
|
|
1054
|
+
const insertedRes = await client.docx.documentBlock.list({
|
|
1055
|
+
path: { document_id: docToken },
|
|
1056
|
+
params: { page_size: 500 },
|
|
1057
|
+
});
|
|
1058
|
+
const insertedBlocks = insertedRes.data?.items ?? [];
|
|
1059
|
+
const imagesProcessed = await processImages(client, docToken, markdown, insertedBlocks);
|
|
737
1060
|
|
|
738
1061
|
return {
|
|
739
1062
|
success: true,
|
|
740
1063
|
blocks_deleted: deleted,
|
|
741
|
-
blocks_added:
|
|
1064
|
+
blocks_added: blocksInserted,
|
|
742
1065
|
tables_created: tablesCreated,
|
|
743
1066
|
images_processed: imagesProcessed,
|
|
744
|
-
...(skipped.length > 0 && {
|
|
745
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}.`,
|
|
746
|
-
}),
|
|
747
1067
|
};
|
|
748
1068
|
}
|
|
749
1069
|
|
|
750
1070
|
async function appendDoc(client: Lark.Client, docToken: string, markdown: string) {
|
|
751
1071
|
// 1. Convert markdown to blocks
|
|
752
|
-
const { blocks } = await convertMarkdown(client, markdown);
|
|
1072
|
+
const { blocks, firstLevelBlockIds } = await convertMarkdown(client, markdown);
|
|
753
1073
|
if (blocks.length === 0) {
|
|
754
1074
|
throw new Error("Content is empty");
|
|
755
1075
|
}
|
|
756
1076
|
|
|
757
|
-
// 2.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
let tablesCreated = 0;
|
|
765
|
-
for (const tableData of tables) {
|
|
766
|
-
const success = await createAndFillTable(client, docToken, docToken, tableData);
|
|
767
|
-
if (success) tablesCreated++;
|
|
1077
|
+
// 2. Get current insert index
|
|
1078
|
+
let startIndex = 0;
|
|
1079
|
+
const existing = await client.docx.documentBlockChildren.get({
|
|
1080
|
+
path: { document_id: docToken, block_id: docToken },
|
|
1081
|
+
});
|
|
1082
|
+
if (existing.code === 0) {
|
|
1083
|
+
startIndex = existing.data?.items?.length ?? 0;
|
|
768
1084
|
}
|
|
769
1085
|
|
|
770
|
-
//
|
|
771
|
-
const
|
|
1086
|
+
// 3. Insert blocks recursively (preserves nested structure)
|
|
1087
|
+
const { blocksInserted, tablesCreated } = await insertBlocksRecursively(
|
|
1088
|
+
client,
|
|
1089
|
+
docToken,
|
|
1090
|
+
blocks,
|
|
1091
|
+
firstLevelBlockIds,
|
|
1092
|
+
startIndex
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
// 4. Process images (get inserted blocks for image processing)
|
|
1096
|
+
const insertedRes = await client.docx.documentBlock.list({
|
|
1097
|
+
path: { document_id: docToken },
|
|
1098
|
+
params: { page_size: 500 },
|
|
1099
|
+
});
|
|
1100
|
+
const insertedBlocks = insertedRes.data?.items ?? [];
|
|
1101
|
+
const imagesProcessed = await processImages(client, docToken, markdown, insertedBlocks);
|
|
772
1102
|
|
|
773
1103
|
return {
|
|
774
1104
|
success: true,
|
|
775
|
-
blocks_added:
|
|
1105
|
+
blocks_added: blocksInserted,
|
|
776
1106
|
tables_created: tablesCreated,
|
|
777
1107
|
images_processed: imagesProcessed,
|
|
778
|
-
block_ids: inserted.map((b: any) => b.block_id),
|
|
779
|
-
...(skipped.length > 0 && {
|
|
780
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}.`,
|
|
781
|
-
}),
|
|
782
1108
|
};
|
|
783
1109
|
}
|
|
784
1110
|
|