File scoped namespaces

This commit is contained in:
Aleh Khantsevich
2025-02-16 11:35:43 +01:00
committed by GitHub
parent c1336428dc
commit d31d8f574e
617 changed files with 32118 additions and 32737 deletions

View File

@@ -5,251 +5,250 @@ using MimeKit;
using MimeKit.Text;
using MimeKit.Tnef;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// </summary>
public class HtmlPreviewVisitor : MimeVisitor
{
List<MultipartRelated> stack = new List<MultipartRelated>();
List<MimeEntity> attachments = new List<MimeEntity>();
readonly string tempDir;
public string Body { get; set; }
/// <summary>
/// Visits a MimeMessage and generates HTML suitable to be rendered by a browser control.
/// Creates a new HtmlPreviewVisitor.
/// </summary>
public class HtmlPreviewVisitor : MimeVisitor
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor(string tempDirectory)
{
List<MultipartRelated> stack = new List<MultipartRelated>();
List<MimeEntity> attachments = new List<MimeEntity>();
tempDir = tempDirectory;
}
readonly string tempDir;
/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments
{
get { return attachments; }
}
public string Body { get; set; }
/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody
{
get { return Body ?? string.Empty; }
}
/// <summary>
/// Creates a new HtmlPreviewVisitor.
/// </summary>
/// <param name="tempDirectory">A temporary directory used for storing image files.</param>
public HtmlPreviewVisitor(string tempDirectory)
protected override void VisitMultipartAlternative(MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && Body == null; i--)
alternative[i].Accept(this);
}
protected override void VisitMultipartRelated(MultipartRelated related)
{
var root = related.Root;
// push this multipart/related onto our stack
stack.Add(related);
// visit the root document
root.Accept(this);
// pop this multipart/related off our stack
stack.RemoveAt(stack.Count - 1);
}
// look up the image based on the img src url within our multipart/related stack
bool TryGetImage(string url, out MimePart image)
{
UriKind kind;
int index;
Uri uri;
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
kind = UriKind.Absolute;
else if (Uri.IsWellFormedUriString(url, UriKind.Relative))
kind = UriKind.Relative;
else
kind = UriKind.RelativeOrAbsolute;
try
{
tempDir = tempDirectory;
uri = new Uri(url, kind);
}
/// <summary>
/// The list of attachments that were in the MimeMessage.
/// </summary>
public IList<MimeEntity> Attachments
catch
{
get { return attachments; }
}
/// <summary>
/// The HTML string that can be set on the BrowserControl.
/// </summary>
public string HtmlBody
{
get { return Body ?? string.Empty; }
}
protected override void VisitMultipartAlternative(MultipartAlternative alternative)
{
// walk the multipart/alternative children backwards from greatest level of faithfulness to the least faithful
for (int i = alternative.Count - 1; i >= 0 && Body == null; i--)
alternative[i].Accept(this);
}
protected override void VisitMultipartRelated(MultipartRelated related)
{
var root = related.Root;
// push this multipart/related onto our stack
stack.Add(related);
// visit the root document
root.Accept(this);
// pop this multipart/related off our stack
stack.RemoveAt(stack.Count - 1);
}
// look up the image based on the img src url within our multipart/related stack
bool TryGetImage(string url, out MimePart image)
{
UriKind kind;
int index;
Uri uri;
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
kind = UriKind.Absolute;
else if (Uri.IsWellFormedUriString(url, UriKind.Relative))
kind = UriKind.Relative;
else
kind = UriKind.RelativeOrAbsolute;
try
{
uri = new Uri(url, kind);
}
catch
{
image = null;
return false;
}
for (int i = stack.Count - 1; i >= 0; i--)
{
if ((index = stack[i].IndexOf(uri)) == -1)
continue;
image = stack[i][index] as MimePart;
return image != null;
}
image = null;
return false;
}
// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "data:" url instead.
string SaveImage(MimePart image)
for (int i = stack.Count - 1; i >= 0; i--)
{
using (var memory = new MemoryStream())
{
image.Content.DecodeTo(memory);
var buffer = memory.GetBuffer();
var length = (int)memory.Length;
var base64 = Convert.ToBase64String(buffer, 0, length);
if ((index = stack[i].IndexOf(uri)) == -1)
continue;
return string.Format("data:{0};base64,{1}", image.ContentType.MimeType, base64);
}
//string fileName = url
// .Replace(':', '_')
// .Replace('\\', '_')
// .Replace('/', '_');
//string path = Path.Combine(tempDir, fileName);
//if (!File.Exists(path))
//{
// using (var output = File.Create(path))
// image.Content.DecodeTo(output);
//}
//return "file://" + path.Replace('\\', '/');
image = stack[i][index] as MimePart;
return image != null;
}
// Replaces <img src=...> urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback(HtmlTagContext ctx, HtmlWriter htmlWriter)
image = null;
return false;
}
// Save the image to our temp directory and return a "file://" url suitable for
// the browser control to load.
// Note: if you'd rather embed the image data into the HTML, you can construct a
// "data:" url instead.
string SaveImage(MimePart image)
{
using (var memory = new MemoryStream())
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0)
image.Content.DecodeTo(memory);
var buffer = memory.GetBuffer();
var length = (int)memory.Length;
var base64 = Convert.ToBase64String(buffer, 0, length);
return string.Format("data:{0};base64,{1}", image.ContentType.MimeType, base64);
}
//string fileName = url
// .Replace(':', '_')
// .Replace('\\', '_')
// .Replace('/', '_');
//string path = Path.Combine(tempDir, fileName);
//if (!File.Exists(path))
//{
// using (var output = File.Create(path))
// image.Content.DecodeTo(output);
//}
//return "file://" + path.Replace('\\', '/');
}
// Replaces <img src=...> urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback(HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0)
{
ctx.WriteTag(htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes)
{
ctx.WriteTag(htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes)
if (attribute.Id == HtmlAttributeId.Src)
{
if (attribute.Id == HtmlAttributeId.Src)
{
MimePart image;
string url;
MimePart image;
string url;
if (!TryGetImage(attribute.Value, out image))
{
htmlWriter.WriteAttribute(attribute);
continue;
}
url = SaveImage(image);
htmlWriter.WriteAttributeName(attribute.Name);
htmlWriter.WriteAttributeValue(url);
}
else
if (!TryGetImage(attribute.Value, out image))
{
htmlWriter.WriteAttribute(attribute);
}
}
}
else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag)
{
ctx.WriteTag(htmlWriter, false);
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes)
{
if (attribute.Name.ToLowerInvariant() == "oncontextmenu")
continue;
}
htmlWriter.WriteAttribute(attribute);
}
url = SaveImage(image);
htmlWriter.WriteAttribute("oncontextmenu", "return false;");
}
else
{
if (ctx.TagId == HtmlTagId.Unknown)
{
ctx.DeleteTag = true;
ctx.DeleteEndTag = true;
htmlWriter.WriteAttributeName(attribute.Name);
htmlWriter.WriteAttributeValue(url);
}
else
{
ctx.WriteTag(htmlWriter, true);
htmlWriter.WriteAttribute(attribute);
}
}
}
protected override void VisitTextPart(TextPart entity)
else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag)
{
TextConverter converter;
ctx.WriteTag(htmlWriter, false);
if (Body != null)
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes)
{
// since we've already found the body, treat this as an attachment
attachments.Add(entity);
return;
if (attribute.Name.ToLowerInvariant() == "oncontextmenu")
continue;
htmlWriter.WriteAttribute(attribute);
}
if (entity.IsHtml)
htmlWriter.WriteAttribute("oncontextmenu", "return false;");
}
else
{
if (ctx.TagId == HtmlTagId.Unknown)
{
converter = new HtmlToHtml
{
HtmlTagCallback = HtmlTagCallback
};
}
else if (entity.IsFlowed)
{
var flowed = new FlowedToHtml();
string delsp;
if (entity.ContentType.Parameters.TryGetValue("delsp", out delsp))
flowed.DeleteSpace = delsp.ToLowerInvariant() == "yes";
converter = flowed;
ctx.DeleteTag = true;
ctx.DeleteEndTag = true;
}
else
{
converter = new TextToHtml();
ctx.WriteTag(htmlWriter, true);
}
Body = converter.Convert(entity.Text);
}
protected override void VisitTnefPart(TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange(entity.ExtractAttachments());
}
protected override void VisitMessagePart(MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add(entity);
}
protected override void VisitMimePart(MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add(entity);
}
}
protected override void VisitTextPart(TextPart entity)
{
TextConverter converter;
if (Body != null)
{
// since we've already found the body, treat this as an attachment
attachments.Add(entity);
return;
}
if (entity.IsHtml)
{
converter = new HtmlToHtml
{
HtmlTagCallback = HtmlTagCallback
};
}
else if (entity.IsFlowed)
{
var flowed = new FlowedToHtml();
string delsp;
if (entity.ContentType.Parameters.TryGetValue("delsp", out delsp))
flowed.DeleteSpace = delsp.ToLowerInvariant() == "yes";
converter = flowed;
}
else
{
converter = new TextToHtml();
}
Body = converter.Convert(entity.Text);
}
protected override void VisitTnefPart(TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange(entity.ExtractAttachments());
}
protected override void VisitMessagePart(MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add(entity);
}
protected override void VisitMimePart(MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add(entity);
}
}

