-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Fix image paste failures by prioritizing clipboard image files over blob URLs #14009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Fix image paste failures by prioritizing clipboard image files over blob URLs #14009
Conversation
Add paste handler to insert image files from clipboard
WalkthroughA paste event handler is introduced to intercept clipboard paste events, detect image files, and route them to the file insertion command system instead of allowing default paste behavior. Event listener registration and cleanup are managed through the component lifecycle hooks. Changes
Sequence DiagramsequenceDiagram
participant User
participant Browser
participant LitHost
participant CommandSystem
participant Editor
User->>Browser: Paste (Ctrl+V)
Browser->>LitHost: paste event
LitHost->>LitHost: Detect image in clipboard
alt Image Found
LitHost->>LitHost: Prevent default behavior
LitHost->>CommandSystem: Trigger editor:insert-file
CommandSystem->>Editor: Insert image
else No Image
LitHost->>Browser: Allow default paste
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15–25 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
blocksuite/framework/std/src/view/element/lit-host.ts (1)
121-122: Consider whetherstopPropagation()is necessary.Using both
preventDefault()andstopPropagation()prevents other paste event handlers (e.g., from widgets or browser extensions) from processing the event. While this ensures the image file is handled exclusively by your logic, it may interfere with other components that rely on paste events.If you intend to prevent all other paste handling when an image file is detected, the current implementation is correct. Otherwise, consider removing
stopPropagation():if (imageFile) { e.preventDefault(); - e.stopPropagation(); try {Please confirm this is the intended behavior based on your testing.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
blocksuite/framework/std/src/view/element/lit-host.ts(2 hunks)
🔇 Additional comments (2)
blocksuite/framework/std/src/view/element/lit-host.ts (2)
161-161: Event listener registration looks good.The paste event listener is properly registered on the EditorHost element in
connectedCallback, which ensures it only intercepts paste events within the editor context. The arrow function syntax for_handlePastecorrectly preserves thethisbinding.
167-167: Event listener cleanup is properly implemented.The event listener is correctly removed in
disconnectedCallbackusing the same function reference, preventing memory leaks when the component is disconnected from the DOM.
| private readonly _handlePaste = (e: ClipboardEvent) => { | ||
| const files = e.clipboardData?.files; | ||
| if (!files || files.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| // when copy-pasting from some websites, the clipboard may contain both an image file | ||
| // and an html string with a `blob:` url. The `blob:` url is not accessible, | ||
| // so we should prioritize handling the image file. | ||
| const imageFile = Array.from(files).find(file => | ||
| file.type.startsWith('image/') | ||
| ); | ||
|
|
||
| if (imageFile) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| this.command.execute('editor:insert-file', { | ||
| file: imageFile, | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Add error handling for command execution.
The command.execute call lacks error handling. If the editor:insert-file command fails or throws an exception, it will propagate uncaught and potentially break the paste operation or leave the editor in an inconsistent state.
Consider wrapping the command execution in a try-catch block:
if (imageFile) {
e.preventDefault();
e.stopPropagation();
- this.command.execute('editor:insert-file', {
- file: imageFile,
- });
+ try {
+ this.command.execute('editor:insert-file', {
+ file: imageFile,
+ });
+ } catch (error) {
+ console.error('Failed to insert image file:', error);
+ // Optionally re-enable default paste behavior or show user feedback
+ }
}Verify the command API and its availability.
Please confirm that the editor:insert-file command exists in the command registry and accepts a file parameter of type File. Also verify that this command is always registered when the editor is mounted.
Run the following script to verify the command registration and usage:
🏁 Script executed:
#!/bin/bash
# Description: Search for editor:insert-file command registration and usage
# Search for command registration
echo "=== Searching for editor:insert-file command registration ==="
rg -n "editor:insert-file" --type=ts -C3
# Search for command manager execute calls to understand error handling patterns
echo -e "\n=== Searching for command.execute patterns ==="
rg -n "command\.execute\(" --type=ts -A2 -B2 | head -50Length of output: 1110
I need to verify more about the command system and error handling patterns. Let me search for command initialization and registration.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where this.command is initialized in lit-host.ts and related files
echo "=== Searching for 'this.command' initialization in lit-host.ts ==="
rg -n "this\.command\s*=" --type=ts -B5 -A5 blocksuite/framework/std/src/view/element/lit-host.ts
echo -e "\n=== Searching for class/interface definition in lit-host.ts ==="
rg -n "export\s+(class|interface)" --type=ts blocksuite/framework/std/src/view/element/lit-host.ts
echo -e "\n=== Searching for command property/field definition ==="
rg -n "command\s*:" --type=ts -B2 -A2 blocksuite/framework/std/src/view/element/lit-host.ts | head -30
echo -e "\n=== Searching for command registration patterns (register) ==="
rg -n "\.register.*command\|registerCommand" --type=ts -C2 | head -50
Length of output: 302
---
🏁 Script executed:
```shell
#!/bin/bash
# Find where command property comes from and error handling patterns
echo "=== Searching for SignalWatcher definition ==="
rg -n "export.*SignalWatcher|class SignalWatcher" --type=ts -A5 | head -30
echo -e "\n=== Searching for 'this.command' usage in codebase (not just lit-host) ==="
rg -n "this\.command\." --type=ts -B2 -A2 | head -50
echo -e "\n=== Searching for command.execute with try-catch or error handling ==="
rg -n "command\.execute" --type=ts -B3 -A3 | grep -E "(try|catch|error|Error|throw)" -C5 | head -40
echo -e "\n=== Searching for EditorHost full class structure ==="
rg -n "export class EditorHost" --type=ts -A30 | head -50
Length of output: 6492
---
🏁 Script executed:
```shell
#!/bin/bash
# Find EditorHost constructor, command initialization, and command definition
echo "=== Searching for EditorHost constructor and command setup ==="
rg -n "constructor|@property|@attribute|command\s*\?:" --type=ts blocksuite/framework/std/src/view/element/lit-host.ts -A2 | head -40
echo -e "\n=== Searching for editor:insert-file command definition ==="
rg -n "editor:insert-file|insert-file" --type=ts -B3 -A3
echo -e "\n=== Searching for command execution in entire codebase with context ==="
rg -n "\.execute\(" --type=ts -B2 -A2 | grep -i "command\|execute" -A3 -B3 | head -60
echo -e "\n=== Searching for command property provider or injection ==="
rg -n "command.*=" --type=ts blocksuite/framework/std/src/view/element/lit-host.ts -B2 -A2
Length of output: 3513
---
🏁 Script executed:
```shell
#!/bin/bash
# Find BlockStdScope definition and command property, check full EditorHost class
echo "=== Searching for BlockStdScope definition ==="
rg -n "export.*BlockStdScope|class BlockStdScope|interface BlockStdScope" --type=ts -A10 | head -40
echo -e "\n=== Searching for command property in BlockStdScope or related types ==="
rg -n "command.*:\s*" --type=ts | grep -i "scope\|std" -B2 -A2 | head -30
echo -e "\n=== Full EditorHost class to find all properties ==="
rg -n "export class EditorHost" --type=ts -A80 | head -100
echo -e "\n=== Search for command registration or creation ==="
rg -n "\.command\s*=|new Command|createCommand" --type=ts -B2 -A2 | head -40
Length of output: 16397
---
🏁 Script executed:
```shell
#!/bin/bash
# Search exhaustively for editor:insert-file command registration or definition
echo "=== Exact search for editor:insert-file with all context ==="
rg -n "editor:insert-file" --type=ts -B5 -A5
echo -e "\n=== Search for insert-file command registration ==="
rg -n "insert-file" --type=ts -B3 -A3
echo -e "\n=== Search for CommandManager.register or .execute implementation ==="
rg -n "class CommandManager|execute\s*\(" --type=ts blocksuite/framework/std/src/command/manager.ts -A10 | head -60
echo -e "\n=== Search for command registration in BlockStdScope ==="
rg -n "BlockStdScope" --type=ts blocksuite/framework/std/src -A5 | head -50
echo -e "\n=== Check if command property has null safety ==="
cat blocksuite/framework/std/src/view/element/lit-host.ts | sed -n '107,135p'
Length of output: 7096
---
🏁 Script executed:
```shell
#!/bin/bash
# Final verification: search for where editor:insert-file command might be registered
# Check if it's in a different package or file structure
echo "=== Search entire repo for insert command registration patterns ==="
rg -n "register.*insert|'insert.*'|\"insert.*\"" --type=ts | grep -i "command\|execute" -B2 -A2 | head -50
echo -e "\n=== Search for any file containing command registrations ==="
rg -n "\.register\(" --type=ts -B1 -A1 | head -40
echo -e "\n=== Check BlockStdScope file for command initialization ==="
fd -n "std-scope\|BlockStdScope" --type=ts | head -5
Length of output: 3079
Add error handling and verify command registration for editor:insert-file.
The command.execute() call lacks error handling, and the editor:insert-file command does not appear to be registered anywhere in the codebase. This will cause unhandled exceptions and broken paste functionality.
- Error handling required: Wrap the command execution in try-catch.
- Command registration missing: Ensure
editor:insert-filecommand is registered before use.
Without these fixes, the paste handler will fail silently or throw uncaught exceptions.
if (imageFile) {
e.preventDefault();
e.stopPropagation();
- this.command.execute('editor:insert-file', {
- file: imageFile,
- });
+ try {
+ this.command.execute('editor:insert-file', {
+ file: imageFile,
+ });
+ } catch (error) {
+ console.error('Failed to insert image file:', error);
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private readonly _handlePaste = (e: ClipboardEvent) => { | |
| const files = e.clipboardData?.files; | |
| if (!files || files.length === 0) { | |
| return; | |
| } | |
| // when copy-pasting from some websites, the clipboard may contain both an image file | |
| // and an html string with a `blob:` url. The `blob:` url is not accessible, | |
| // so we should prioritize handling the image file. | |
| const imageFile = Array.from(files).find(file => | |
| file.type.startsWith('image/') | |
| ); | |
| if (imageFile) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| this.command.execute('editor:insert-file', { | |
| file: imageFile, | |
| }); | |
| } | |
| }; | |
| private readonly _handlePaste = (e: ClipboardEvent) => { | |
| const files = e.clipboardData?.files; | |
| if (!files || files.length === 0) { | |
| return; | |
| } | |
| // when copy-pasting from some websites, the clipboard may contain both an image file | |
| // and an html string with a `blob:` url. The `blob:` url is not accessible, | |
| // so we should prioritize handling the image file. | |
| const imageFile = Array.from(files).find(file => | |
| file.type.startsWith('image/') | |
| ); | |
| if (imageFile) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| try { | |
| this.command.execute('editor:insert-file', { | |
| file: imageFile, | |
| }); | |
| } catch (error) { | |
| console.error('Failed to insert image file:', error); | |
| } | |
| } | |
| }; |
🤖 Prompt for AI Agents
In blocksuite/framework/std/src/view/element/lit-host.ts around lines 107 to
127, the paste handler calls this.command.execute('editor:insert-file', ...)
without error handling and the command may not be registered; wrap the execute
call in a try-catch to handle/log errors and prevent uncaught exceptions, and
before invoking ensure the command is registered (either by checking
this.command.has('editor:insert-file') or registering a no-op/handler during
component initialization); if the command is missing, either register the
handler that inserts the file or fall back to an alternative paste behavior and
log a clear warning.
Summary
This PR fixes an issue where copying images from certain websites fails to paste correctly in Affine. Some pages place both an image file and an HTML snippet containing a
blob:URL into the clipboard. Theblob:URL is not accessible outside the original page context, causing the paste operation to fail.What This Change Does
lit-host.ts.editor:insert-file.Why This Fix Works
Previously, the paste handler attempted to use a
blob:URL from the HTML clipboard data, which is invalid in our environment. By checking for and using the real image file first, Affine can reliably paste images from webpages that include such mixed clipboard content.Testing
blob:and image file entries).This resolves the image copy-paste issue reported in v0.24.1 (web + Chrome + self-hosted).
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.