diff --git a/Wino.Core.Domain/Enums/ReaderFont.cs b/Wino.Core.Domain/Enums/ReaderFont.cs deleted file mode 100644 index 53c0e39a..00000000 --- a/Wino.Core.Domain/Enums/ReaderFont.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Wino.Core.Domain.Enums -{ - public enum ReaderFont - { - Arial, - TimesNewRoman, - Verdana, - Tahoma, - CourierNew, - Georgia, - TrebuchetMS, - Calibri, - Helvetica - } -} diff --git a/Wino.Core.Domain/Enums/WinoPage.cs b/Wino.Core.Domain/Enums/WinoPage.cs index e6ff92c5..e3326ac3 100644 --- a/Wino.Core.Domain/Enums/WinoPage.cs +++ b/Wino.Core.Domain/Enums/WinoPage.cs @@ -19,7 +19,7 @@ PersonalizationPage, MessageListPage, MailListPage, - ReadingPanePage, + ReadComposePanePage, LanguageTimePage, SettingOptionsPage, } diff --git a/Wino.Core.Domain/Interfaces/IFontService.cs b/Wino.Core.Domain/Interfaces/IFontService.cs index ff00cdab..ac938bd9 100644 --- a/Wino.Core.Domain/Interfaces/IFontService.cs +++ b/Wino.Core.Domain/Interfaces/IFontService.cs @@ -1,16 +1,16 @@ using System.Collections.Generic; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Models.Reader; namespace Wino.Core.Domain.Interfaces { + /// + /// Service to access available fonts. + /// public interface IFontService { - List GetReaderFonts(); - ReaderFontModel GetCurrentReaderFont(); - int GetCurrentReaderFontSize(); - - void ChangeReaderFont(ReaderFont font); - void ChangeReaderFontSize(int size); + /// + /// Get available fonts. Default + installed system fonts. + /// Fonts initialized only once. To refresh fonts, restart the application. + /// + List GetFonts(); } } diff --git a/Wino.Core.Domain/Interfaces/IPreferencesService.cs b/Wino.Core.Domain/Interfaces/IPreferencesService.cs index 38474cdb..f3d43016 100644 --- a/Wino.Core.Domain/Interfaces/IPreferencesService.cs +++ b/Wino.Core.Domain/Interfaces/IPreferencesService.cs @@ -122,15 +122,25 @@ namespace Wino.Core.Domain.Interfaces AppLanguage CurrentLanguage { get; set; } /// - /// Setting: Display font for the mail reader. Not composer. + /// Setting: Display font for the mail reader. /// - ReaderFont ReaderFont { get; set; } + string ReaderFont { get; set; } /// - /// Setting: Font size for the mail reader. Not composer. + /// Setting: Font size for the mail reader. /// int ReaderFontSize { get; set; } + /// + /// Setting: Display font for the mail composer. + /// + string ComposerFont { get; set; } + + /// + /// Setting: Font size for the mail composer. + /// + int ComposerFontSize { get; set; } + /// /// Setting: Whether the navigation pane is opened on the last session or not. /// diff --git a/Wino.Core.Domain/Models/Reader/ReaderFontModel.cs b/Wino.Core.Domain/Models/Reader/ReaderFontModel.cs deleted file mode 100644 index 7530e92a..00000000 --- a/Wino.Core.Domain/Models/Reader/ReaderFontModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Wino.Core.Domain.Enums; - -namespace Wino.Core.Domain.Models.Reader -{ - public record ReaderFontModel(ReaderFont Font, string FontFamilyName); -} diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json index 01c35517..5b3f7486 100644 --- a/Wino.Core.Domain/Translations/en_US/resources.json +++ b/Wino.Core.Domain/Translations/en_US/resources.json @@ -100,7 +100,7 @@ "EditorToolbarOption_Insert": "Insert", "EditorToolbarOption_None": "None", "EditorToolbarOption_Options": "Options", - "EditorTooltip_WebViewEditor": "Use web view editor", + "EditorTooltip_WebViewEditor": "Use web view editor", "ElementTheme_Dark": "Dark mode", "ElementTheme_Default": "Use system setting", "ElementTheme_Light": "Light mode", @@ -444,8 +444,10 @@ "SettingsPersonalization_Title": "Personalization", "SettingsPrivacyPolicy_Description": "Review privacy policy.", "SettingsPrivacyPolicy_Title": "Privacy Policy", - "SettingsReadingPane_Description": "Mail rendering options.", - "SettingsReadingPane_Title": "Reading Pane", + "SettingsReader_Title": "Reader", + "SettingsComposer_Title": "Composer", + "SettingsReadComposePane_Description": "Fonts, external content.", + "SettingsReadComposePane_Title": "Reader & Composer", "SettingsReaderFont_Title": "Default Reader Font", "SettingsReaderFontFamily_Description": "Change the default font family and font size for reading mails.", "SettingsFontFamily_Title": "Font Family", diff --git a/Wino.Core.Domain/Translator.Designer.cs b/Wino.Core.Domain/Translator.Designer.cs index 359c41c8..4964614c 100644 --- a/Wino.Core.Domain/Translator.Designer.cs +++ b/Wino.Core.Domain/Translator.Designer.cs @@ -2244,14 +2244,24 @@ namespace Wino.Core.Domain public static string SettingsPrivacyPolicy_Title => Resources.GetTranslatedString(@"SettingsPrivacyPolicy_Title"); /// - /// Mail rendering options. + /// Reader /// - public static string SettingsReadingPane_Description => Resources.GetTranslatedString(@"SettingsReadingPane_Description"); + public static string SettingsReader_Title => Resources.GetTranslatedString(@"SettingsReader_Title"); /// - /// Reading Pane + /// Composer /// - public static string SettingsReadingPane_Title => Resources.GetTranslatedString(@"SettingsReadingPane_Title"); + public static string SettingsComposer_Title => Resources.GetTranslatedString(@"SettingsComposer_Title"); + + /// + /// Fonts, external content. + /// + public static string SettingsReadComposePane_Description => Resources.GetTranslatedString(@"SettingsReadComposePane_Description"); + + /// + /// Reader & Composer + /// + public static string SettingsReadComposePane_Title => Resources.GetTranslatedString(@"SettingsReadComposePane_Title"); /// /// Default Reader Font diff --git a/Wino.Core/Services/FontService.cs b/Wino.Core/Services/FontService.cs index b6dfc16a..8ab37871 100644 --- a/Wino.Core/Services/FontService.cs +++ b/Wino.Core/Services/FontService.cs @@ -1,50 +1,26 @@ -using System.Collections.Generic; -using Serilog; -using Wino.Core.Domain.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using SkiaSharp; using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Reader; -namespace Wino.Core.Services +namespace Wino.Core.Services; + +public class FontService() : IFontService { - public class FontService : IFontService + private static readonly Lazy> _availableFonts = new(InitializeFonts); + private static readonly List _defaultFonts = ["Arial", "Calibri", "Trebuchet MS", "Tahoma", "Verdana", "Courier New", "Georgia", "Times New Roman"]; + + private static List InitializeFonts() { - private readonly IPreferencesService _preferencesService; - private ILogger _logger = Log.ForContext(); + // TODO: Skia used to get system fonts. This is a temporary solution to support UWP and WinUI together. + // After migration to WinUI, this code should be replaced with lightweight solution. + var fontFamilies = SKFontManager.Default.FontFamilies; - private readonly List _availableFonts = - [ - new ReaderFontModel(ReaderFont.Arial, "Arial"), - new ReaderFontModel(ReaderFont.Calibri, "Calibri"), - new ReaderFontModel(ReaderFont.TimesNewRoman, "Times New Roman"), - new ReaderFontModel(ReaderFont.TrebuchetMS, "Trebuchet MS"), - new ReaderFontModel(ReaderFont.Tahoma, "Tahoma"), - new ReaderFontModel(ReaderFont.Verdana, "Verdana"), - new ReaderFontModel(ReaderFont.Georgia, "Georgia"), - new ReaderFontModel(ReaderFont.CourierNew, "Courier New") - ]; + List combinedFonts = [.. fontFamilies, .. _defaultFonts]; - public FontService(IPreferencesService preferencesService) - { - _preferencesService = preferencesService; - } - - public List GetReaderFonts() => _availableFonts; - - public void ChangeReaderFont(ReaderFont font) - { - _preferencesService.ReaderFont = font; - - _logger.Information("Default reader font is changed to {Font}", font); - } - - public void ChangeReaderFontSize(int size) - { - _preferencesService.ReaderFontSize = size; - - _logger.Information("Default reader font size is changed to {Size}", size); - } - - public ReaderFontModel GetCurrentReaderFont() => _availableFonts.Find(f => f.Font == _preferencesService.ReaderFont); - public int GetCurrentReaderFontSize() => _preferencesService.ReaderFontSize; + return [.. combinedFonts.Distinct().OrderBy(x => x)]; } + + public List GetFonts() => _availableFonts.Value; } diff --git a/Wino.Core/Services/MailService.cs b/Wino.Core/Services/MailService.cs index d96dcf5c..f6c64974 100644 --- a/Wino.Core/Services/MailService.cs +++ b/Wino.Core/Services/MailService.cs @@ -28,6 +28,7 @@ namespace Wino.Core.Services private readonly ISignatureService _signatureService; private readonly IThreadingStrategyProvider _threadingStrategyProvider; private readonly IMimeFileService _mimeFileService; + private readonly IPreferencesService _preferencesService; private readonly ILogger _logger = Log.ForContext(); @@ -38,7 +39,8 @@ namespace Wino.Core.Services IAccountService accountService, ISignatureService signatureService, IThreadingStrategyProvider threadingStrategyProvider, - IMimeFileService mimeFileService) : base(databaseService) + IMimeFileService mimeFileService, + IPreferencesService preferencesService) : base(databaseService) { _folderService = folderService; _contactService = contactService; @@ -46,6 +48,7 @@ namespace Wino.Core.Services _signatureService = signatureService; _threadingStrategyProvider = threadingStrategyProvider; _mimeFileService = mimeFileService; + _preferencesService = preferencesService; } public async Task CreateDraftAsync(MailAccount composerAccount, @@ -651,6 +654,9 @@ namespace Wino.Core.Services message.From.Add(new MailboxAddress(account.SenderName, account.Address)); + // It contains empty blocks with inlined font, to make sure when users starts typing,it will follow selected font. + var gapHtml = CreateHtmlGap(); + // Manage "To" if (reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll) { @@ -682,8 +688,7 @@ namespace Wino.Core.Services { message.InReplyTo = referenceMessage.MessageId; - foreach (var id in referenceMessage.References) - message.References.Add(id); + message.References.AddRange(referenceMessage.References); message.References.Add(referenceMessage.MessageId); } @@ -711,14 +716,18 @@ namespace Wino.Core.Services if (string.IsNullOrWhiteSpace(builder.HtmlBody)) { - builder.HtmlBody = $"


{signature.HtmlBody}"; + builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}"; } else { - builder.HtmlBody = $"


{signature.HtmlBody}" + builder.HtmlBody; + builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}{gapHtml}{builder.HtmlBody}"; } } } + else + { + builder.HtmlBody = $"{gapHtml}{builder.HtmlBody}"; + } // Manage Subject if (reason == DraftCreationReason.Forward && !referenceMessage.Subject.StartsWith("FW: ", StringComparison.OrdinalIgnoreCase)) @@ -794,7 +803,7 @@ namespace Wino.Core.Services { var htmlMimeInfo = string.Empty; // Separation Line - htmlMimeInfo += "


