Improve mailto links handling (#310)
* Refactor draft creation * try scoped namespace * Refactor mailto protocol and revert namespaces * Remove useless account query * Fix typo and CC/BCC in replies * Replace convert with existing extension * Small fixes * Fix CC/Bcc in replies to automatically show if needed. * Fixed body parameter position from mailto parameters * Fixed issue with ReplyAll self not removed
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
using System.Collections.Specialized;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
|
||||
namespace Wino.Core.Domain.Interfaces
|
||||
namespace Wino.Core.Domain.Interfaces;
|
||||
|
||||
public interface ILaunchProtocolService
|
||||
{
|
||||
public interface ILaunchProtocolService
|
||||
{
|
||||
object LaunchParameter { get; set; }
|
||||
NameValueCollection MailtoParameters { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Used to handle toasts.
|
||||
/// </summary>
|
||||
object LaunchParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to handle mailto links.
|
||||
/// </summary>
|
||||
MailToUri MailToUri { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Wino.Core.Domain.Interfaces
|
||||
{
|
||||
Task<MailCopy> GetSingleMailItemAsync(string mailCopyId, string remoteFolderId);
|
||||
Task<MailCopy> GetSingleMailItemAsync(Guid uniqueMailId);
|
||||
Task<MailCopy> CreateDraftAsync(MailAccount composerAccount, string generatedReplyMimeMessageBase64, MimeMessage replyingMimeMessage = null, IMailItem replyingMailItem = null);
|
||||
Task<List<IMailItem>> FetchMailsAsync(MailListInitializationOptions options);
|
||||
|
||||
/// <summary>
|
||||
@@ -44,23 +43,12 @@ namespace Wino.Core.Domain.Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Maps new mail item with the existing local draft copy.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="newMailCopyId"></param>
|
||||
/// <param name="newDraftId"></param>
|
||||
/// <param name="newThreadId"></param>
|
||||
Task MapLocalDraftAsync(string newMailCopyId, string newDraftId, string newThreadId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a draft message with the given options.
|
||||
/// </summary>
|
||||
/// <param name="accountId">Account to create draft for.</param>
|
||||
/// <param name="options">Draft creation options.</param>
|
||||
/// <returns>
|
||||
/// Base64 encoded string of MimeMessage object.
|
||||
/// This is mainly for serialization purposes.
|
||||
/// </returns>
|
||||
Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions options);
|
||||
Task UpdateMailAsync(MailCopy mailCopy);
|
||||
|
||||
/// <summary>
|
||||
@@ -106,9 +94,18 @@ namespace Wino.Core.Domain.Interfaces
|
||||
/// Checks whether the mail exists in the folder.
|
||||
/// When deciding Create or Update existing mail, we need to check if the mail exists in the folder.
|
||||
/// </summary>
|
||||
/// <param name="messageId">Message id</param>
|
||||
/// <param name="mailCopyId">MailCopy id</param>
|
||||
/// <param name="folderId">Folder's local id.</param>
|
||||
/// <returns>Whether mail exists in the folder or not.</returns>
|
||||
Task<bool> IsMailExistsAsync(string mailCopyId, Guid folderId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a draft MailCopy and MimeMessage based on the given options.
|
||||
/// For forward/reply it would include the referenced message.
|
||||
/// </summary>
|
||||
/// <param name="composerAccount">Account which should have new draft.</param>
|
||||
/// <param name="draftCreationOptions">Options like new email/forward/draft.</param>
|
||||
/// <returns>Draft MailCopy and Draft MimeMessage as base64.</returns>
|
||||
Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(MailAccount composerAccount, DraftCreationOptions draftCreationOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Wino.Core.Domain.Interfaces
|
||||
/// Queues new draft creation request for synchronizer.
|
||||
/// </summary>
|
||||
/// <param name="draftPreperationRequest">A class that holds the parameters for creating a draft.</param>
|
||||
Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest);
|
||||
Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest);
|
||||
|
||||
/// <summary>
|
||||
/// Queues a new request for synchronizer to send a draft.
|
||||
|
||||
76
Wino.Core.Domain/Models/Launch/MailToUri.cs
Normal file
76
Wino.Core.Domain/Models/Launch/MailToUri.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace Wino.Core.Domain.Models.Launch;
|
||||
|
||||
public class MailToUri
|
||||
{
|
||||
public string Subject { get; private set; }
|
||||
public string Body { get; private set; }
|
||||
public List<string> To { get; } = [];
|
||||
public List<string> Cc { get; } = [];
|
||||
public List<string> Bcc { get; } = [];
|
||||
public Dictionary<string, string> OtherParameters { get; } = [];
|
||||
|
||||
public MailToUri(string mailToUrl)
|
||||
{
|
||||
ParseMailToUrl(mailToUrl);
|
||||
}
|
||||
|
||||
private void ParseMailToUrl(string mailToUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(mailToUrl))
|
||||
throw new ArgumentException("mailtoUrl cannot be null or empty.", nameof(mailToUrl));
|
||||
|
||||
if (!mailToUrl.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase))
|
||||
throw new ArgumentException("URL must start with 'mailto:'.", nameof(mailToUrl));
|
||||
|
||||
var mailToWithoutScheme = mailToUrl.Substring(7); // Remove "mailto:"
|
||||
var components = mailToWithoutScheme.Split('?');
|
||||
if (!string.IsNullOrEmpty(components[0]))
|
||||
{
|
||||
To.AddRange(components[0].Split(',').Select(email => HttpUtility.UrlDecode(email).Trim()));
|
||||
}
|
||||
|
||||
if (components.Length <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = components[1].Split('&');
|
||||
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var keyValue = parameter.Split('=');
|
||||
if (keyValue.Length != 2)
|
||||
continue;
|
||||
|
||||
var key = keyValue[0].ToLowerInvariant();
|
||||
var value = HttpUtility.UrlDecode(keyValue[1]);
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "to":
|
||||
To.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||
break;
|
||||
case "subject":
|
||||
Subject = value;
|
||||
break;
|
||||
case "body":
|
||||
Body = value;
|
||||
break;
|
||||
case "cc":
|
||||
Cc.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||
break;
|
||||
case "bcc":
|
||||
Bcc.AddRange(value.Split(',').Select(email => email.Trim()));
|
||||
break;
|
||||
default:
|
||||
OtherParameters[key] = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,27 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using MimeKit;
|
||||
using MimeKit;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
|
||||
namespace Wino.Core.Domain.Models.MailItem
|
||||
namespace Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public class DraftCreationOptions
|
||||
{
|
||||
public class DraftCreationOptions
|
||||
{
|
||||
[JsonIgnore]
|
||||
public MimeMessage ReferenceMimeMessage { get; set; }
|
||||
public MailCopy ReferenceMailCopy { get; set; }
|
||||
public DraftCreationReason Reason { get; set; }
|
||||
public DraftCreationReason Reason { get; set; }
|
||||
|
||||
#region Mailto Protocol Related Stuff
|
||||
/// <summary>
|
||||
/// Used for forward/reply
|
||||
/// </summary>
|
||||
public ReferencedMessage ReferencedMessage { get; set; }
|
||||
|
||||
public const string MailtoSubjectParameterKey = "subject";
|
||||
public const string MailtoBodyParameterKey = "body";
|
||||
public const string MailtoToParameterKey = "mailto";
|
||||
public const string MailtoCCParameterKey = "cc";
|
||||
public const string MailtoBCCParameterKey = "bcc";
|
||||
|
||||
public NameValueCollection MailtoParameters { get; set; }
|
||||
|
||||
private bool IsMailtoParameterExists(string parameterKey)
|
||||
=> MailtoParameters != null
|
||||
&& MailtoParameters.AllKeys.Contains(parameterKey);
|
||||
|
||||
public bool TryGetMailtoValue(string key, out string value)
|
||||
{
|
||||
bool valueExists = IsMailtoParameterExists(key);
|
||||
|
||||
value = valueExists ? MailtoParameters[key] : string.Empty;
|
||||
|
||||
return valueExists;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
/// <summary>
|
||||
/// Used to create mails from Mailto links
|
||||
/// </summary>
|
||||
public MailToUri MailToUri { get; set; }
|
||||
}
|
||||
|
||||
public class ReferencedMessage
|
||||
{
|
||||
public MailCopy MailCopy { get; set; }
|
||||
public MimeMessage MimeMessage { get; set; }
|
||||
}
|
||||
|
||||
48
Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
Normal file
48
Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using MimeKit;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
|
||||
namespace Wino.Core.Domain.Models.MailItem;
|
||||
|
||||
public class DraftPreparationRequest
|
||||
{
|
||||
public DraftPreparationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage, MailCopy referenceMailCopy = null)
|
||||
{
|
||||
Account = account ?? throw new ArgumentNullException(nameof(account));
|
||||
|
||||
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
||||
ReferenceMailCopy = referenceMailCopy;
|
||||
|
||||
// MimeMessage is not serializable with System.Text.Json. Convert to base64 string.
|
||||
// This is additional work when deserialization needed, but not much to do atm.
|
||||
|
||||
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private DraftPreparationRequest() { }
|
||||
|
||||
public MailCopy CreatedLocalDraftCopy { get; set; }
|
||||
|
||||
public MailCopy ReferenceMailCopy { get; set; }
|
||||
|
||||
public string Base64LocalDraftMimeMessage { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
private MimeMessage createdLocalDraftMimeMessage;
|
||||
|
||||
[JsonIgnore]
|
||||
public MimeMessage CreatedLocalDraftMimeMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
createdLocalDraftMimeMessage ??= Base64LocalDraftMimeMessage.GetMimeMessageFromBase64();
|
||||
|
||||
return createdLocalDraftMimeMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public MailAccount Account { get; }
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using MimeKit;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Extensions;
|
||||
|
||||
namespace Wino.Core.Domain.Models.MailItem
|
||||
{
|
||||
public class DraftPreperationRequest : DraftCreationOptions
|
||||
{
|
||||
public DraftPreperationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage)
|
||||
{
|
||||
Account = account ?? throw new ArgumentNullException(nameof(account));
|
||||
|
||||
CreatedLocalDraftCopy = createdLocalDraftCopy ?? throw new ArgumentNullException(nameof(createdLocalDraftCopy));
|
||||
|
||||
// MimeMessage is not serializable with System.Text.Json. Convert to base64 string.
|
||||
// This is additional work when deserialization needed, but not much to do atm.
|
||||
|
||||
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private DraftPreperationRequest() { }
|
||||
|
||||
public MailCopy CreatedLocalDraftCopy { get; set; }
|
||||
|
||||
public string Base64LocalDraftMimeMessage { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
private MimeMessage createdLocalDraftMimeMessage;
|
||||
|
||||
[JsonIgnore]
|
||||
public MimeMessage CreatedLocalDraftMimeMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (createdLocalDraftMimeMessage == null)
|
||||
{
|
||||
createdLocalDraftMimeMessage = Base64LocalDraftMimeMessage.GetMimeMessageFromBase64();
|
||||
}
|
||||
|
||||
return createdLocalDraftMimeMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public MailAccount Account { get; }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Core.Requests
|
||||
{
|
||||
public record CreateDraftRequest(DraftPreperationRequest DraftPreperationRequest)
|
||||
public record CreateDraftRequest(DraftPreparationRequest DraftPreperationRequest)
|
||||
: RequestBase<BatchCreateDraftRequest>(DraftPreperationRequest.CreatedLocalDraftCopy, MailSynchronizerOperation.CreateDraft),
|
||||
ICustomFolderSynchronizationRequest
|
||||
{
|
||||
@@ -36,7 +36,7 @@ namespace Wino.Core.Requests
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public record class BatchCreateDraftRequest(IEnumerable<IRequest> Items, DraftPreperationRequest DraftPreperationRequest)
|
||||
public record class BatchCreateDraftRequest(IEnumerable<IRequest> Items, DraftPreparationRequest DraftPreperationRequest)
|
||||
: BatchRequestBase(Items, MailSynchronizerOperation.CreateDraft)
|
||||
{
|
||||
public override void ApplyUIChanges()
|
||||
|
||||
10
Wino.Core/Services/LaunchProtocolService.cs
Normal file
10
Wino.Core/Services/LaunchProtocolService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
|
||||
namespace Wino.Core.Services;
|
||||
|
||||
public class LaunchProtocolService : ILaunchProtocolService
|
||||
{
|
||||
public object LaunchParameter { get; set; }
|
||||
public MailToUri MailToUri { get; set; }
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Kiota.Abstractions.Extensions;
|
||||
@@ -32,7 +31,6 @@ namespace Wino.Core.Services
|
||||
private readonly IMimeFileService _mimeFileService;
|
||||
private readonly IPreferencesService _preferencesService;
|
||||
|
||||
|
||||
private readonly ILogger _logger = Log.ForContext<MailService>();
|
||||
|
||||
public MailService(IDatabaseService databaseService,
|
||||
@@ -53,18 +51,9 @@ namespace Wino.Core.Services
|
||||
_preferencesService = preferencesService;
|
||||
}
|
||||
|
||||
public async Task<MailCopy> CreateDraftAsync(MailAccount composerAccount,
|
||||
string generatedReplyMimeMessageBase64,
|
||||
MimeMessage replyingMimeMessage = null,
|
||||
IMailItem replyingMailItem = null)
|
||||
public async Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(MailAccount composerAccount, DraftCreationOptions draftCreationOptions)
|
||||
{
|
||||
var createdDraftMimeMessage = generatedReplyMimeMessageBase64.GetMimeMessageFromBase64();
|
||||
|
||||
bool isImapAccount = composerAccount.ServerInformation != null;
|
||||
|
||||
string fromName;
|
||||
|
||||
fromName = composerAccount.SenderName;
|
||||
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions);
|
||||
|
||||
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
|
||||
|
||||
@@ -78,7 +67,7 @@ namespace Wino.Core.Services
|
||||
Id = Guid.NewGuid().ToString(), // This will be replaced after network call with the remote draft id.
|
||||
CreationDate = DateTime.UtcNow,
|
||||
FromAddress = composerAccount.Address,
|
||||
FromName = fromName,
|
||||
FromName = composerAccount.SenderName,
|
||||
HasAttachments = false,
|
||||
Importance = MailImportance.Normal,
|
||||
Subject = createdDraftMimeMessage.Subject,
|
||||
@@ -93,28 +82,25 @@ namespace Wino.Core.Services
|
||||
};
|
||||
|
||||
// If replying, add In-Reply-To, ThreadId and References.
|
||||
bool isReplying = replyingMimeMessage != null;
|
||||
|
||||
if (isReplying)
|
||||
if (draftCreationOptions.ReferencedMessage != null)
|
||||
{
|
||||
if (replyingMimeMessage.References != null)
|
||||
copy.References = string.Join(",", replyingMimeMessage.References);
|
||||
if (draftCreationOptions.ReferencedMessage.MimeMessage.References != null)
|
||||
copy.References = string.Join(",", draftCreationOptions.ReferencedMessage.MimeMessage.References);
|
||||
|
||||
if (!string.IsNullOrEmpty(replyingMimeMessage.MessageId))
|
||||
copy.InReplyTo = replyingMimeMessage.MessageId;
|
||||
if (!string.IsNullOrEmpty(draftCreationOptions.ReferencedMessage.MimeMessage.MessageId))
|
||||
copy.InReplyTo = draftCreationOptions.ReferencedMessage.MimeMessage.MessageId;
|
||||
|
||||
if (!string.IsNullOrEmpty(replyingMailItem?.ThreadId))
|
||||
copy.ThreadId = replyingMailItem.ThreadId;
|
||||
if (!string.IsNullOrEmpty(draftCreationOptions.ReferencedMessage.MailCopy?.ThreadId))
|
||||
copy.ThreadId = draftCreationOptions.ReferencedMessage.MailCopy.ThreadId;
|
||||
}
|
||||
|
||||
await Connection.InsertAsync(copy);
|
||||
|
||||
|
||||
await _mimeFileService.SaveMimeMessageAsync(copy.FileId, createdDraftMimeMessage, composerAccount.Id);
|
||||
|
||||
ReportUIChange(new DraftCreated(copy, composerAccount));
|
||||
|
||||
return copy;
|
||||
return (copy, createdDraftMimeMessage.GetBase64MimeMessage());
|
||||
}
|
||||
|
||||
public async Task<List<MailCopy>> GetMailsByFolderIdAsync(Guid folderId)
|
||||
@@ -629,85 +615,45 @@ namespace Wino.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> CreateDraftMimeBase64Async(Guid accountId, DraftCreationOptions draftCreationOptions)
|
||||
private async Task<MimeMessage> CreateDraftMimeAsync(MailAccount account, DraftCreationOptions draftCreationOptions)
|
||||
{
|
||||
// This unique id is stored in mime headers for Wino to identify remote message with local copy.
|
||||
// Same unique id will be used for the local copy as well.
|
||||
// Synchronizer will map this unique id to the local draft copy after synchronization.
|
||||
|
||||
var messageUniqueId = Guid.NewGuid();
|
||||
|
||||
var message = new MimeMessage()
|
||||
{
|
||||
Headers = { { Constants.WinoLocalDraftHeader, messageUniqueId.ToString() } }
|
||||
Headers = { { Constants.WinoLocalDraftHeader, Guid.NewGuid().ToString() } },
|
||||
From = { new MailboxAddress(account.SenderName, account.Address) }
|
||||
};
|
||||
|
||||
var builder = new BodyBuilder();
|
||||
|
||||
var account = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
|
||||
var signature = await GetSignature(account, draftCreationOptions.Reason);
|
||||
|
||||
if (account == null)
|
||||
_ = draftCreationOptions.Reason switch
|
||||
{
|
||||
_logger.Warning("Can't create draft mime message because account {AccountId} does not exist.", accountId);
|
||||
DraftCreationReason.Empty => CreateEmptyDraft(builder, message, draftCreationOptions, signature),
|
||||
_ => CreateReferencedDraft(builder, message, draftCreationOptions, account, signature),
|
||||
};
|
||||
|
||||
return null;
|
||||
if (!string.IsNullOrEmpty(builder.HtmlBody))
|
||||
{
|
||||
builder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(builder.HtmlBody);
|
||||
}
|
||||
|
||||
var reason = draftCreationOptions.Reason;
|
||||
var referenceMessage = draftCreationOptions.ReferenceMimeMessage;
|
||||
message.Body = builder.ToMessageBody();
|
||||
|
||||
message.From.Add(new MailboxAddress(account.SenderName, account.Address));
|
||||
return message;
|
||||
}
|
||||
|
||||
// It contains empty blocks with inlined font, to make sure when users starts typing,it will follow selected font.
|
||||
var gapHtml = CreateHtmlGap();
|
||||
private string CreateHtmlGap()
|
||||
{
|
||||
var template = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px"><br></div>""";
|
||||
return string.Concat(Enumerable.Repeat(template, 2));
|
||||
}
|
||||
|
||||
// Manage "To"
|
||||
if (reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll)
|
||||
{
|
||||
// Reply to the sender of the message
|
||||
|
||||
if (referenceMessage.ReplyTo.Count > 0)
|
||||
message.To.AddRange(referenceMessage.ReplyTo);
|
||||
else if (referenceMessage.From.Count > 0)
|
||||
message.To.AddRange(referenceMessage.From);
|
||||
else if (referenceMessage.Sender != null)
|
||||
message.To.Add(referenceMessage.Sender);
|
||||
|
||||
if (reason == DraftCreationReason.ReplyAll)
|
||||
{
|
||||
// Include all of the other original recipients
|
||||
message.To.AddRange(referenceMessage.To);
|
||||
|
||||
// Find self and remove
|
||||
var self = message.To.FirstOrDefault(a => a is MailboxAddress mailboxAddress && mailboxAddress.Address == account.Address);
|
||||
|
||||
if (self != null)
|
||||
message.To.Remove(self);
|
||||
|
||||
message.Cc.AddRange(referenceMessage.Cc);
|
||||
}
|
||||
|
||||
// Manage "ThreadId-ConversationId"
|
||||
if (!string.IsNullOrEmpty(referenceMessage.MessageId))
|
||||
{
|
||||
message.InReplyTo = referenceMessage.MessageId;
|
||||
|
||||
message.References.AddRange(referenceMessage.References);
|
||||
|
||||
message.References.Add(referenceMessage.MessageId);
|
||||
}
|
||||
|
||||
message.Headers.Add("Thread-Topic", referenceMessage.Subject);
|
||||
|
||||
builder.HtmlBody = CreateHtmlForReferencingMessage(referenceMessage);
|
||||
}
|
||||
|
||||
if (reason == DraftCreationReason.Forward)
|
||||
{
|
||||
builder.HtmlBody = CreateHtmlForReferencingMessage(referenceMessage);
|
||||
}
|
||||
|
||||
// Append signatures if needed.
|
||||
private async Task<string> GetSignature(MailAccount account, DraftCreationReason reason)
|
||||
{
|
||||
if (account.Preferences.IsSignatureEnabled)
|
||||
{
|
||||
var signatureId = reason == DraftCreationReason.Empty ?
|
||||
@@ -718,26 +664,88 @@ namespace Wino.Core.Services
|
||||
{
|
||||
var signature = await _signatureService.GetSignatureAsync(signatureId.Value);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(builder.HtmlBody))
|
||||
{
|
||||
builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}";
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.HtmlBody = $"{gapHtml}{signature.HtmlBody}{gapHtml}{builder.HtmlBody}";
|
||||
}
|
||||
return signature.HtmlBody;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private MimeMessage CreateEmptyDraft(BodyBuilder builder, MimeMessage message, DraftCreationOptions draftCreationOptions, string signature)
|
||||
{
|
||||
builder.HtmlBody = CreateHtmlGap();
|
||||
if (draftCreationOptions.MailToUri != null)
|
||||
{
|
||||
builder.HtmlBody = $"{gapHtml}{builder.HtmlBody}";
|
||||
if (draftCreationOptions.MailToUri.Subject != null)
|
||||
message.Subject = draftCreationOptions.MailToUri.Subject;
|
||||
|
||||
if (draftCreationOptions.MailToUri.Body != null)
|
||||
{
|
||||
builder.HtmlBody = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px">{draftCreationOptions.MailToUri.Body}</div>""" + builder.HtmlBody;
|
||||
}
|
||||
|
||||
if (draftCreationOptions.MailToUri.To.Any())
|
||||
message.To.AddRange(draftCreationOptions.MailToUri.To.Select(x => new MailboxAddress(x, x)));
|
||||
|
||||
if (draftCreationOptions.MailToUri.Cc.Any())
|
||||
message.Cc.AddRange(draftCreationOptions.MailToUri.Cc.Select(x => new MailboxAddress(x, x)));
|
||||
|
||||
if (draftCreationOptions.MailToUri.Bcc.Any())
|
||||
message.Bcc.AddRange(draftCreationOptions.MailToUri.Bcc.Select(x => new MailboxAddress(x, x)));
|
||||
}
|
||||
|
||||
if (signature != null)
|
||||
builder.HtmlBody += signature;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private MimeMessage CreateReferencedDraft(BodyBuilder builder, MimeMessage message, DraftCreationOptions draftCreationOptions, MailAccount account, string signature)
|
||||
{
|
||||
var reason = draftCreationOptions.Reason;
|
||||
var referenceMessage = draftCreationOptions.ReferencedMessage.MimeMessage;
|
||||
|
||||
var gap = CreateHtmlGap();
|
||||
builder.HtmlBody = gap + CreateHtmlForReferencingMessage(referenceMessage);
|
||||
|
||||
if (signature != null)
|
||||
{
|
||||
builder.HtmlBody = gap + signature + builder.HtmlBody;
|
||||
}
|
||||
|
||||
// Manage "To"
|
||||
if (reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll)
|
||||
{
|
||||
// Reply to the sender of the message
|
||||
if (referenceMessage.ReplyTo.Count > 0)
|
||||
message.To.AddRange(referenceMessage.ReplyTo);
|
||||
else if (referenceMessage.From.Count > 0)
|
||||
message.To.AddRange(referenceMessage.From);
|
||||
else if (referenceMessage.Sender != null)
|
||||
message.To.Add(referenceMessage.Sender);
|
||||
|
||||
if (reason == DraftCreationReason.ReplyAll)
|
||||
{
|
||||
// Include all of the other original recipients
|
||||
message.To.AddRange(referenceMessage.To.Where(x => x is MailboxAddress mailboxAddress && !mailboxAddress.Address.Equals(account.Address, StringComparison.OrdinalIgnoreCase)));
|
||||
message.Cc.AddRange(referenceMessage.Cc.Where(x => x is MailboxAddress mailboxAddress && !mailboxAddress.Address.Equals(account.Address, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
// Manage "ThreadId-ConversationId"
|
||||
if (!string.IsNullOrEmpty(referenceMessage.MessageId))
|
||||
{
|
||||
message.InReplyTo = referenceMessage.MessageId;
|
||||
message.References.AddRange(referenceMessage.References);
|
||||
message.References.Add(referenceMessage.MessageId);
|
||||
}
|
||||
|
||||
message.Headers.Add("Thread-Topic", referenceMessage.Subject);
|
||||
}
|
||||
|
||||
// Manage Subject
|
||||
if (reason == DraftCreationReason.Forward && !referenceMessage.Subject.StartsWith("FW: ", StringComparison.OrdinalIgnoreCase))
|
||||
message.Subject = $"FW: {referenceMessage.Subject}";
|
||||
else if ((reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll) &&
|
||||
!referenceMessage.Subject.StartsWith("RE: ", StringComparison.OrdinalIgnoreCase))
|
||||
else if ((reason == DraftCreationReason.Reply || reason == DraftCreationReason.ReplyAll) && !referenceMessage.Subject.StartsWith("RE: ", StringComparison.OrdinalIgnoreCase))
|
||||
message.Subject = $"RE: {referenceMessage.Subject}";
|
||||
else if (referenceMessage != null)
|
||||
message.Subject = referenceMessage.Subject;
|
||||
@@ -751,63 +759,7 @@ namespace Wino.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(builder.HtmlBody))
|
||||
{
|
||||
builder.TextBody = HtmlAgilityPackExtensions.GetPreviewText(builder.HtmlBody);
|
||||
}
|
||||
|
||||
message.Body = builder.ToMessageBody();
|
||||
|
||||
// Apply mail-to protocol parameters if exists.
|
||||
|
||||
if (draftCreationOptions.MailtoParameters != null)
|
||||
{
|
||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoSubjectParameterKey, out string subjectParameter))
|
||||
message.Subject = subjectParameter;
|
||||
|
||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoBodyParameterKey, out string bodyParameter))
|
||||
{
|
||||
builder.TextBody = bodyParameter;
|
||||
builder.HtmlBody = bodyParameter;
|
||||
|
||||
message.Body = builder.ToMessageBody();
|
||||
}
|
||||
|
||||
static InternetAddressList ExtractRecipients(string parameterValue)
|
||||
{
|
||||
var list = new InternetAddressList();
|
||||
|
||||
var splittedRecipients = parameterValue.Split(',');
|
||||
|
||||
foreach (var recipient in splittedRecipients)
|
||||
list.Add(new MailboxAddress(recipient, recipient));
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoToParameterKey, out string toParameter))
|
||||
message.To.AddRange(ExtractRecipients(toParameter));
|
||||
|
||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoCCParameterKey, out string ccParameter))
|
||||
message.Cc.AddRange(ExtractRecipients(ccParameter));
|
||||
|
||||
if (draftCreationOptions.TryGetMailtoValue(DraftCreationOptions.MailtoBCCParameterKey, out string bccParameter))
|
||||
message.Bcc.AddRange(ExtractRecipients(bccParameter));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update TextBody from existing HtmlBody if exists.
|
||||
}
|
||||
|
||||
using MemoryStream memoryStream = new();
|
||||
message.WriteTo(FormatOptions.Default, memoryStream);
|
||||
byte[] buffer = memoryStream.GetBuffer();
|
||||
int count = (int)memoryStream.Length;
|
||||
|
||||
return Convert.ToBase64String(buffer);
|
||||
|
||||
// return message;
|
||||
return message;
|
||||
|
||||
// Generates html representation of To/Cc/From/Time and so on from referenced message.
|
||||
string CreateHtmlForReferencingMessage(MimeMessage referenceMessage)
|
||||
@@ -820,28 +772,22 @@ namespace Wino.Core.Services
|
||||
visitor.Visit(referenceMessage);
|
||||
|
||||
htmlMimeInfo += $"""
|
||||
<div id="divRplyFwdMsg" dir="ltr">
|
||||
<font face="Calibri, sans-serif" style="font-size: 11pt;" color="#000000">
|
||||
<b>From:</b> {ParticipantsToHtml(referenceMessage.From)}<br>
|
||||
<b>Sent:</b> {referenceMessage.Date.ToLocalTime()}<br>
|
||||
<b>To:</b> {ParticipantsToHtml(referenceMessage.To)}<br>
|
||||
{(referenceMessage.Cc.Count > 0 ? $"<b>Cc:</b> {ParticipantsToHtml(referenceMessage.Cc)}<br>" : string.Empty)}
|
||||
<b>Subject:</b> {referenceMessage.Subject}
|
||||
</font>
|
||||
<div> </div>
|
||||
{visitor.HtmlBody}
|
||||
</div>
|
||||
""";
|
||||
<div id="divRplyFwdMsg" dir="ltr">
|
||||
<font face="Calibri, sans-serif" style="font-size: 11pt;" color="#000000">
|
||||
<b>From:</b> {ParticipantsToHtml(referenceMessage.From)}<br>
|
||||
<b>Sent:</b> {referenceMessage.Date.ToLocalTime()}<br>
|
||||
<b>To:</b> {ParticipantsToHtml(referenceMessage.To)}<br>
|
||||
{(referenceMessage.Cc.Count > 0 ? $"<b>Cc:</b> {ParticipantsToHtml(referenceMessage.Cc)}<br>" : string.Empty)}
|
||||
<b>Subject:</b> {referenceMessage.Subject}
|
||||
</font>
|
||||
<div> </div>
|
||||
{visitor.HtmlBody}
|
||||
</div>
|
||||
""";
|
||||
|
||||
return htmlMimeInfo;
|
||||
}
|
||||
|
||||
string CreateHtmlGap()
|
||||
{
|
||||
var template = $"""<div style="font-family: '{_preferencesService.ComposerFont}', Arial, sans-serif; font-size: {_preferencesService.ComposerFontSize}px"><br></div>""";
|
||||
return string.Concat(Enumerable.Repeat(template, 5));
|
||||
}
|
||||
|
||||
static string ParticipantsToHtml(InternetAddressList internetAddresses) =>
|
||||
string.Join("; ", internetAddresses.Mailboxes
|
||||
.Select(x => $"{x.Name ?? Translator.UnknownSender} <<a href=\"mailto:{x.Address ?? Translator.UnknownAddress}\">{x.Address ?? Translator.UnknownAddress}</a>>"));
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Wino.Core.Services
|
||||
QueueSynchronization(accountId);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(DraftPreperationRequest draftPreperationRequest)
|
||||
public async Task ExecuteAsync(DraftPreparationRequest draftPreperationRequest)
|
||||
{
|
||||
var request = new CreateDraftRequest(draftPreperationRequest);
|
||||
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace Wino.Mail.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasMailtoActivation = _launchProtocolService.MailtoParameters != null;
|
||||
bool hasMailtoActivation = _launchProtocolService.MailToUri != null;
|
||||
|
||||
if (hasMailtoActivation)
|
||||
{
|
||||
@@ -774,16 +774,13 @@ namespace Wino.Mail.ViewModels
|
||||
var draftOptions = new DraftCreationOptions
|
||||
{
|
||||
Reason = DraftCreationReason.Empty,
|
||||
|
||||
// Include mail to parameters for parsing mailto if any.
|
||||
MailtoParameters = _launchProtocolService.MailtoParameters
|
||||
MailToUri = _launchProtocolService.MailToUri
|
||||
};
|
||||
|
||||
var createdBase64EncodedMimeMessage = await _mailService.CreateDraftMimeBase64Async(account.Id, draftOptions).ConfigureAwait(false);
|
||||
var createdDraftMailMessage = await _mailService.CreateDraftAsync(account, createdBase64EncodedMimeMessage).ConfigureAwait(false);
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var draftPreperationRequest = new DraftPreperationRequest(account, createdDraftMailMessage, createdBase64EncodedMimeMessage);
|
||||
await _winoRequestDelegator.ExecuteAsync(draftPreperationRequest);
|
||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
|
||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
}
|
||||
|
||||
protected override async void OnAccountUpdated(MailAccount updatedAccount)
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -58,7 +57,7 @@ namespace Wino.Mail.ViewModels
|
||||
private MessageImportance selectedMessageImportance;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool isCCBCCVisible = true;
|
||||
private bool isCCBCCVisible;
|
||||
|
||||
[ObservableProperty]
|
||||
private string subject;
|
||||
@@ -77,21 +76,20 @@ namespace Wino.Mail.ViewModels
|
||||
[ObservableProperty]
|
||||
private bool isDraggingOverImagesDropZone;
|
||||
|
||||
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = new ObservableCollection<MailAttachmentViewModel>();
|
||||
|
||||
public ObservableCollection<MailAccount> Accounts { get; set; } = new ObservableCollection<MailAccount>();
|
||||
public ObservableCollection<AddressInformation> ToItems { get; set; } = new ObservableCollection<AddressInformation>();
|
||||
public ObservableCollection<AddressInformation> CCItemsItems { get; set; } = new ObservableCollection<AddressInformation>();
|
||||
public ObservableCollection<AddressInformation> BCCItems { get; set; } = new ObservableCollection<AddressInformation>();
|
||||
public ObservableCollection<MailAttachmentViewModel> IncludedAttachments { get; set; } = [];
|
||||
public ObservableCollection<MailAccount> Accounts { get; set; } = [];
|
||||
public ObservableCollection<AddressInformation> ToItems { get; set; } = [];
|
||||
public ObservableCollection<AddressInformation> CCItems { get; set; } = [];
|
||||
public ObservableCollection<AddressInformation> BCCItems { get; set; } = [];
|
||||
|
||||
|
||||
public List<EditorToolbarSection> ToolbarSections { get; set; } = new List<EditorToolbarSection>()
|
||||
{
|
||||
public List<EditorToolbarSection> ToolbarSections { get; set; } =
|
||||
[
|
||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Format },
|
||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Insert },
|
||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Draw },
|
||||
new EditorToolbarSection(){ SectionType = EditorToolbarSectionType.Options }
|
||||
};
|
||||
];
|
||||
|
||||
private EditorToolbarSection selectedToolbarSection;
|
||||
|
||||
@@ -190,7 +188,7 @@ namespace Wino.Mail.ViewModels
|
||||
// Save recipients.
|
||||
|
||||
SaveAddressInfo(ToItems, CurrentMimeMessage.To);
|
||||
SaveAddressInfo(CCItemsItems, CurrentMimeMessage.Cc);
|
||||
SaveAddressInfo(CCItems, CurrentMimeMessage.Cc);
|
||||
SaveAddressInfo(BCCItems, CurrentMimeMessage.Bcc);
|
||||
|
||||
SaveImportance();
|
||||
@@ -239,12 +237,7 @@ namespace Wino.Mail.ViewModels
|
||||
{
|
||||
if (GetHTMLBodyFunction != null)
|
||||
{
|
||||
var htmlBody = await GetHTMLBodyFunction();
|
||||
|
||||
if (!string.IsNullOrEmpty(htmlBody))
|
||||
{
|
||||
bodyBuilder.HtmlBody = Regex.Unescape(htmlBody);
|
||||
}
|
||||
bodyBuilder.HtmlBody = await GetHTMLBodyFunction();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(bodyBuilder.HtmlBody))
|
||||
@@ -309,7 +302,7 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
// Check if there is any delivering mail address from protocol launch.
|
||||
|
||||
if (_launchProtocolService.MailtoParameters != null)
|
||||
if (_launchProtocolService.MailToUri != null)
|
||||
{
|
||||
// TODO
|
||||
//var requestedMailContact = await GetAddressInformationAsync(_launchProtocolService.MailtoParameters, ToItems);
|
||||
@@ -322,7 +315,7 @@ namespace Wino.Mail.ViewModels
|
||||
// DialogService.InfoBarMessage("Invalid Address", "Address is not a valid e-mail address.", InfoBarMessageType.Warning);
|
||||
|
||||
// Clear the address.
|
||||
_launchProtocolService.MailtoParameters = null;
|
||||
_launchProtocolService.MailToUri = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,15 +420,18 @@ namespace Wino.Mail.ViewModels
|
||||
// Extract information
|
||||
|
||||
ToItems.Clear();
|
||||
CCItemsItems.Clear();
|
||||
CCItems.Clear();
|
||||
BCCItems.Clear();
|
||||
|
||||
LoadAddressInfo(replyingMime.To, ToItems);
|
||||
LoadAddressInfo(replyingMime.Cc, CCItemsItems);
|
||||
LoadAddressInfo(replyingMime.Cc, CCItems);
|
||||
LoadAddressInfo(replyingMime.Bcc, BCCItems);
|
||||
|
||||
LoadAttachments(replyingMime.Attachments);
|
||||
|
||||
if (replyingMime.Cc.Any() || replyingMime.Bcc.Any())
|
||||
IsCCBCCVisible = true;
|
||||
|
||||
Subject = replyingMime.Subject;
|
||||
|
||||
CurrentMimeMessage = replyingMime;
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Wino.Mail.ViewModels
|
||||
#endregion
|
||||
|
||||
public INativeAppService NativeAppService { get; }
|
||||
public IStatePersistanceService StatePersistanceService { get; }
|
||||
public IStatePersistanceService StatePersistenceService { get; }
|
||||
public IPreferencesService PreferencesService { get; }
|
||||
|
||||
public MailRenderingPageViewModel(IDialogService dialogService,
|
||||
@@ -127,14 +127,14 @@ namespace Wino.Mail.ViewModels
|
||||
Core.Domain.Interfaces.IMailService mailService,
|
||||
IFileService fileService,
|
||||
IWinoRequestDelegator requestDelegator,
|
||||
IStatePersistanceService statePersistanceService,
|
||||
IStatePersistanceService statePersistenceService,
|
||||
IClipboardService clipboardService,
|
||||
IUnsubscriptionService unsubscriptionService,
|
||||
IPreferencesService preferencesService,
|
||||
IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
|
||||
{
|
||||
NativeAppService = nativeAppService;
|
||||
StatePersistanceService = statePersistanceService;
|
||||
StatePersistenceService = statePersistenceService;
|
||||
PreferencesService = preferencesService;
|
||||
_winoServerConnectionManager = winoServerConnectionManager;
|
||||
_clipboardService = clipboardService;
|
||||
@@ -255,37 +255,27 @@ namespace Wino.Mail.ViewModels
|
||||
if (initializedMailItemViewModel == null) return;
|
||||
|
||||
// Create new draft.
|
||||
var draftOptions = new DraftCreationOptions();
|
||||
|
||||
if (operation == MailOperation.Reply)
|
||||
draftOptions.Reason = DraftCreationReason.Reply;
|
||||
else if (operation == MailOperation.ReplyAll)
|
||||
draftOptions.Reason = DraftCreationReason.ReplyAll;
|
||||
else if (operation == MailOperation.Forward)
|
||||
draftOptions.Reason = DraftCreationReason.Forward;
|
||||
|
||||
// TODO: Separate mailto related stuff out of DraftCreationOptions and provide better
|
||||
// model for draft preperation request. Right now it's a mess.
|
||||
|
||||
draftOptions.ReferenceMailCopy = initializedMailItemViewModel.MailCopy;
|
||||
draftOptions.ReferenceMimeMessage = initializedMimeMessageInformation.MimeMessage;
|
||||
|
||||
var createdMimeMessage = await _mailService.CreateDraftMimeBase64Async(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var createdDraftMailMessage = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount,
|
||||
createdMimeMessage,
|
||||
initializedMimeMessageInformation.MimeMessage,
|
||||
initializedMailItemViewModel).ConfigureAwait(false);
|
||||
|
||||
var draftPreperationRequest = new DraftPreperationRequest(initializedMailItemViewModel.AssignedAccount,
|
||||
createdDraftMailMessage,
|
||||
createdMimeMessage)
|
||||
var draftOptions = new DraftCreationOptions()
|
||||
{
|
||||
ReferenceMimeMessage = initializedMimeMessageInformation.MimeMessage,
|
||||
ReferenceMailCopy = initializedMailItemViewModel.MailCopy
|
||||
Reason = operation switch
|
||||
{
|
||||
MailOperation.Reply => DraftCreationReason.Reply,
|
||||
MailOperation.ReplyAll => DraftCreationReason.ReplyAll,
|
||||
MailOperation.Forward => DraftCreationReason.Forward,
|
||||
_ => DraftCreationReason.Empty
|
||||
},
|
||||
ReferencedMessage = new ReferencedMessage()
|
||||
{
|
||||
MimeMessage = initializedMimeMessageInformation.MimeMessage,
|
||||
MailCopy = initializedMailItemViewModel.MailCopy
|
||||
}
|
||||
};
|
||||
|
||||
await _requestDelegator.ExecuteAsync(draftPreperationRequest);
|
||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount, draftOptions).ConfigureAwait(false);
|
||||
|
||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
|
||||
|
||||
await _requestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||
|
||||
}
|
||||
else if (initializedMailItemViewModel != null)
|
||||
@@ -453,7 +443,7 @@ namespace Wino.Mail.ViewModels
|
||||
|
||||
OnPropertyChanged(nameof(IsImageRenderingDisabled));
|
||||
|
||||
StatePersistanceService.IsReadingMail = true;
|
||||
StatePersistenceService.IsReadingMail = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -477,7 +467,7 @@ namespace Wino.Mail.ViewModels
|
||||
Attachments.Clear();
|
||||
MenuItems.Clear();
|
||||
|
||||
StatePersistanceService.IsReadingMail = false;
|
||||
StatePersistenceService.IsReadingMail = false;
|
||||
}
|
||||
|
||||
private void LoadAddressInfo(InternetAddressList list, ObservableCollection<AddressInformation> collection)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Launch;
|
||||
using Wino.Messaging.Client.Authorization;
|
||||
using Wino.Messaging.Client.Shell;
|
||||
|
||||
@@ -36,11 +36,7 @@ namespace Wino.Activation
|
||||
else if (protocolString.StartsWith(MailtoProtocolTag))
|
||||
{
|
||||
// mailto activation. Try to parse params.
|
||||
|
||||
var replaced = protocolString.Replace(MailtoProtocolTag, "mailto=");
|
||||
replaced = Wino.Core.Extensions.StringExtensions.ReplaceFirst(replaced, "?", "&");
|
||||
|
||||
_launchProtocolService.MailtoParameters = HttpUtility.ParseQueryString(replaced);
|
||||
_launchProtocolService.MailToUri = new MailToUri(protocolString);
|
||||
|
||||
if (_nativeAppService.IsAppRunning())
|
||||
{
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Specialized;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
|
||||
namespace Wino.Core.UWP.Services
|
||||
{
|
||||
public class LaunchProtocolService : ILaunchProtocolService
|
||||
{
|
||||
public object LaunchParameter { get; set; }
|
||||
public NameValueCollection MailtoParameters { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -493,7 +493,7 @@
|
||||
VerticalAlignment="Center"
|
||||
Click="ShowCCBCCClicked"
|
||||
GotFocus="CCBBCGotFocus"
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}">
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseBoolToVisibilityConverter(ViewModel.IsCCBCCVisible), Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<PathIcon
|
||||
HorizontalAlignment="Center"
|
||||
@@ -512,13 +512,14 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="Cc: "
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<controls1:TokenizingTextBox
|
||||
x:Name="CCBox"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind ViewModel.CCItems, Mode=OneTime}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||
@@ -526,7 +527,7 @@
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<TextBlock
|
||||
x:Name="BccTextBlock"
|
||||
@@ -534,13 +535,14 @@
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="Bcc: "
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<controls1:TokenizingTextBox
|
||||
x:Name="BccBox"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind ViewModel.BCCItems, Mode=OneTime}"
|
||||
LostFocus="AddressBoxLostFocus"
|
||||
PlaceholderText="{x:Bind domain:Translator.ComposerToPlaceholder}"
|
||||
SuggestedItemTemplate="{StaticResource SuggestionBoxTemplate}"
|
||||
@@ -548,7 +550,7 @@
|
||||
TokenDelimiter=";"
|
||||
TokenItemAdding="TokenItemAdding"
|
||||
TokenItemTemplate="{StaticResource TokenBoxTemplate}"
|
||||
Visibility="{x:Bind helpers:XamlHelpers.ReverseVisibilityConverter(CCBCCShowButton.Visibility), Mode=OneWay}" />
|
||||
Visibility="{x:Bind ViewModel.IsCCBCCVisible, Mode=OneWay}" />
|
||||
|
||||
<!-- Subject -->
|
||||
<TextBlock
|
||||
|
||||
@@ -562,12 +562,7 @@ namespace Wino.Views
|
||||
|
||||
private void ShowCCBCCClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CCBCCShowButton.Visibility = Visibility.Collapsed;
|
||||
|
||||
CCTextBlock.Visibility = Visibility.Visible;
|
||||
CCBox.Visibility = Visibility.Visible;
|
||||
BccTextBlock.Visibility = Visibility.Visible;
|
||||
BccBox.Visibility = Visibility.Visible;
|
||||
ViewModel.IsCCBCCVisible = true;
|
||||
}
|
||||
|
||||
private async void TokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args)
|
||||
@@ -591,7 +586,7 @@ namespace Wino.Views
|
||||
if (boxTag == "ToBox")
|
||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.ToItems);
|
||||
else if (boxTag == "CCBox")
|
||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.CCItemsItems);
|
||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.CCItems);
|
||||
else if (boxTag == "BCCBox")
|
||||
addedItem = await ViewModel.GetAddressInformationAsync(args.TokenText, ViewModel.BCCItems);
|
||||
|
||||
@@ -660,7 +655,7 @@ namespace Wino.Views
|
||||
if (boxTag == "ToBox")
|
||||
addressCollection = ViewModel.ToItems;
|
||||
else if (boxTag == "CCBox")
|
||||
addressCollection = ViewModel.CCItemsItems;
|
||||
addressCollection = ViewModel.CCItems;
|
||||
else if (boxTag == "BCCBox")
|
||||
addressCollection = ViewModel.BCCItems;
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace Wino.Views
|
||||
// We don't have shell initialized here. It's only standalone EML viewing.
|
||||
// Shift command bar from top to adjust the design.
|
||||
|
||||
if (ViewModel.StatePersistanceService.ShouldShiftMailRenderingDesign)
|
||||
if (ViewModel.StatePersistenceService.ShouldShiftMailRenderingDesign)
|
||||
RendererGridFrame.Margin = new Thickness(0, 24, 0, 0);
|
||||
else
|
||||
RendererGridFrame.Margin = new Thickness(0, 0, 0, 0);
|
||||
|
||||
@@ -335,7 +335,6 @@
|
||||
<Compile Include="Selectors\RendererCommandBarItemTemplateSelector.cs" />
|
||||
<Compile Include="Services\ApplicationResourceManager.cs" />
|
||||
<Compile Include="Services\DialogService.cs" />
|
||||
<Compile Include="Services\LaunchProtocolService.cs" />
|
||||
<Compile Include="Services\WinoNavigationService.cs" />
|
||||
<Compile Include="Styles\CommandBarItems.xaml.cs">
|
||||
<DependentUpon>CommandBarItems.xaml</DependentUpon>
|
||||
@@ -892,4 +891,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user