fix: route clipboard image pastes to the attachment strip #295

Merged
talksik merged 5 commits from claude/markdown-image-upload-fix-t9ggfa into main 2026-06-21 16:15:21 +00:00
talksik commented 2026-06-21 01:48:28 +00:00 (Migrated from github.com)

Pasting an image while the markdown editor was focused left it stuck on
ProseMirror's inline "uploading" placeholder instead of going to the
attachment strip. The capture-phase paste interceptor bailed before it
could redirect the file because:

  • pasted images usually surface only through clipboardData.items
    (getAsFile), with clipboardData.files left empty, and
  • image pastes often advertise an empty text/plain entry, which the
    old types.includes('text/plain') check mistook for a text paste.

Read files from items (falling back to files), and gate on the actual
text payload rather than the advertised type, so real text pastes
(Excel/Word renditions) still paste as text while pure image pastes
become attachments.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01QtNQeBpkDJPC9obiB25pV6

Summary by CodeRabbit

  • Bug Fixes
    • Improved clipboard paste and drag-and-drop handling to more reliably distinguish text pastes from actual file drops, preventing incorrect attachment behavior.
  • UI Updates
    • Updated the markdown editor’s image behavior by disabling image upload controls.
    • Adjusted editor image rendering to respect width/height limits for more consistent display.
Pasting an image while the markdown editor was focused left it stuck on ProseMirror's inline "uploading" placeholder instead of going to the attachment strip. The capture-phase paste interceptor bailed before it could redirect the file because: - pasted images usually surface only through `clipboardData.items` (`getAsFile`), with `clipboardData.files` left empty, and - image pastes often advertise an *empty* `text/plain` entry, which the old `types.includes('text/plain')` check mistook for a text paste. Read files from `items` (falling back to `files`), and gate on the actual text payload rather than the advertised type, so real text pastes (Excel/Word renditions) still paste as text while pure image pastes become attachments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QtNQeBpkDJPC9obiB25pV6 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved clipboard paste and drag-and-drop handling to more reliably distinguish text pastes from actual file drops, preventing incorrect attachment behavior. * **UI Updates** * Updated the markdown editor’s image behavior by disabling image upload controls. * Adjusted editor image rendering to respect width/height limits for more consistent display. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
coderabbitai[bot] commented 2026-06-21 01:48:37 +00:00 (Migrated from github.com)

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Free

Run ID: 7a10ac15-13a3-4cbf-9414-713f26433f2e

📥 Commits

Reviewing files that changed from the base of the PR and between 372b12f339 and fa88a7c07b.

📒 Files selected for processing (1)
  • js/desktop/src/hooks/use-file-input.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/desktop/src/hooks/use-file-input.ts

📝 Walkthrough

Walkthrough

The PR introduces a shared transferFiles helper that robustly extracts file objects from DataTransfer by prioritizing items with fallback to files. The useFileInput hook's clipboard paste handler now distinguishes file payloads from text pastes using content-based text detection (getData('text/plain')), while both paste and drop handlers leverage transferFiles for file extraction. The markdown editor disables image file uploads by turning off Crepe.Feature.ImageBlock, adds an explicit drop handler to decline dropped files, and updates CSS to constrain rendered image height to 420px.

Changes

Compose Editor File and Image Handling

