Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,10 @@ private async void InfoAreaPivot_OnSelectionChanged(object sender, SelectionChan
private async void DocumentationTextblock_OnLinkClicked(object sender, LinkClickedEventArgs e)
{
TrackingManager.TrackEvent("Link", e.Link);
await Launcher.LaunchUriAsync(new Uri(e.Link));
if (Uri.TryCreate(e.Link, UriKind.Absolute, out Uri result))
{
await Launcher.LaunchUriAsync(new Uri(e.Link));
}
}

private async void DocumentationTextblock_ImageResolving(object sender, ImageResolvingEventArgs e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls
/// </summary>
public class LinkClickedEventArgs : EventArgs
{
internal LinkClickedEventArgs(string link)
internal LinkClickedEventArgs(string link, LinkReturnType type)
{
Link = link;
Type = type;
}

/// <summary>
/// Gets the link that was tapped.
/// </summary>
public string Link { get; }

/// <summary>
/// Gets the type of link that was tapped.(Image/Link)
/// </summary>
public LinkReturnType Type { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// ******************************************************************
// 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.
// ******************************************************************

namespace Microsoft.Toolkit.Uwp.UI.Controls
{
/// <summary>
/// Return type for Clicked Event
/// </summary>
public enum LinkReturnType
{
/// <summary>
/// Returns when Link Clicked is Hyperlink
/// </summary>
Hyperlink,

/// <summary>
/// Returns when Link Clicked is Image
/// </summary>
Image
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Reflection;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;

namespace Microsoft.Toolkit.Uwp.UI.Controls
Expand Down Expand Up @@ -46,29 +47,17 @@ private void OnPropertyChanged(DependencyObject d, DependencyProperty prop)
/// <summary>
/// Fired when a user taps one of the link elements
/// </summary>
private async void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args)
private void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args)
{
// Links that are nested within superscript elements cause the Click event to fire multiple times.
// e.g. this markdown "[^bot](http://www.reddit.com/r/youtubefactsbot/wiki/index)"
// Therefore we detect and ignore multiple clicks.
if (multiClickDetectionTriggered)
{
return;
}

multiClickDetectionTriggered = true;
await Dispatcher.RunAsync(CoreDispatcherPriority.High, () => multiClickDetectionTriggered = false);

// Get the hyperlink URL.
var url = (string)sender.GetValue(HyperlinkUrlProperty);
if (url == null)
{
return;
}
LinkHandled((string)sender.GetValue(HyperlinkUrlProperty), LinkReturnType.Hyperlink);
}

// Fire off the event.
var eventArgs = new LinkClickedEventArgs(url);
LinkClicked?.Invoke(this, eventArgs);
/// <summary>
/// Fired when a user taps one of the image elements
/// </summary>
private void NewImagelink_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
LinkHandled((string)(sender as Image).GetValue(HyperlinkUrlProperty), LinkReturnType.Image);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
using ColorCode;
using Microsoft.Toolkit.Parsers.Markdown;
using Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Render;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
Expand Down Expand Up @@ -164,9 +166,16 @@ private void RenderMarkdown()
private void UnhookListeners()
{
// Clear any hyper link events if we have any
foreach (Hyperlink link in _listeningHyperlinks)
foreach (object link in _listeningHyperlinks)
{
link.Click -= Hyperlink_Click;
if (link is Hyperlink hyperlink)
{
hyperlink.Click -= Hyperlink_Click;
}
else if (link is Image image)
{
image.Tapped -= NewImagelink_Tapped;
}
}

// Clear everything that exists.
Expand All @@ -188,6 +197,21 @@ public void RegisterNewHyperLink(Hyperlink newHyperlink, string linkUrl)
_listeningHyperlinks.Add(newHyperlink);
}

/// <summary>
/// Called when the render has a link we need to listen to.
/// </summary>
public void RegisterNewHyperLink(Image newImagelink, string linkUrl)
{
// Setup a listener for clicks.
newImagelink.Tapped += NewImagelink_Tapped;

// Associate the URL with the hyperlink.
newImagelink.SetValue(HyperlinkUrlProperty, linkUrl);

// Add it to our list
_listeningHyperlinks.Add(newImagelink);
}

/// <summary>
/// Called when the renderer needs to display a image.
/// </summary>
Expand Down Expand Up @@ -277,5 +301,32 @@ bool ICodeBlockResolver.ParseSyntax(InlineCollection inlineCollection, string te
return false;
}
}

/// <summary>
/// Called when a link needs to be handled
/// </summary>
internal async void LinkHandled(string url, LinkReturnType type)
{
// Links that are nested within superscript elements cause the Click event to fire multiple times.
// e.g. this markdown "[^bot](http://www.reddit.com/r/youtubefactsbot/wiki/index)"
// Therefore we detect and ignore multiple clicks.
if (multiClickDetectionTriggered)
{
return;
}

multiClickDetectionTriggered = true;
await Dispatcher.RunAsync(CoreDispatcherPriority.High, () => multiClickDetectionTriggered = false);

// Get the hyperlink URL.
if (url == null)
{
return;
}

// Fire off the event.
var eventArgs = new LinkClickedEventArgs(url, type);
LinkClicked?.Invoke(this, eventArgs);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ public string UriPrefix
/// <summary>
/// Holds a list of hyperlinks we are listening to.
/// </summary>
private readonly List<Hyperlink> _listeningHyperlinks = new List<Hyperlink>();
private readonly List<object> _listeningHyperlinks = new List<object>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to wrap this into a new class, or tuple or something, for Type safety.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could change it to UIElement (I think).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image is derived from UIElement. Pretty sure Hyperlink is not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's right. I'm ok with this being how it is right now unless @WilliamABradley has any concerns


/// <summary>
/// The root element for our rendering.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE.
// ******************************************************************

using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;

namespace Microsoft.Toolkit.Uwp.UI.Controls.Markdown.Render
Expand All @@ -25,5 +26,12 @@ public interface ILinkRegister
/// <param name="newHyperlink">Hyperlink to Register.</param>
/// <param name="linkUrl">Url to Register.</param>
void RegisterNewHyperLink(Hyperlink newHyperlink, string linkUrl);

/// <summary>
/// Registers a Hyperlink with a LinkUrl.
/// </summary>
/// <param name="newImagelink">ImageLink to Register.</param>
/// <param name="linkUrl">Url to Register.</param>
void RegisterNewHyperLink(Image newImagelink, string linkUrl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ protected override async void RenderImage(ImageInline element, IRenderContext co
var image = new Image();
var imageContainer = new InlineUIContainer() { Child = image };

LinkRegister.RegisterNewHyperLink(image, element.Url);

image.Source = resolvedImage;
image.HorizontalAlignment = HorizontalAlignment.Left;
image.VerticalAlignment = VerticalAlignment.Top;
Expand Down
6 changes: 5 additions & 1 deletion docs/controls/MarkdownTextBlock.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Here are some limitations you may encounter:

- Images cannot be embedded inside a hyperlink
- All images are stretched with the same stretch value (defined by ImageStretch property)
- Relative Links & Relative Images needs to be handled manually using `LinkClicked` event.

## Example Image
Note: scrolling is smooth, the gif below is not.
Expand Down Expand Up @@ -95,7 +96,7 @@ The MarkdownTextBlock control is highly customizable to blend with any theme. Cu

### LinkClicked

Use this event to handle clicking on links for Markdown, by default the MarkdownTextBlock does not handle Clicking on Links.
Use this event to handle clicking on links or images for Markdown, by default the MarkdownTextBlock does not handle Clicking on Links or Images.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a note here that taping any image will raise the LinkClicked event and with the uri of the image?Developers should use the LinkType property to decide how to handle it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Let me know if there is anything else that i need to update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. @WilliamABradley up to you


```c#
private async void MarkdownText_LinkClicked(object sender, LinkClickedEventArgs e)
Expand All @@ -107,6 +108,9 @@ private async void MarkdownText_LinkClicked(object sender, LinkClickedEventArgs
}
```

> [!NOTE]
The **LinkClicked** event will be raised when either a Hyperlink or an Image is Tapped and will return element Type in **e.LinkType** on **LinkClickedEventArgs**.


### ImageResolving

Expand Down