Skip to content

feat(Editor): add OnFileUpload parameter#532

Merged
ArgoZhang merged 11 commits intomasterfrom
feat-editor
Aug 18, 2025
Merged

feat(Editor): add OnFileUpload parameter#532
ArgoZhang merged 11 commits intomasterfrom
feat-editor

Conversation

@j4587698
Copy link
Copy Markdown
Contributor

@j4587698 j4587698 commented Aug 17, 2025

Link issues

fixes #531

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add custom file upload support to the SummerNote Editor component by introducing a new OnFileUpload callback, wiring up JS interop for image uploads, and encapsulating upload details in an EditorUploadFile class.

New Features:

  • Expose OnFileUpload parameter in the Editor component for handling file uploads via a user-provided callback
  • Implement ImageUpload JSInvokable method to fetch file streams from JavaScript and invoke the OnFileUpload delegate
  • Enhance JavaScript init logic to register summernote’s onImageUpload callback and relay files to .NET
  • Add EditorUploadFile class to encapsulate file metadata, provide upload stream access, and support saving uploads to disk

@bb-auto
Copy link
Copy Markdown

bb-auto Bot commented Aug 17, 2025

Thanks for your PR, @j4587698. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@bb-auto bb-auto Bot added the enhancement New feature or request label Aug 17, 2025
@bb-auto bb-auto Bot requested a review from ArgoZhang August 17, 2025 07:51
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 17, 2025

Reviewer's Guide

This PR adds file upload callback support to the SummerNote Editor component by introducing a new OnFileUpload parameter in Editor.razor.cs, enhancing the JavaScript interop to handle image uploads, and providing a file metadata model for processing and saving uploads.

Sequence diagram for Editor file upload callback process

sequenceDiagram
    participant JS as Editor.razor.js
    participant Editor as Editor.razor.cs
    participant User as actor User
    participant Callback as OnFileUpload (callback)
    User->>JS: Uploads image in Editor
    JS->>Editor: invokeMethodAsync('ImageUpload', file metadata)
    Editor->>JS: fetch file stream
    Editor->>Callback: OnFileUpload(uploadFile)
    Callback-->>Editor: Returns image URL
    Editor-->>JS: Returns image URL
    JS->>JS: Insert image into editor
Loading

Entity relationship diagram for Editor and EditorUploadFile

erDiagram
    EDITOR ||--o| EDITORUPLOADFILE : handles
    EDITORUPLOADFILE {
        string FileName
        long FileSize
        string LastModified
        string ContentType
        Stream UploadStream
        int Index
        int Code
        string Error
    }
Loading

Class diagram for new EditorUploadFile model and Editor changes

classDiagram
    class EditorUploadFile {
        +string? FileName
        +long FileSize
        +string? LastModified
        +string? ContentType
        +Stream? UploadStream
        +int Index
        +int Code
        +string? Error
        +Task<bool> SaveToFile(string fileName, CancellationToken token)
    }
    class Editor {
        +Func<EditorUploadFile, Task<string>>? OnFileUpload
        +Task<string> ImageUpload(EditorUploadFile uploadFile)
    }
    Editor --> "1" EditorUploadFile: uses
Loading

File-Level Changes

Change Details Files
Add file upload callback support in Editor.razor.cs
  • Expose OnFileUpload Func<EditorUploadFile, Task> parameter
  • Update InvokeInitAsync to pass hasUpload flag to JS
  • Implement JSInvokable ImageUpload method fetching file stream and invoking callback
src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.cs
Extend JS interop to handle image uploads
  • Add hasUpload argument to init and conditionally set onImageUpload callback
  • Invoke ImageUpload via invoker.invokeMethodAsync and insert returned image
  • Define fetch helper to retrieve selected file stream by index
src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js
Introduce EditorUploadFile model for upload metadata and saving
  • Define file metadata properties (name, size, type, index, stream)
  • Implement SaveToFile with file creation, buffered copy, and error codes
src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#531 Add support for a callback function when uploading images in the Editor component.

Possibly linked issues


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