Layer / File(s) Summary
Shared file transfer helper
js/desktop/src/lib/data-transfer.ts
Exports transferFiles(data: DataTransfer): File[] that extracts file objects by filtering data.items entries of kind 'file' via getAsFile() with null filtering, falling back to Array.from(data.files) when no items are found.
File input paste and drop handlers
js/desktop/src/hooks/use-file-input.ts
Updates paste handler to detect text pastes via getData('text/plain').trim().length instead of checking advertised types, uses transferFiles for file extraction, and only prevents default/propagation when files are found. Drop handler similarly refactored to use transferFiles instead of direct Array.from(dataTransfer.files).
Markdown editor image upload disabling and rendering
js/desktop/src/features/compose/markdown-editor.tsx, js/desktop/src/features/compose/markdown-editor.css
Disables Crepe.Feature.ImageBlock feature flag to prevent image file uploads while allowing image links to render, adds explicit drop handler using transferFiles to decline dropped files, and replaces prior CSS that hid image uploader controls with a new rule constraining images in .llink-crepe .milkdown .ProseMirror img to max-height: 420px.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A helper now handles all files with great care,
Detecting true text when it's truly in there.
Images upload-free, yet still render with grace,
With height-bound constraints in their rightful place.
Drop handlers decline with composure and flair!


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

<!-- This is an auto-generated comment: summarize by coderabbit.ai --> <!-- review_stack_entry_start --> [![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/flowy-live/llink/pull/295?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <details> <summary>ℹ️ Recent review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Free **Run ID**: `7a10ac15-13a3-4cbf-9414-713f26433f2e` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 372b12f339d78737e13da26bb27a3be03c8270d0 and fa88a7c07b4650520c3800164c23d0cd31816b93. </details> <details> <summary>📒 Files selected for processing (1)</summary> * `js/desktop/src/hooks/use-file-input.ts` </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary> * js/desktop/src/hooks/use-file-input.ts </details> </details> --- <!-- walkthrough_start --> <details> <summary>📝 Walkthrough</summary> ## Walkthrough The PR introduces a shared `transferFiles` helper that robustly extracts file objects from `DataTransfer` by prioritizing `items` with fallback to `files`. The `useFileInput` hook's clipboard paste handler now distinguishes file payloads from text pastes using content-based text detection (`getData('text/plain')`), while both paste and drop handlers leverage `transferFiles` for file extraction. The markdown editor disables image file uploads by turning off `Crepe.Feature.ImageBlock`, adds an explicit drop handler to decline dropped files, and updates CSS to constrain rendered image height to 420px. ## Changes **Compose Editor File and Image Handling** | Layer / File(s) | Summary | |---|---| | **Shared file transfer helper** <br> `js/desktop/src/lib/data-transfer.ts` | Exports `transferFiles(data: DataTransfer): File[]` that extracts file objects by filtering `data.items` entries of kind `'file'` via `getAsFile()` with null filtering, falling back to `Array.from(data.files)` when no items are found. | | **File input paste and drop handlers** <br> `js/desktop/src/hooks/use-file-input.ts` | Updates paste handler to detect text pastes via `getData('text/plain').trim().length` instead of checking advertised types, uses `transferFiles` for file extraction, and only prevents default/propagation when files are found. Drop handler similarly refactored to use `transferFiles` instead of direct `Array.from(dataTransfer.files)`. | | **Markdown editor image upload disabling and rendering** <br> `js/desktop/src/features/compose/markdown-editor.tsx`, `js/desktop/src/features/compose/markdown-editor.css` | Disables `Crepe.Feature.ImageBlock` feature flag to prevent image file uploads while allowing image links to render, adds explicit drop handler using `transferFiles` to decline dropped files, and replaces prior CSS that hid image uploader controls with a new rule constraining images in `.llink-crepe .milkdown .ProseMirror img` to `max-height: 420px`. | ## Estimated code review effort 🎯 3 (Moderate) | ⏱️ ~20 minutes ## Poem > 🐇 A helper now handles all files with great care, > Detecting true text when it's truly in there. > Images upload-free, yet still render with grace, > With height-bound constraints in their rightful place. > Drop handlers decline with composure and flair! ✨ </details> <!-- walkthrough_end --> <!-- tips_start --> --- > [!NOTE] > <details> > <summary>🎁 Summarized by CodeRabbit Free</summary> > > Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting <https://app.coderabbit.ai/login>. > > </details> <sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> <!-- tips_end -->
Sign in to join this conversation.