diff --git a/samples/BlazorServer/BlazorServer.csproj b/samples/BlazorServer/BlazorServer.csproj
index daab3e6..971e014 100644
--- a/samples/BlazorServer/BlazorServer.csproj
+++ b/samples/BlazorServer/BlazorServer.csproj
@@ -14,4 +14,8 @@
+
+
+
+
diff --git a/samples/BlazorServer/Controllers/FileController.cs b/samples/BlazorServer/Controllers/FileController.cs
new file mode 100644
index 0000000..43efc20
--- /dev/null
+++ b/samples/BlazorServer/Controllers/FileController.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace BlazorServer.Controllers
+{
+ [Route("[Controller]/[Action]")]
+ public class FileController : Controller
+ {
+ private readonly IConfiguration _config;
+ public FileController(IConfiguration config)
+ {
+ _config = config;
+ }
+
+ [HttpPost]
+ public async Task Upload(List files)
+ {
+ var file = files[0];
+ var root = _config.GetValue(WebHostDefaults.ContentRootKey) ?? "";
+ var path = Path.Combine(root, $"wwwroot{Path.DirectorySeparatorChar}images", file.FileName);
+ FileStream filestream = new FileStream(path, FileMode.Create, FileAccess.Write);
+ await file.CopyToAsync(filestream);
+ filestream.Close();
+
+ return Content($"images/{file.FileName}");
+ }
+ }
+}
diff --git a/samples/BlazorServer/Pages/EditorView.razor b/samples/BlazorServer/Pages/EditorView.razor
index b39375d..b064231 100644
--- a/samples/BlazorServer/Pages/EditorView.razor
+++ b/samples/BlazorServer/Pages/EditorView.razor
@@ -1,4 +1,6 @@
-
+@inject IConfiguration _config;
+
+
Cancel
Reset
Save Changes
@@ -33,4 +35,16 @@
{
await OnSave.InvokeAsync(_html);
}
+
+ // Upload the image into the wwwroot/images folder for demonstration
+ private async Task ImageUploadHandler(string imageName, string imageContentType, Stream imageStream)
+ {
+ var root = _config.GetValue(WebHostDefaults.ContentRootKey) ?? "";
+ var path = Path.Combine(root, $"wwwroot{Path.DirectorySeparatorChar}images", imageName);
+ FileStream filestream = new FileStream(path, FileMode.Create, FileAccess.Write);
+ await imageStream.CopyToAsync(filestream);
+ filestream.Close();
+
+ return $"images/{imageName}";
+ }
}
\ No newline at end of file
diff --git a/samples/BlazorServer/Program.cs b/samples/BlazorServer/Program.cs
index 015118e..7478fee 100644
--- a/samples/BlazorServer/Program.cs
+++ b/samples/BlazorServer/Program.cs
@@ -23,6 +23,7 @@
app.UseRouting();
+app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs
index 7b70358..5e1c623 100644
--- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs
+++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.cs
@@ -34,6 +34,15 @@ public sealed partial class MudHtmlEditor : IAsyncDisposable
[Parameter]
public EventCallback TextChanged { get; set; }
+ [Parameter]
+ public Func>? ImageUploadHandler { get; set; }
+
+ [Parameter]
+ public string? ImageUploadUrl { get; set; }
+
+ [Parameter]
+ public IEnumerable AllowedImageMimeTypes { get; set; } = new List() { "image/png", "image/jpeg" };
+
[Parameter]
public bool Resizable { get; set; } = true;
@@ -75,7 +84,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
_dotNetRef = DotNetObjectReference.Create(this);
await using var module = await JS.InvokeAsync("import", "./_content/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js");
- _quill = await module.InvokeAsync("createQuillInterop", _dotNetRef, _editor, _toolbar, Placeholder);
+
+ var settings = new
+ {
+ Placeholder = Placeholder,
+ BlazorImageUpload = ImageUploadHandler != null,
+ ImageUploadUrl = ImageUploadUrl ?? "",
+ AllowedImageMimeTypes = AllowedImageMimeTypes
+ };
+
+ _quill = await module.InvokeAsync("createQuillInterop", _dotNetRef, _editor, _toolbar, settings);
await SetHtml(Html);
@@ -101,6 +119,22 @@ public async void HandleTextContentChanged(string text)
await TextChanged.InvokeAsync(text);
}
+ [JSInvokable]
+ public async Task SaveImage(string imageName, string fileType, long size)
+ {
+ if(_quill is not null)
+ {
+ var imageJsStream = await _quill.InvokeAsync("getImageBytes", imageName);
+
+ await using var imageStream = await imageJsStream.OpenReadStreamAsync(size);
+
+ if (ImageUploadHandler is not null)
+ return await ImageUploadHandler(imageName, fileType, imageStream);
+ }
+
+ return "";
+ }
+
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (_quill is not null)
diff --git a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js
index 974d5b6..13b7a46 100644
--- a/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js
+++ b/src/Tizzani.MudBlazor.HtmlEditor/MudHtmlEditor.razor.js
@@ -1,4 +1,5 @@
var Embed = Quill.import('blots/block/embed');
+const Delta = Quill.import('delta');
class Divider extends Embed {
static create(value) {
@@ -16,18 +17,34 @@ try {
Quill.register('modules/blotFormatter', QuillBlotFormatter.default);
} catch { }
-export function createQuillInterop(dotNetRef, editorRef, toolbarRef, placeholder) {
- var quill = new Quill(editorRef, {
+
+export function createQuillInterop(dotNetRef, editorRef, toolbarRef, settings) {
+ var interop = new MudQuillInterop(dotNetRef, editorRef, toolbarRef, settings);
+
+ var properties = {
modules: {
toolbar: {
container: toolbarRef
},
- blotFormatter: {}
+ blotFormatter: {},
+ uploader: {
+ mimetypes: settings.allowedImageMimeTypes
+ }
},
- placeholder: placeholder,
+ placeholder: settings.placeholder,
theme: 'snow'
- });
- return new MudQuillInterop(dotNetRef, quill, editorRef, toolbarRef);
+ };
+
+ // Use custom handler if specified
+ if (settings.blazorImageUpload || settings.imageUploadUrl) {
+ properties.modules.uploader.handler = interop.uploadImageHandler;
+ }
+
+ var quill = new Quill(editorRef, properties);
+
+ interop.addQuill(quill);
+
+ return interop;
}
export class MudQuillInterop {
@@ -35,14 +52,22 @@ export class MudQuillInterop {
* @param {Quill} quill
* @param {Element} editorRef
* @param {Element} toolbarRef
+ * @param {object} toolbarRef
*/
- constructor(dotNetRef, quill, editorRef, toolbarRef) {
- quill.getModule('toolbar').addHandler('hr', this.insertDividerHandler);
- quill.on('text-change', this.textChangedHandler);
+ constructor(dotNetRef, editorRef, toolbarRef, settings) {
this.dotNetRef = dotNetRef;
- this.quill = quill;
+ this.quill;
this.editorRef = editorRef;
this.toolbarRef = toolbarRef;
+ this.blazorImageUpload = settings.blazorImageUpload;
+ this.imageBytes = {};
+ this.imageUploadUrl = settings.imageUploadUrl;
+ }
+
+ addQuill = (quill) => {
+ quill.getModule('toolbar').addHandler('hr', this.insertDividerHandler);
+ quill.on('text-change', this.textChangedHandler);
+ this.quill = quill;
}
getText = () => {
@@ -75,4 +100,65 @@ export class MudQuillInterop {
this.dotNetRef.invokeMethodAsync('HandleHtmlContentChanged', this.getHtml());
this.dotNetRef.invokeMethodAsync('HandleTextContentChanged', this.getText());
};
-}
\ No newline at end of file
+
+ // Get imageBytes by filename
+ getImageBytes = (key) => {
+ return this.imageBytes[key];
+ }
+
+ // Read file and upload it via the blazor or controller method
+ upload(file) {
+ const fileReader = new FileReader();
+ return new Promise((resolve, reject) => {
+ fileReader.addEventListener("load", () => {
+ // Pass file via DotNetInterop
+ if (this.blazorImageUpload) {
+ this.imageBytes[file.name] = new Uint8Array(fileReader.result);
+ this.dotNetRef.invokeMethodAsync("SaveImage", file.name, file.type, file.size).then(url => resolve({name: file.name, url: url }));
+ }
+ // Upload to API endpoint
+ else if (this.imageUploadUrl) {
+ const formData = new FormData();
+ formData.append("files", file);
+
+ fetch(this.imageUploadUrl, {
+ method: "POST",
+ headers: {},
+ body: formData
+ })
+ .then(response => {
+ if (response.status === 200) {
+ response.text().then(url =>
+ resolve({ name: file.name, url: url })
+ );
+ }
+ else {
+ reject("Error uploading image to " + this.imageUploadUrl);
+ }
+ });
+ }
+ });
+
+ if (file) {
+ fileReader.readAsArrayBuffer(file);
+ } else {
+ reject("No file selected");
+ }
+ });
+ }
+
+ // Handle when images are copy/pasted or dragged/dropped into the editor
+ uploadImageHandler = (range, files) => {
+ for (let file of files) {
+ this.upload(file).then((data) => {
+ let delta = new Delta().retain(range.index).delete(range.length).insert({ image: data.url });
+ delete this.imageBytes[data.name]
+ this.quill.updateContents(delta, Quill.sources.USER);
+ this.quill.setSelection(range.index, Quill.sources.SILENT);
+ },
+ (error) => {
+ console.warn(error);
+ });
+ }
+ }
+}