diff --git a/Documents/Markdig-readme.md b/Documents/Markdig-readme.md
index 6d231f2..9d593fa 100644
--- a/Documents/Markdig-readme.md
+++ b/Documents/Markdig-readme.md
@@ -1,4 +1,11 @@
-# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
+* [Markdig](#markdig)
+ * [Features](#features)
+ * [Documentation](#documentation)
+ * [Download](#download)
+ * [Usage](#usage)
+
+
+# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
diff --git a/src/Markdig.Wpf/Commands.cs b/src/Markdig.Wpf/Commands.cs
index 633a887..0643095 100644
--- a/src/Markdig.Wpf/Commands.cs
+++ b/src/Markdig.Wpf/Commands.cs
@@ -20,5 +20,10 @@ public static class Commands
/// Routed command for Images.
///
public static RoutedCommand Image { get; } = new RoutedCommand(nameof(Image), typeof(Commands));
+
+ ///
+ /// Routed command for navigating to a heading in a document. Command parameter contains the heading id
+ ///
+ public static RoutedCommand Navigate { get; } = new RoutedCommand(nameof(Navigate), typeof(Commands));
}
}
diff --git a/src/Markdig.Wpf/MarkdownExtensions.cs b/src/Markdig.Wpf/MarkdownExtensions.cs
index 7470dc2..5925a6a 100644
--- a/src/Markdig.Wpf/MarkdownExtensions.cs
+++ b/src/Markdig.Wpf/MarkdownExtensions.cs
@@ -25,7 +25,8 @@ public static MarkdownPipelineBuilder UseSupportedExtensions(this MarkdownPipeli
.UseGridTables()
.UsePipeTables()
.UseTaskLists()
- .UseAutoLinks();
+ .UseAutoLinks()
+ .UseAutoIdentifiers();
}
}
}
diff --git a/src/Markdig.Wpf/MarkdownViewer.cs b/src/Markdig.Wpf/MarkdownViewer.cs
index 5059503..eed80d6 100644
--- a/src/Markdig.Wpf/MarkdownViewer.cs
+++ b/src/Markdig.Wpf/MarkdownViewer.cs
@@ -2,9 +2,12 @@
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
+using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
namespace Markdig.Wpf
{
@@ -35,11 +38,28 @@ public class MarkdownViewer : Control
public static readonly DependencyProperty PipelineProperty =
DependencyProperty.Register(nameof(Pipeline), typeof(MarkdownPipeline), typeof(MarkdownViewer), new FrameworkPropertyMetadata(PipelineChanged));
+ ///
+ /// Defines the MarkdownViewer.AnchorName attached property used for in-document linking (e.g. "#my-id")
+ ///
+ public static readonly DependencyProperty AnchorNameProperty = DependencyProperty.RegisterAttached(
+ "AnchorName", typeof(string), typeof(MarkdownViewer), new PropertyMetadata(default(string)));
+
+ public static void SetAnchorName(DependencyObject element, string value)
+ {
+ element.SetValue(AnchorNameProperty, value);
+ }
+
+ public static string GetAnchorName(DependencyObject element)
+ {
+ return (string)element.GetValue(AnchorNameProperty);
+ }
static MarkdownViewer()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MarkdownViewer), new FrameworkPropertyMetadata(typeof(MarkdownViewer)));
}
+ private FlowDocumentScrollViewer? docViewer;
+
///
/// Gets the flow document to display.
///
@@ -79,9 +99,88 @@ private static void PipelineChanged(object sender, DependencyPropertyChangedEven
control.RefreshDocument();
}
+ public MarkdownViewer()
+ {
+ CommandBindings.Add(new CommandBinding(Commands.Navigate, NavigateCommandExecuted));
+ }
+
+ private void NavigateCommandExecuted(object sender, ExecutedRoutedEventArgs e)
+ {
+ string url = e.Parameter?.ToString() ?? "";
+ if (url.Length > 1 && url[0] == '#')
+ {
+ string anchorName = url.Substring(1);
+ e.Handled = NavigateTo(anchorName);
+ }
+ }
+
protected virtual void RefreshDocument()
{
Document = Markdown != null ? Wpf.Markdown.ToFlowDocument(Markdown, Pipeline ?? DefaultPipeline) : null;
}
+
+ public override void OnApplyTemplate()
+ {
+ docViewer = GetTemplateChild("PART_DocViewer") as FlowDocumentScrollViewer;
+
+ base.OnApplyTemplate();
+ }
+
+ public bool NavigateTo(string anchorName)
+ {
+ if (Document == null)
+ throw new InvalidOperationException("No rendered content found");
+
+ foreach (var block in Document.Blocks)
+ {
+ string blockAnchorName = GetAnchorName(block);
+ if (String.Equals(blockAnchorName, anchorName, StringComparison.OrdinalIgnoreCase))
+ {
+ return ScrollIntoView(block);
+ }
+ }
+
+ return false;
+ }
+
+ private bool ScrollIntoView(Block block)
+ {
+ if (docViewer == null)
+ return false;
+ double top = block.ContentStart.GetCharacterRect(LogicalDirection.Forward).Top;
+ var scrollViewer = FindVisualChild(docViewer);
+ if (scrollViewer != null)
+ {
+ scrollViewer.ScrollToVerticalOffset(top);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets a visual child of the specific type.
+ ///
+ /// The type of child to find.
+ /// Where to start the "search".
+ /// The child or null.
+ public static T? FindVisualChild(
+ DependencyObject element) where T : class
+ {
+ if (element is T retVal)
+ return retVal;
+
+ int childCnt = VisualTreeHelper.GetChildrenCount(element);
+ for (int i = 0; i < childCnt; ++i)
+ {
+ DependencyObject child = VisualTreeHelper.GetChild(element, i);
+
+ var result = FindVisualChild(child);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
}
}
diff --git a/src/Markdig.Wpf/Renderers/Wpf/HeadingRenderer.cs b/src/Markdig.Wpf/Renderers/Wpf/HeadingRenderer.cs
index 705ea98..3c823af 100644
--- a/src/Markdig.Wpf/Renderers/Wpf/HeadingRenderer.cs
+++ b/src/Markdig.Wpf/Renderers/Wpf/HeadingRenderer.cs
@@ -5,7 +5,7 @@
using System;
using System.Windows;
using System.Windows.Documents;
-
+using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Wpf;
@@ -36,6 +36,12 @@ protected override void Write(WpfRenderer renderer, HeadingBlock obj)
paragraph.SetResourceReference(FrameworkContentElement.StyleProperty, styleKey);
}
+ var attributes = obj.TryGetAttributes();
+ if (!String.IsNullOrEmpty(attributes?.Id))
+ {
+ MarkdownViewer.SetAnchorName(paragraph, attributes.Id);
+ }
+
renderer.Push(paragraph);
renderer.WriteLeafInline(obj);
renderer.Pop();
diff --git a/src/Markdig.Wpf/Renderers/Wpf/Inlines/LinkInlineRenderer.cs b/src/Markdig.Wpf/Renderers/Wpf/Inlines/LinkInlineRenderer.cs
index eaf6d67..0d8ee3c 100644
--- a/src/Markdig.Wpf/Renderers/Wpf/Inlines/LinkInlineRenderer.cs
+++ b/src/Markdig.Wpf/Renderers/Wpf/Inlines/LinkInlineRenderer.cs
@@ -27,7 +27,7 @@ protected override void Write(WpfRenderer renderer, LinkInline link)
var url = link.GetDynamicUrl != null ? link.GetDynamicUrl() ?? link.Url : link.Url;
- if (!Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
+ if (!Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out _))
{
url = "#";
}
@@ -53,12 +53,11 @@ protected override void Write(WpfRenderer renderer, LinkInline link)
{
var hyperlink = new Hyperlink
{
- Command = Commands.Hyperlink,
+ Command = url.StartsWith("#") ? Commands.Navigate : Commands.Hyperlink,
CommandParameter = url,
NavigateUri = new Uri(url, UriKind.RelativeOrAbsolute),
ToolTip = !string.IsNullOrEmpty(link.Title) ? link.Title : null,
};
-
hyperlink.SetResourceReference(FrameworkContentElement.StyleProperty, Styles.HyperlinkStyleKey);
renderer.Push(hyperlink);
diff --git a/src/Markdig.Wpf/Themes/generic.xaml b/src/Markdig.Wpf/Themes/generic.xaml
index b40f68a..86c6f8f 100644
--- a/src/Markdig.Wpf/Themes/generic.xaml
+++ b/src/Markdig.Wpf/Themes/generic.xaml
@@ -99,6 +99,7 @@