Skip to content

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Dec 2, 2025

Description


Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Improve image loading, blob handling, and clipboard copying behavior for editor image blocks.

Bug Fixes:

  • Ensure images from authenticated AppFlowy storage and public URLs are reliably validated and copied via the image toolbar, including conversion to PNG for better clipboard compatibility.

Enhancements:

  • Refactor image validation logic into reusable helpers, add MIME type inference for blobs based on URLs, and introduce a utility to convert arbitrary image blobs to PNG before clipboard operations.
  • Improve logging and error handling around image fetching, blob conversion, and clipboard write operations for easier debugging.

@sourcery-ai
Copy link

sourcery-ai bot commented Dec 2, 2025

Reviewer's Guide

Refactors image loading and fetching utilities to be promise-based, improves MIME-type handling for image blobs, adds PNG conversion support for clipboard copying, and updates the image toolbar to reliably copy images (especially from authenticated AppFlowy storage) to the clipboard.

Sequence diagram for the updated image copy to clipboard flow

sequenceDiagram
  actor User
  participant ImageToolbar
  participant ImageUtils
  participant AppFlowyStorage
  participant Clipboard

  User->>ImageToolbar: clickCopyImage()
  ImageToolbar->>ImageUtils: fetchImageBlob(imageUrl)
  alt isAppFlowyFileStorageUrl
    ImageUtils->>AppFlowyStorage: getTokenParsed()
    alt tokenAvailable
      ImageUtils->>AppFlowyStorage: fetch(fullUrl, AuthorizationBearer)
      alt responseOk
        AppFlowyStorage-->>ImageUtils: Blob
        ImageUtils->>ImageUtils: inferMimeTypeIfGeneric(url)
        ImageUtils-->>ImageToolbar: Blob
      else responseNotOk
        ImageUtils-->>ImageToolbar: null
      end
    else noToken
      ImageUtils-->>ImageToolbar: null
    end
  else externalUrl
    ImageUtils->>AppFlowyStorage: fetch(url)
    alt responseOk
      AppFlowyStorage-->>ImageUtils: Blob
      ImageUtils->>ImageUtils: inferMimeTypeIfGeneric(url)
      ImageUtils-->>ImageToolbar: Blob
    else responseNotOk
      ImageUtils-->>ImageToolbar: null
    end
  end

  alt blobReturned
    alt blobTypeNotPng
      ImageToolbar->>ImageUtils: convertBlobToPng(blob)
      ImageUtils-->>ImageToolbar: pngBlob
      ImageToolbar->>Clipboard: navigator.clipboard.write(ClipboardItem{imagePng})
      Clipboard-->>ImageToolbar: success
      ImageToolbar-->>User: showSuccessNotification
    else blobTypeIsPng
      ImageToolbar->>Clipboard: navigator.clipboard.write(ClipboardItem{blob})
      Clipboard-->>ImageToolbar: success
      ImageToolbar-->>User: showSuccessNotification
    end
  else noBlob
    ImageToolbar-->>User: noActionOrError
  end
Loading

Class diagram for image utilities and ImageToolbar changes

classDiagram
  class ImageUtils {
    +resolveImageUrl(url string) string
    +validateImageLoad(imageUrl string) Promise~CheckImageResult~
    +checkImage(url string) Promise~CheckImageResult~
    +fetchImageBlob(url string) Promise~Blob or null~
    +convertBlobToPng(blob Blob) Promise~Blob~
    +getMimeTypeFromUrl(url string) string or null
  }

  class CheckImageResult {
    +ok boolean
    +status number
    +statusText string
    +error string
    +validatedUrl string
  }

  class ImageToolbar {
    -editor YjsEditor
    -node ImageBlockNode
    +onCopyImage() Promise~void~
  }

  class ClipboardAPI {
    +write(items ClipboardItem[]) Promise~void~
  }

  ImageToolbar ..> ImageUtils : uses
  ImageToolbar ..> ClipboardAPI : uses
  ImageUtils ..> CheckImageResult : returns
