Handling of OutlookSynchronizer alias.
This commit is contained in:
@@ -28,7 +28,7 @@ namespace Wino.Core.Authenticators
|
|||||||
|
|
||||||
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
|
public string ClientId { get; } = "b19c2035-d740-49ff-b297-de6ec561b208";
|
||||||
|
|
||||||
private readonly string[] MailScope = ["email", "mail.readwrite", "offline_access", "mail.send"];
|
private readonly string[] MailScope = ["email", "mail.readwrite", "offline_access", "mail.send", "Mail.Send.Shared", "Mail.ReadWrite.Shared"];
|
||||||
|
|
||||||
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
public override MailProviderType ProviderType => MailProviderType.Outlook;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.Graph.Models;
|
using Microsoft.Graph.Models;
|
||||||
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
|
|
||||||
@@ -61,5 +65,143 @@ namespace Wino.Core.Extensions
|
|||||||
|
|
||||||
return mailCopy;
|
return mailCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Message AsOutlookMessage(this MimeMessage mime, string threadId)
|
||||||
|
{
|
||||||
|
var fromAddress = GetRecipients(mime.From).ElementAt(0);
|
||||||
|
var toAddresses = GetRecipients(mime.To).ToList();
|
||||||
|
var ccAddresses = GetRecipients(mime.Cc).ToList();
|
||||||
|
var bccAddresses = GetRecipients(mime.Bcc).ToList();
|
||||||
|
var replyToAddresses = GetRecipients(mime.ReplyTo).ToList();
|
||||||
|
|
||||||
|
var message = new Message()
|
||||||
|
{
|
||||||
|
Subject = mime.Subject,
|
||||||
|
Importance = GetImportance(mime.Importance),
|
||||||
|
Body = new ItemBody() { ContentType = BodyType.Html, Content = mime.HtmlBody },
|
||||||
|
IsDraft = false,
|
||||||
|
IsRead = true, // Sent messages are always read.
|
||||||
|
ToRecipients = toAddresses,
|
||||||
|
CcRecipients = ccAddresses,
|
||||||
|
BccRecipients = bccAddresses,
|
||||||
|
From = fromAddress,
|
||||||
|
InternetMessageId = GetMessageIdHeader(mime.MessageId),
|
||||||
|
ConversationId = threadId,
|
||||||
|
InternetMessageHeaders = GetHeaderList(mime),
|
||||||
|
ReplyTo = replyToAddresses,
|
||||||
|
Attachments = []
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var part in mime.BodyParts)
|
||||||
|
{
|
||||||
|
if (part.IsAttachment)
|
||||||
|
{
|
||||||
|
// File attachment.
|
||||||
|
|
||||||
|
using var memory = new MemoryStream();
|
||||||
|
((MimePart)part).Content.DecodeTo(memory);
|
||||||
|
|
||||||
|
var bytes = memory.ToArray();
|
||||||
|
|
||||||
|
var fileAttachment = new FileAttachment()
|
||||||
|
{
|
||||||
|
ContentId = part.ContentId,
|
||||||
|
Name = part.ContentDisposition?.FileName ?? part.ContentType.Name,
|
||||||
|
ContentBytes = bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
message.Attachments.Add(fileAttachment);
|
||||||
|
}
|
||||||
|
else if (part.ContentDisposition != null && part.ContentDisposition.Disposition == "inline")
|
||||||
|
{
|
||||||
|
// Inline attachment.
|
||||||
|
|
||||||
|
using var memory = new MemoryStream();
|
||||||
|
((MimePart)part).Content.DecodeTo(memory);
|
||||||
|
|
||||||
|
var bytes = memory.ToArray();
|
||||||
|
var inlineAttachment = new FileAttachment()
|
||||||
|
{
|
||||||
|
IsInline = true,
|
||||||
|
ContentId = part.ContentId,
|
||||||
|
Name = part.ContentDisposition?.FileName ?? part.ContentType.Name,
|
||||||
|
ContentBytes = bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
message.Attachments.Add(inlineAttachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Mime to Outlook Message Helpers
|
||||||
|
|
||||||
|
private static IEnumerable<Recipient> GetRecipients(this InternetAddressList internetAddresses)
|
||||||
|
{
|
||||||
|
foreach (var address in internetAddresses)
|
||||||
|
{
|
||||||
|
if (address is MailboxAddress mailboxAddress)
|
||||||
|
yield return new Recipient() { EmailAddress = new EmailAddress() { Address = mailboxAddress.Address, Name = mailboxAddress.Name } };
|
||||||
|
else if (address is GroupAddress groupAddress)
|
||||||
|
{
|
||||||
|
// TODO: Group addresses are not directly supported.
|
||||||
|
// It'll be individually added.
|
||||||
|
|
||||||
|
foreach (var mailbox in groupAddress.Members)
|
||||||
|
if (mailbox is MailboxAddress groupMemberMailAddress)
|
||||||
|
yield return new Recipient() { EmailAddress = new EmailAddress() { Address = groupMemberMailAddress.Address, Name = groupMemberMailAddress.Name } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Importance? GetImportance(MessageImportance importance)
|
||||||
|
{
|
||||||
|
return importance switch
|
||||||
|
{
|
||||||
|
MessageImportance.Low => Importance.Low,
|
||||||
|
MessageImportance.Normal => Importance.Normal,
|
||||||
|
MessageImportance.High => Importance.High,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InternetMessageHeader> GetHeaderList(this MimeMessage mime)
|
||||||
|
{
|
||||||
|
// Graph API only allows max of 5 headers.
|
||||||
|
// Here we'll try to ignore some headers that are not neccessary.
|
||||||
|
// Outlook API will generate them automatically.
|
||||||
|
|
||||||
|
string[] headersToIgnore = ["Date", "To", "MIME-Version", "From", "Subject", "Message-Id"];
|
||||||
|
|
||||||
|
var headers = new List<InternetMessageHeader>();
|
||||||
|
|
||||||
|
int includedHeaderCount = 0;
|
||||||
|
|
||||||
|
foreach (var header in mime.Headers)
|
||||||
|
{
|
||||||
|
if (!headersToIgnore.Contains(header.Field))
|
||||||
|
{
|
||||||
|
headers.Add(new InternetMessageHeader() { Name = header.Field, Value = header.Value });
|
||||||
|
includedHeaderCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includedHeaderCount >= 5) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetMessageIdHeader(string messageId)
|
||||||
|
{
|
||||||
|
// Message-Id header must always start with "X-" or "x-".
|
||||||
|
if (string.IsNullOrEmpty(messageId)) return string.Empty;
|
||||||
|
|
||||||
|
if (!messageId.StartsWith("x-") || !messageId.StartsWith("X-"))
|
||||||
|
return $"X-{messageId}";
|
||||||
|
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,12 +109,9 @@ namespace Wino.Core.Services
|
|||||||
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId).ConfigureAwait(false);
|
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId).ConfigureAwait(false);
|
||||||
var completeFilePath = GetEMLPath(resourcePath);
|
var completeFilePath = GetEMLPath(resourcePath);
|
||||||
|
|
||||||
var fileStream = File.Create(completeFilePath);
|
using var fileStream = File.Open(completeFilePath, FileMode.OpenOrCreate);
|
||||||
|
|
||||||
using (fileStream)
|
|
||||||
{
|
|
||||||
await mimeMessage.WriteToAsync(fileStream).ConfigureAwait(false);
|
await mimeMessage.WriteToAsync(fileStream).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Graph;
|
using Microsoft.Graph;
|
||||||
|
using Microsoft.Graph.Me.SendMail;
|
||||||
using Microsoft.Graph.Models;
|
using Microsoft.Graph.Models;
|
||||||
using Microsoft.Kiota.Abstractions;
|
using Microsoft.Kiota.Abstractions;
|
||||||
using Microsoft.Kiota.Abstractions.Authentication;
|
using Microsoft.Kiota.Abstractions.Authentication;
|
||||||
@@ -17,8 +18,8 @@ using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
|
|||||||
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
|
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using MoreLinq.Extensions;
|
using MoreLinq.Extensions;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Wino.Core.Domain;
|
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Exceptions;
|
using Wino.Core.Domain.Exceptions;
|
||||||
@@ -633,41 +634,26 @@ namespace Wino.Core.Synchronizers
|
|||||||
var mailCopyId = sendDraftPreparationRequest.MailItem.Id;
|
var mailCopyId = sendDraftPreparationRequest.MailItem.Id;
|
||||||
var mimeMessage = sendDraftPreparationRequest.Mime;
|
var mimeMessage = sendDraftPreparationRequest.Mime;
|
||||||
|
|
||||||
var batchDeleteRequest = new BatchDeleteRequest(new List<IRequest>()
|
var batchDeleteRequest = new BatchDeleteRequest(
|
||||||
{
|
[
|
||||||
new DeleteRequest(sendDraftPreparationRequest.MailItem)
|
new DeleteRequest(sendDraftPreparationRequest.MailItem)
|
||||||
});
|
]);
|
||||||
|
|
||||||
var deleteBundle = Delete(batchDeleteRequest).ElementAt(0);
|
var deleteBundle = Delete(batchDeleteRequest).ElementAt(0);
|
||||||
|
|
||||||
mimeMessage.Prepare(EncodingConstraint.None);
|
// Convert mime message to Outlook message.
|
||||||
|
// Outlook synchronizer does not send MIME messages directly anymore.
|
||||||
|
// Alias support is lacking with direct MIMEs.
|
||||||
|
// Therefore we convert the MIME message to Outlook message and use proper APIs.
|
||||||
|
|
||||||
var plainTextBytes = Encoding.UTF8.GetBytes(mimeMessage.ToString());
|
var outlookMessage = mimeMessage.AsOutlookMessage(sendDraftPreparationRequest.MailItem.ThreadId);
|
||||||
var base64Encoded = Convert.ToBase64String(plainTextBytes);
|
|
||||||
|
|
||||||
var outlookMessage = new Message()
|
var sendMailPostRequest = _graphClient.Me.SendMail.ToPostRequestInformation(new SendMailPostRequestBody()
|
||||||
{
|
|
||||||
ConversationId = sendDraftPreparationRequest.MailItem.ThreadId
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply importance here as well just in case.
|
|
||||||
if (mimeMessage.Importance != MessageImportance.Normal)
|
|
||||||
outlookMessage.Importance = mimeMessage.Importance == MessageImportance.High ? Importance.High : Importance.Low;
|
|
||||||
|
|
||||||
var body = new Microsoft.Graph.Me.SendMail.SendMailPostRequestBody()
|
|
||||||
{
|
{
|
||||||
Message = outlookMessage
|
Message = outlookMessage
|
||||||
};
|
});
|
||||||
|
|
||||||
var sendRequest = _graphClient.Me.SendMail.ToPostRequestInformation(body);
|
var sendMailRequest = new HttpRequestBundle<RequestInformation>(sendMailPostRequest, request);
|
||||||
|
|
||||||
sendRequest.Headers.Clear();
|
|
||||||
sendRequest.Headers.Add("Content-Type", "text/plain");
|
|
||||||
|
|
||||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(base64Encoded));
|
|
||||||
sendRequest.SetStreamContent(stream, "text/plain");
|
|
||||||
|
|
||||||
var sendMailRequest = new HttpRequestBundle<RequestInformation>(sendRequest, request);
|
|
||||||
|
|
||||||
return [deleteBundle, sendMailRequest];
|
return [deleteBundle, sendMailRequest];
|
||||||
}
|
}
|
||||||
@@ -675,8 +661,6 @@ namespace Wino.Core.Synchronizers
|
|||||||
public override IEnumerable<IRequestBundle<RequestInformation>> Archive(BatchArchiveRequest request)
|
public override IEnumerable<IRequestBundle<RequestInformation>> Archive(BatchArchiveRequest request)
|
||||||
=> Move(new BatchMoveRequest(request.Items, request.FromFolder, request.ToFolder));
|
=> Move(new BatchMoveRequest(request.Items, request.FromFolder, request.ToFolder));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public override async Task DownloadMissingMimeMessageAsync(IMailItem mailItem,
|
public override async Task DownloadMissingMimeMessageAsync(IMailItem mailItem,
|
||||||
MailKit.ITransferProgress transferProgress = null,
|
MailKit.ITransferProgress transferProgress = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -774,7 +758,10 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
throw new SynchronizerException(string.Format(Translator.Exception_SynchronizerFailureHTTP, httpResponseMessage.StatusCode));
|
var content = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||||
|
var errorJson = JObject.Parse(content);
|
||||||
|
|
||||||
|
throw new SynchronizerException($"({httpResponseMessage.StatusCode}) {errorJson["error"]["code"]} - {errorJson["error"]["message"]}");
|
||||||
}
|
}
|
||||||
else if (bundle is HttpRequestBundle<RequestInformation, Message> messageBundle)
|
else if (bundle is HttpRequestBundle<RequestInformation, Message> messageBundle)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -135,19 +135,19 @@ namespace Wino.Mail.ViewModels
|
|||||||
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
||||||
{
|
{
|
||||||
NativeAppService = nativeAppService;
|
NativeAppService = nativeAppService;
|
||||||
_folderService = folderService;
|
|
||||||
ContactService = contactService;
|
ContactService = contactService;
|
||||||
FontService = fontService;
|
FontService = fontService;
|
||||||
|
PreferencesService = preferencesService;
|
||||||
|
|
||||||
|
_folderService = folderService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_launchProtocolService = launchProtocolService;
|
_launchProtocolService = launchProtocolService;
|
||||||
_mimeFileService = mimeFileService;
|
_mimeFileService = mimeFileService;
|
||||||
_accountService = accountService;
|
_accountService = accountService;
|
||||||
_worker = worker;
|
_worker = worker;
|
||||||
|
_winoServerConnectionManager = winoServerConnectionManager;
|
||||||
|
|
||||||
SelectedToolbarSection = ToolbarSections[0];
|
SelectedToolbarSection = ToolbarSections[0];
|
||||||
PreferencesService = preferencesService;
|
|
||||||
_winoServerConnectionManager = winoServerConnectionManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -202,6 +202,16 @@ namespace Wino.Mail.ViewModels
|
|||||||
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
await _worker.ExecuteAsync(draftSendPreparationRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task IncludeAttachmentAsync(MailAttachmentViewModel viewModel)
|
||||||
|
{
|
||||||
|
//if (bodyBuilder == null) return;
|
||||||
|
|
||||||
|
//bodyBuilder.Attachments.Add(viewModel.FileName, new MemoryStream(viewModel.Content));
|
||||||
|
|
||||||
|
//LoadAttachments();
|
||||||
|
IncludedAttachments.Add(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpdateMimeChangesAsync()
|
private async Task UpdateMimeChangesAsync()
|
||||||
{
|
{
|
||||||
if (isUpdatingMimeBlocked || CurrentMimeMessage == null || ComposingAccount == null || CurrentMailDraftItem == null) return;
|
if (isUpdatingMimeBlocked || CurrentMimeMessage == null || ComposingAccount == null || CurrentMailDraftItem == null) return;
|
||||||
@@ -230,6 +240,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
CurrentMailDraftItem.Subject = CurrentMimeMessage.Subject;
|
CurrentMailDraftItem.Subject = CurrentMimeMessage.Subject;
|
||||||
CurrentMailDraftItem.PreviewText = CurrentMimeMessage.TextBody;
|
CurrentMailDraftItem.PreviewText = CurrentMimeMessage.TextBody;
|
||||||
CurrentMailDraftItem.FromAddress = SelectedAlias.AliasAddress;
|
CurrentMailDraftItem.FromAddress = SelectedAlias.AliasAddress;
|
||||||
|
CurrentMailDraftItem.HasAttachments = CurrentMimeMessage.Attachments.Any();
|
||||||
|
|
||||||
// Update database.
|
// Update database.
|
||||||
await _mailService.UpdateMailAsync(CurrentMailDraftItem.MailCopy);
|
await _mailService.UpdateMailAsync(CurrentMailDraftItem.MailCopy);
|
||||||
@@ -260,6 +271,31 @@ namespace Wino.Mail.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearCurrentMimeAttachments()
|
||||||
|
{
|
||||||
|
var attachments = new List<MimePart>();
|
||||||
|
var multiparts = new List<Multipart>();
|
||||||
|
var iter = new MimeIterator(CurrentMimeMessage);
|
||||||
|
|
||||||
|
// collect our list of attachments and their parent multiparts
|
||||||
|
while (iter.MoveNext())
|
||||||
|
{
|
||||||
|
var multipart = iter.Parent as Multipart;
|
||||||
|
var part = iter.Current as MimePart;
|
||||||
|
|
||||||
|
if (multipart != null && part != null && part.IsAttachment)
|
||||||
|
{
|
||||||
|
// keep track of each attachment's parent multipart
|
||||||
|
multiparts.Add(multipart);
|
||||||
|
attachments.Add(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now remove each attachment from its parent multipart...
|
||||||
|
for (int i = 0; i < attachments.Count; i++)
|
||||||
|
multiparts[i].Remove(attachments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SaveBodyAsync()
|
private async Task SaveBodyAsync()
|
||||||
{
|
{
|
||||||
if (GetHTMLBodyFunction != null)
|
if (GetHTMLBodyFunction != null)
|
||||||
@@ -267,7 +303,6 @@ namespace Wino.Mail.ViewModels
|
|||||||
bodyBuilder.SetHtmlBody(await GetHTMLBodyFunction());
|
bodyBuilder.SetHtmlBody(await GetHTMLBodyFunction());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bodyBuilder.HtmlBody != null && bodyBuilder.TextBody != null)
|
|
||||||
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
CurrentMimeMessage.Body = bodyBuilder.ToMessageBody();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,9 +341,11 @@ namespace Wino.Mail.ViewModels
|
|||||||
base.OnNavigatedFrom(mode, parameters);
|
base.OnNavigatedFrom(mode, parameters);
|
||||||
|
|
||||||
await UpdateMimeChangesAsync().ConfigureAwait(false);
|
await UpdateMimeChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
Messenger.Send(new KillChromiumRequested());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
{
|
{
|
||||||
base.OnNavigatedTo(mode, parameters);
|
base.OnNavigatedTo(mode, parameters);
|
||||||
|
|
||||||
@@ -316,29 +353,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
{
|
{
|
||||||
CurrentMailDraftItem = mailItem;
|
CurrentMailDraftItem = mailItem;
|
||||||
|
|
||||||
_ = TryPrepareComposeAsync(true);
|
await TryPrepareComposeAsync(true);
|
||||||
}
|
|
||||||
|
|
||||||
ToItems.CollectionChanged -= ContactListCollectionChanged;
|
|
||||||
ToItems.CollectionChanged += ContactListCollectionChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContactListCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
|
|
||||||
{
|
|
||||||
// Prevent duplicates.
|
|
||||||
if (!(sender is ObservableCollection<AddressInformation> list))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var item in e.NewItems)
|
|
||||||
{
|
|
||||||
if (item is AddressInformation addedInfo && list.Count(a => a == addedInfo) > 1)
|
|
||||||
{
|
|
||||||
var addedIndex = list.IndexOf(addedInfo);
|
|
||||||
list.RemoveAt(addedIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +451,8 @@ namespace Wino.Mail.ViewModels
|
|||||||
{
|
{
|
||||||
// Extract information
|
// Extract information
|
||||||
|
|
||||||
|
CurrentMimeMessage = replyingMime;
|
||||||
|
|
||||||
ToItems.Clear();
|
ToItems.Clear();
|
||||||
CCItems.Clear();
|
CCItems.Clear();
|
||||||
BCCItems.Clear();
|
BCCItems.Clear();
|
||||||
@@ -444,22 +461,22 @@ namespace Wino.Mail.ViewModels
|
|||||||
LoadAddressInfo(replyingMime.Cc, CCItems);
|
LoadAddressInfo(replyingMime.Cc, CCItems);
|
||||||
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
||||||
|
|
||||||
LoadAttachments(replyingMime.Attachments);
|
LoadAttachments();
|
||||||
|
|
||||||
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
||||||
IsCCBCCVisible = true;
|
IsCCBCCVisible = true;
|
||||||
|
|
||||||
Subject = replyingMime.Subject;
|
Subject = replyingMime.Subject;
|
||||||
|
|
||||||
CurrentMimeMessage = replyingMime;
|
|
||||||
|
|
||||||
Messenger.Send(new CreateNewComposeMailRequested(renderModel));
|
Messenger.Send(new CreateNewComposeMailRequested(renderModel));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadAttachments(IEnumerable<MimeEntity> mimeEntities)
|
private void LoadAttachments()
|
||||||
{
|
{
|
||||||
foreach (var attachment in mimeEntities)
|
if (CurrentMimeMessage == null) return;
|
||||||
|
|
||||||
|
foreach (var attachment in CurrentMimeMessage.Attachments)
|
||||||
{
|
{
|
||||||
if (attachment.IsAttachment && attachment is MimePart attachmentPart)
|
if (attachment.IsAttachment && attachment is MimePart attachmentPart)
|
||||||
{
|
{
|
||||||
@@ -481,18 +498,15 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
private void SaveFromAddress()
|
private void SaveFromAddress()
|
||||||
{
|
{
|
||||||
if (SelectedAlias == null || CurrentMimeMessage == null) return;
|
if (SelectedAlias == null) return;
|
||||||
|
|
||||||
|
|
||||||
CurrentMimeMessage.From.Clear();
|
CurrentMimeMessage.From.Clear();
|
||||||
CurrentMimeMessage.From.Add(new MailboxAddress(ComposingAccount.SenderName, SelectedAlias.AliasAddress));
|
CurrentMimeMessage.From.Add(new MailboxAddress(ComposingAccount.SenderName, SelectedAlias.AliasAddress));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveReplyToAddress()
|
private void SaveReplyToAddress()
|
||||||
{
|
{
|
||||||
if (SelectedAlias == null || CurrentMimeMessage == null) return;
|
if (SelectedAlias == null) return;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(SelectedAlias.ReplyToAddress))
|
if (!string.IsNullOrEmpty(SelectedAlias.ReplyToAddress))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ namespace Wino.Mail.ViewModels.Data
|
|||||||
public string MessageId => ((IMailItem)MailCopy).MessageId;
|
public string MessageId => ((IMailItem)MailCopy).MessageId;
|
||||||
public string FromName => ((IMailItem)MailCopy).FromName ?? FromAddress;
|
public string FromName => ((IMailItem)MailCopy).FromName ?? FromAddress;
|
||||||
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
|
public DateTime CreationDate => ((IMailItem)MailCopy).CreationDate;
|
||||||
public bool HasAttachments => ((IMailItem)MailCopy).HasAttachments;
|
|
||||||
public string References => ((IMailItem)MailCopy).References;
|
public string References => ((IMailItem)MailCopy).References;
|
||||||
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
public string InReplyTo => ((IMailItem)MailCopy).InReplyTo;
|
||||||
|
|
||||||
@@ -82,6 +81,12 @@ namespace Wino.Mail.ViewModels.Data
|
|||||||
set => SetProperty(MailCopy.FromAddress, value, MailCopy, (u, n) => u.FromAddress = n);
|
set => SetProperty(MailCopy.FromAddress, value, MailCopy, (u, n) => u.FromAddress = n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasAttachments
|
||||||
|
{
|
||||||
|
get => MailCopy.HasAttachments;
|
||||||
|
set => SetProperty(MailCopy.HasAttachments, value, MailCopy, (u, n) => u.HasAttachments = n);
|
||||||
|
}
|
||||||
|
|
||||||
public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder;
|
public MailItemFolder AssignedFolder => ((IMailItem)MailCopy).AssignedFolder;
|
||||||
|
|
||||||
public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount;
|
public MailAccount AssignedAccount => ((IMailItem)MailCopy).AssignedAccount;
|
||||||
@@ -99,6 +104,8 @@ namespace Wino.Mail.ViewModels.Data
|
|||||||
OnPropertyChanged(nameof(DraftId));
|
OnPropertyChanged(nameof(DraftId));
|
||||||
OnPropertyChanged(nameof(Subject));
|
OnPropertyChanged(nameof(Subject));
|
||||||
OnPropertyChanged(nameof(PreviewText));
|
OnPropertyChanged(nameof(PreviewText));
|
||||||
|
OnPropertyChanged(nameof(FromAddress));
|
||||||
|
OnPropertyChanged(nameof(HasAttachments));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
|
public IEnumerable<Guid> GetContainingIds() => new[] { UniqueId };
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ namespace Wino.Views
|
|||||||
WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested());
|
WeakReferenceMessenger.Default.Send(new ClearMailSelectionsRequested());
|
||||||
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
|
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
|
||||||
WeakReferenceMessenger.Default.Send(new ShellStateUpdated());
|
WeakReferenceMessenger.Default.Send(new ShellStateUpdated());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void MenuItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
|
private async void MenuItemContextRequested(UIElement sender, ContextRequestedEventArgs args)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Wino.Core.Domain.Models.Navigation;
|
|||||||
using Wino.Helpers;
|
using Wino.Helpers;
|
||||||
using Wino.Mail.ViewModels.Data;
|
using Wino.Mail.ViewModels.Data;
|
||||||
using Wino.Mail.ViewModels.Messages;
|
using Wino.Mail.ViewModels.Messages;
|
||||||
|
using Wino.Messaging.Client.Mails;
|
||||||
using Wino.Views;
|
using Wino.Views;
|
||||||
using Wino.Views.Account;
|
using Wino.Views.Account;
|
||||||
using Wino.Views.Settings;
|
using Wino.Views.Settings;
|
||||||
@@ -116,6 +117,7 @@ namespace Wino.Services
|
|||||||
{
|
{
|
||||||
// No need for new navigation, just refresh the folder.
|
// No need for new navigation, just refresh the folder.
|
||||||
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
|
WeakReferenceMessenger.Default.Send(new ActiveMailFolderChangedEvent(folderNavigationArgs.BaseFolderMenuItem, folderNavigationArgs.FolderInitLoadAwaitTask));
|
||||||
|
WeakReferenceMessenger.Default.Send(new DisposeRenderingFrameRequested());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -138,6 +140,13 @@ namespace Wino.Services
|
|||||||
{
|
{
|
||||||
WeakReferenceMessenger.Default.Send(new NewMailItemRenderingRequestedEvent(mailItemViewModel));
|
WeakReferenceMessenger.Default.Send(new NewMailItemRenderingRequestedEvent(mailItemViewModel));
|
||||||
}
|
}
|
||||||
|
else if (listingFrame.Content != null
|
||||||
|
&& listingFrame.Content.GetType() == GetPageType(WinoPage.IdlePage)
|
||||||
|
&& pageType == typeof(IdlePage))
|
||||||
|
{
|
||||||
|
// Idle -> Idle navigation. Ignore.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
listingFrame.Navigate(pageType, parameter, transitionInfo);
|
listingFrame.Navigate(pageType, parameter, transitionInfo);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -39,7 +39,8 @@ namespace Wino.Views
|
|||||||
public sealed partial class ComposePage : ComposePageAbstract,
|
public sealed partial class ComposePage : ComposePageAbstract,
|
||||||
IRecipient<NavigationPaneModeChanged>,
|
IRecipient<NavigationPaneModeChanged>,
|
||||||
IRecipient<CreateNewComposeMailRequested>,
|
IRecipient<CreateNewComposeMailRequested>,
|
||||||
IRecipient<ApplicationThemeChanged>
|
IRecipient<ApplicationThemeChanged>,
|
||||||
|
IRecipient<KillChromiumRequested>
|
||||||
{
|
{
|
||||||
public bool IsComposerDarkMode
|
public bool IsComposerDarkMode
|
||||||
{
|
{
|
||||||
@@ -236,13 +237,10 @@ namespace Wino.Views
|
|||||||
|
|
||||||
// Convert files to MailAttachmentViewModel.
|
// Convert files to MailAttachmentViewModel.
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
|
||||||
if (!ViewModel.IncludedAttachments.Any(a => a.FileName == file.Path))
|
|
||||||
{
|
{
|
||||||
var attachmentViewModel = await file.ToAttachmentViewModelAsync();
|
var attachmentViewModel = await file.ToAttachmentViewModelAsync();
|
||||||
|
|
||||||
ViewModel.IncludedAttachments.Add(attachmentViewModel);
|
await ViewModel.IncludeAttachmentAsync(attachmentViewModel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,13 +415,6 @@ namespace Wino.Views
|
|||||||
return await ExecuteScriptFunctionAsync("initializeJodit", fonts, composerFont, composerFontSize, readerFont, readerFontSize);
|
return await ExecuteScriptFunctionAsync("initializeJodit", fonts, composerFont, composerFontSize, readerFont, readerFontSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnNavigatingFrom(e);
|
|
||||||
|
|
||||||
DisposeDisposables();
|
|
||||||
DisposeWebView2();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeWebView2()
|
private void DisposeWebView2()
|
||||||
{
|
{
|
||||||
@@ -700,5 +691,11 @@ namespace Wino.Views
|
|||||||
ToBox.Focus(FocusState.Programmatic);
|
ToBox.Focus(FocusState.Programmatic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Receive(KillChromiumRequested message)
|
||||||
|
{
|
||||||
|
DisposeDisposables();
|
||||||
|
DisposeWebView2();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
Wino.Messages/Client/Mails/KillChromiumRequested.cs
Normal file
7
Wino.Messages/Client/Mails/KillChromiumRequested.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Wino.Messaging.Client.Mails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Terminates all chromum instances.
|
||||||
|
/// </summary>
|
||||||
|
public record KillChromiumRequested;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user