Skip to content

Commit

Permalink
Merge pull request #1639 from avknaidu/MarkdownTextBlockFix
Browse files Browse the repository at this point in the history
Implementing Relative Links/Images/Emails in MarkdownTextBlock
  • Loading branch information
michael-hawker authored Feb 10, 2018
2 parents 3861859 + d5441a8 commit b63ee18
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -280,22 +280,56 @@ There are a couple of ways to get HTML links. The easiest is to just paste a va
However, usually you'll want to have text that functions as a link. In that case, include the text inside of square brackets followed by the URL in parentheses. So:

>\[Wikipedia\]\(http://en.wikipedia.org).
>\[Wikipedia\]\(http\://en.wikipedia.org).
results in:

>[Wikipedia](http://en.wikipedia.org).
You can also provide tooltip text for links like so:

>\[Wikipedia\]\(http://en.wikipedia.org "tooltip text"\).
>\[Wikipedia\]\(http\://en.wikipedia.org "tooltip text"\).
results in:

>[Wikipedia](http://en.wikipedia.org "tooltip text").
There are other methods of generating links that aren't appropriate for discussion-board style comments. See the [Markdown Syntax](http://daringfireball.net/projects/markdown/syntax#link) if you're interested in more info.

 

Relative links are also supported

>\[Relative Link\]\(/Assets/Photos/Photos.json\)
results in:

>[Relative Link](/Assets/Photos/Photos.json)
 

>\[Relative Link 2\]\(../Photos/Photos.json\)
results in:

>[Relative Link 2](../Photos/Photos.json)
**Note:** Relative Links has to be Manually Handled in `LinkClicked` Event.

*****

# Email Links

Emails can be used as Masked Links or Direct email links.

>[Email\]\(`email@email.com`)
will be rendered to [Email](email@email.com)

>`email@email.com`
will be rendered to email@email.com

*****

# IMAGES
Expand All @@ -304,11 +338,25 @@ To add an image, it is almost like a link. You just need to add a \! before.

So inline image syntax looks like this:

>\!\[Toolkit logo](https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/Microsoft.Toolkit.Uwp.SampleApp/Assets/ToolkitLogo.png)
>\!\[Toolkit logo](https:\//raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/Microsoft.Toolkit.Uwp.SampleApp/Assets/Helpers.png)
which renders in:

![Toolkit logo](https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/Microsoft.Toolkit.Uwp.SampleApp/Assets/ToolkitLogo.png)
![Toolkit logo](https://raw.githubusercontent.com/Microsoft/UWPCommunityToolkit/master/Microsoft.Toolkit.Uwp.SampleApp/Assets/Helpers.png)

Rendering Images is now supported through prefix. use property **UriPrefix**

 

Example: if you set **UriPrefix** to **ms-appx://** then

>\!\[Local Image](/Assets/NotificationAssets/Sunny-Square.png)
 

renders in

![Local Image](/Assets/NotificationAssets/Sunny-Square.png)

*****

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
Header1Foreground="{ThemeResource SystemControlForegroundAccentBrush}"
Foreground="Black"
LinkForeground="BlueViolet"
UriPrefix="ms-appx://"
Text="{Binding ElementName=UnformattedText, Path=Text}" />
</ScrollViewer>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.System;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

Expand Down Expand Up @@ -69,7 +70,14 @@ private void SetInitalText(string text)

private async void MarkdownText_LinkClicked(object sender, UI.Controls.LinkClickedEventArgs e)
{
await Launcher.LaunchUriAsync(new Uri(e.Link));
if (!Uri.TryCreate(e.Link, UriKind.Absolute, out Uri result))
{
await new MessageDialog("Masked relative links needs to be manually handled.").ShowAsync();
}
else
{
await Launcher.LaunchUriAsync(new Uri(e.Link));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ public Thickness CodeBorderThickness
typeof(MarkdownTextBlock),
new PropertyMetadata(null, OnPropertyChangedStatic));

/// <summary>
/// Gets or sets the Prefix of Uri.
/// </summary>
public string UriPrefix
{
get { return (string)GetValue(UriPrefixProperty); }
set { SetValue(UriPrefixProperty, value); }
}

/// <summary>
/// Gets the dependency property for <see cref="UriPrefix"/>.
/// </summary>
public static readonly DependencyProperty UriPrefixProperty = DependencyProperty.Register(
nameof(UriPrefix),
typeof(string),
typeof(MarkdownTextBlock),
new PropertyMetadata(string.Empty, OnPropertyChangedStatic));

/// <summary>
/// Gets or sets the brush used to render the text inside a code block. If this is
/// <c>null</c>, then Foreground is used.
Expand Down Expand Up @@ -1263,6 +1281,14 @@ private async void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs arg
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
async Task<ImageSource> IImageResolver.ResolveImageAsync(string url, string tooltip)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
{
if (!string.IsNullOrEmpty(UriPrefix))
{
url = string.Format("{0}{1}", UriPrefix, url);
}
}

var eventArgs = new ImageResolvingEventArgs(url, tooltip);
ImageResolving?.Invoke(this, eventArgs);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Helpers;
using System.Linq;

namespace Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Parse
{
Expand Down Expand Up @@ -375,14 +376,18 @@ internal static Common.InlineParseResult ParseEmailAddress(string markdown, int
// Note: it is intended that this code match the reddit.com markdown parser; there are
// many characters which are legal in email addresses but which aren't picked up by
// reddit (for example: '$' and '!').

// Special characters as per https://en.wikipedia.org/wiki/Email_address#Local-part allowed
char[] allowedchars = new char[] { '!', '#', '$', '%', '&', '\'', '*', '+', '-', '/', '=', '?', '^', '_', '`', '{', '|', '}', '~' };

int start = tripPos;
while (start > minStart)
{
char c = markdown[start - 1];
if ((c < 'a' || c > 'z') &&
(c < 'A' || c > 'Z') &&
(c < '0' || c > '9') &&
c != '+' && c != '-' && c != '_' && c != '.')
!allowedchars.Contains(c))
{
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ internal static Common.InlineParseResult Parse(string markdown, int start, int e
/// <returns> The textual representation of this object. </returns>
public override string ToString()
{
return string.Format("[{0}]: {1}", Tooltip, Url);
return string.Format("![{0}]: {1}", Tooltip, Url);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using System;
using System.Collections.Generic;
using Microsoft.Toolkit.Extensions;
using Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Helpers;

namespace Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Parse
Expand Down Expand Up @@ -193,9 +194,16 @@ internal static Common.InlineParseResult Parse(string markdown, int start, int m
}

// Check the URL is okay.
if (!IsUrlValid(url))
if (!url.IsEmail())
{
return null;
if (!IsUrlValid(url))
{
return null;
}
}
else
{
tooltip = url = string.Format("mailto:{0}", url);
}

// We found a regular stand-alone link.
Expand Down Expand Up @@ -272,7 +280,7 @@ public void ResolveReference(MarkdownDocument document)
private static bool IsUrlValid(string url)
{
// URLs can be relative.
if (url.StartsWith("/") || url.StartsWith("#"))
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
{
return true;
}
Expand Down
34 changes: 34 additions & 0 deletions Microsoft.Toolkit/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// ******************************************************************
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************

using System.Text.RegularExpressions;

namespace Microsoft.Toolkit.Extensions
{
/// <summary>
/// All common string extensions should go here
/// </summary>
public static class StringExtensions
{
internal const string EmailRegex = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])";

/// <summary>
/// Returns whether said string is a valid email or not.
/// Uses general Email Regex (RFC 5322 Official Standard) from emailregex.com
/// </summary>
/// <returns><c>true</c> for valid email.<c>false</c> otherwise</returns>
public static bool IsEmail(this string str)
{
return Regex.IsMatch(str, EmailRegex);
}
}
}

0 comments on commit b63ee18

Please sign in to comment.