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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user