Skip to content

fix: backslash newlines when copying from a code block#2709

Merged
nperez0111 merged 3 commits into
mainfrom
feat/fix-codeblock-copy
May 15, 2026
Merged

fix: backslash newlines when copying from a code block#2709
nperez0111 merged 3 commits into
mainfrom
feat/fix-codeblock-copy

Conversation

@nperez0111
Copy link
Copy Markdown
Contributor

@nperez0111 nperez0111 commented May 5, 2026

Summary

Copying content from inside a code block produced a clipboard payload littered with backslashes — every newline in text/plain was prefixed with \ (markdown's hard-break syntax), and text/html had <br> separators inside the code instead of literal newlines.

Reproduction: select code in a code block → paste into any plain-text app:

{\
abc: '34\
\
}

This PR fixes the root cause so both text/html and text/plain come out clean.

Rationale

Tracing the clipboard pipeline showed two compounding issues:

  1. Inline-only export path was used for code-block selections. When the selection lives inside a single block, copyExtension takes a shortcut that strips the block wrapper — producing inline HTML for cleaner pastes. For paragraphs that's right; for code blocks it's wrong, because the <pre><code> wrapper carries semantic meaning. Without it, the markdown converter has no idea this is code and treats every line break as a paragraph hard-break (\ + newline).

  2. serializeInlineContentExternalHTML re-introduced <br> for code content. It calls inlineContentToNodes without a blockType, which makes it split every \n into a hardBreak PM node. Those serialize as <br> — even when wrapped in <pre><code>. The internal-HTML serializer already passed blockType through; the external one didn't.

Changes

  • copyExtension.ts — guard the inline-only optimization with a parent.type.spec.code check. Code-block selections fall through to the existing block-export path, which invokes the code block's own toExternalHTML (proper <pre><code class="language-X" data-language="X">).
  • serializeBlocksExternalHTML.ts — plumb blockType through serializeInlineContentExternalHTML (via options.blockType) so inlineContentToNodes keeps \n as literal text for code-content blocks. serializeBlock passes block.type.
  • tests/.../exportTestExecutors.tsprettify(..., { ignore: ["code"] }) so code-block snapshots show real newlines instead of having them collapsed by the prettifier.
  • tests/.../codeBlockMarkdown.test.ts — three regression tests: full code-block copy, partial selection within a code block, and a paragraph-copy guard to prove non-code paths are untouched.
  • Snapshots: 3 code-block HTML snapshots regenerated to show literal newlines (no more <br /> inside <pre><code>).

Impact

After the fix, for the user's repro:

  • text/html: `
    {
    abc: '34

}` — proper code semantics, real newlines.

  • text/plain: clean fenced markdown block, no backslashes.

No behavior change for non-code blocks. The inline-only optimization for paragraphs, headings, etc. is preserved exactly.

Testing

  • Added 3 regression tests in tests/src/unit/core/clipboard/copy/codeBlockMarkdown.test.ts (full code-block copy, partial selection, paragraph guard).
  • Updated 3 code-block HTML snapshots (now show literal newlines instead of <br />).
  • All 430 core unit tests pass.
  • All 49 clipboard tests (copy, paste, copyPaste, copyPasteEquality) pass — confirming no regressions in paste behavior.
  • The pre-existing failures in src/unit/nextjs/serverUtil and src/unit/react/BlockNoteView*Remount* are unrelated (fail on main too).

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature — N/A, this is a bug fix with no API change.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of code block content when copying or exporting selections within code blocks, ensuring raw text formatting is properly preserved instead of converting through intermediate HTML.
    • Enhanced block type information tracking during export serialization for more accurate content handling.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview May 13, 2026 8:48am
blocknote-website Ready Ready Preview May 13, 2026 8:48am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

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: f7cacd3b-6cbd-4866-a814-46532df4f11e

📥 Commits

Reviewing files that changed from the base of the PR and between eb3498b and ec02d44.

📒 Files selected for processing (2)
  • packages/core/src/api/clipboard/toClipboard/copyExtension.ts
  • packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts

📝 Walkthrough

Walkthrough

Code block selections in clipboard operations now bypass HTML-to-markdown conversion and use raw text directly. HTML serialization pipelines thread block type metadata through to enable this, and test helpers are configured to properly validate code element handling.

Changes

Code block clipboard and export handling

Layer / File(s) Summary
Block type threading in HTML serialization
packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
serializeInlineContentExternalHTML accepts blockType in options and forwards it to inlineContentToNodes. Call site in serializeBlock includes the originating block type in serialization options.
Code block markdown handling in clipboard
packages/core/src/api/clipboard/toClipboard/copyExtension.ts
selectedFragmentToHTML detects selections within code blocks and uses raw selected text for markdown instead of HTML-to-markdown conversion when selection stays within a code block (meta.code === true).
Test configuration for code element handling
tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts
Shared PRETTIFY_OPTIONS constant wraps tags and ignores <code> elements. Test export helpers use this configuration for consistent code element validation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • TypeCellOS/BlockNote#2688: Adds regression tests for non-editable-block copy/paste behavior, closely related to clipboard serialization changes in this PR.

Poem

🐰 With code blocks now handled with care,
The clipboard knows what's there—
Raw text stays pure, no HTML guise,
Type metadata's our prize!
Block-aware exports rise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: backslash newlines when copying from a code block' directly and clearly describes the main bug being fixed in the changeset.
Description check ✅ Passed The PR description is comprehensive and covers all template sections: Summary, Rationale, Changes, Impact, Testing, and Checklist. All required information is present and well-detailed.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fix-codeblock-copy

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.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 5, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2709

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2709

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2709

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2709

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2709

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2709

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2709

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2709

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2709

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2709

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2709

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2709

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2709

commit: ec02d44

Copy link
Copy Markdown
Collaborator

@matthewlipski matthewlipski left a comment

Choose a reason for hiding this comment

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

Looks good, agree that copying within code blocks should preserve the block HTML, rather than just the inline HTML. Though I'm seeing different behaviour on main to what you describe. Given this code block:

const a = 1;
const b = 2;

This is the plain text I see get pasted:

const a = 1; const b = 2;

Whereas if I understand correctly, you were seeing this:

const a = 1;\n
const b = 2;

Any idea what's up with that?

@nperez0111
Copy link
Copy Markdown
Contributor Author

@matthewlipski, it depends on where you paste the content after.

But, on main, this is what is on my clipboard:
image

On this branch, this is on my clipboard:
image

@nperez0111
Copy link
Copy Markdown
Contributor Author

^ just realizing the code fence will mess this up

nperez0111 and others added 3 commits May 13, 2026 10:08
When copying inline content from inside a code block, the text/plain
clipboard payload had a backslash before every newline (markdown's
hard-break syntax) and the text/html had `<br>` separators inside the
code instead of literal newlines.

Two changes fix this:

- copyExtension routes selections inside a code block through the
  block-export path so the code block's own toExternalHTML produces
  the proper `<pre><code>` wrapper.
- serializeInlineContentExternalHTML now plumbs blockType through to
  inlineContentToNodes (mirroring the internal HTML serializer) so
  `\n` in code-block content stays as literal text instead of being
  split into hardBreak nodes that render as `<br>`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`as const` typed `ignore` as a readonly tuple, which doesn't match
`UserConfig.ignore: string[]`, breaking CI typecheck.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address PR review:
- Drop standalone codeBlockMarkdown.test.ts; add `codeBlockFullContent` and
  `codeBlockPartialSelection` cases to copyTestInstances.ts and snapshot
  text/plain markdown for all copy test instances via a new
  `Copy tests (Markdown)` describe block (mirrors the export test pattern).
- Trim verbose comments in copyExtension and serializeBlocksExternalHTML.
- copyPasteEquality executor now passes the actual markdown payload as the
  text/plain MIME instead of a literal "text" placeholder, so paste handlers
  that prefer text/plain (e.g. inside code blocks) round-trip correctly.
- Update mixedInParagraph snapshots: prettify's `ignore: ["code"]` preserves
  trailing whitespace inside `<code>` spans, which is the actual HTML output.
@nperez0111
Copy link
Copy Markdown
Contributor Author

OK, here is the output now (for a text selection within the code block):
image

And here is for a node selection on the code block:
image

So, this looks corrected to me now

@nperez0111 nperez0111 merged commit c255558 into main May 15, 2026
21 of 23 checks passed
@nperez0111 nperez0111 deleted the feat/fix-codeblock-copy branch May 15, 2026 08:06
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.

2 participants