diff --git a/src/components/BootstrapBlazor.SummerNote/BootstrapBlazor.SummerNote.csproj b/src/components/BootstrapBlazor.SummerNote/BootstrapBlazor.SummerNote.csproj index 6a7a2619..bd3df802 100644 --- a/src/components/BootstrapBlazor.SummerNote/BootstrapBlazor.SummerNote.csproj +++ b/src/components/BootstrapBlazor.SummerNote/BootstrapBlazor.SummerNote.csproj @@ -1,7 +1,7 @@ - 9.0.4 + 9.0.5 diff --git a/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.cs b/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.cs index 4660d0d0..afc42478 100644 --- a/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.cs +++ b/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.cs @@ -60,6 +60,12 @@ public partial class Editor [Parameter] public bool ShowSubmit { get; set; } = true; + /// + /// 获得/设置 Editor 组件内上传文件时回调此方法 + /// + [Parameter] + public Func? OnFileUpload { get; set; } + private bool _lastShowSubmit = true; private string? ShowSubmitString => ShowSubmit ? "true" : null; @@ -83,7 +89,7 @@ public partial class Editor public string? Language { get; set; } /// - /// 获得/设置 语言扩展脚本路径 默认 null 如加载德语可设置为 + /// 获得/设置 语言扩展脚本路径 默认 null 如加载德语可设置为 /// [Parameter] public string? LanguageUrl { get; set; } @@ -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); } /// @@ -256,6 +262,24 @@ public async Task ClickPluginItem(string pluginItemName) return ret; } + /// + /// 文件上传回调 + /// + /// + /// + /// + /// + [JSInvokable] + public async Task ImageUpload(string name, string contentType, long size, IJSStreamReference stream) + { + var data = await stream.OpenReadStreamAsync(size); + var file = new EditorUploadFile(name, contentType, size, data); + if (OnFileUpload != null) + { + await OnFileUpload(file); + } + } + /// /// 执行 editor 的方法 /// diff --git a/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js b/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js index a0b6bb8f..0a0c4cb6 100644 --- a/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js +++ b/src/components/BootstrapBlazor.SummerNote/Components/Editor/Editor.razor.js @@ -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 @@ -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 = async files => { + editor.files = files + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const image = createImage(file); + editor.$editor.summernote('insertNode', image); + + const buffer = await file.arrayBuffer(); + const stream = DotNet.createJSStreamReference(buffer); + await editor.invoker.invokeMethodAsync('ImageUpload', + file.name, + file.type || 'application/octet-stream', + file.size, + stream + ) + } + } + } + if (!showSubmit) { const externalOnChange = option.callbacks.onChange; option.callbacks.onChange = function (contents) { @@ -125,7 +145,7 @@ export async function init(id, invoker, methodGetPluginAttrs, methodClickPluginI } const reloadCallbacks = (id, option) => { - const events = ['Blur', 'BlurCodeview', 'Change', 'ChangeCodeview', 'DialogShown', 'Enter', 'Focus', 'ImageLinkInsert', 'ImageUploadError', 'Init', 'Keydown', 'Keyup', 'Mousedown', 'Mouseup', 'Paste', 'Scroll']; + const events = ['Blur', 'BlurCodeview', 'Change', 'ChangeCodeview', 'DialogShown', 'Enter', 'Focus', 'ImageUpload', 'ImageLinkInsert', 'ImageUploadError', 'Init', 'Keydown', 'Keyup', 'Mousedown', 'Mouseup', 'Paste', 'Scroll']; events.forEach(event => { option.callbacks[`on${event}`] = function () { @@ -200,6 +220,12 @@ export function dispose(id) { } } +const createImage = file => { + const element = document.createElement('img'); + element.src = URL.createObjectURL(file); + return element; +} + const offEvent = eventEl => { if (eventEl) { EventHandler.off(eventEl, 'click') diff --git a/src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs b/src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs new file mode 100644 index 00000000..37178e1c --- /dev/null +++ b/src/components/BootstrapBlazor.SummerNote/Components/Editor/EditorUploadFile.cs @@ -0,0 +1,36 @@ +// 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; + +/// +/// Editor 上传文件信息类 +/// +public class EditorUploadFile(string fileName, string contentType, long fileSize, Stream stream) +{ + /// + /// 获得/设置 文件名 + /// + public string FileName => fileName; + + /// + /// 获得/设置 文件大小 + /// + public long FileSize => fileSize; + + /// + /// 获得/设置 文件类型 + /// + public string ContentType => contentType; + + /// + /// 获得/设置 上传的文件流 + /// + public Stream Stream => stream; + + /// + /// 获得/设置 错误信息 + /// + public Exception? Error { get; set; } +} diff --git a/src/components/BootstrapBlazor.SummerNote/Extensions/EditorUploadFileExtensions.cs b/src/components/BootstrapBlazor.SummerNote/Extensions/EditorUploadFileExtensions.cs new file mode 100644 index 00000000..c2bd7e5a --- /dev/null +++ b/src/components/BootstrapBlazor.SummerNote/Extensions/EditorUploadFileExtensions.cs @@ -0,0 +1,65 @@ +// 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; + +/// +/// EditorUploadFile 扩展方法类 +/// +public static class EditorUploadFileExtensions +{ + /// + /// 保存到文件方法 + /// + /// + /// + /// + /// + /// + public static async Task SaveToFileAsync(this EditorUploadFile upload, string fileName, int bufferSize = 64 * 1024, CancellationToken token = default) + { + var ret = false; + + // 文件保护,如果文件存在则先删除 + if (File.Exists(fileName)) + { + try + { + File.Delete(fileName); + } + catch (Exception ex) + { + upload.Error = ex; + return ret; + } + } + + var folder = Path.GetDirectoryName(fileName); + if (!string.IsNullOrEmpty(folder) && !Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + using var uploadFile = File.OpenWrite(fileName); + try + { + // 打开文件流 + var buffer = new byte[bufferSize]; + int bytesRead = 0; + + // 开始读取文件 + while ((bytesRead = await upload.Stream.ReadAsync(buffer, token)) > 0) + { + await uploadFile.WriteAsync(buffer.AsMemory(0, bytesRead), token); + } + ret = true; + } + catch (Exception ex) + { + upload.Error = ex; + } + + return ret; + } +}