View File

@@ -1,15 +1,14 @@
using System;
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
/// <summary>
/// An interface that returns the UniqueId store for IMailItem.
/// For threads, it may be multiple items.
/// For single mails, it'll always be one item.
/// </summary>
public interface IMailHashContainer
{
IEnumerable<Guid> GetContainingIds();
}
IEnumerable<Guid> GetContainingIds();
}

View File

@@ -2,34 +2,33 @@
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem : IMailHashContainer
{
Guid UniqueId { get; }
string Id { get; }
string Subject { get; }
string ThreadId { get; }
string MessageId { get; }
string References { get; }
string InReplyTo { get; }
string PreviewText { get; }
string FromName { get; }
DateTime CreationDate { get; }
string FromAddress { get; }
bool HasAttachments { get; }
bool IsFlagged { get; }
bool IsFocused { get; }
bool IsRead { get; }
string DraftId { get; }
bool IsDraft { get; }
Guid FileId { get; }
namespace Wino.Core.Domain.Models.MailItem;
MailItemFolder AssignedFolder { get; }
MailAccount AssignedAccount { get; }
AccountContact SenderContact { get; }
}
/// <summary>
/// Interface of simplest representation of a MailCopy.
/// </summary>
public interface IMailItem : IMailHashContainer
{
Guid UniqueId { get; }
string Id { get; }
string Subject { get; }
string ThreadId { get; }
string MessageId { get; }
string References { get; }
string InReplyTo { get; }
string PreviewText { get; }
string FromName { get; }
DateTime CreationDate { get; }
string FromAddress { get; }
bool HasAttachments { get; }
bool IsFlagged { get; }
bool IsFocused { get; }
bool IsRead { get; }
string DraftId { get; }
bool IsDraft { get; }
Guid FileId { get; }
MailItemFolder AssignedFolder { get; }
MailAccount AssignedAccount { get; }
AccountContact SenderContact { get; }
}

