Skip to content

feat(editor): Table cell merging and row text alignment features - #14885

Open
001-mak wants to merge 10 commits into
toeverything:canaryfrom
001-mak:001-mak/feat/14443/12786/cell-merging-text-alignment
Open

feat(editor): Table cell merging and row text alignment features#14885
001-mak wants to merge 10 commits into
toeverything:canaryfrom
001-mak:001-mak/feat/14443/12786/cell-merging-text-alignment

Conversation

@001-mak

@001-mak 001-mak commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

This PR implements #14443 and #12786
Table cell merging and row text alignment features

Summary by CodeRabbit

  • New Features

    • Added table cell merging and unmerging, including support for row and column spans.
    • Added horizontal and vertical text alignment controls for selected cells.
    • Added context-menu actions for merging, unmerging, and text alignment.
  • Bug Fixes

    • Hidden cells covered by merged areas are no longer rendered redundantly.
    • Table exports now preserve merged-cell structure, cell alignment, and GFM markdown alignment.
    • Cell content is retained correctly when cells are merged.

@001-mak
001-mak requested a review from a team as a code owner April 29, 2026 13:35
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 191b899a-0a0f-4dc6-9c07-faa6407ad07e

📥 Commits

Reviewing files that changed from the base of the PR and between e3d1fc6 and 2b84e0e.

📒 Files selected for processing (2)
  • blocksuite/affine/blocks/table/src/table-block.ts
  • blocksuite/affine/blocks/table/src/table-cell.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • blocksuite/affine/blocks/table/src/table-block.ts

📝 Walkthrough

Walkthrough

Table cells now support merging, unmerging, hidden covered cells, horizontal and vertical alignment, span-aware rendering, and preservation of alignment and span metadata in HTML and Markdown exports.

Changes

Table layout editing

Layer / File(s) Summary
Cell state and editing operations
blocksuite/affine/model/src/blocks/table/table-model.ts, blocksuite/affine/blocks/table/src/table-data-manager.ts
Table cell types and serialized data include span, hidden, and alignment fields. TableDataManager adds transactional merge, unmerge, and alignment operations.
Cell menus and rendering
blocksuite/affine/blocks/table/src/table-cell.ts, blocksuite/affine/blocks/table/src/table-block.ts
Context menus expose merge, unmerge, and text alignment actions. Rendering omits hidden cells, emits spans, and applies alignment styles.
HTML and Markdown export
blocksuite/affine/blocks/table/src/adapters/html.ts, blocksuite/affine/blocks/table/src/adapters/markdown.ts, blocksuite/affine/blocks/table/src/adapters/utils.ts
HTML export preserves ordering, hidden cells, spans, and text alignment; Markdown export maps text alignment to GFM cell alignment.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TableCell
  participant TableDataManager
  participant Store
  participant table-block
  TableCell->>TableDataManager: mergeCells(selection)
  TableDataManager->>Store: transact span and hidden-cell updates
  Store-->>table-block: updated table data
  table-block->>TableCell: render visible cells with colspan and rowspan
Loading

Suggested labels: mod:component, app:core

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: table cell merging and alignment support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
blocksuite/affine/blocks/table/src/adapters/html.ts (1)

79-84: Consider extracting the inline style building logic.

The style string concatenation could be simplified.