Loading

Flow diagram for the refactored checkImage function

flowchart TD
  A[checkImage url] --> B{isAppFlowyFileStorageUrl url}
  B -- No --> C[return validateImageLoad url]
  B -- Yes --> D[getTokenParsed]
  D --> E{tokenAvailable}
  E -- No --> F[fullUrl = resolveImageUrl url]
  F --> G[return validateImageLoad fullUrl]
  E -- Yes --> H[fullUrl = resolveImageUrl url]
  H --> I[fetch fullUrl with AuthorizationBearer]
  I --> J{response.ok}
  J -- Yes --> K[blob = await response.blob]
  K --> L[blobUrl = URL.createObjectURL blob]
  L --> M[return ok 200 validatedUrl blobUrl]
  J -- No --> N[log authenticated fetch failed]
  N --> O[return validateImageLoad fullUrl]

  subgraph validateImageLoad
    P[create Image]
    P --> Q[start 10s timeout]
    P --> R[img.onload]
    P --> S[img.onerror]
    Q --> T[resolve ok false 408 RequestTimeout]
    R --> U[clearTimeout]
    R --> V[resolve ok true 200 validatedUrl imageUrl]
    S --> W[clearTimeout]
    S --> X[resolve ok false 404 ImageNotFound]
  end
Loading

File-Level Changes

Change Details Files
Refactor image validation helper and simplify checkImage control flow with clearer async/await logic and typed result.
  • Introduce a reusable CheckImageResult interface for image validation results.
  • Replace callback-based checkImageWithImageElement with a Promise-based validateImageLoad helper using Image().
  • Rewrite checkImage to use async/await, performing authenticated fetch for AppFlowy file-storage URLs when a token is present and falling back to validateImageLoad in all other cases.
  • Ensure non-AppFlowy URLs are validated exclusively through validateImageLoad.
src/utils/image.ts
Enhance image blob fetching to better support authenticated AppFlowy URLs and infer correct MIME types from URLs when missing or generic.
  • Add debug logging for AppFlowy image blob fetch attempts and responses, plus explicit logging when no auth token is available.
  • When fetching blobs (both authenticated and unauthenticated), inspect the blob.type and, if it is missing or application/octet-stream, infer a MIME type via a new getMimeTypeFromUrl helper and adjust the blob via slice().
  • Implement getMimeTypeFromUrl to derive image MIME types from data URLs or common file extensions (jpg, png, gif, webp, svg).
src/utils/image.ts
Add a utility to convert arbitrary image blobs to PNG using a canvas pipeline, primarily for clipboard compatibility.
  • Introduce convertBlobToPng, which renders a blob into an Image, draws it to a canvas, and exports a PNG blob via canvas.toBlob.
  • Handle canvas/context errors and image load failures by rejecting the conversion promise and cleaning up created object URLs.
src/utils/image.ts
Update the image toolbar copy action to normalize images to PNG before writing to the clipboard and improve observability and error handling.
  • Import convertBlobToPng into ImageToolbar and log the copy action and source URL for debugging.
  • Before writing to the clipboard, convert non-PNG blobs to PNG via convertBlobToPng, falling back to the original blob if conversion fails.
  • Add debug logging of blob type and size before clipboard write, and log clipboard write failures to the console while still surfacing user-facing notifications.
src/components/editor/components/blocks/image/ImageToolbar.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

@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 there - I've reviewed your changes and they look great!


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.

@appflowy appflowy merged commit 682cb83 into main Dec 2, 2025
11 of 12 checks passed
@appflowy appflowy deleted the fix_copy_image branch December 2, 2025 05:04
appflowy added a commit that referenced this pull request Dec 2, 2025
* fix: copy image

* fix: log
josue693 pushed a commit to josue693/AppFlowy-Web that referenced this pull request Dec 21, 2025
* fix: copy image

* fix: log
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