Skip to content

Improve editor paste handling#358

Merged
appflowy merged 1 commit into
mainfrom
wd/copy-paste-fragment-detector
May 21, 2026
Merged

Improve editor paste handling#358
appflowy merged 1 commit into
mainfrom
wd/copy-paste-fragment-detector

Conversation

@appflowy
Copy link
Copy Markdown
Contributor

@appflowy appflowy commented May 21, 2026

Summary

  • auto-detect unfenced Mermaid fragments when pasting plain text
  • split nested HTML clipboard lines into readable paragraphs
  • scope select-all to editor/title content and render Mermaid blocks as diagrams

Tests

  • pnpm jest src/components/editor/parsers/tests src/components/view-meta/tests/TitleEditable.test.tsx --runInBand
  • pnpm exec tsc --noEmit --project tsconfig.web.json --pretty false

Summary by Sourcery

Improve paste handling and selection behavior in the editor and title input, and render Mermaid code blocks as diagrams instead of raw code.

New Features:

  • Auto-detect Mermaid diagrams in plain-text pastes and convert them into Mermaid code blocks while preserving surrounding content.
  • Split pasted HTML paragraphs with nested divs or line breaks into multiple readable paragraph blocks with preserved inline formatting.
  • Scope select-all behavior to the editor or title content instead of the entire document, including in code blocks and the title field.
  • Render Mermaid code blocks as diagram components while visually hiding the underlying code text in the editor.

Enhancements:

  • Normalize and clean pasted plain text by removing invisible markers and trimming empty lines before converting them into paragraph blocks.

Tests:

  • Add unit tests covering Mermaid fragment detection in plain-text paste, including mixed prose, Markdown, tables, fenced code, Windows line endings, and indentation handling.
  • Add unit tests for splitting HTML clipboard content into multiple paragraph blocks and preserving inline formats when doing so.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 21, 2026

Reviewer's Guide

Enhances editor paste behavior by detecting unfenced Mermaid diagrams in plain-text paste, splitting complex HTML/clipboard content into multiple paragraph blocks with preserved inline formatting, scoping select-all behavior for titles and code blocks, and rendering Mermaid code blocks as diagrams instead of visible code text.

Sequence diagram for plain-text Mermaid paste detection

sequenceDiagram
  actor User
  participant Editor
  participant WithPasted as handlePlainTextPaste
  participant FragmentParser as parsePlainTextFragments
  participant MermaidDetector as parseFragmentChunk
  participant MermaidFragment as parseMermaidFragment

  User->>Editor: paste plain text
  Editor->>WithPasted: handlePlainTextPaste(text)
  WithPasted->>FragmentParser: parsePlainTextFragments(text)
  FragmentParser->>MermaidDetector: parseFragmentChunk(chunks, index)
  MermaidDetector->>MermaidFragment: parseMermaidFragment(chunks, startIndex)
  MermaidFragment-->>MermaidDetector: PasteFragmentMatch(CodeBlock mermaid)
  MermaidDetector-->>FragmentParser: PasteFragmentMatch
  FragmentParser-->>WithPasted: ParsedBlock[]
  WithPasted->>Editor: insertParsedBlocks(editor, blocks)
Loading

Flow diagram for rendering Mermaid code blocks as diagrams

flowchart TD
  A[CodeBlock render] --> B{language === mermaid}
  B -- No --> C["Render <code>{children}</code>"]
  B -- Yes --> D[Set isMermaid]
  D --> E["Render <code> with hidden styles"]
  D --> F[Render MermaidChat node]
  C & E & F --> G[User sees diagram or code]
Loading

File-Level Changes

Change Details Files
Split complex HTML clipboard paragraphs into multiple paragraph blocks while preserving inline styles and handling divider lines.
  • Introduced paragraph segment extraction that walks HAST elements, tracks active inline formats (including style attributes), and splits segments on
    and paragraph-boundary tags.
  • Adjusted paragraph parsing to return either a single ParsedBlock or an array of blocks based on extracted segments, with special handling for '---' dividers and invisible paste markers.
  • Extended unit tests to cover splitting of
    -separated lines, nested
    structures, and preservation of bold/italic inline formats when splitting.
