Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ public partial class Editor
[Parameter]
public bool ShowSubmit { get; set; } = true;

/// <summary>
/// 获得/设置 Editor组件内上传文件时回调此方法
/// </summary>
[Parameter]
public Func<EditorUploadFile, Task<string>>? OnFileUpload { get; set; }

private bool _lastShowSubmit = true;

private string? ShowSubmitString => ShowSubmit ? "true" : null;
Expand All @@ -83,7 +89,7 @@ public partial class Editor
public string? Language { get; set; }

/// <summary>
/// 获得/设置 语言扩展脚本路径 默认 null 如加载德语可设置为
/// 获得/设置 语言扩展脚本路径 默认 null 如加载德语可设置为
/// </summary>
[Parameter]
public string? LanguageUrl { get; set; }
Expand Down Expand Up @@ -186,7 +192,7 @@ protected override async Task InvokeInitAsync()
methodClickPluginItem = nameof(ClickPluginItem);
}

await InvokeVoidAsync("init", Id, Interop, methodGetPluginAttributes, methodClickPluginItem, Height, Value ?? "", Language, LanguageUrl);
await InvokeVoidAsync("init", Id, Interop, methodGetPluginAttributes, methodClickPluginItem, Height, Value ?? "", Language, LanguageUrl, OnFileUpload is not null);
}

/// <summary>
Expand Down Expand Up @@ -256,6 +262,35 @@ public async Task<string> ClickPluginItem(string pluginItemName)
return ret;
}

/// <summary>
/// 文件上传回调
/// </summary>
/// <param name="uploadFile"></param>
[JSInvokable]
public async Task<string> ImageUpload(EditorUploadFile uploadFile)
{
#if NET6_0_OR_GREATER
var ret = "";
if (Module != null)
{
var stream = await InvokeAsync<IJSStreamReference>("fetch", Id, uploadFile.Index);
if (stream != null)
{
using var data = await stream.OpenReadStreamAsync();
uploadFile.UploadStream = data;
if (OnFileUpload != null)
{
ret = await OnFileUpload(uploadFile);
}
}
}
return ret;
#else
await Task.Yield();
throw new NotSupportedException();
#endif
}

/// <summary>
/// 执行 editor 的方法
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if (window.BootstrapBlazor === void 0) {
window.BootstrapBlazor = {};
}

export async function init(id, invoker, methodGetPluginAttrs, methodClickPluginItem, height, value, lang, langUrl) {
export async function init(id, invoker, methodGetPluginAttrs, methodClickPluginItem, height, value, lang, langUrl, hasUpload) {
const el = document.getElementById(id)
if (el === null) {
return
Expand Down Expand Up @@ -46,6 +46,26 @@ export async function init(id, invoker, methodGetPluginAttrs, methodClickPluginI
const showSubmit = el.getAttribute("data-bb-submit") === "true"
option.toolbar = toolbar;
reloadCallbacks(id, option);
if (hasUpload) {
option.callbacks.onImageUpload = function (files) {
editor.files = files
for (let i = 0; i < files.length; i++) {
const file = files[i];
editor.invoker.invokeMethodAsync('ImageUpload', {
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,

index: i
}).then(data => {
if (data !== "") {
editor.$editor.summernote('insertImage', data, file.name)
}
})
}
}
}

if (!showSubmit) {
const externalOnChange = option.callbacks.onChange;
option.callbacks.onChange = function (contents) {
Expand Down Expand Up @@ -146,6 +166,11 @@ export function update(id, val) {
}
}

export function fetch(id, index) {
const md = Data.get(id)
return md.files[index]
}

export function invoke(id, method, parameter) {
const editor = Data.get(id)
editor.$editor.summernote(method, ...parameter)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

namespace BootstrapBlazor.Components;

/// <summary>
/// 文件信息
/// </summary>
public class EditorUploadFile
{
/// <summary>
/// 文件名
/// </summary>
public string? FileName { get; set; }

/// <summary>
/// 文件大小
/// </summary>
public long FileSize { get; set; }

/// <summary>
/// 最后修改日期
/// </summary>
public string? LastModified { get; set; }

/// <summary>
/// 文件类型
/// </summary>
public string? ContentType { get; set; }

/// <summary>
/// 上传的文件流
/// </summary>
public Stream? UploadStream { get; set; }

/// <summary>
/// 文件索引
/// <para>用于标识文件在上传列表中的位置</para>
/// <para>例如在上传多个文件时可以通过此属性区分每个文件的索引位置</para>
/// </summary>
public int Index { get; set; }

/// <summary>
/// 返回码,0为成功,非0失败
/// </summary>
public int Code { get; set; }

/// <summary>
/// 错误信息
/// </summary>
public string? Error { get; set; }

/// <summary>
/// 保存到文件
/// </summary>
/// <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.

{
var ret = false;
if (UploadStream != null)
{
// 文件保护,如果文件存在则先删除
if (System.IO.File.Exists(fileName))
{
try
{
System.IO.File.Delete(fileName);
}
catch (Exception ex)
{
Code = 1002;
Error = ex.Message;
}
}

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.

{
Directory.CreateDirectory(folder);
}

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.


try
{
// 打开文件流
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];

int bytesRead = 0;

// 开始读取文件
while ((bytesRead = await stream.ReadAsync(buffer, token)) > 0)
{
await uploadFile.WriteAsync(buffer.AsMemory(0, bytesRead), token);

}
ret = true;
}
catch (Exception ex)
{
Code = 1003;
Error = ex.Message;
}
}
}
return ret;
}

}