@bb-auto bb-auto Bot added this to the v9.2.0 milestone Aug 17, 2025
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 there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs:94` </location>
<code_context>
+                    // 打开文件流
+                    var stream = UploadStream;
+
+                    var buffer = new byte[4 * 1096];
+                    int bytesRead = 0;
+
</code_context>

<issue_to_address>
Buffer size calculation uses 1096 instead of 1024.

Please confirm whether 1096 is intentional or if 1024 was intended, as powers of two are standard for buffer sizes.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                    var buffer = new byte[4 * 1096];
=======
                    // Use a standard power-of-two buffer size for optimal performance
                    var buffer = new byte[4 * 1024];
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs:80` </location>
<code_context>
+            }
+
+            var folder = Path.GetDirectoryName(fileName);
+            if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder))
+            {
+                Directory.CreateDirectory(folder);
</code_context>

<issue_to_address>
Directory creation does not handle exceptions.

Wrap Directory.CreateDirectory(folder) in a try-catch block to handle potential exceptions and update Code and Error as needed.
</issue_to_address>

### Comment 3
<location> `src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs:87` </location>
<code_context>
+
+            if (Code == 0)
+            {
+                using var uploadFile = File.OpenWrite(fileName);
+
+                try
</code_context>

<issue_to_address>
File.OpenWrite is not wrapped in a try-catch block.

Move File.OpenWrite inside the try-catch block to handle exceptions and ensure Code/Error are set appropriately.
</issue_to_address>

### Comment 4
<location> `src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js:58` </location>
<code_context>
+                            fileName: file.name,
+                            fileSize: file.size,
+                            contentType: file.type,
+                            lastModified: new Date(file.lastModified).toISOString(),
+                            index: i
+                        }).then(data => {
</code_context>

<issue_to_address>
lastModified conversion may not be accurate for all browsers.

Add a check to verify that file.lastModified is a valid number before converting it to an ISO string, as its type or presence may vary across browsers.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
                            lastModified: new Date(file.lastModified).toISOString(),
=======
                            lastModified: (typeof file.lastModified === 'number' && !isNaN(file.lastModified))
                                ? new Date(file.lastModified).toISOString()
                                : null,
>>>>>>> REPLACE

</suggested_fix>

### Comment 5
<location> `src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs:60` </location>
<code_context>
+    /// <param name="fileName"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    public async Task<bool> SaveToFile(string fileName, CancellationToken token = default)
+    {
+        var ret = false;
</code_context>

<issue_to_address>
Consider simplifying the SaveToFile method by using built-in file and stream operations to handle folder creation, file overwriting, and stream copying.

Here’s a drastically simplified version of `SaveToFile` that preserves your error‐reporting (via `Code`/`Error`), but collapses all of the manual folder‐creation, delete‐if‐exists and read/write loops into a few lines:

```csharp
public async Task<bool> SaveToFile(string filePath, CancellationToken token = default)
{
    try
    {
        // ensure folder exists (no need to check Exists first)
        var folder = Path.GetDirectoryName(filePath);
        if (!string.IsNullOrEmpty(folder))
            Directory.CreateDirectory(folder);

        // File.Create will truncate/overwrite existing file
        using var output = File.Create(filePath);

        if (UploadStream != null)
        {
            // CopyToAsync handles the buffer loops for you
            await UploadStream.CopyToAsync(output, 81920, token);
        }

        return true;
    }
    catch (Exception ex)
    {
        // you can distinguish codes if you really need to
        Code = 1003;
        Error = ex.Message;
        return false;
    }
}
```

Steps to apply:

1. Remove your manual `File.Exists`/`File.Delete` block and the `new byte[...]` read/write loop.
2. Call `Directory.CreateDirectory(...)` unconditionally – it’s a no‐op if the folder already exists.
3. Use `File.Create(...)` (which overwrites) instead of `File.OpenWrite`.
4. Let `Stream.CopyToAsync(...)` do the heavy lifting.
5. Wrap the whole thing in a single `try/catch` for error reporting.
</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.

// 打开文件流
var stream = UploadStream;

var buffer = new byte[4 * 1096];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Buffer size calculation uses 1096 instead of 1024.

Please confirm whether 1096 is intentional or if 1024 was intended, as powers of two are standard for buffer sizes.

Suggested change
var buffer = new byte[4 * 1096];
// Use a standard power-of-two buffer size for optimal performance
var buffer = new byte[4 * 1024];

}

var folder = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder))
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: Directory creation does not handle exceptions.

Wrap Directory.CreateDirectory(folder) in a try-catch block to handle potential exceptions and update Code and Error as needed.


if (Code == 0)
{
using var uploadFile = File.OpenWrite(fileName);
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): File.OpenWrite is not wrapped in a try-catch block.

Move File.OpenWrite inside the try-catch block to handle exceptions and ensure Code/Error are set appropriately.

fileName: file.name,
fileSize: file.size,
contentType: file.type,
lastModified: new Date(file.lastModified).toISOString(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: lastModified conversion may not be accurate for all browsers.

Add a check to verify that file.lastModified is a valid number before converting it to an ISO string, as its type or presence may vary across browsers.

Suggested change
lastModified: new Date(file.lastModified).toISOString(),
lastModified: (typeof file.lastModified === 'number' && !isNaN(file.lastModified))
? new Date(file.lastModified).toISOString()
: null,

/// <param name="fileName"></param>
/// <param name="token"></param>
/// <returns></returns>
public async Task<bool> SaveToFile(string fileName, CancellationToken token = default)
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 (complexity): Consider simplifying the SaveToFile method by using built-in file and stream operations to handle folder creation, file overwriting, and stream copying.

Here’s a drastically simplified version of SaveToFile that preserves your error‐reporting (via Code/Error), but collapses all of the manual folder‐creation, delete‐if‐exists and read/write loops into a few lines:

public async Task<bool> SaveToFile(string filePath, CancellationToken token = default)
{
    try
    {
        // ensure folder exists (no need to check Exists first)
        var folder = Path.GetDirectoryName(filePath);
        if (!string.IsNullOrEmpty(folder))
            Directory.CreateDirectory(folder);

        // File.Create will truncate/overwrite existing file
        using var output = File.Create(filePath);

        if (UploadStream != null)
        {
            // CopyToAsync handles the buffer loops for you
            await UploadStream.CopyToAsync(output, 81920, token);
        }

        return true;
    }
    catch (Exception ex)
    {
        // you can distinguish codes if you really need to
        Code = 1003;
        Error = ex.Message;
        return false;
    }
}

Steps to apply:

  1. Remove your manual File.Exists/File.Delete block and the new byte[...] read/write loop.
  2. Call Directory.CreateDirectory(...) unconditionally – it’s a no‐op if the folder already exists.
  3. Use File.Create(...) (which overwrites) instead of File.OpenWrite.
  4. Let Stream.CopyToAsync(...) do the heavy lifting.
  5. Wrap the whole thing in a single try/catch for error reporting.

@ArgoZhang ArgoZhang changed the title Editor增加Upload回调 feat(Editor): add OnFileUpload parameter Aug 18, 2025
@ArgoZhang ArgoZhang merged commit 0975ebd into master Aug 18, 2025
1 check passed
@ArgoZhang ArgoZhang deleted the feat-editor branch August 18, 2025 06:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(Editor): 自带的图片上传支持回调

2 participants