SpreadsheetView: keep bullets as single cells with text-wrap
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 44s
CI / test-go-edu-search (push) Successful in 35s
CI / test-python-klausur (push) Failing after 2m37s
CI / test-python-agent-core (push) Successful in 27s
CI / test-nodejs-website (push) Successful in 31s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 44s
CI / test-go-edu-search (push) Successful in 35s
CI / test-python-klausur (push) Failing after 2m37s
CI / test-python-agent-core (push) Successful in 27s
CI / test-nodejs-website (push) Successful in 31s
Revert row expansion — multi-line bullet cells stay as single cells with \n and text-wrap (tb='2'). This way the text reflows when the user resizes the column, like normal Excel behavior. Row height auto-scales by line count (24px * lines). Vertical alignment: top (vt=0) for multi-line cells. Removed leading-space indentation hack (didn't work reliably). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,48 +24,7 @@ interface SpreadsheetViewProps {
|
|||||||
height?: number
|
height?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Expand multi-line cells (\n) into separate rows for better display. */
|
/** No expansion — keep multi-line cells as single cells with \n and text-wrap. */
|
||||||
function expandMultiLineCells(zone: GridZone): { cells: any[]; numRows: number; rowMeta: any[] } {
|
|
||||||
const expandedCells: any[] = []
|
|
||||||
const rowMeta: any[] = [] // metadata per expanded row
|
|
||||||
let expandedRow = 0
|
|
||||||
|
|
||||||
for (const row of (zone.rows || [])) {
|
|
||||||
const rowCells = (zone.cells || []).filter((c) => c.row_index === row.index)
|
|
||||||
const isHeader = row.is_header ?? false
|
|
||||||
|
|
||||||
// Check if any cell in this row has \n
|
|
||||||
const maxLines = Math.max(1, ...rowCells.map((c) => (c.text ?? '').split('\n').length))
|
|
||||||
|
|
||||||
if (maxLines > 1) {
|
|
||||||
// Expand: each line becomes its own row
|
|
||||||
for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
|
|
||||||
const isFirstLine = lineIdx === 0
|
|
||||||
rowMeta.push({ isHeader, isIndented: !isFirstLine, originalRow: row.index })
|
|
||||||
for (const cell of rowCells) {
|
|
||||||
const lines = (cell.text ?? '').split('\n')
|
|
||||||
const lineText = lineIdx < lines.length ? lines[lineIdx] : ''
|
|
||||||
expandedCells.push({
|
|
||||||
...cell,
|
|
||||||
row_index: expandedRow,
|
|
||||||
text: lineText,
|
|
||||||
_isIndented: !isFirstLine,
|
|
||||||
_isFirstBulletLine: isFirstLine,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
expandedRow++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Single line — keep as is
|
|
||||||
rowMeta.push({ isHeader, isIndented: false, originalRow: row.index })
|
|
||||||
for (const cell of rowCells) {
|
|
||||||
expandedCells.push({ ...cell, row_index: expandedRow })
|
|
||||||
}
|
|
||||||
expandedRow++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { cells: expandedCells, numRows: expandedRow, rowMeta }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert a single zone to a Fortune Sheet sheet object. */
|
/** Convert a single zone to a Fortune Sheet sheet object. */
|
||||||
function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any {
|
function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any {
|
||||||
@@ -83,9 +42,8 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
}
|
}
|
||||||
|
|
||||||
const numCols = zone.columns?.length || 1
|
const numCols = zone.columns?.length || 1
|
||||||
|
const numRows = zone.rows?.length || 0
|
||||||
// Expand multi-line cells into separate rows
|
const expandedCells = zone.cells || []
|
||||||
const { cells: expandedCells, numRows, rowMeta } = expandMultiLineCells(zone)
|
|
||||||
|
|
||||||
// Compute zone-wide median word height for font-size detection
|
// Compute zone-wide median word height for font-size detection
|
||||||
const allWordHeights = zone.cells
|
const allWordHeights = zone.cells
|
||||||
@@ -103,7 +61,10 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
const r = cell.row_index
|
const r = cell.row_index
|
||||||
const c = cell.col_index
|
const c = cell.col_index
|
||||||
const text = cell.text ?? ''
|
const text = cell.text ?? ''
|
||||||
const meta = rowMeta[r] || {}
|
|
||||||
|
// Row metadata
|
||||||
|
const row = zone.rows?.find((rr) => rr.index === r)
|
||||||
|
const isHeader = row?.is_header ?? false
|
||||||
|
|
||||||
// Font size detection from word_boxes
|
// Font size detection from word_boxes
|
||||||
const avgWbH = cell.word_boxes?.length
|
const avgWbH = cell.word_boxes?.length
|
||||||
@@ -113,8 +74,8 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
|
|
||||||
const v: any = { v: text, m: text }
|
const v: any = { v: text, m: text }
|
||||||
|
|
||||||
// Bold: headers, is_bold, larger font, first bullet line with •
|
// Bold: headers, is_bold, larger font
|
||||||
if (cell.is_bold || meta.isHeader || isLargerFont) {
|
if (cell.is_bold || isHeader || isLargerFont) {
|
||||||
v.bl = 1
|
v.bl = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,28 +84,19 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
v.fs = 12
|
v.fs = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indentation for bullet continuation lines
|
// Multi-line text (bullets with \n): enable text wrap + vertical top align
|
||||||
if (cell._isIndented) {
|
if (text.includes('\n')) {
|
||||||
v.ht = 0 // left-align
|
v.tb = '2' // text wrap
|
||||||
// Add visual indent via leading spaces (Fortune Sheet has no padding-left)
|
v.vt = 0 // vertical align: top
|
||||||
if (text && !text.startsWith(' ')) {
|
|
||||||
v.v = ' ' + text
|
|
||||||
v.m = ' ' + text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bullet line: keep • visible
|
|
||||||
if (cell._isFirstBulletLine && text.startsWith('•')) {
|
|
||||||
v.bl = 1 // bold bullet marker line
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header row background
|
// Header row background
|
||||||
if (meta.isHeader) {
|
if (isHeader) {
|
||||||
v.bg = isBox ? `${boxColor || '#2563eb'}18` : '#f0f4ff'
|
v.bg = isBox ? `${boxColor || '#2563eb'}18` : '#f0f4ff'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Box cells: light tinted background
|
// Box cells: light tinted background
|
||||||
if (isBox && !meta.isHeader && boxColor) {
|
if (isBox && !isHeader && boxColor) {
|
||||||
v.bg = `${boxColor}08`
|
v.bg = `${boxColor}08`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +107,9 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
|
|
||||||
celldata.push({ r, c, v })
|
celldata.push({ r, c, v })
|
||||||
|
|
||||||
// Colspan → merge (only on non-expanded rows)
|
// Colspan → merge
|
||||||
const colspan = cell.colspan || 0
|
const colspan = cell.colspan || 0
|
||||||
if ((colspan > 1 || cell.col_type === 'spanning_header') && !cell._isIndented) {
|
if (colspan > 1 || cell.col_type === 'spanning_header') {
|
||||||
const cs = colspan || numCols
|
const cs = colspan || numCols
|
||||||
merges[`${r}_${c}`] = { r, c, rs: 1, cs }
|
merges[`${r}_${c}`] = { r, c, rs: 1, cs }
|
||||||
}
|
}
|
||||||
@@ -180,10 +132,13 @@ function zoneToSheet(zone: GridZone, sheetIndex: number, isFirst: boolean): any
|
|||||||
columnlen[String(col.index)] = Math.round(Math.max(autoWidth, scaledPxW))
|
columnlen[String(col.index)] = Math.round(Math.max(autoWidth, scaledPxW))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Row heights
|
// Row heights — taller for multi-line cells
|
||||||
const rowlen: Record<string, number> = {}
|
const rowlen: Record<string, number> = {}
|
||||||
for (let ri = 0; ri < numRows; ri++) {
|
for (const row of (zone.rows || [])) {
|
||||||
rowlen[String(ri)] = 24
|
const rowCells = expandedCells.filter((c: any) => c.row_index === row.index)
|
||||||
|
const maxLines = Math.max(1, ...rowCells.map((c: any) => (c.text ?? '').split('\n').length))
|
||||||
|
const baseH = 24
|
||||||
|
rowlen[String(row.index)] = Math.max(baseH, baseH * maxLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Border info
|
// Border info
|
||||||
|
|||||||
Reference in New Issue
Block a user