/** * Calculate adaptive column widths based on cell content length. * * Algorithm: * 1. For each column, find the max content length across all rows * 2. Weight CJK characters as 2x width (they render wider) * 3. Calculate proportional widths based on content length * 4. Apply min/max constraints * 5. Redistribute remaining space to fill total table width * * Total width is derived from the original column_width values returned * by the Convert API, ensuring tables match Feishu's expected dimensions. * * @param blocks - Array of blocks from Convert API * @param tableBlockId - The block_id of the table block * @returns Array of column widths in pixels
*/ function normalizeChildBlockIds(children: string[] | string | undefined): string[] { if (Array.isArray(children)) { return children;
} returntypeof children === "string" ? [children] : [];
}
// Use original total width from Convert API, or fall back to default const totalWidth =
originalWidths && originalWidths.length > 0
? originalWidths.reduce((a: number, b: number) => a + b, 0)
: DEFAULT_TABLE_WIDTH; const cellIds = normalizeChildBlockIds(tableBlock.children);
// Build block lookup map const blockMap = new Map<string, FeishuDocxBlock>(); for (const block of blocks) { if (block.block_id) {
blockMap.set(block.block_id, block);
}
}
// Extract text content from a table cell function getCellText(cellId: string): string { const cell = blockMap.get(cellId);
let text = ""; const childIds = normalizeChildBlockIds(cell?.children);
for (const childId of childIds) { const child = blockMap.get(childId); if (child?.text?.elements) { for (const elem of child.text.elements) { if (elem.text_run?.content) {
text += elem.text_run.content;
}
}
}
} return text;
}
// Calculate weighted length (CJK chars count as 2) // CJK (Chinese/Japanese/Korean) characters render ~2x wider than ASCII function getWeightedLength(text: string): number { return Array.from(text).reduce((sum, char) => { return sum + (char.charCodeAt(0) > 255 ? 2 : 1);
}, 0);
}
// Find max content length per column const maxLengths = Array.from({ length: column_size }, () => 0);
for (let row = 0; row < row_size; row++) { for (let col = 0; col < column_size; col++) { const cellIndex = row * column_size + col; const cellId = cellIds[cellIndex]; if (cellId) { const content = getCellText(cellId); const length = getWeightedLength(content);
maxLengths[col] = Math.max(maxLengths[col], length);
}
}
}
// Handle empty table: distribute width equally, clamped to [MIN, MAX] so // wide tables (e.g. 15+ columns) don't produce sub-50 widths that Feishu // rejects as invalid column_width values. const totalLength = maxLengths.reduce((a, b) => a + b, 0); if (totalLength === 0) { const equalWidth = Math.max(
MIN_COLUMN_WIDTH,
Math.min(MAX_COLUMN_WIDTH, Math.floor(totalWidth / column_size)),
); return Array.from({ length: column_size }, () => equalWidth);
}
// Redistribute remaining space to fill total width
let remaining = totalWidth - widths.reduce((a, b) => a + b, 0); while (remaining > 0) { // Find columns that can still grow (not at max) const growable = widths.map((w, i) => (w < MAX_COLUMN_WIDTH ? i : -1)).filter((i) => i >= 0); if (growable.length === 0) { break;
}
// Distribute evenly among growable columns const perColumn = Math.floor(remaining / growable.length); if (perColumn === 0) { break;
}
for (const i of growable) { const add = Math.min(perColumn, MAX_COLUMN_WIDTH - widths[i]);
widths[i] += add;
remaining -= add;
}
}
return widths;
}
/** * Clean blocks for Descendant API with adaptive column widths. * * - Removes parent_id from all blocks * - Fixes children type (string → array) for TableCell blocks * - Removes merge_info (read-only, causes API error) * - Calculates and applies adaptive column_width for tables * * @param blocks - Array of blocks from Convert API * @returns Cleaned blocks ready for Descendant API
*/
export function cleanBlocksForDescendant(blocks: FeishuDocxBlock[]): FeishuDocxBlock[] { // Pre-calculate adaptive widths for all tables const tableWidths = new Map<string, number[]>(); for (const block of blocks) { if (block.block_type === 31 && block.block_id) { const widths = calculateAdaptiveColumnWidths(blocks, block.block_id);
tableWidths.set(block.block_id, widths);
}
}
// Fix: Convert API sometimes returns children as string for TableCell if (cleanBlock.block_type === 32 && typeof cleanBlock.children === "string") {
cleanBlock.children = [cleanBlock.children];
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.