Skip to content

Add external table paste support (HTML and markdown)#161

Merged
hackerwins merged 5 commits into
mainfrom
feat/external-table-paste
Apr 26, 2026
Merged

Add external table paste support (HTML and markdown)#161
hackerwins merged 5 commits into
mainfrom
feat/external-table-paste

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add parseHtmlTableToTableCells() to convert pasted HTML <table> elements (from Google Docs, Sheets, web browsers) into doc table blocks
  • Add parseMarkdownTableToTableCells() to convert pasted markdown table syntax (| col | col |) into doc table blocks
  • Update handlePaste flow to try table parsers before existing fallbacks
  • Update Phase 3 section in docs-table-copy-paste.md design doc

This implements Phase 3 of the docs table copy-paste design.

Test plan

  • Paste an HTML table from Google Sheets → should create a table block
  • Paste an HTML table from a web page → should create a table block with inline formatting preserved
  • Paste a markdown table (header + separator + rows) → should create a table block
  • Paste mixed HTML (paragraph + table) → should fall through to block paste, not table
  • Paste plain text that is not a markdown table → should insert as plain text
  • Paste a markdown table into an existing table cell → should fill cells from cursor position
  • Shift+paste HTML table → should insert as plain text (shift forces plain text)
  • 21 unit tests added for both parsers (edge cases, formatting, ragged rows, rejection)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Users can now paste tables from external HTML and markdown sources directly into the editor with automatic format detection and proper structure preservation.
  • Documentation

    • Updated design documentation to describe external table paste behavior and parsing specifications.
  • Tests

    • Added comprehensive test coverage for table parsing from multiple formats and edge cases.

Pasting HTML tables from Google Docs/Sheets/web browsers and markdown
tables from text editors now inserts a proper table block instead of
plain text. This implements Phase 3 of the docs-table-copy-paste
design.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hackerwins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 34 minutes and 8 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 34 minutes and 8 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6492b65a-25cb-4cc3-af9b-fe36966103e7

📥 Commits

Reviewing files that changed from the base of the PR and between aa94b0a and e44a182.

📒 Files selected for processing (4)
  • docs/design/docs/docs-table-copy-paste.md
  • packages/docs/src/view/clipboard.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/view/clipboard.test.ts
📝 Walkthrough

Walkthrough

The pull request adds support for pasting external HTML and markdown tables into the document editor. It introduces table parsing functions for both HTML and markdown formats, updates the paste control flow to detect and handle tables before other content types, and includes comprehensive test coverage for the new parsing logic.

Changes

Cohort / File(s) Summary
Documentation
docs/design/docs/docs-table-copy-paste.md
Updated design doc to include Phase 3 scope and added new section describing external table paste behavior with clipboard parsing functions for HTML <table> and markdown pipe syntax, updated handlePaste flow to prioritize table parsing.
Table Parsing Logic
packages/docs/src/view/clipboard.ts
Added parseHtmlTableToTableCells() and parseMarkdownTableToTableCells() functions to convert HTML/markdown table formats into TableCell[][]. Introduced shared inline-extraction logic (collectInlines) and cell-construction helper (makeCellFromInlines) for standardized cell creation with optional styling support.
Editor Integration
packages/docs/src/view/text-editor.ts
Modified handlePaste() to prioritize table parsing: attempts parseHtmlTableToTableCells for text/html and parseMarkdownTableToTableCells for text/plain, inserting table cells directly on success while maintaining save/delete/render sequencing.
Unit Tests
packages/docs/test/view/clipboard.test.ts
Added comprehensive test coverage for HTML table parsing (structure validation, inline styles, headers, row padding, link handling) and markdown table parsing (format validation, row padding, alignment separators, invalid input handling).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant TextEditor as text-editor.ts
    participant Parser as clipboard.ts
    participant Renderer

    User->>TextEditor: Paste with table data
    
    alt text/html content
        TextEditor->>Parser: parseHtmlTableToTableCells(html)
        Parser-->>TextEditor: TableCell[][] or null
        alt Table detected
            TextEditor->>TextEditor: saveSnapshot()
            TextEditor->>TextEditor: deleteSelection()
            TextEditor->>TextEditor: pasteTableCells(cells)
            TextEditor->>Renderer: requestRender()
            TextEditor-->>User: Table inserted
        end
    else text/plain fallback
        TextEditor->>Parser: parseMarkdownTableToTableCells(text)
        Parser-->>TextEditor: TableCell[][] or null
        alt Table detected
            TextEditor->>TextEditor: saveSnapshot()
            TextEditor->>TextEditor: deleteSelection()
            TextEditor->>TextEditor: pasteTableCells(cells)
            TextEditor->>Renderer: requestRender()
            TextEditor-->>User: Table inserted
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hop-hop, paste a table true!
HTML, Markdown, whatever you do,
From clipboard to canvas, cells align just right,
No more plain-text scramble—your tables take flight! ✨📋

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add external table paste support (HTML and markdown)' clearly and specifically summarizes the main change: enabling users to paste HTML and markdown tables from external sources, which is the primary focus of all file modifications.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/external-table-paste

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 and usage tips.

@github-actions

github-actions Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 122.5s

Lane Status Duration
sheets:build ✅ pass 13.7s
docs:build ✅ pass 8.4s
verify:fast ✅ pass 61.4s
frontend:build ✅ pass 15.9s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.8s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 16.1s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Apr 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.79181% with 68 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/docs/src/view/text-editor.ts 0.00% 40 Missing ⚠️
packages/docs/src/view/clipboard.ts 88.93% 24 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/docs/src/view/clipboard.ts (1)

379-396: Tables from Google Sheets/Docs will be rejected due to wrapper elements in clipboard HTML.

Real-world clipboards (Google Sheets, Google Docs) embed tables within additional markup — including <style> blocks, data attributes, and nested structural elements — rather than placing <table> as a direct child of <body>. The current implementation iterates only over doc.body.childNodes, so wrapped tables will be rejected even when the content is pure table data. This impacts compatibility with major sources.

If broader clipboard compatibility is desired, either:

  • Walk only the siblings of the detected <table> to check for meaningful non-table content (rather than checking all body children), or
  • Find the minimal container that holds the table and check only its siblings

This may be intentional if the design requires "pure table only" at the body level, but it narrows compatibility with common real-world clipboard sources.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/view/clipboard.ts` around lines 379 - 396, The current
check iterates over doc.body.childNodes and rejects clipboard tables wrapped by
extra markup (e.g., Google Sheets/Docs); modify the logic so it only inspects
the table's surrounding context instead of every body child: locate the detected
table (the variable table) and either walk its sibling nodes or determine the
minimal container element that contains the table and iterate only over that
container's childNodes/siblings to look for meaningful non-table content,
keeping the same text-content checks and allowed tag list
('meta','style','br','colgroup') so wrapped-but-pure-table clipboard HTML is
accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/docs/src/view/clipboard.ts`:
- Around line 398-417: The current traversal uses table.querySelectorAll('tr')
which includes nested-table rows; update the row selection to only direct row
containers so nested tables don't leak: replace uses of
table.querySelectorAll('tr') (in the block that builds rows/rows.push) with a
scoped selector that targets only immediate children like ':scope > tr, :scope >
thead > tr, :scope > tbody > tr' (or equivalently iterate table.children and
then their tr children) so that collectInlines and makeCellFromInlines process
only the outer table's cell content and nested-table rows are not re-emitted.

In `@packages/docs/src/view/text-editor.ts`:
- Around line 825-834: The Markdown-table parsing branch runs regardless of
Shift key state causing Shift+paste to still convert Markdown tables; guard the
markdown path with the same shift check as the HTML branch by skipping
parseMarkdownTableToTableCells when this.shiftHeld is true. In practice, wrap
the existing mdTableCells logic (the call to parseMarkdownTableToTableCells and
the subsequent this.saveSnapshot(), this.deleteSelection(),
this.pasteTableCells(mdTableCells), this.selection.setRange(null),
this.requestRender(), return) in an if (!this.shiftHeld && mdTableCells) check
(or check this.shiftHeld before calling parseMarkdownTableToTableCells) so
Shift+paste inserts plain text instead of creating a table.

---

Nitpick comments:
In `@packages/docs/src/view/clipboard.ts`:
- Around line 379-396: The current check iterates over doc.body.childNodes and
rejects clipboard tables wrapped by extra markup (e.g., Google Sheets/Docs);
modify the logic so it only inspects the table's surrounding context instead of
every body child: locate the detected table (the variable table) and either walk
its sibling nodes or determine the minimal container element that contains the
table and iterate only over that container's childNodes/siblings to look for
meaningful non-table content, keeping the same text-content checks and allowed
tag list ('meta','style','br','colgroup') so wrapped-but-pure-table clipboard
HTML is accepted.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d4cb9e2a-1f69-4f2d-8ca3-08d9b8581b18

📥 Commits

Reviewing files that changed from the base of the PR and between fb7a831 and aa94b0a.

📒 Files selected for processing (4)
  • docs/design/docs/docs-table-copy-paste.md
  • packages/docs/src/view/clipboard.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/view/clipboard.test.ts

Comment thread packages/docs/src/view/clipboard.ts
Comment thread packages/docs/src/view/text-editor.ts Outdated
hackerwins and others added 4 commits April 26, 2026 12:50
When pasting a full markdown document containing tables, table regions
are now converted to table blocks while surrounding text becomes
paragraph blocks.  Previously only a pure markdown table (with no
other content) was recognized.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Two fixes for paste from rendered markdown sources (e.g. Claude chat):

1. parseHtmlToBlocks now handles <table> elements inline, converting
   them to table blocks within the block array.  Previously tables
   were only recognized when they were the sole clipboard content.

2. Skip whitespace-only text nodes between block-level siblings
   (e.g. newlines between <li> tags inside <ul>).  These produced
   spurious empty paragraphs between list items.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Address code review feedback:

1. Use scoped :scope selectors for <tr> queries to avoid matching
   rows from nested tables inside cells.

2. Guard markdown table parsers with shiftHeld check so that
   shift+paste forces plain-text insertion consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Reflect the actual implementation: parseHtmlToBlocks handles <table>
inline, scoped selectors for nested tables, mixed markdown support
via parseMarkdownWithTables, shift+paste bypass, and whitespace
handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit 2683811 into main Apr 26, 2026
4 checks passed
@hackerwins
hackerwins deleted the feat/external-table-paste branch April 26, 2026 04:15
@hackerwins hackerwins mentioned this pull request May 1, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant