Skip to content

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

Open
nperez0111 wants to merge 2 commits intomainfrom
feat/fix-codeblock-copy
Open

fix: backslash newlines when copying from a code block#2709
nperez0111 wants to merge 2 commits intomainfrom
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

    • Copying and exporting now correctly preserve code-block formatting and language metadata: exported HTML uses proper pre/code wrappers with language attributes and exported Markdown uses fenced code blocks without escaped newlines. Selections that are inline within non-code blocks are handled without stripping surrounding inline content, preventing accidental loss of formatting.
  • Tests

    • Added regression tests to verify copy/export behavior for code and non-code blocks.

@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 5, 2026 3:26pm
blocknote-website Ready Ready Preview May 5, 2026 3:26pm

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: d79429ea-ee3e-4712-a2e8-d0ed362eb51f

📥 Commits

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

⛔ Files ignored due to path filters (3)
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html is excluded by !**/__snapshots__/**
  • tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html is excluded by !**/__snapshots__/**
📒 Files selected for processing (4)
  • packages/core/src/api/clipboard/toClipboard/copyExtension.ts
  • packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
  • tests/src/unit/core/clipboard/copy/codeBlockMarkdown.test.ts
  • tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts
✅ Files skipped from review due to trivial changes (1)
  • tests/src/unit/core/clipboard/copy/codeBlockMarkdown.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts
  • packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
  • packages/core/src/api/clipboard/toClipboard/copyExtension.ts

📝 Walkthrough

Walkthrough

Determines when a selection is fully inline and whether its parent block is a code block; if inside a code block, inline wrapper-stripping is disabled. The originating block type is passed into inline HTML serialization. Tests added for code-block copy HTML/Markdown and prettify options adjusted for code preservation.

Changes

Code Block Copy Preservation

Layer / File(s) Summary
Data Shape / API
packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
serializeInlineContentExternalHTML signature extended: options?: { document?: Document; blockType?: string }.
Core Logic: Selection Analysis
packages/core/src/api/clipboard/toClipboard/copyExtension.ts
fragmentToExternalHTML now computes isFullyInline and parentIsCode and sets isWithinBlockContent = isFullyInline && !parentIsCode, changing when inline-only wrapper-stripping runs.
Core Logic: Serialization
packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts
Calls to serializeInlineContentExternalHTML forward { ...options, blockType: block.type } so inline conversion can be block-type-aware (newline/code handling).
Test Helpers / Snapshots
tests/src/unit/shared/formatConversion/export/exportTestExecutors.ts
Introduced PRETTIFY_OPTIONS = { tag_wrap: true, ignore: ["code"] } and replaced inline prettify options in HTML test helpers to preserve <code> content.
Behavioral Tests
tests/src/unit/core/clipboard/copy/codeBlockMarkdown.test.ts
Added tests: copying from a codeBlock produces <pre><code> external HTML with language attributes and raw newlines and yields fenced code block markdown; copying from a paragraph remains unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • matthewlipski

Poem

🐰 I hopped through fragments, sniffed each line,
Found code kept snug where fences shine.
I left the newlines, wrapped the tongue,
Copied code sings — a carrot-song sprung.

🚥 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 specifically describes the bug being fixed—backslashes appearing in clipboard content when copying from code blocks—which is the main focus of the changeset.
Description check ✅ Passed The PR description comprehensively covers all required template sections: Summary explains the bug, Rationale details root causes, Changes lists code modifications, Impact describes outcome, Testing documents verification, and Checklist is completed with appropriate N/A justification.
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: eb3498b

nperez0111 and others added 2 commits May 5, 2026 17:21
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>
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