"; + htmlMimeInfo += "
"; var visitor = _mimeFileService.CreateHTMLPreviewVisitor(referenceMessage, string.Empty); visitor.Visit(referenceMessage); @@ -816,6 +825,12 @@ namespace Wino.Core.Services return htmlMimeInfo; } + string CreateHtmlGap() + { + var template = $"""

"""; + return string.Concat(Enumerable.Repeat(template, 5)); + } + static string ParticipantsToHtml(InternetAddressList internetAddresses) => string.Join("; ", internetAddresses.Mailboxes .Select(x => $"{x.Name ?? Translator.UnknownSender} <{x.Address ?? Translator.UnknownAddress}>")); diff --git a/Wino.Core/Wino.Core.csproj b/Wino.Core/Wino.Core.csproj index 07ec28a4..5a83fd36 100644 --- a/Wino.Core/Wino.Core.csproj +++ b/Wino.Core/Wino.Core.csproj @@ -30,6 +30,7 @@ + diff --git a/Wino.Mail.ViewModels/ComposePageViewModel.cs b/Wino.Mail.ViewModels/ComposePageViewModel.cs index be582c1f..bd10ad5e 100644 --- a/Wino.Mail.ViewModels/ComposePageViewModel.cs +++ b/Wino.Mail.ViewModels/ComposePageViewModel.cs @@ -108,35 +108,38 @@ namespace Wino.Mail.ViewModels private readonly IMailService _mailService; private readonly ILaunchProtocolService _launchProtocolService; private readonly IMimeFileService _mimeFileService; - private readonly IStatePersistanceService _statePersistanceService; private readonly IFolderService _folderService; private readonly IAccountService _accountService; private readonly IWinoRequestDelegator _worker; + public readonly IFontService FontService; + public readonly IPreferencesService PreferencesService; public readonly IContactService ContactService; public ComposePageViewModel(IDialogService dialogService, IMailService mailService, ILaunchProtocolService launchProtocolService, IMimeFileService mimeFileService, - IStatePersistanceService statePersistanceService, INativeAppService nativeAppService, IFolderService folderService, IAccountService accountService, IWinoRequestDelegator worker, - IContactService contactService) : base(dialogService) + IContactService contactService, + IFontService fontService, + IPreferencesService preferencesService) : base(dialogService) { NativeAppService = nativeAppService; _folderService = folderService; ContactService = contactService; + FontService = fontService; _mailService = mailService; _launchProtocolService = launchProtocolService; _mimeFileService = mimeFileService; - _statePersistanceService = statePersistanceService; _accountService = accountService; _worker = worker; SelectedToolbarSection = ToolbarSections[0]; + PreferencesService = preferencesService; } [RelayCommand] diff --git a/Wino.Mail.ViewModels/MessageListPageViewModel.cs b/Wino.Mail.ViewModels/MessageListPageViewModel.cs index 8ec9dc68..9f872813 100644 --- a/Wino.Mail.ViewModels/MessageListPageViewModel.cs +++ b/Wino.Mail.ViewModels/MessageListPageViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Wino.Core.Domain; using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; @@ -9,23 +10,40 @@ namespace Wino.Mail.ViewModels { public IPreferencesService PreferencesService { get; } - private List availableHoverActions = new List + private int selectedMarkAsOptionIndex; + + public int SelectedMarkAsOptionIndex { + get => selectedMarkAsOptionIndex; + set + { + if (SetProperty(ref selectedMarkAsOptionIndex, value)) + { + if (value >= 0) + { + PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues(typeof(MailMarkAsOption)).GetValue(value); + } + } + } + } + + private readonly List availableHoverActions = + [ MailOperation.Archive, MailOperation.SoftDelete, MailOperation.SetFlag, MailOperation.MarkAsRead, MailOperation.MoveToJunk - }; + ]; - public List AvailableHoverActionsTranslations { get; set; } = new List() - { + public List AvailableHoverActionsTranslations { get; set; } = + [ Translator.HoverActionOption_Archive, Translator.HoverActionOption_Delete, Translator.HoverActionOption_ToggleFlag, Translator.HoverActionOption_ToggleRead, Translator.HoverActionOption_MoveJunk - }; + ]; #region Properties @@ -82,6 +100,8 @@ namespace Wino.Mail.ViewModels leftHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.LeftHoverAction); centerHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.CenterHoverAction); rightHoverActionIndex = availableHoverActions.IndexOf(PreferencesService.RightHoverAction); + + SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues(typeof(MailMarkAsOption)), PreferencesService.MarkAsPreference); } } } diff --git a/Wino.Mail.ViewModels/ReadComposePanePageViewModel.cs b/Wino.Mail.ViewModels/ReadComposePanePageViewModel.cs new file mode 100644 index 00000000..f4abd433 --- /dev/null +++ b/Wino.Mail.ViewModels/ReadComposePanePageViewModel.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using CommunityToolkit.Mvvm.Messaging.Messages; +using Wino.Core.Domain.Interfaces; + +namespace Wino.Mail.ViewModels +{ + public partial class ReadComposePanePageViewModel : BaseViewModel, + IRecipient>, + IRecipient> + { + private readonly IFontService _fontService; + + public IPreferencesService PreferencesService { get; set; } + public List AvailableFonts => _fontService.GetFonts(); + + [ObservableProperty] + [NotifyPropertyChangedRecipients] + string currentReaderFont; + + [ObservableProperty] + [NotifyPropertyChangedRecipients] + int currentReaderFontSize; + + [ObservableProperty] + [NotifyPropertyChangedRecipients] + string currentComposerFont; + + [ObservableProperty] + [NotifyPropertyChangedRecipients] + int currentComposerFontSize; + + public ReadComposePanePageViewModel(IDialogService dialogService, + IFontService fontService, + IPreferencesService preferencesService) : base(dialogService) + { + _fontService = fontService; + PreferencesService = preferencesService; + + CurrentReaderFont = fontService.GetCurrentReaderFont(); + CurrentReaderFontSize = fontService.GetCurrentReaderFontSize(); + + CurrentComposerFont = fontService.GetCurrentComposerFont(); + CurrentComposerFontSize = fontService.GetCurrentComposerFontSize(); + } + + public void Receive(PropertyChangedMessage message) + { + if (message.PropertyName == nameof(CurrentReaderFont) && message.OldValue != message.NewValue) + { + _fontService.SetReaderFont(message.NewValue); + } + + if (message.PropertyName == nameof(CurrentComposerFont) && message.OldValue != message.NewValue) + { + _fontService.SetComposerFont(message.NewValue); + } + } + + public void Receive(PropertyChangedMessage message) + { + if (message.PropertyName == nameof(CurrentReaderFontSize)) + { + _fontService.SetReaderFontSize(CurrentReaderFontSize); + } + else if (message.PropertyName == nameof(CurrentComposerFontSize)) + { + _fontService.SetComposerFontSize(CurrentComposerFontSize); + } + } + } +} diff --git a/Wino.Mail.ViewModels/ReadingPanePageViewModel.cs b/Wino.Mail.ViewModels/ReadingPanePageViewModel.cs deleted file mode 100644 index 5acd18f3..00000000 --- a/Wino.Mail.ViewModels/ReadingPanePageViewModel.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using CommunityToolkit.Mvvm.Messaging.Messages; -using Wino.Core.Domain.Enums; -using Wino.Core.Domain.Interfaces; -using Wino.Core.Domain.Models.Reader; - -namespace Wino.Mail.ViewModels -{ - public partial class ReadingPanePageViewModel : BaseViewModel, - IRecipient>, - IRecipient> - { - public IPreferencesService PreferencesService { get; set; } - - private int selectedMarkAsOptionIndex; - private readonly IFontService _fontService; - - public int SelectedMarkAsOptionIndex - { - get => selectedMarkAsOptionIndex; - set - { - if (SetProperty(ref selectedMarkAsOptionIndex, value)) - { - if (value >= 0) - { - PreferencesService.MarkAsPreference = (MailMarkAsOption)Enum.GetValues(typeof(MailMarkAsOption)).GetValue(value); - } - } - } - } - - public List ReaderFonts => _fontService.GetReaderFonts(); - - [ObservableProperty] - [NotifyPropertyChangedRecipients] - ReaderFontModel currentReaderFont; - - [ObservableProperty] - [NotifyPropertyChangedRecipients] - int currentReaderFontSize; - - public ReadingPanePageViewModel(IDialogService dialogService, - IFontService fontService, - IPreferencesService preferencesService) : base(dialogService) - { - _fontService = fontService; - - PreferencesService = preferencesService; - SelectedMarkAsOptionIndex = Array.IndexOf(Enum.GetValues(typeof(MailMarkAsOption)), PreferencesService.MarkAsPreference); - - CurrentReaderFont = fontService.GetCurrentReaderFont(); - CurrentReaderFontSize = fontService.GetCurrentReaderFontSize(); - } - - public void Receive(PropertyChangedMessage message) - { - if (message.OldValue != message.NewValue) - { - _fontService.ChangeReaderFont(message.NewValue.Font); - Debug.WriteLine("Changed reader font."); - } - } - - public void Receive(PropertyChangedMessage message) - { - if (message.PropertyName == nameof(CurrentReaderFontSize)) - { - _fontService.ChangeReaderFontSize(CurrentReaderFontSize); - } - } - } -} diff --git a/Wino.Mail.ViewModels/SettingOptionsPageViewModel.cs b/Wino.Mail.ViewModels/SettingOptionsPageViewModel.cs index fdd670eb..4231d19a 100644 --- a/Wino.Mail.ViewModels/SettingOptionsPageViewModel.cs +++ b/Wino.Mail.ViewModels/SettingOptionsPageViewModel.cs @@ -9,18 +9,11 @@ using Wino.Core.Messages.Navigation; namespace Wino.Mail.ViewModels { - public partial class SettingOptionsPageViewModel : BaseViewModel + public partial class SettingOptionsPageViewModel(IDialogService dialogService) : BaseViewModel(dialogService) { - public SettingOptionsPageViewModel(IDialogService dialogService) : base(dialogService) { } - [RelayCommand] private void GoAccountSettings() => Messenger.Send(); - public override void OnNavigatedTo(NavigationMode mode, object parameters) - { - base.OnNavigatedTo(mode, parameters); - } - [RelayCommand] public void NavigateSubDetail(object type) { @@ -31,7 +24,7 @@ namespace Wino.Mail.ViewModels WinoPage.PersonalizationPage => Translator.SettingsPersonalization_Title, WinoPage.AboutPage => Translator.SettingsAbout_Title, WinoPage.MessageListPage => Translator.SettingsMessageList_Title, - WinoPage.ReadingPanePage => Translator.SettingsReadingPane_Title, + WinoPage.ReadComposePanePage => Translator.SettingsReadComposePane_Title, WinoPage.LanguageTimePage => Translator.SettingsLanguageTime_Title, _ => throw new NotImplementedException() }; diff --git a/Wino.Mail/App.xaml.cs b/Wino.Mail/App.xaml.cs index f99f32be..1c31c92f 100644 --- a/Wino.Mail/App.xaml.cs +++ b/Wino.Mail/App.xaml.cs @@ -135,7 +135,7 @@ namespace Wino services.AddTransient(typeof(AccountDetailsPageViewModel)); services.AddTransient(typeof(SignatureManagementPageViewModel)); services.AddTransient(typeof(MessageListPageViewModel)); - services.AddTransient(typeof(ReadingPanePageViewModel)); + services.AddTransient(typeof(ReadComposePanePageViewModel)); services.AddTransient(typeof(MergedAccountDetailsPageViewModel)); services.AddTransient(typeof(LanguageTimePageViewModel)); } diff --git a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs index 753ae356..1a61afa5 100644 --- a/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs +++ b/Wino.Mail/Dialogs/SignatureEditorDialog.xaml.cs @@ -19,7 +19,11 @@ namespace Wino.Dialogs { private Func> _getHTMLBodyFunction; private readonly TaskCompletionSource _domLoadedTask = new TaskCompletionSource(); + private readonly INativeAppService _nativeAppService = App.Current.Services.GetService(); + private readonly IFontService _fontService = App.Current.Services.GetService(); + private readonly IPreferencesService _preferencesService = App.Current.Services.GetService(); + public AccountSignature Result; public bool IsComposerDarkMode @@ -36,7 +40,7 @@ namespace Wino.Dialogs SignatureNameTextBox.Header = Translator.SignatureEditorDialog_SignatureName_TitleNew; Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); - Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation"); + Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,FontAccess"); // TODO: Should be added additional logic to enable/disable primary button when webview content changed. IsPrimaryButtonEnabled = true; @@ -275,6 +279,7 @@ namespace Wino.Dialogs await _domLoadedTask.Task; await UpdateEditorThemeAsync(); + await InitializeEditorAsync(); if (string.IsNullOrEmpty(htmlBody)) { @@ -288,6 +293,16 @@ namespace Wino.Dialogs } } + private async Task InitializeEditorAsync() + { + var fonts = _fontService.GetFonts(); + var composerFont = _preferencesService.ComposerFont; + int composerFontSize = _preferencesService.ComposerFontSize; + var readerFont = _preferencesService.ReaderFont; + int readerFontSize = _preferencesService.ReaderFontSize; + return await ExecuteScriptFunctionAsync("initializeJodit", fonts, composerFont, composerFontSize, readerFont, readerFontSize); + } + private async void ChromiumInitialized(Microsoft.UI.Xaml.Controls.WebView2 sender, Microsoft.UI.Xaml.Controls.CoreWebView2InitializedEventArgs args) { var editorBundlePath = (await _nativeAppService.GetEditorBundlePathAsync()).Replace("editor.html", string.Empty); diff --git a/Wino.Mail/JS/editor.js b/Wino.Mail/JS/editor.js index 280cb5cc..38582f3b 100644 --- a/Wino.Mail/JS/editor.js +++ b/Wino.Mail/JS/editor.js @@ -1,4 +1,4 @@ -const editor = Jodit.make("#editor", { +const joditConfig = { "useSearch": false, "toolbar": true, "buttons": "bold,italic,underline,strikethrough,brush,ul,ol,font,fontsize,paragraph,image,link,indent,outdent,align,lineHeight,table", @@ -6,7 +6,6 @@ const editor = Jodit.make("#editor", { "toolbarAdaptive": false, "toolbarInlineForSelection": false, "showCharsCounter": false, - style: { font: "14px Arial" }, "showWordsCounter": false, "showXPathInStatusbar": false, "disablePlugins": "add-new-line", @@ -16,61 +15,85 @@ const editor = Jodit.make("#editor", { }, "enter": "DIV", "minHeight": 200 -}); +} -// Handle the image input change event -imageInput.addEventListener('change', () => { - const file = imageInput.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = function (event) { - const base64Image = event.target.result; - insertImages([base64Image]); - }; - reader.readAsDataURL(file); +// This method should be called first all the time. +function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, defaultReaderFont, defaultReaderFontSize) { + const fontsWithFallabckObject = fonts.reduce((acc, font) => { acc[`'${font}',Arial,sans-serif`] = font; return acc; }, {}); + const mergedConfig = { + ...joditConfig, + controls: { + font: { + list: Jodit.atom(fontsWithFallabckObject) + } + }, + style: { font: `${defaultReaderFontSize}px ${defaultReaderFont}` }, } -}); -const disabledButtons = ["indent", "outdent"]; -const ariaPressedButtons = ["bold", "italic", "underline", "strikethrough", "ul", "ol"]; + Jodit.plugins.add('inlineFonts', jodit => { + jodit.events.on('afterEnter', e => { + const current = jodit.selection.current().parentNode; + current.style.fontFamily = `'${defaultComposerFont}',Arial,sans-serif`; + current.style.fontSize = `${defaultComposerFontSize}px`; + }); + }); -const alignmentButton = document.querySelector(`[ref='left']`).firstChild.firstChild; -const alignmentObserver = new MutationObserver(function () { - const value = alignmentButton.firstChild.getAttribute('class').split(' ')[0]; - window.chrome.webview.postMessage({ type: 'alignment', value: value }); -}); -alignmentObserver.observe(alignmentButton, { childList: true, attributes: true, attributeFilter: ["class"] }); + // Don't add const/let/var here, it should be global + editor = Jodit.make("#editor", mergedConfig); -const ariaObservers = ariaPressedButtons.map(button => { - const buttonContainer = document.querySelector(`[ref='${button}']`); - const observer = new MutationObserver(function () { pressedChanged(buttonContainer) }); - observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["aria-pressed"] }); + // Handle the image input change event + imageInput.addEventListener('change', () => { + const file = imageInput.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function (event) { + const base64Image = event.target.result; + insertImages([base64Image]); + }; + reader.readAsDataURL(file); + } + }); - return observer; -}); + // Listeners for button events + const disabledButtons = ["indent", "outdent"]; + const ariaPressedButtons = ["bold", "italic", "underline", "strikethrough", "ul", "ol"]; -const disabledObservers = disabledButtons.map(button => { - const buttonContainer = document.querySelector(`[ref='${button}']`); - const observer = new MutationObserver(function () { disabledButtonChanged(buttonContainer) }); - observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["disabled"] }); + const alignmentButton = document.querySelector(`[ref='left']`).firstChild.firstChild; + const alignmentObserver = new MutationObserver(function () { + const value = alignmentButton.firstChild.getAttribute('class').split(' ')[0]; + window.chrome.webview.postMessage({ type: 'alignment', value: value }); + }); + alignmentObserver.observe(alignmentButton, { childList: true, attributes: true, attributeFilter: ["class"] }); - return observer; -}); + const ariaObservers = ariaPressedButtons.map(button => { + const buttonContainer = document.querySelector(`[ref='${button}']`); + const observer = new MutationObserver(function () { pressedChanged(buttonContainer) }); + observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["aria-pressed"] }); -function pressedChanged(buttonContainer) { - const ref = buttonContainer.getAttribute('ref'); - const value = buttonContainer.firstChild.getAttribute('aria-pressed'); - window.chrome.webview.postMessage({ type: ref, value: value }); + return observer; + }); + + const disabledObservers = disabledButtons.map(button => { + const buttonContainer = document.querySelector(`[ref='${button}']`); + const observer = new MutationObserver(function () { disabledButtonChanged(buttonContainer) }); + observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["disabled"] }); + + return observer; + }); + + function pressedChanged(buttonContainer) { + const ref = buttonContainer.getAttribute('ref'); + const value = buttonContainer.firstChild.getAttribute('aria-pressed'); + window.chrome.webview.postMessage({ type: ref, value: value }); + } + + function disabledButtonChanged(buttonContainer) { + const ref = buttonContainer.getAttribute('ref'); + const value = buttonContainer.firstChild.getAttribute('disabled'); + window.chrome.webview.postMessage({ type: ref, value: value }); + } } -function disabledButtonChanged(buttonContainer) { - const ref = buttonContainer.getAttribute('ref'); - const value = buttonContainer.firstChild.getAttribute('disabled'); - console.log(buttonContainer, ref, value); - window.chrome.webview.postMessage({ type: ref, value: value }); -} - - function RenderHTML(htmlString) { editor.s.insertHTML(htmlString); editor.synchronizeValues(); diff --git a/Wino.Mail/Services/PreferencesService.cs b/Wino.Mail/Services/PreferencesService.cs index 7b2fe0fa..953ada88 100644 --- a/Wino.Mail/Services/PreferencesService.cs +++ b/Wino.Mail/Services/PreferencesService.cs @@ -166,9 +166,9 @@ namespace Wino.Services set => SaveProperty(propertyName: nameof(CurrentLanguage), value); } - public ReaderFont ReaderFont + public string ReaderFont { - get => _configurationService.Get(nameof(ReaderFont), ReaderFont.Calibri); + get => _configurationService.Get(nameof(ReaderFont), "Calibri"); set => SaveProperty(propertyName: nameof(ReaderFont), value); } @@ -178,6 +178,18 @@ namespace Wino.Services set => SaveProperty(propertyName: nameof(ReaderFontSize), value); } + public string ComposerFont + { + get => _configurationService.Get(nameof(ComposerFont), "Calibri"); + set => SaveProperty(propertyName: nameof(ComposerFont), value); + } + + public int ComposerFontSize + { + get => _configurationService.Get(nameof(ComposerFontSize), 14); + set => SaveProperty(propertyName: nameof(ComposerFontSize), value); + } + public bool IsNavigationPaneOpened { get => _configurationService.Get(nameof(IsNavigationPaneOpened), true); diff --git a/Wino.Mail/Services/WinoNavigationService.cs b/Wino.Mail/Services/WinoNavigationService.cs index d9026d12..67e086cc 100644 --- a/Wino.Mail/Services/WinoNavigationService.cs +++ b/Wino.Mail/Services/WinoNavigationService.cs @@ -72,8 +72,8 @@ namespace Wino.Services return typeof(PersonalizationPage); case WinoPage.MessageListPage: return typeof(MessageListPage); - case WinoPage.ReadingPanePage: - return typeof(ReadingPanePage); + case WinoPage.ReadComposePanePage: + return typeof(ReadComposePanePage); case WinoPage.MailRenderingPage: return typeof(MailRenderingPage); case WinoPage.ComposePage: diff --git a/Wino.Mail/Views/Abstract/ReadComposePanePageAbstract.cs b/Wino.Mail/Views/Abstract/ReadComposePanePageAbstract.cs new file mode 100644 index 00000000..0785f6e7 --- /dev/null +++ b/Wino.Mail/Views/Abstract/ReadComposePanePageAbstract.cs @@ -0,0 +1,6 @@ +using Wino.Mail.ViewModels; + +namespace Wino.Views.Abstract +{ + public abstract class ReadComposePanePageAbstract : BasePage { } +} diff --git a/Wino.Mail/Views/Abstract/ReadingPanePageAbstract.cs b/Wino.Mail/Views/Abstract/ReadingPanePageAbstract.cs deleted file mode 100644 index 01ab1f48..00000000 --- a/Wino.Mail/Views/Abstract/ReadingPanePageAbstract.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Wino.Mail.ViewModels; - -namespace Wino.Views.Abstract -{ - public abstract class ReadingPanePageAbstract : BasePage { } -} diff --git a/Wino.Mail/Views/ComposePage.xaml.cs b/Wino.Mail/Views/ComposePage.xaml.cs index e2a71e7b..d6c5c43b 100644 --- a/Wino.Mail/Views/ComposePage.xaml.cs +++ b/Wino.Mail/Views/ComposePage.xaml.cs @@ -59,7 +59,7 @@ namespace Wino.Views InitializeComponent(); Environment.SetEnvironmentVariable("WEBVIEW2_DEFAULT_BACKGROUND_COLOR", "00FFFFFF"); - Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation"); + Environment.SetEnvironmentVariable("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS", "--enable-features=OverlayScrollbar,msOverlayScrollbarWinStyle,msOverlayScrollbarWinStyleAnimation,FontAccess"); } private static async void OnIsComposerDarkModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) @@ -389,6 +389,7 @@ namespace Wino.Views await DOMLoadedTask.Task; await UpdateEditorThemeAsync(); + await InitializeEditorAsync(); if (string.IsNullOrEmpty(htmlBody)) { @@ -400,6 +401,16 @@ namespace Wino.Views } } + private async Task InitializeEditorAsync() + { + var fonts = ViewModel.FontService.GetFonts(); + var composerFont = ViewModel.PreferencesService.ComposerFont; + int composerFontSize = ViewModel.PreferencesService.ComposerFontSize; + var readerFont = ViewModel.PreferencesService.ReaderFont; + int readerFontSize = ViewModel.PreferencesService.ReaderFontSize; + return await ExecuteScriptFunctionAsync("initializeJodit", fonts, composerFont, composerFontSize, readerFont, readerFontSize); + } + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { base.OnNavigatingFrom(e); diff --git a/Wino.Mail/Views/MailRenderingPage.xaml.cs b/Wino.Mail/Views/MailRenderingPage.xaml.cs index 7d8cc7cb..c5a5abbe 100644 --- a/Wino.Mail/Views/MailRenderingPage.xaml.cs +++ b/Wino.Mail/Views/MailRenderingPage.xaml.cs @@ -17,6 +17,7 @@ using Wino.Core.Domain.Enums; using Wino.Core.Domain.Interfaces; using Wino.Core.Messages.Mails; using Wino.Core.Messages.Shell; +using Wino.Core.Services; using Wino.Mail.ViewModels.Data; using Wino.Views.Abstract; @@ -273,13 +274,11 @@ namespace Wino.Views await ExecuteScriptFunctionAsync("ChangeFontSize", _fontService.GetCurrentReaderFontSize()); // Prepare font family name with fallback to sans-serif by default. - var fontName = _fontService.GetCurrentReaderFont()?.FontFamilyName ?? "Arial"; + var fontName = _fontService.GetCurrentReaderFont(); // If font family name is not supported by the browser, fallback to sans-serif. fontName += ", sans-serif"; - // var fontName = "Starborn"; - await ExecuteScriptFunctionAsync("ChangeFontFamily", fontName); } diff --git a/Wino.Mail/Views/Settings/MessageListPage.xaml b/Wino.Mail/Views/Settings/MessageListPage.xaml index e0fd6783..117bbb52 100644 --- a/Wino.Mail/Views/Settings/MessageListPage.xaml +++ b/Wino.Mail/Views/Settings/MessageListPage.xaml @@ -7,6 +7,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:domain="using:Wino.Core.Domain" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:muxc="using:Microsoft.UI.Xaml.Controls" mc:Ignorable="d"> @@ -92,6 +93,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wino.Mail/Views/Settings/ReadingPanePage.xaml b/Wino.Mail/Views/Settings/ReadComposePanePage.xaml similarity index 63% rename from Wino.Mail/Views/Settings/ReadingPanePage.xaml rename to Wino.Mail/Views/Settings/ReadComposePanePage.xaml index 9f6c0ff3..380945ce 100644 --- a/Wino.Mail/Views/Settings/ReadingPanePage.xaml +++ b/Wino.Mail/Views/Settings/ReadComposePanePage.xaml @@ -1,35 +1,34 @@ - - + + + Data="F1 M 0 11.865234 C 0 11.813151 0.029297 11.699219 0.087891 11.523438 C 0.146484 11.347656 0.219727 11.139323 0.307617 10.898438 C 0.395508 10.657553 0.494792 10.398764 0.605469 10.12207 C 0.716146 9.845378 0.82194 9.581706 0.922852 9.331055 C 1.023763 9.080404 1.116536 8.854167 1.201172 8.652344 C 1.285807 8.450521 1.344401 8.304037 1.376953 8.212891 L 4.414062 0.400391 C 4.466146 0.276693 4.544271 0.179037 4.648438 0.107422 C 4.752604 0.035809 4.869792 0 5 0 C 5.143229 0 5.262044 0.035809 5.356445 0.107422 C 5.450846 0.179037 5.527344 0.276693 5.585938 0.400391 C 5.644531 0.5306 5.698242 0.665691 5.74707 0.805664 C 5.795898 0.945639 5.846354 1.08073 5.898438 1.210938 L 6.630859 3.105469 C 7.001953 4.05599 7.371419 5.003256 7.739258 5.947266 C 8.107096 6.891276 8.476562 7.838542 8.847656 8.789062 L 8.183594 10.537109 L 7.617188 9.072266 L 2.382812 9.072266 L 1.210938 12.099609 C 1.158854 12.223308 1.079102 12.320964 0.97168 12.392578 C 0.864258 12.464193 0.745443 12.5 0.615234 12.5 C 0.439453 12.5 0.292969 12.436523 0.175781 12.30957 C 0.058594 12.182617 0 12.034506 0 11.865234 Z M 5 2.353516 L 2.871094 7.822266 L 7.128906 7.822266 Z M 5.625 20 C 5.455729 20 5.309245 19.93815 5.185547 19.814453 C 5.061849 19.690756 5 19.544271 5 19.375 C 5 19.238281 5.027669 19.127604 5.083008 19.042969 C 5.138346 18.958334 5.211588 18.893229 5.302734 18.847656 C 5.39388 18.802084 5.494792 18.772787 5.605469 18.759766 C 5.716146 18.746744 5.830078 18.740234 5.947266 18.740234 C 6.038411 18.740234 6.126302 18.741861 6.210938 18.745117 C 6.295573 18.748373 6.373698 18.75 6.445312 18.75 L 11.914062 4.160156 C 11.959635 4.036459 12.036133 3.937176 12.143555 3.862305 C 12.250976 3.787436 12.369791 3.75 12.5 3.75 C 12.630208 3.75 12.749023 3.785809 12.856445 3.857422 C 12.963867 3.929037 13.040364 4.026693 13.085938 4.150391 L 18.671875 18.75 C 18.743488 18.75 18.813477 18.748373 18.881836 18.745117 C 18.950195 18.741861 19.020182 18.740234 19.091797 18.740234 C 19.202473 18.740234 19.31315 18.746744 19.423828 18.759766 C 19.534504 18.772787 19.63216 18.802084 19.716797 18.847656 C 19.801432 18.893229 19.869791 18.958334 19.921875 19.042969 C 19.973957 19.127604 20 19.238281 20 19.375 C 20 19.544271 19.93815 19.690756 19.814453 19.814453 C 19.690754 19.93815 19.54427 20 19.375 20 L 16.875 20 C 16.705729 20 16.559244 19.93815 16.435547 19.814453 C 16.311848 19.690756 16.25 19.544271 16.25 19.375 C 16.25 19.22526 16.280924 19.108072 16.342773 19.023438 C 16.404621 18.938803 16.482746 18.876953 16.577148 18.837891 C 16.671549 18.798828 16.777344 18.774414 16.894531 18.764648 C 17.011719 18.754883 17.128906 18.75 17.246094 18.75 L 17.333984 18.75 L 15.898438 15 L 9.179688 15 L 7.773438 18.75 C 7.890625 18.75 8.006185 18.754883 8.120117 18.764648 C 8.234049 18.774414 8.338216 18.798828 8.432617 18.837891 C 8.527018 18.876953 8.603516 18.938803 8.662109 19.023438 C 8.720703 19.108072 8.75 19.22526 8.75 19.375 C 8.75 19.544271 8.68815 19.690756 8.564453 19.814453 C 8.440755 19.93815 8.294271 20 8.125 20 Z M 12.509766 6.142578 L 9.648438 13.75 L 15.419922 13.75 Z " /> - + - - + + @@ -37,60 +36,23 @@ + SpinButtonPlacementMode="Inline" + Value="{x:Bind ViewModel.CurrentReaderFontSize, Mode=TwoWay}" /> + TextWrapping="WrapWholeWords" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -109,7 +71,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Wino.Mail/Views/Settings/ReadingPanePage.xaml.cs b/Wino.Mail/Views/Settings/ReadComposePanePage.xaml.cs similarity index 52% rename from Wino.Mail/Views/Settings/ReadingPanePage.xaml.cs rename to Wino.Mail/Views/Settings/ReadComposePanePage.xaml.cs index 3f8187a1..764acf8d 100644 --- a/Wino.Mail/Views/Settings/ReadingPanePage.xaml.cs +++ b/Wino.Mail/Views/Settings/ReadComposePanePage.xaml.cs @@ -2,9 +2,9 @@ namespace Wino.Views.Settings { - public sealed partial class ReadingPanePage : ReadingPanePageAbstract + public sealed partial class ReadComposePanePage : ReadComposePanePageAbstract { - public ReadingPanePage() + public ReadComposePanePage() { InitializeComponent(); } diff --git a/Wino.Mail/Views/Settings/SettingOptionsPage.xaml b/Wino.Mail/Views/Settings/SettingOptionsPage.xaml index 3d3c3887..b1680fdc 100644 --- a/Wino.Mail/Views/Settings/SettingOptionsPage.xaml +++ b/Wino.Mail/Views/Settings/SettingOptionsPage.xaml @@ -68,10 +68,10 @@ + Header="{x:Bind domain:Translator.SettingsReadComposePane_Title}" + Description="{x:Bind domain:Translator.SettingsReadComposePane_Description}"> typeof(AboutPage), WinoPage.PersonalizationPage => typeof(PersonalizationPage), WinoPage.MessageListPage => typeof(MessageListPage), - WinoPage.ReadingPanePage => typeof(ReadingPanePage), + WinoPage.ReadComposePanePage => typeof(ReadComposePanePage), WinoPage.LanguageTimePage => typeof(LanguageTimePage), _ => null, }; diff --git a/Wino.Mail/Wino.Mail.csproj b/Wino.Mail/Wino.Mail.csproj index a758c37c..efe74e37 100644 --- a/Wino.Mail/Wino.Mail.csproj +++ b/Wino.Mail/Wino.Mail.csproj @@ -356,7 +356,7 @@ - + @@ -419,8 +419,8 @@ AppShell.xaml - - ReadingPanePage.xaml + + ReadComposePanePage.xaml SettingOptionsPage.xaml @@ -647,7 +647,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile