diff --git a/extensions/feishu/src/docx-batch-insert.ts b/extensions/feishu/src/docx-batch-insert.ts index d629cd34fad..e38552a4857 100644 --- a/extensions/feishu/src/docx-batch-insert.ts +++ b/extensions/feishu/src/docx-batch-insert.ts @@ -128,6 +128,16 @@ export async function insertBlocksInBatches( const descendants = collectDescendants(blocks, [firstLevelId]); const newBlocks = descendants.filter((b) => !usedBlockIds.has(b.block_id)); + // A single block whose subtree exceeds the API limit cannot be split + // (a table or other compound block must be inserted atomically). + if (newBlocks.length > BATCH_SIZE) { + throw new Error( + `Block "${firstLevelId}" has ${newBlocks.length} descendants, which exceeds the ` + + `Feishu API limit of ${BATCH_SIZE} blocks per request. ` + + `Please split the content into smaller sections.`, + ); + } + // If adding this first-level block would exceed limit, start new batch if ( currentBatch.blocks.length + newBlocks.length > BATCH_SIZE && diff --git a/extensions/feishu/src/docx-color-text.ts b/extensions/feishu/src/docx-color-text.ts index b25a3633553..0ea6f2c6a5e 100644 --- a/extensions/feishu/src/docx-color-text.ts +++ b/extensions/feishu/src/docx-color-text.ts @@ -55,11 +55,13 @@ interface Segment { */ export function parseColorMarkup(content: string): Segment[] { const segments: Segment[] = []; - // Match [tag]...[/tag] or plain text between tags. - // The closing tag name is intentionally not validated against the opening tag: - // mismatched tags like [red]text[/green] are treated as [red]text[/red] — - // the opening tag's style is applied and the closing tag is consumed. - const tagPattern = /\[([^\]]+)\](.*?)\[\/(?:[^\]]+)\]|([^[]+)/gs; + // Match [tag]...[/tag], plain text, or a bare '[' that is not part of a + // complete tag pair. Without the trailing `|\[` fallback, a '[' that has no + // matching '[/...]' closer (e.g. "[Q1]" with no "[/...]") would be silently + // dropped, corrupting the surrounding text. The closing tag name is not + // validated against the opening tag: [red]text[/green] is treated as + // [red]text[/red] — opening tag style applies, closing tag is consumed. + const tagPattern = /\[([^\]]+)\](.*?)\[\/(?:[^\]]+)\]|([^[]+|\[)/gs; let match; while ((match = tagPattern.exec(content)) !== null) {