diff --git a/components/elements/response-pdf-download.tsx b/components/elements/response-pdf-download.tsx index 04954800a..1f426aad4 100644 --- a/components/elements/response-pdf-download.tsx +++ b/components/elements/response-pdf-download.tsx @@ -167,6 +167,36 @@ function createPrintOverrides(targetWindow: Window) { border-radius: 10px !important; } + .claw-pdf-split-table-group { + display: block; + width: 100%; + margin: 12px 0; + break-inside: auto; + page-break-inside: auto; + } + + .claw-pdf-split-card { + width: 100%; + margin: 0 0 10px; + overflow: hidden; + border: 1px solid #e5e7eb; + border-radius: 10px; + break-inside: auto; + page-break-inside: auto; + background: #ffffff; + } + + .claw-pdf-split-caption { + padding: 5px 8px; + border-bottom: 1px solid #e5e7eb; + background: #f8fafc; + color: #6b7280; + font-size: 10px; + line-height: 1.4; + font-weight: 600; + text-align: right; + } + .chat-card-x-scroll { display: block !important; overflow: visible !important; @@ -188,16 +218,23 @@ function createPrintOverrides(targetWindow: Window) { border-collapse: collapse !important; page-break-inside: auto; break-inside: auto; - font-size: 11px !important; - line-height: 1.45 !important; + font-size: 12px !important; + line-height: 1.5 !important; + } + + .claw-pdf-split-table { + margin: 0 !important; + border: 0 !important; + border-radius: 0 !important; + font-size: 11.5px !important; } .claw-pdf-wide-table { - font-size: 10px !important; + font-size: 11px !important; } .claw-pdf-extra-wide-table { - font-size: 9px !important; + font-size: 10px !important; } thead { @@ -224,7 +261,17 @@ function createPrintOverrides(targetWindow: Window) { text-overflow: clip !important; vertical-align: top !important; padding: 5px 7px !important; - line-height: 1.45 !important; + line-height: 1.5 !important; + } + + th *, + td * { + min-width: 0 !important; + max-width: 100% !important; + white-space: normal !important; + overflow-wrap: anywhere !important; + word-break: break-word !important; + text-overflow: clip !important; } th { @@ -274,15 +321,157 @@ function getTableColumnCount(table: HTMLTableElement) { ); } +const PRINTABLE_TABLE_SPLIT_THRESHOLD = 5; +const PRINTABLE_TABLE_PINNED_COLUMNS = 1; +const PRINTABLE_TABLE_MAX_COLUMNS_PER_SEGMENT = 4; + +function getPrintableTableColumnGroups(columnCount: number) { + if (columnCount < PRINTABLE_TABLE_SPLIT_THRESHOLD) return null; + + const pinnedColumnCount = Math.min( + PRINTABLE_TABLE_PINNED_COLUMNS, + columnCount + ); + const pinnedColumns = Array.from( + { length: pinnedColumnCount }, + (_, index) => index + ); + const remainingColumns = Array.from( + { length: columnCount - pinnedColumnCount }, + (_, index) => index + pinnedColumnCount + ); + const chunkSize = Math.max( + 1, + PRINTABLE_TABLE_MAX_COLUMNS_PER_SEGMENT - pinnedColumnCount + ); + + const columnGroups: number[][] = []; + for (let index = 0; index < remainingColumns.length; index += chunkSize) { + columnGroups.push([ + ...pinnedColumns, + ...remainingColumns.slice(index, index + chunkSize), + ]); + } + + return columnGroups.length > 1 ? columnGroups : null; +} + +function getPrintableTableRows(table: HTMLTableElement) { + let headRows = table.tHead ? Array.from(table.tHead.rows) : []; + const allRows = Array.from(table.rows); + + if (!headRows.length && allRows[0]) { + const firstRow = allRows[0]; + if (Array.from(firstRow.cells).some((cell) => cell.tagName === "TH")) { + headRows = [firstRow]; + } + } + + const headRowSet = new Set(headRows); + const bodyRows = allRows.filter((row) => !headRowSet.has(row)); + + return { headRows, bodyRows }; +} + +function appendSplitTableRows( + targetDocument: Document, + targetSection: HTMLTableSectionElement, + sourceRows: HTMLTableRowElement[], + columnIndexes: number[], + fallbackCellTag: "td" | "th" +) { + sourceRows.forEach((sourceRow) => { + const targetRow = targetDocument.createElement("tr"); + columnIndexes.forEach((columnIndex) => { + const sourceCell = sourceRow.cells[columnIndex]; + const cellTag = + sourceCell?.tagName.toLowerCase() === "th" || fallbackCellTag === "th" + ? "th" + : "td"; + const targetCell = targetDocument.createElement(cellTag); + if (sourceCell) { + targetCell.innerHTML = sourceCell.innerHTML; + } + targetRow.appendChild(targetCell); + }); + targetSection.appendChild(targetRow); + }); +} + +function createSplitPrintableTableGroup( + targetDocument: Document, + table: HTMLTableElement, + columnGroups: number[][] +) { + const { headRows, bodyRows } = getPrintableTableRows(table); + const group = targetDocument.createElement("div"); + group.className = "claw-pdf-split-table-group"; + + columnGroups.forEach((columnIndexes, index) => { + const card = targetDocument.createElement("section"); + card.className = "claw-pdf-split-card"; + + const caption = targetDocument.createElement("div"); + caption.className = "claw-pdf-split-caption"; + caption.textContent = `续表 ${index + 1}/${columnGroups.length}`; + card.appendChild(caption); + + const splitTable = targetDocument.createElement("table"); + splitTable.className = "claw-pdf-split-table"; + splitTable.dataset.pdfColumnCount = String(columnIndexes.length); + + if (headRows.length) { + const thead = targetDocument.createElement("thead"); + appendSplitTableRows( + targetDocument, + thead, + headRows, + columnIndexes, + "th" + ); + splitTable.appendChild(thead); + } + + const tbody = targetDocument.createElement("tbody"); + appendSplitTableRows( + targetDocument, + tbody, + bodyRows, + columnIndexes, + "td" + ); + splitTable.appendChild(tbody); + + card.appendChild(splitTable); + group.appendChild(card); + }); + + return group; +} + function preparePrintableTables(root: HTMLElement) { root.querySelectorAll("table").forEach((table) => { + if (!table.isConnected) return; + const columnCount = getTableColumnCount(table); + const columnGroups = getPrintableTableColumnGroups(columnCount); + const scrollWrapper = table.closest(".chat-card-x-scroll"); + const tableCard = scrollWrapper?.parentElement; + + if (columnGroups) { + const splitGroup = createSplitPrintableTableGroup( + root.ownerDocument, + table, + columnGroups + ); + (tableCard || table).replaceWith(splitGroup); + return; + } + table.dataset.pdfColumnCount = String(columnCount); table.classList.toggle("claw-pdf-wide-table", columnCount >= 5); table.classList.toggle("claw-pdf-extra-wide-table", columnCount >= 7); - - const scrollWrapper = table.closest(".chat-card-x-scroll"); - scrollWrapper?.parentElement?.classList.add("claw-pdf-table-card"); + tableCard?.classList.add("claw-pdf-table-card"); }); }