src/components/editor/parsers/block-converters.ts
src/components/editor/parsers/__tests__/block-converters.test.ts
Auto-detect unfenced Mermaid fragments in plain-text paste and integrate them with existing markdown/TSV parsing.
  • Added a generic paste fragment detection pipeline with chunking, fragment detectors, and plain-text fallback parsing.
  • Implemented a Mermaid fragment detector that recognizes various Mermaid diagram starts, supports preamble lines, dedents content, and emits CodeBlock ParsedBlocks with language 'mermaid'.
  • Updated plain-text paste handling to run fragment detection before markdown detection, normalizing lines and preserving non-Mermaid content as paragraphs, markdown blocks, TSV tables, or code blocks.
  • Added comprehensive tests for Mermaid fragment detection across different diagram types, prose/diagram mixing, fenced/unfenced diagrams, TSV and markdown coexistence, and Windows line endings.
src/components/editor/parsers/paste-fragment-detectors.ts
src/components/editor/parsers/__tests__/paste-fragment-detectors.test.ts
src/components/editor/plugins/withPasted.ts
Render Mermaid code blocks as diagrams and hide their raw code in the editor.
  • Detected Mermaid language in the CodeBlock component and conditionally hid the text via visually-hidden styles while keeping it in the DOM.
  • Rendered MermaidChat for Mermaid language blocks within a Suspense boundary to show diagrams instead of plain code.
src/components/editor/components/blocks/code/Code.tsx
Refine select-all behavior to scope it to the current editable region and support title editing.
  • Added a helper to select all content in a contentEditable title div and wired Cmd/Ctrl+A within the TitleEditable keydown handler to select only the title text.
  • Changed the editor-wide SELECT_ALL shortcut to always prevent default, select only the active code block when applicable, and otherwise select the entire editor document range.
src/components/view-meta/TitleEditable.tsx
src/components/editor/shortcut.hooks.ts
src/components/view-meta/__tests__/TitleEditable.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new parseParagraph signature returning ParsedBlock | ParsedBlock[] complicates usage and forces call sites/tests to handle unions; consider either always returning an array or extracting the paragraph-splitting behavior into a separate helper so the core API stays consistent.
  • In parsePlainTextFragments, you first scan chunks.some with parseFragmentChunk and then immediately re-run parseFragmentChunk in the main loop, effectively doing the fragment parsing work twice; you could fold the initial detection into the main loop and track whether any fragment was seen to avoid redundant parsing.
  • There are multiple places removing the BOM (\uFEFF) from text (e.g., removeInvisiblePasteMarkers, handleMultiLinePlainText); reusing the helper everywhere would make the behavior consistent and reduce duplication.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `parseParagraph` signature returning `ParsedBlock | ParsedBlock[]` complicates usage and forces call sites/tests to handle unions; consider either always returning an array or extracting the paragraph-splitting behavior into a separate helper so the core API stays consistent.
- In `parsePlainTextFragments`, you first scan `chunks.some` with `parseFragmentChunk` and then immediately re-run `parseFragmentChunk` in the main loop, effectively doing the fragment parsing work twice; you could fold the initial detection into the main loop and track whether any fragment was seen to avoid redundant parsing.
- There are multiple places removing the BOM (`\uFEFF`) from text (e.g., `removeInvisiblePasteMarkers`, `handleMultiLinePlainText`); reusing the helper everywhere would make the behavior consistent and reduce duplication.

## Individual Comments

### Comment 1
<location path="src/components/editor/parsers/block-converters.ts" line_range="51-55" />
<code_context>
+  'ul',
+]);
+
+function parseInlineStyle(style: string): Record<string, string> {
+  const styles: Record<string, string> = {};
+
+  style.split(';').forEach((part) => {
+    const [key, value] = part.split(':');
+
+    if (key && value) {
</code_context>
<issue_to_address>
**issue (bug_risk):** Limit the split on CSS declarations to handle values containing `:`.

`part.split(':')` discards everything after the first colon, so values containing `:` (e.g. URLs, data URIs, or `content: '12:34'`) are truncated. To preserve the full value while still extracting the property name, limit the split to two parts, e.g.:

```ts
const [key, value] = part.split(':', 2); // or part.split(/:(.+)/)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +51 to +55
function parseInlineStyle(style: string): Record<string, string> {
const styles: Record<string, string> = {};

style.split(';').forEach((part) => {
const [key, value] = part.split(':');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Limit the split on CSS declarations to handle values containing :.

part.split(':') discards everything after the first colon, so values containing : (e.g. URLs, data URIs, or content: '12:34') are truncated. To preserve the full value while still extracting the property name, limit the split to two parts, e.g.:

const [key, value] = part.split(':', 2); // or part.split(/:(.+)/)

@appflowy appflowy merged commit 9704635 into main May 21, 2026
12 of 13 checks passed
@appflowy appflowy deleted the wd/copy-paste-fragment-detector branch May 21, 2026 07:09
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