View File

@@ -1,16 +1,15 @@
using System.Collections.ObjectModel;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Interface that represents conversation threads.
/// Even though this type has 1 single UI representation most of the time,
/// it can contain multiple IMailItem.
/// </summary>
public interface IMailItemThread : IMailItem
{
/// <summary>
/// Interface that represents conversation threads.
/// Even though this type has 1 single UI representation most of the time,
/// it can contain multiple IMailItem.
/// </summary>
public interface IMailItemThread : IMailItem
{
ObservableCollection<IMailItem> ThreadItems { get; }
IMailItem LatestMailItem { get; }
IMailItem FirstMailItem { get; }
}
ObservableCollection<IMailItem> ThreadItems { get; }
IMailItem LatestMailItem { get; }
IMailItem FirstMailItem { get; }
}

View File

@@ -1,20 +1,19 @@
using MailKit;
using MimeKit;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates all required information to create a MimeMessage for IMAP synchronizer.
/// </summary>
public class ImapMessageCreationPackage
{
public IMessageSummary MessageSummary { get; }
public MimeMessage MimeMessage { get; }
namespace Wino.Core.Domain.Models.MailItem;
public ImapMessageCreationPackage(IMessageSummary messageSummary, MimeMessage mimeMessage)
{
MessageSummary = messageSummary;
MimeMessage = mimeMessage;
}
/// <summary>
/// Encapsulates all required information to create a MimeMessage for IMAP synchronizer.
/// </summary>
public class ImapMessageCreationPackage
{
public IMessageSummary MessageSummary { get; }
public MimeMessage MimeMessage { get; }
public ImapMessageCreationPackage(IMessageSummary messageSummary, MimeMessage mimeMessage)
{
MessageSummary = messageSummary;
MimeMessage = mimeMessage;
}
}

View File

@@ -1,18 +1,17 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
public class MailDetailInformation
{
public string Id { get; set; }
namespace Wino.Core.Domain.Models.MailItem;
public Guid AccountId { get; set; }
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public SpecialFolderType SpecialFolderType { get; set; }
public bool IsRead { get; set; }
public bool IsFlagged { get; set; }
public bool IsDraft { get; set; }
}
public class MailDetailInformation
{
public string Id { get; set; }
public Guid AccountId { get; set; }
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public SpecialFolderType SpecialFolderType { get; set; }
public bool IsRead { get; set; }
public bool IsFlagged { get; set; }
public bool IsDraft { get; set; }
}

View File

@@ -1,25 +1,24 @@
using System.Collections.Generic;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Class that holds information when the drag/drop of mails are performed.
/// </summary>
public class MailDragPackage
{
/// <summary>
/// Class that holds information when the drag/drop of mails are performed.
/// </summary>
public class MailDragPackage
public MailDragPackage(IEnumerable<IMailItem> draggingMails)
{
public MailDragPackage(IEnumerable<IMailItem> draggingMails)
{
DraggingMails = draggingMails;
}
public MailDragPackage(IMailItem draggingMail)
{
DraggingMails =
[
draggingMail
];
}
public IEnumerable<IMailItem> DraggingMails { get; set; }
DraggingMails = draggingMails;
}
public MailDragPackage(IMailItem draggingMail)
{
DraggingMails =
[
draggingMail
];
}
public IEnumerable<IMailItem> DraggingMails { get; set; }
}

View File

@@ -1,11 +1,10 @@
using System;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
public class MailFolderPairMetadata
{
public class MailFolderPairMetadata
{
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public string MailCopyId { get; set; }
}
public Guid FolderId { get; set; }
public string RemoteFolderId { get; set; }
public string MailCopyId { get; set; }
}

View File

@@ -1,7 +1,6 @@
using MimeKit;
using Wino.Core.Domain.Entities.Mail;
namespace Wino.Core.Domain.Models.MailItem
{
public record NewMailItemPackage(MailCopy Copy, MimeMessage Mime, string AssignedRemoteFolderId);
}
namespace Wino.Core.Domain.Models.MailItem;
public record NewMailItemPackage(MailCopy Copy, MimeMessage Mime, string AssignedRemoteFolderId);

View File

@@ -3,13 +3,12 @@ using System.Collections.Generic;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Core.Domain.Models.MailItem
{
public record MailListInitializationOptions(IEnumerable<IMailItemFolder> Folders,
FilterOptionType FilterType,
SortingOptionType SortingOptionType,
bool CreateThreads,
bool? IsFocusedOnly,
string SearchQuery,
IEnumerable<Guid> ExistingUniqueIds);
}
namespace Wino.Core.Domain.Models.MailItem;
public record MailListInitializationOptions(IEnumerable<IMailItemFolder> Folders,
FilterOptionType FilterType,
SortingOptionType SortingOptionType,
bool CreateThreads,
bool? IsFocusedOnly,
string SearchQuery,
IEnumerable<Guid> ExistingUniqueIds);

View File

@@ -4,36 +4,35 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.Folders;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc.
/// </summary>
/// <param name="Action"> Action to execute. </param>
/// <param name="MailItems"> Mail copies execute the action on. </param>
/// <param name="ToggleExecution"> Whether the operation can be reverted if needed.
/// eg. MarkAsRead on already read item will set the action to MarkAsUnread.
/// This is used in hover actions for example. </param>
/// <param name="IgnoreHardDeleteProtection"> Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection. </param>
/// <param name="MoveTargetFolder"> Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder. </param>
public record MailOperationPreperationRequest(MailOperation Action, IEnumerable<MailCopy> MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder)
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> mailItems,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, mailItems ?? throw new ArgumentNullException(nameof(mailItems)), toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
namespace Wino.Core.Domain.Models.MailItem;
public MailOperationPreperationRequest(MailOperation action,
MailCopy singleMailItem,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, new List<MailCopy>() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
/// <summary>
/// Encapsulates the options for preparing requests to execute mail operations for mail items like Move, Delete, MarkAsRead, etc.
/// </summary>
/// <param name="Action"> Action to execute. </param>
/// <param name="MailItems"> Mail copies execute the action on. </param>
/// <param name="ToggleExecution"> Whether the operation can be reverted if needed.
/// eg. MarkAsRead on already read item will set the action to MarkAsUnread.
/// This is used in hover actions for example. </param>
/// <param name="IgnoreHardDeleteProtection"> Whether hard delete protection should be ignored.
/// Discard draft requests for example should ignore hard delete protection. </param>
/// <param name="MoveTargetFolder"> Moving folder for the Move operation.
/// If null and the action is Move, the user will be prompted to select a folder. </param>
public record MailOperationPreperationRequest(MailOperation Action, IEnumerable<MailCopy> MailItems, bool ToggleExecution, bool IgnoreHardDeleteProtection, IMailItemFolder MoveTargetFolder)
{
public MailOperationPreperationRequest(MailOperation action,
IEnumerable<MailCopy> mailItems,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, mailItems ?? throw new ArgumentNullException(nameof(mailItems)), toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
public MailOperationPreperationRequest(MailOperation action,
MailCopy singleMailItem,
bool toggleExecution = false,
IMailItemFolder moveTargetFolder = null,
bool ignoreHardDeleteProtection = false) : this(action, new List<MailCopy>() { singleMailItem }, toggleExecution, ignoreHardDeleteProtection, moveTargetFolder)
{
}
}

View File

@@ -1,9 +1,8 @@
using MimeKit;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Encapsulates MimeMessage and the path to the file.
/// </summary>
public record MimeMessageInformation(MimeMessage MimeMessage, string Path);
}
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Encapsulates MimeMessage and the path to the file.
/// </summary>
public record MimeMessageInformation(MimeMessage MimeMessage, string Path);

View File

@@ -1,13 +1,12 @@
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Class that holds immutable information about special folders in Outlook.
/// </summary>
/// <param name="InboxId"></param>
/// <param name="TrashId"></param>
/// <param name="JunkId"></param>
/// <param name="DraftId"></param>
/// <param name="SentId"></param>
/// <param name="ArchiveId"></param>
public record OutlookSpecialFolderIdInformation(string InboxId, string TrashId, string JunkId, string DraftId, string SentId, string ArchiveId);
}
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Class that holds immutable information about special folders in Outlook.
/// </summary>
/// <param name="InboxId"></param>
/// <param name="TrashId"></param>
/// <param name="JunkId"></param>
/// <param name="DraftId"></param>
/// <param name="SentId"></param>
/// <param name="ArchiveId"></param>
public record OutlookSpecialFolderIdInformation(string InboxId, string TrashId, string JunkId, string DraftId, string SentId, string ArchiveId);

View File

@@ -4,19 +4,18 @@ using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
using Wino.Core.Domain.Extensions;
namespace Wino.Core.Domain.Models.MailItem
{
public record SendDraftPreparationRequest(MailCopy MailItem,
MailAccountAlias SendingAlias,
MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailAccountPreferences AccountPreferences,
string Base64MimeMessage)
{
[JsonIgnore]
private MimeMessage mime;
namespace Wino.Core.Domain.Models.MailItem;
[JsonIgnore]
public MimeMessage Mime => mime ??= Base64MimeMessage.GetMimeMessageFromBase64();
}
public record SendDraftPreparationRequest(MailCopy MailItem,
MailAccountAlias SendingAlias,
MailItemFolder SentFolder,
MailItemFolder DraftFolder,
MailAccountPreferences AccountPreferences,
string Base64MimeMessage)
{
[JsonIgnore]
private MimeMessage mime;
[JsonIgnore]
public MimeMessage Mime => mime ??= Base64MimeMessage.GetMimeMessageFromBase64();
}

View File

@@ -5,91 +5,90 @@ using System.Linq;
using Wino.Core.Domain.Entities.Mail;
using Wino.Core.Domain.Entities.Shared;
namespace Wino.Core.Domain.Models.MailItem
namespace Wino.Core.Domain.Models.MailItem;
public class ThreadMailItem : IMailItemThread
{
public class ThreadMailItem : IMailItemThread
// TODO: Ideally this should be SortedList.
public ObservableCollection<IMailItem> ThreadItems { get; } = new ObservableCollection<IMailItem>();
public IMailItem LatestMailItem => ThreadItems.LastOrDefault();
public IMailItem FirstMailItem => ThreadItems.FirstOrDefault();
public bool AddThreadItem(IMailItem item)
{
// TODO: Ideally this should be SortedList.
public ObservableCollection<IMailItem> ThreadItems { get; } = new ObservableCollection<IMailItem>();
if (item == null) return false;
public IMailItem LatestMailItem => ThreadItems.LastOrDefault();
public IMailItem FirstMailItem => ThreadItems.FirstOrDefault();
public bool AddThreadItem(IMailItem item)
if (ThreadItems.Any(a => a.Id == item.Id))
{
if (item == null) return false;
if (ThreadItems.Any(a => a.Id == item.Id))
{
return false;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return true;
}
var insertItem = ThreadItems.FirstOrDefault(a => !a.IsDraft && a.CreationDate < item.CreationDate);
if (insertItem == null)
ThreadItems.Insert(ThreadItems.Count, item);
else
{
var index = ThreadItems.IndexOf(insertItem);
ThreadItems.Insert(index, item);
}
return false;
}
if (item != null && item.IsDraft)
{
ThreadItems.Insert(0, item);
return true;
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
var insertItem = ThreadItems.FirstOrDefault(a => !a.IsDraft && a.CreationDate < item.CreationDate);
#region IMailItem
if (insertItem == null)
ThreadItems.Insert(ThreadItems.Count, item);
else
{
var index = ThreadItems.IndexOf(insertItem);
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;
public string Id => LatestMailItem?.Id ?? string.Empty;
ThreadItems.Insert(index, item);
}
// Show subject from last item.
public string Subject => LatestMailItem?.Subject ?? string.Empty;
public string ThreadId => LatestMailItem?.ThreadId ?? string.Empty;
public string PreviewText => FirstMailItem?.PreviewText ?? string.Empty;
public string FromName => LatestMailItem?.FromName ?? string.Empty;
public string FromAddress => LatestMailItem?.FromAddress ?? string.Empty;
public bool HasAttachments => ThreadItems.Any(a => a.HasAttachments);
public bool IsFlagged => ThreadItems.Any(a => a.IsFlagged);
public bool IsFocused => LatestMailItem?.IsFocused ?? false;
public bool IsRead => ThreadItems.All(a => a.IsRead);
public DateTime CreationDate => FirstMailItem?.CreationDate ?? DateTime.MinValue;
public bool IsDraft => ThreadItems.Any(a => a.IsDraft);
public string DraftId => string.Empty;
public string MessageId => LatestMailItem?.MessageId;
public string References => LatestMailItem?.References ?? string.Empty;
public string InReplyTo => LatestMailItem?.InReplyTo ?? string.Empty;
public MailItemFolder AssignedFolder => LatestMailItem?.AssignedFolder;
public MailAccount AssignedAccount => LatestMailItem?.AssignedAccount;
public Guid FileId => LatestMailItem?.FileId ?? Guid.Empty;
public AccountContact SenderContact => LatestMailItem?.SenderContact;
#endregion
return true;
}
public IEnumerable<Guid> GetContainingIds() => ThreadItems?.Select(a => a.UniqueId) ?? default;
#region IMailItem
public Guid UniqueId => LatestMailItem?.UniqueId ?? Guid.Empty;
public string Id => LatestMailItem?.Id ?? string.Empty;
// Show subject from last item.
public string Subject => LatestMailItem?.Subject ?? string.Empty;
public string ThreadId => LatestMailItem?.ThreadId ?? string.Empty;
public string PreviewText => FirstMailItem?.PreviewText ?? string.Empty;
public string FromName => LatestMailItem?.FromName ?? string.Empty;
public string FromAddress => LatestMailItem?.FromAddress ?? string.Empty;
public bool HasAttachments => ThreadItems.Any(a => a.HasAttachments);
public bool IsFlagged => ThreadItems.Any(a => a.IsFlagged);
public bool IsFocused => LatestMailItem?.IsFocused ?? false;
public bool IsRead => ThreadItems.All(a => a.IsRead);
public DateTime CreationDate => FirstMailItem?.CreationDate ?? DateTime.MinValue;
public bool IsDraft => ThreadItems.Any(a => a.IsDraft);
public string DraftId => string.Empty;
public string MessageId => LatestMailItem?.MessageId;
public string References => LatestMailItem?.References ?? string.Empty;
public string InReplyTo => LatestMailItem?.InReplyTo ?? string.Empty;
public MailItemFolder AssignedFolder => LatestMailItem?.AssignedFolder;
public MailAccount AssignedAccount => LatestMailItem?.AssignedAccount;
public Guid FileId => LatestMailItem?.FileId ?? Guid.Empty;
public AccountContact SenderContact => LatestMailItem?.SenderContact;
#endregion
}

View File

@@ -1,14 +1,13 @@
using System;
using Wino.Core.Domain.Enums;
namespace Wino.Core.Domain.Models.MailItem
{
/// <summary>
/// Defines a single rule for toggling user actions if needed.
/// For example: If user wants to mark a mail as read, but it's already read, then it should be marked as unread.
/// </summary>
/// <param name="SourceAction"></param>
/// <param name="TargetAction"></param>
/// <param name="Condition"></param>
public record ToggleRequestRule(MailOperation SourceAction, MailOperation TargetAction, Func<IMailItem, bool> Condition);
}
namespace Wino.Core.Domain.Models.MailItem;
/// <summary>
/// Defines a single rule for toggling user actions if needed.
/// For example: If user wants to mark a mail as read, but it's already read, then it should be marked as unread.
/// </summary>
/// <param name="SourceAction"></param>
/// <param name="TargetAction"></param>
/// <param name="Condition"></param>
public record ToggleRequestRule(MailOperation SourceAction, MailOperation TargetAction, Func<IMailItem, bool> Condition);