♻️ Optional simplification
-        const divStyle = [
-          `min-height: 22px;min-width:${DefaultColumnWidth}px;padding: 8px 12px;`,
-          textAlign ? `text-align: ${textAlign};` : '',
-        ]
-          .filter(Boolean)
-          .join('');
+        const divStyle = `min-height: 22px;min-width:${DefaultColumnWidth}px;padding: 8px 12px;${textAlign ? `text-align: ${textAlign};` : ''}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/blocks/table/src/adapters/html.ts` around lines 79 - 84,
The inline style construction around divStyle (using DefaultColumnWidth and
optional textAlign) should be extracted into a small helper (e.g.,
buildCellStyle or createStyleString) that accepts parameters like width and
textAlign and returns the final style string; replace the inline
array/filter/join in adapters/html.ts with a call to that helper to simplify
concatenation and make testing reusable, keeping the same behavior for
`min-height: 22px;min-width:${DefaultColumnWidth}px;padding: 8px 12px;` and
optional `text-align` when textAlign is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/blocks/table/src/table-data-manager.ts`:
- Around line 436-444: The concatenation for the merged top-left cell can
produce a leading newline when topLeft.text is empty: change the logic around
topLeft, extraTexts and combined so you only prepend '\n' when topLeft.text has
non-empty content; build combined by either using topLeft.text.toString() + '\n'
+ extraTexts.join('\n') when topLeft.text.length > 0, or using
extraTexts.join('\n') alone when topLeft.text is empty, then call
topLeft.text.replace(0, topLeft.text.length, combined) and still set
topLeft.colSpan and topLeft.rowSpan as before.

---

Nitpick comments:
In `@blocksuite/affine/blocks/table/src/adapters/html.ts`:
- Around line 79-84: The inline style construction around divStyle (using
DefaultColumnWidth and optional textAlign) should be extracted into a small
helper (e.g., buildCellStyle or createStyleString) that accepts parameters like
width and textAlign and returns the final style string; replace the inline
array/filter/join in adapters/html.ts with a call to that helper to simplify
concatenation and make testing reusable, keeping the same behavior for
`min-height: 22px;min-width:${DefaultColumnWidth}px;padding: 8px 12px;` and
optional `text-align` when textAlign is present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b57153d7-da13-4174-846c-870aa162e901

📥 Commits

Reviewing files that changed from the base of the PR and between 207bd93 and bab9d46.

📒 Files selected for processing (8)
  • blocksuite/affine/blocks/table/src/adapters/html.ts
  • blocksuite/affine/blocks/table/src/adapters/markdown.ts
  • blocksuite/affine/blocks/table/src/adapters/utils.ts
  • blocksuite/affine/blocks/table/src/table-block.ts
  • blocksuite/affine/blocks/table/src/table-cell-css.ts
  • blocksuite/affine/blocks/table/src/table-cell.ts
  • blocksuite/affine/blocks/table/src/table-data-manager.ts
  • blocksuite/affine/model/src/blocks/table/table-model.ts

Comment thread blocksuite/affine/blocks/table/src/table-data-manager.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/blocks/table/src/table-data-manager.ts`:
- Around line 425-430: The merge routine currently copies covered cell text into
the top-left cell but only marks covered cells hidden (cell.hidden = true),
leaving their Y.Text content intact so it reappears after unmerge; update the
merge handling (the block that builds extraTexts and sets cell.hidden=true, and
the similar block around lines 474-485) to also clear/normalize the covered
cell's text content after copying it (i.e., remove or reset the Y.Text payload
for those covered cells) and ensure the unmerge/unhide path does not restore
that cleared text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 753fa2fd-8880-4c1a-a335-2e9e1d7ca412

📥 Commits

Reviewing files that changed from the base of the PR and between bab9d46 and ca33469.

📒 Files selected for processing (1)
  • blocksuite/affine/blocks/table/src/table-data-manager.ts

Comment thread blocksuite/affine/blocks/table/src/table-data-manager.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
blocksuite/affine/blocks/table/src/adapters/utils.ts (1)

88-91: Consider preserving the hidden marker for consistency.

The Cell type now declares hidden?: boolean, but the hidden-cell branch doesn't populate it when pushing the empty cell. While current adapters (plain-text.ts and markdown.ts) only read the cell content and ignore the field, preserving it maintains consistency for future adapters that may reference the Cell shape.

Suggested fix
       if (cell?.hidden) {
-        row.cells.push({ value: { delta: [] } });
+        row.cells.push({ value: { delta: [] }, hidden: true });
         return;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/blocks/table/src/adapters/utils.ts` around lines 88 - 91,
When handling hidden cells in adapters/utils.ts (in the branch checking
cell?.hidden), preserve the hidden marker on the pushed empty cell by setting
hidden: true on the object pushed to row.cells (so the shape matches the Cell
type with hidden?: boolean); update the row.cells.push call in that branch
accordingly so future adapters (e.g., plain-text.ts and markdown.ts) can rely on
the hidden flag being present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/blocks/table/src/table-data-manager.ts`:
- Around line 421-430: When re-hiding covered cells you must also clear any
stale merge metadata so a previously merged top-left cell doesn't retain
colSpan/rowSpan after being hidden; update the hide logic that operates on
this.model.props.cells[key] (the block that touches cell.text and cell.hidden)
to normalize merge fields by resetting cell.colSpan and cell.rowSpan back to
their defaults (e.g., 1) or deleting those properties before setting cell.hidden
= true; apply the same change to the analogous block covering lines 437-445 so
both hide paths clear prior merge metadata consistently.

---

Nitpick comments:
In `@blocksuite/affine/blocks/table/src/adapters/utils.ts`:
- Around line 88-91: When handling hidden cells in adapters/utils.ts (in the
branch checking cell?.hidden), preserve the hidden marker on the pushed empty
cell by setting hidden: true on the object pushed to row.cells (so the shape
matches the Cell type with hidden?: boolean); update the row.cells.push call in
that branch accordingly so future adapters (e.g., plain-text.ts and markdown.ts)
can rely on the hidden flag being present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c4861fc-4a01-46d7-8f27-f79435fd308e

📥 Commits

Reviewing files that changed from the base of the PR and between dd59f1b and df72ff0.

📒 Files selected for processing (2)
  • blocksuite/affine/blocks/table/src/adapters/utils.ts
  • blocksuite/affine/blocks/table/src/table-data-manager.ts

Comment thread blocksuite/affine/blocks/table/src/table-data-manager.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/blocks/table/src/table-data-manager.ts`:
- Around line 414-431: The merge logic currently mutates covered cells in-place
without guarding against intersecting existing merged regions; update the
mergeCells flow to detect any covered cell that is an existing merge top-left
(has cell.colSpan or cell.rowSpan) whose span area extends outside the new
selection and reject the merge (or require prior normalization) instead of
silently masking descendants. Concretely: inside the loop that iterates
rows/columns and inspects this.model.props.cells[key], if cell.colSpan ||
cell.rowSpan is present compute the merged span rectangle (using
row.rowId/col.columnId plus cell.rowSpan/colSpan) and check whether that
rectangle is fully contained within the new selection bounds
(rowStartIndex..rowEndIndex and columnStartIndex..columnEndIndex); if it is not,
abort/return an error from mergeCells (or surface a clear rejection) so callers
must normalize/unmerge first, preventing orphaned hidden cells and preserving
table-state integrity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2d86329c-4486-45ea-ae01-3b5896ff7f62

📥 Commits

Reviewing files that changed from the base of the PR and between df72ff0 and faea63b.

📒 Files selected for processing (1)
  • blocksuite/affine/blocks/table/src/table-data-manager.ts

Comment thread blocksuite/affine/blocks/table/src/table-data-manager.ts
@001-mak 001-mak changed the title Table cell merging and row text alignment features feat(editor): Table cell merging and row text alignment features May 3, 2026
@codecov

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 2.59740% with 150 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.63%. Comparing base (4e169ea) to head (e3d1fc6).

Files with missing lines Patch % Lines
...uite/affine/blocks/table/src/table-data-manager.ts 0.00% 102 Missing ⚠️
blocksuite/affine/blocks/table/src/table-cell.ts 8.69% 21 Missing ⚠️
...locksuite/affine/blocks/table/src/adapters/html.ts 10.00% 18 Missing ⚠️
...ocksuite/affine/blocks/table/src/adapters/utils.ts 0.00% 4 Missing ⚠️
blocksuite/affine/blocks/table/src/table-block.ts 0.00% 3 Missing ⚠️
...suite/affine/blocks/table/src/adapters/markdown.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14885      +/-   ##
==========================================
+ Coverage   55.11%   56.63%   +1.52%     
==========================================
  Files        3006     3144     +138     
  Lines      168964   171204    +2240     
  Branches    24860    25265     +405     
==========================================
+ Hits        93128    96966    +3838     
+ Misses      72761    71086    -1675     
- Partials     3075     3152      +77     
Flag Coverage Δ
server-test 78.34% <ø> (+0.01%) ⬆️
unittest 32.50% <2.59%> (+2.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rcky844

rcky844 commented Jul 18, 2026

Copy link
Copy Markdown

FYI Notion has implemented this feature on May 26, 2026:
https://www.notion.com/releases/2026-05-26

It would be great if this feature got some love too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants