Add external table paste support (HTML and markdown)#161
Conversation
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]>
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Verification: verify:selfResult: ✅ PASS in 122.5s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
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 overdoc.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
📒 Files selected for processing (4)
docs/design/docs/docs-table-copy-paste.mdpackages/docs/src/view/clipboard.tspackages/docs/src/view/text-editor.tspackages/docs/test/view/clipboard.test.ts
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]>
Summary
parseHtmlTableToTableCells()to convert pasted HTML<table>elements (from Google Docs, Sheets, web browsers) into doc table blocksparseMarkdownTableToTableCells()to convert pasted markdown table syntax (| col | col |) into doc table blockshandlePasteflow to try table parsers before existing fallbacksdocs-table-copy-paste.mddesign docThis implements Phase 3 of the docs table copy-paste design.
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests