Migrate from Quill to Jodit (#264)
* Refactored JS folder. Removed useless files * Add border to signature webview ( Fix for light theme) * migrated quill to jodit(links not working) * Removed quill subfolder * removed table styles and fixed trigger color * disable addaptive toolbar * Remove direction button * MinHeight, Toolbar toggle, style for combobox * Added reference mail to replies and forward * Command bar in compose page Fixed align behaviour with Jodit * Fix theme toggle in composer * switch cc and to in mail chain * default selected value for aligment * make CC/To/From to be mailto links * Added drag and drop for images Fixed dropzones visual states Corrected border radius Fixed null reference exception when event dispatched when chromium still not initialized
This commit is contained in:
@@ -9,6 +9,7 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using EmailValidation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Toolkit.Uwp.Helpers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using MimeKit;
|
||||
@@ -26,6 +27,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Messages.Mails;
|
||||
using Wino.Core.Messages.Shell;
|
||||
using Wino.Extensions;
|
||||
@@ -121,7 +123,7 @@ namespace Wino.Views
|
||||
|
||||
private void OnFileDropGridDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
ViewModel.IsDraggingOverDropZone = true;
|
||||
ViewModel.IsDraggingOverFilesDropZone = true;
|
||||
|
||||
e.AcceptedOperation = DataPackageOperation.Copy;
|
||||
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
|
||||
@@ -132,21 +134,94 @@ namespace Wino.Views
|
||||
|
||||
private void OnFileDropGridDragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
ViewModel.IsDraggingOverDropZone = false;
|
||||
ViewModel.IsDraggingOverFilesDropZone = false;
|
||||
}
|
||||
|
||||
private async void OnFileDropGridFileDropped(object sender, DragEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
var storageItems = await e.DataView.GetStorageItemsAsync();
|
||||
var files = storageItems.OfType<StorageFile>();
|
||||
|
||||
await AttachFiles(files);
|
||||
}
|
||||
}
|
||||
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
|
||||
finally
|
||||
{
|
||||
ViewModel.IsDraggingOverComposerGrid = false;
|
||||
ViewModel.IsDraggingOverFilesDropZone = false;
|
||||
}
|
||||
}
|
||||
private void OnImageDropGridDragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
bool isValid = false;
|
||||
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
var storageItems = await e.DataView.GetStorageItemsAsync();
|
||||
var files = storageItems.OfType<StorageFile>();
|
||||
// We can't use async/await here because DragUIOverride becomes inaccessible.
|
||||
// https://github.com/microsoft/microsoft-ui-xaml/issues/9296
|
||||
var files = e.DataView.GetStorageItemsAsync().GetAwaiter().GetResult().OfType<StorageFile>();
|
||||
|
||||
await AttachFiles(files);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (ValidateImageFile(file))
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ViewModel.IsDraggingOverComposerGrid = false;
|
||||
ViewModel.IsDraggingOverDropZone = false;
|
||||
e.AcceptedOperation = isValid ? DataPackageOperation.Copy : DataPackageOperation.None;
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
ViewModel.IsDraggingOverImagesDropZone = true;
|
||||
e.DragUIOverride.Caption = Translator.ComposerAttachmentsDragDropAttach_Message;
|
||||
e.DragUIOverride.IsCaptionVisible = true;
|
||||
e.DragUIOverride.IsGlyphVisible = true;
|
||||
e.DragUIOverride.IsContentVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImageDropGridDragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
ViewModel.IsDraggingOverImagesDropZone = false;
|
||||
}
|
||||
|
||||
private async void OnImageDropGridImageDropped(object sender, DragEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
var storageItems = await e.DataView.GetStorageItemsAsync();
|
||||
var files = storageItems.OfType<StorageFile>();
|
||||
|
||||
var imageDataURLs = new List<string>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (ValidateImageFile(file))
|
||||
imageDataURLs.Add(await GetDataURL(file));
|
||||
}
|
||||
|
||||
await InvokeScriptSafeAsync($"insertImages({JsonConvert.SerializeObject(imageDataURLs)});");
|
||||
}
|
||||
}
|
||||
// State should be reset even when an exception occurs, otherwise the UI will be stuck in a dragging state.
|
||||
finally
|
||||
{
|
||||
ViewModel.IsDraggingOverComposerGrid = false;
|
||||
ViewModel.IsDraggingOverImagesDropZone = false;
|
||||
}
|
||||
|
||||
static async Task<string> GetDataURL(StorageFile file)
|
||||
{
|
||||
return $"data:image/{file.FileType.Replace(".", "")};base64,{Convert.ToBase64String(await file.ReadBytesAsync())}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AttachFiles(IEnumerable<StorageFile> files)
|
||||
@@ -165,49 +240,52 @@ namespace Wino.Views
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateImageFile(StorageFile file)
|
||||
{
|
||||
string[] allowedTypes = new string[] { ".jpg", ".jpeg", ".png" };
|
||||
var fileType = file.FileType.ToLower();
|
||||
|
||||
return allowedTypes.Contains(fileType);
|
||||
}
|
||||
|
||||
private async void BoldButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('boldButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('bold')");
|
||||
}
|
||||
|
||||
private async void ItalicButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('italicButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('italic')");
|
||||
}
|
||||
|
||||
private async void UnderlineButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('underlineButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('underline')");
|
||||
}
|
||||
|
||||
private async void StrokeButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('strikeButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('strikethrough')");
|
||||
}
|
||||
|
||||
private async void BulletListButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('bulletListButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('insertunorderedlist')");
|
||||
}
|
||||
|
||||
private async void OrderedListButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('orderedListButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('insertorderedlist')");
|
||||
}
|
||||
|
||||
private async void IncreaseIndentClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('increaseIndentButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('indent')");
|
||||
}
|
||||
|
||||
private async void DecreaseIndentClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('decreaseIndentButton').click();");
|
||||
}
|
||||
|
||||
private async void DirectionButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('directionButton').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('outdent')");
|
||||
}
|
||||
|
||||
private async void AlignmentChanged(object sender, SelectionChangedEventArgs e)
|
||||
@@ -218,20 +296,26 @@ namespace Wino.Views
|
||||
switch (alignment)
|
||||
{
|
||||
case "left":
|
||||
await InvokeScriptSafeAsync("document.getElementById('ql-align-left').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('justifyleft')");
|
||||
break;
|
||||
case "center":
|
||||
await InvokeScriptSafeAsync("document.getElementById('ql-align-center').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('justifycenter')");
|
||||
break;
|
||||
case "right":
|
||||
await InvokeScriptSafeAsync("document.getElementById('ql-align-right').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('justifyright')");
|
||||
break;
|
||||
case "justify":
|
||||
await InvokeScriptSafeAsync("document.getElementById('ql-align-justify').click();");
|
||||
await InvokeScriptSafeAsync("editor.execCommand('justifyfull')");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void WebViewToggleButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var enable = WebviewToolBarButton.IsChecked == true ? "true" : "false";
|
||||
await InvokeScriptSafeAsync($"toggleToolbar('{enable}');");
|
||||
}
|
||||
|
||||
public async Task<string> ExecuteScriptFunctionAsync(string functionName, params object[] parameters)
|
||||
{
|
||||
string script = functionName + "(";
|
||||
@@ -252,21 +336,24 @@ namespace Wino.Views
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Chromium.ExecuteScriptAsync(function);
|
||||
return await Chromium?.ExecuteScriptAsync(function);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private async void AddImageClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync("document.getElementById('addImageButton').click();");
|
||||
await InvokeScriptSafeAsync("imageInput.click();");
|
||||
}
|
||||
|
||||
private async Task FocusEditorAsync()
|
||||
{
|
||||
await InvokeScriptSafeAsync("quill.focus();");
|
||||
await InvokeScriptSafeAsync("editor.selection.focus();");
|
||||
|
||||
Chromium.Focus(FocusState.Keyboard);
|
||||
Chromium.Focus(FocusState.Programmatic);
|
||||
@@ -279,24 +366,6 @@ namespace Wino.Views
|
||||
await FocusEditorAsync();
|
||||
}
|
||||
|
||||
private async Task<string> TryGetSelectedTextAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await Chromium.ExecuteScriptAsync("getSelectedText();");
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private async void LinkButtonClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Get selected text from Quill.
|
||||
|
||||
HyperlinkTextBox.Text = await TryGetSelectedTextAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateEditorThemeAsync()
|
||||
{
|
||||
await DOMLoadedTask.Task;
|
||||
@@ -346,7 +415,7 @@ namespace Wino.Views
|
||||
if (Chromium.CoreWebView2 != null)
|
||||
{
|
||||
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
|
||||
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageRecieved;
|
||||
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
|
||||
}
|
||||
|
||||
Chromium.Close();
|
||||
@@ -379,9 +448,9 @@ namespace Wino.Views
|
||||
|
||||
ViewModel.GetHTMLBodyFunction = new Func<Task<string>>(async () =>
|
||||
{
|
||||
var quillContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
||||
var editorContent = await InvokeScriptSafeAsync("GetHTMLContent();");
|
||||
|
||||
return JsonConvert.DeserializeObject<string>(quillContent);
|
||||
return JsonConvert.DeserializeObject<string>(editorContent);
|
||||
});
|
||||
|
||||
var underlyingThemeService = App.Current.Services.GetService<IUnderlyingThemeService>();
|
||||
@@ -391,60 +460,66 @@ namespace Wino.Views
|
||||
|
||||
private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args)
|
||||
{
|
||||
var editorBundlePath = (await ViewModel.NativeAppService.GetQuillEditorBundlePathAsync()).Replace("full.html", string.Empty);
|
||||
var editorBundlePath = (await ViewModel.NativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty);
|
||||
|
||||
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.example", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
|
||||
Chromium.Source = new Uri("https://app.example/full.html");
|
||||
Chromium.CoreWebView2.SetVirtualHostNameToFolderMapping("app.editor", editorBundlePath, CoreWebView2HostResourceAccessKind.Allow);
|
||||
Chromium.Source = new Uri("https://app.editor/editor.html");
|
||||
|
||||
Chromium.CoreWebView2.DOMContentLoaded -= DOMLoaded;
|
||||
Chromium.CoreWebView2.DOMContentLoaded += DOMLoaded;
|
||||
|
||||
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageRecieved;
|
||||
Chromium.CoreWebView2.WebMessageReceived += ScriptMessageRecieved;
|
||||
Chromium.CoreWebView2.WebMessageReceived -= ScriptMessageReceived;
|
||||
Chromium.CoreWebView2.WebMessageReceived += ScriptMessageReceived;
|
||||
}
|
||||
|
||||
private void ScriptMessageRecieved(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
||||
private void ScriptMessageReceived(CoreWebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
|
||||
{
|
||||
var change = JsonConvert.DeserializeObject<string>(args.WebMessageAsJson);
|
||||
var change = JsonConvert.DeserializeObject<WebViewMessage>(args.WebMessageAsJson);
|
||||
|
||||
bool isEnabled = change.EndsWith("ql-active");
|
||||
|
||||
if (change.StartsWith("ql-bold"))
|
||||
BoldButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("ql-italic"))
|
||||
ItalicButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("ql-underline"))
|
||||
UnderlineButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("ql-strike"))
|
||||
StrokeButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("orderedListButton"))
|
||||
OrderedListButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("bulletListButton"))
|
||||
BulletListButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("ql-direction"))
|
||||
DirectionButton.IsChecked = isEnabled;
|
||||
else if (change.StartsWith("ql-align-left"))
|
||||
if (change.type == "bold")
|
||||
{
|
||||
AlignmentListView.SelectionChanged -= AlignmentChanged;
|
||||
AlignmentListView.SelectedIndex = 0;
|
||||
AlignmentListView.SelectionChanged += AlignmentChanged;
|
||||
BoldButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.StartsWith("ql-align-center"))
|
||||
else if (change.type == "italic")
|
||||
{
|
||||
AlignmentListView.SelectionChanged -= AlignmentChanged;
|
||||
AlignmentListView.SelectedIndex = 1;
|
||||
AlignmentListView.SelectionChanged += AlignmentChanged;
|
||||
ItalicButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.StartsWith("ql-align-right"))
|
||||
else if (change.type == "underline")
|
||||
{
|
||||
AlignmentListView.SelectionChanged -= AlignmentChanged;
|
||||
AlignmentListView.SelectedIndex = 2;
|
||||
AlignmentListView.SelectionChanged += AlignmentChanged;
|
||||
UnderlineButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.StartsWith("ql-align-justify"))
|
||||
else if (change.type == "strikethrough")
|
||||
{
|
||||
StrokeButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.type == "ol")
|
||||
{
|
||||
OrderedListButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.type == "ul")
|
||||
{
|
||||
BulletListButton.IsChecked = change.value == "true";
|
||||
}
|
||||
else if (change.type == "indent")
|
||||
{
|
||||
IncreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
|
||||
}
|
||||
else if (change.type == "outdent")
|
||||
{
|
||||
DecreaseIndentButton.IsEnabled = change.value == "disabled" ? false : true;
|
||||
}
|
||||
else if (change.type == "alignment")
|
||||
{
|
||||
var parsedValue = change.value switch
|
||||
{
|
||||
"jodit-icon_left" => 0,
|
||||
"jodit-icon_center" => 1,
|
||||
"jodit-icon_right" => 2,
|
||||
"jodit-icon_justify" => 3,
|
||||
_ => 0
|
||||
};
|
||||
AlignmentListView.SelectionChanged -= AlignmentChanged;
|
||||
AlignmentListView.SelectedIndex = 3;
|
||||
AlignmentListView.SelectedIndex = parsedValue;
|
||||
AlignmentListView.SelectionChanged += AlignmentChanged;
|
||||
}
|
||||
}
|
||||
@@ -464,14 +539,6 @@ namespace Wino.Views
|
||||
await RenderInternalAsync(message.RenderModel.RenderHtml);
|
||||
}
|
||||
|
||||
private async void HyperlinkAddClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await InvokeScriptSafeAsync($"addHyperlink('{LinkUrlTextBox.Text}')");
|
||||
|
||||
LinkUrlTextBox.Text = string.Empty;
|
||||
HyperlinkFlyout.Hide();
|
||||
}
|
||||
|
||||
private void BarDynamicOverflowChanging(CommandBar sender, DynamicOverflowItemsChangingEventArgs args)
|
||||
{
|
||||
if (args.Action == CommandBarDynamicOverflowAction.AddingToOverflow)
|
||||
|
||||
Reference in New Issue
Block a user