Add header row / column option

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-25 15:32:29 +04:00
parent 4840dd510d
commit 48206cbc36
2 changed files with 110 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ import {
import {
BroomIcon,
CopyIcon,
CrownSimpleIcon,
DotsThreeIcon,
PlusIcon,
TrashIcon,
@@ -453,6 +454,54 @@ export function TableColumnMenu({ editor }: TableColumnMenuProps) {
const currentCol = hoveredCol;
const isFirstColumn = currentCol?.colIndex === 0;
const isHeaderColumn = (): boolean => {
if (!currentCol || currentCol.colIndex !== 0) return false;
try {
const table = editor.state.doc.nodeAt(currentCol.tableStart - 1);
if (!table) return false;
const map = TableMap.get(table);
for (let row = 0; row < map.height; row++) {
const cellPos = map.map[row * map.width] + currentCol.tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode || cellNode.type.name !== "tableHeader") return false;
}
return true;
} catch {
return false;
}
};
const handleToggleHeaderColumn = () => {
if (!currentCol || currentCol.colIndex !== 0) return;
const { tableStart } = currentCol;
try {
const table = editor.state.doc.nodeAt(tableStart - 1);
if (!table) return;
const map = TableMap.get(table);
const { tr, schema } = editor.state;
const targetType = isHeaderColumn()
? schema.nodes.tableCell
: schema.nodes.tableHeader;
for (let row = 0; row < map.height; row++) {
const cellPos = map.map[row * map.width] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
tr.setNodeMarkup(cellPos, targetType, cellNode.attrs);
}
editor.view.dispatch(tr);
} catch {
// table may have changed
}
setMenuOpen(false);
};
const handleDeleteColumn = () => {
if (!currentCol) return;
const { colIndex, tableStart } = currentCol;
@@ -634,6 +683,12 @@ export function TableColumnMenu({ editor }: TableColumnMenuProps) {
onMouseDown={e => e.preventDefault()}
className={menu()}
>
{isFirstColumn && (
<MenuButton active={isHeaderColumn()} onClick={handleToggleHeaderColumn}>
<CrownSimpleIcon size={16} weight="bold" />
Header column
</MenuButton>
)}
<MenuButton onClick={handleInsertLeft}>
<PlusIcon size={16} weight="bold" />
Insert column left

View File

@@ -16,6 +16,7 @@ import {
import {
BroomIcon,
CopyIcon,
CrownSimpleIcon,
DotsThreeVerticalIcon,
PlusIcon,
TrashIcon,
@@ -449,6 +450,54 @@ export function TableRowMenu({ editor }: TableRowMenuProps) {
const currentRow = hoveredRow;
const isFirstRow = currentRow?.rowIndex === 0;
const isHeaderRow = (): boolean => {
if (!currentRow || currentRow.rowIndex !== 0) return false;
try {
const table = editor.state.doc.nodeAt(currentRow.tableStart - 1);
if (!table) return false;
const map = TableMap.get(table);
for (let col = 0; col < map.width; col++) {
const cellPos = map.map[col] + currentRow.tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode || cellNode.type.name !== "tableHeader") return false;
}
return true;
} catch {
return false;
}
};
const handleToggleHeaderRow = () => {
if (!currentRow || currentRow.rowIndex !== 0) return;
const { tableStart } = currentRow;
try {
const table = editor.state.doc.nodeAt(tableStart - 1);
if (!table) return;
const map = TableMap.get(table);
const { tr, schema } = editor.state;
const targetType = isHeaderRow()
? schema.nodes.tableCell
: schema.nodes.tableHeader;
for (let col = 0; col < map.width; col++) {
const cellPos = map.map[col] + tableStart;
const cellNode = editor.state.doc.nodeAt(cellPos);
if (!cellNode) continue;
tr.setNodeMarkup(cellPos, targetType, cellNode.attrs);
}
editor.view.dispatch(tr);
} catch {
// table may have changed
}
setMenuOpen(false);
};
const handleDeleteRow = () => {
if (!currentRow) return;
const { rowIndex, tableStart } = currentRow;
@@ -626,6 +675,12 @@ export function TableRowMenu({ editor }: TableRowMenuProps) {
onMouseDown={e => e.preventDefault()}
className={menu()}
>
{isFirstRow && (
<MenuButton active={isHeaderRow()} onClick={handleToggleHeaderRow}>
<CrownSimpleIcon size={16} weight="bold" />
Header row
</MenuButton>
)}
<MenuButton onClick={handleInsertAbove}>
<PlusIcon size={16} weight="bold" />
Insert row above