Implement clickable plaintext links and dark mode fix (#488)
* Plaintext links are now clickable and fixes dark mode. - Change `AppxPackageDir` path from `C:\Users\bkaan\Desktop\Packages\` to `$(USERPROFILE)\Desktop\Packages\`, fixes error when building release. - Plaintext links are now clickable, and match the same subtle style as Windows Mail. - Remove `!important` from inline styles to allow Dark Reader to properly recolor the element. * Implement setting for clickable plaintext link
This commit is contained in:
@@ -93,6 +93,11 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
bool RenderStyles { get; set; }
|
bool RenderStyles { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setting: Set whether plaintext links should be automatically converted to clickable links.
|
||||||
|
/// </summary>
|
||||||
|
bool RenderPlaintextLinks { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the preferred rendering options for HTML rendering.
|
/// Gets the preferred rendering options for HTML rendering.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
{
|
{
|
||||||
private const bool DefaultLoadImageValue = true;
|
private const bool DefaultLoadImageValue = true;
|
||||||
private const bool DefaultLoadStylesValue = true;
|
private const bool DefaultLoadStylesValue = true;
|
||||||
|
private const bool DefaultRenderPlaintextLinksValue = true;
|
||||||
|
|
||||||
public bool LoadImages { get; set; } = DefaultLoadImageValue;
|
public bool LoadImages { get; set; } = DefaultLoadImageValue;
|
||||||
public bool LoadStyles { get; set; } = DefaultLoadStylesValue;
|
public bool LoadStyles { get; set; } = DefaultLoadStylesValue;
|
||||||
|
public bool RenderPlaintextLinks { get; set; } = DefaultRenderPlaintextLinksValue;
|
||||||
|
|
||||||
// HtmlDocument.Load call is redundant if all the settings are in default values.
|
// HtmlDocument.Load call is redundant if all the settings are in default values.
|
||||||
// Therefore we will purify the HTML only if needed.
|
// Therefore we will purify the HTML only if needed.
|
||||||
|
|||||||
@@ -484,6 +484,7 @@
|
|||||||
"SettingsLinkedAccountsSave_Title": "Save Changes",
|
"SettingsLinkedAccountsSave_Title": "Save Changes",
|
||||||
"SettingsLoadImages_Title": "Load images automatically",
|
"SettingsLoadImages_Title": "Load images automatically",
|
||||||
"SettingsLoadStyles_Title": "Load styles automatically",
|
"SettingsLoadStyles_Title": "Load styles automatically",
|
||||||
|
"SettingsLoadPlaintextLinks_Title": "Convert plaintext links to clickable links",
|
||||||
"SettingsMailSpacing_Description": "Adjust the spacing for listing mails.",
|
"SettingsMailSpacing_Description": "Adjust the spacing for listing mails.",
|
||||||
"SettingsMailSpacing_Title": "Mail Spacing",
|
"SettingsMailSpacing_Title": "Mail Spacing",
|
||||||
"SettingsFolderMenuStyle_Title": "Create Nested Folders",
|
"SettingsFolderMenuStyle_Title": "Create Nested Folders",
|
||||||
|
|||||||
@@ -42,7 +42,12 @@ namespace Wino.Core.UWP.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MailRenderingOptions GetRenderingOptions()
|
public MailRenderingOptions GetRenderingOptions()
|
||||||
=> new MailRenderingOptions() { LoadImages = RenderImages, LoadStyles = RenderStyles };
|
=> new MailRenderingOptions()
|
||||||
|
{
|
||||||
|
LoadImages = RenderImages,
|
||||||
|
LoadStyles = RenderStyles,
|
||||||
|
RenderPlaintextLinks = RenderPlaintextLinks
|
||||||
|
};
|
||||||
|
|
||||||
public MailListDisplayMode MailItemDisplayMode
|
public MailListDisplayMode MailItemDisplayMode
|
||||||
{
|
{
|
||||||
@@ -92,6 +97,12 @@ namespace Wino.Core.UWP.Services
|
|||||||
set => SetPropertyAndSave(nameof(RenderStyles), value);
|
set => SetPropertyAndSave(nameof(RenderStyles), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool RenderPlaintextLinks
|
||||||
|
{
|
||||||
|
get => _configurationService.Get(nameof(RenderPlaintextLinks), true);
|
||||||
|
set => SetPropertyAndSave(nameof(RenderPlaintextLinks), value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool RenderImages
|
public bool RenderImages
|
||||||
{
|
{
|
||||||
get => _configurationService.Get(nameof(RenderImages), true);
|
get => _configurationService.Get(nameof(RenderImages), true);
|
||||||
|
|||||||
1
Wino.Mail/JS/libs/linkify-element.min.js
vendored
Normal file
1
Wino.Mail/JS/libs/linkify-element.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
var linkifyElement=function(e){"use strict";const t=1,n=3;function r(e,t,n){let r=n[n.length-1];e.replaceChild(r,t);for(let t=n.length-2;t>=0;t--)e.insertBefore(n[t],r),r=n[t]}function i(e,t,n){const r=[];for(let i=0;i<e.length;i++){const o=e[i];"nl"===o.t&&t.get("nl2br")?r.push(n.createElement("br")):o.isLink&&t.check(o)?r.push(t.render(o)):r.push(n.createTextNode(o.toString()))}return r}function o(l,a,c){if(!l||l.nodeType!==t)throw new Error(`Cannot linkify ${l} - Invalid DOM Node type`);if("A"===l.tagName||a.ignoreTags.indexOf(l.tagName)>=0)return l;let s=l.firstChild;for(;s;){let d,u,f;switch(s.nodeType){case t:o(s,a,c);break;case n:if(d=s.nodeValue,u=e.tokenize(d),0===u.length||1===u.length&&"text"===u[0].t)break;f=i(u,a,c),r(l,s,f),s=f[f.length-1]}s=s.nextSibling}return l}function l(e){return t=>{let{tagName:n,attributes:r,content:i,eventListeners:o}=t;const l=e.createElement(n);for(const e in r)l.setAttribute(e,r[e]);if(o&&l.addEventListener)for(const e in o)l.addEventListener(e,o[e]);return l.appendChild(e.createTextNode(i)),l}}function a(t,n,r){void 0===n&&(n=null),void 0===r&&(r=null);try{r=r||document||window&&window.document||global&&global.document}catch(e){}if(!r)throw new Error("Cannot find document implementation. If you are in a non-browser environment like Node.js, pass the document implementation as the third argument to linkifyElement.");return o(t,new e.Options(n,l(r)),r)}return a.helper=o,a.getDefaultRender=l,a.normalize=(t,n)=>new e.Options(t,l(n)),a}(linkify);
|
||||||
1
Wino.Mail/JS/libs/linkify.min.js
vendored
Normal file
1
Wino.Mail/JS/libs/linkify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,52 +1,111 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="./global.css" />
|
<link rel="stylesheet" href="./global.css" />
|
||||||
<script src="./libs/darkreader.js"></script>
|
<script src="./libs/darkreader.js"></script>
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding-left: 12px;
|
|
||||||
padding-right: 12px;
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
margin: 0px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#readerDiv {
|
<script src="./libs/linkify.min.js"></script>
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
<script src="./libs/linkify-element.min.js"></script>
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<meta name="color-scheme" content="dark light">
|
|
||||||
<script>
|
|
||||||
function RenderHTML(htmlString) {
|
|
||||||
window.scroll(0, 0);
|
|
||||||
var containerDiv = document.getElementById("readerDiv");
|
|
||||||
containerDiv.innerHTML = htmlString;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ChangeFontFamily(fontFamily) {
|
<style>
|
||||||
var containerDiv = document.getElementById("readerDiv");
|
body {
|
||||||
containerDiv.style.fontFamily = fontFamily;
|
padding-left: 12px;
|
||||||
}
|
padding-right: 12px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin: 0px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
function ChangeFontSize(size) {
|
#readerDiv {
|
||||||
var containerDiv = document.getElementById("readerDiv");
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
containerDiv.style.fontSize = size;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetLightEditor() {
|
a.wino-plain-link {
|
||||||
document.documentElement.setAttribute('data-theme', 'light');
|
color: inherit !important;
|
||||||
DarkReader.disable();
|
text-decoration: underline dotted !important;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<meta name="color-scheme" content="dark light" />
|
||||||
|
<script>
|
||||||
|
var _htmlString = "";
|
||||||
|
var _shouldLinkifyText = true;
|
||||||
|
|
||||||
function SetDarkEditor() {
|
// Called when rendering a new email for the first time
|
||||||
document.documentElement.setAttribute('data-theme', 'dark');
|
function RenderHTML(htmlString, shouldLinkifyText = true) {
|
||||||
DarkReader.enable();
|
window.scroll(0, 0);
|
||||||
}
|
|
||||||
</script>
|
_htmlString = htmlString;
|
||||||
<div id="readerDiv"></div>
|
_shouldLinkifyText = shouldLinkifyText;
|
||||||
</body>
|
|
||||||
|
internalRenderHTML(htmlString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called to render or refresh the email
|
||||||
|
function internalRenderHTML(htmlString) {
|
||||||
|
var containerDiv = document.getElementById("readerDiv");
|
||||||
|
try {
|
||||||
|
containerDiv.innerHTML = htmlString;
|
||||||
|
|
||||||
|
// Linkify plain text links if enabled
|
||||||
|
if (_shouldLinkifyText) {
|
||||||
|
linkifyElement(
|
||||||
|
containerDiv,
|
||||||
|
{ className: "wino-plain-link" },
|
||||||
|
document
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove !important from inline styles if dark mode is enabled
|
||||||
|
if (
|
||||||
|
document.documentElement.getAttribute("data-theme") ===
|
||||||
|
"dark"
|
||||||
|
) {
|
||||||
|
removeImportantFromInlineStyles();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
containerDiv.innerHTML = htmlString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeFontFamily(fontFamily) {
|
||||||
|
var containerDiv = document.getElementById("readerDiv");
|
||||||
|
containerDiv.style.fontFamily = fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeFontSize(size) {
|
||||||
|
var containerDiv = document.getElementById("readerDiv");
|
||||||
|
containerDiv.style.fontSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetLightEditor() {
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
DarkReader.disable();
|
||||||
|
|
||||||
|
internalRenderHTML(_htmlString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetDarkEditor() {
|
||||||
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
DarkReader.enable();
|
||||||
|
|
||||||
|
internalRenderHTML(_htmlString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function removeImportantFromInlineStyles() {
|
||||||
|
var allElements = document.querySelectorAll("*");
|
||||||
|
allElements.forEach(function (element) {
|
||||||
|
var style = element.getAttribute("style");
|
||||||
|
if (style) {
|
||||||
|
var newStyle = style.replace(/!important/g, "");
|
||||||
|
element.setAttribute("style", newStyle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div id="readerDiv"></div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ namespace Wino.Views
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await ExecuteScriptFunctionAsync("RenderHTML", htmlBody);
|
var shouldLinkifyText = ViewModel.CurrentRenderModel?.MailRenderingOptions?.RenderPlaintextLinks ?? true;
|
||||||
|
await ExecuteScriptFunctionAsync("RenderHTML", htmlBody, shouldLinkifyText);
|
||||||
}
|
}
|
||||||
|
|
||||||
isRenderingInProgress = false;
|
isRenderingInProgress = false;
|
||||||
|
|||||||
@@ -69,6 +69,12 @@
|
|||||||
</controls:SettingsCard.HeaderIcon>
|
</controls:SettingsCard.HeaderIcon>
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.RenderStyles, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.RenderStyles, Mode=TwoWay}" />
|
||||||
</controls:SettingsCard>
|
</controls:SettingsCard>
|
||||||
|
<controls:SettingsCard Header="{x:Bind domain:Translator.SettingsLoadPlaintextLinks_Title}">
|
||||||
|
<controls:SettingsCard.HeaderIcon>
|
||||||
|
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
||||||
|
</controls:SettingsCard.HeaderIcon>
|
||||||
|
<ToggleSwitch IsOn="{x:Bind ViewModel.PreferencesService.RenderPlaintextLinks, Mode=TwoWay}" />
|
||||||
|
</controls:SettingsCard>
|
||||||
</controls:SettingsExpander.Items>
|
</controls:SettingsExpander.Items>
|
||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
|
|
||||||
@@ -116,5 +122,4 @@
|
|||||||
</controls:SettingsExpander>
|
</controls:SettingsExpander>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</abstract:ReadComposePanePageAbstract>
|
</abstract:ReadComposePanePageAbstract>
|
||||||
|
|
||||||
@@ -567,6 +567,8 @@
|
|||||||
<Content Include="JS\global.css" />
|
<Content Include="JS\global.css" />
|
||||||
<Content Include="JS\libs\jodit.min.css" />
|
<Content Include="JS\libs\jodit.min.css" />
|
||||||
<Content Include="JS\libs\jodit.min.js" />
|
<Content Include="JS\libs\jodit.min.js" />
|
||||||
|
<Content Include="JS\libs\linkify-element.min.js" />
|
||||||
|
<Content Include="JS\libs\linkify.min.js" />
|
||||||
<Content Include="JS\reader.html" />
|
<Content Include="JS\reader.html" />
|
||||||
<Content Include="Assets\ReleaseNotes\190.md" />
|
<Content Include="Assets\ReleaseNotes\190.md" />
|
||||||
<None Include="Package.StoreAssociation.xml" />
|
<None Include="Package.StoreAssociation.xml" />
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||||
<AppxPackageDir>C:\Users\bkaan\Desktop\Packages\</AppxPackageDir>
|
<AppxPackageDir>$(USERPROFILE)\Desktop\Packages\</AppxPackageDir>
|
||||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||||
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
|
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
|
||||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||||
|
|||||||
Reference in New Issue
Block a user