Skip to content

Commit

Permalink
Adds some link generation overloads to handle query and fragment
Browse files Browse the repository at this point in the history
  • Loading branch information
daveaglick committed Feb 24, 2021
1 parent f5aa828 commit 0391a88
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 19 deletions.
2 changes: 1 addition & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- Added an optional `makeAbsolute` parameter to `LinkGenerator.GetLink()` that allows keeping links as relative (#170).
- Fixed fragment support in the Markdown link rewriter (#170, #175, thanks @JoshClose).
- Fixed `LinkGenerator` behavior when using query and/or fragment components (#170).
- Added `makeAbsolute` parameters to `IExecutionState.GetLink()` extension methods as appropriate (#170).
- Added `makeAbsolute` parameters to `IExecutionState.GetLink()` extension methods and others as appropriate (#170).

# 1.0.0-beta.36

Expand Down
21 changes: 21 additions & 0 deletions src/core/Statiq.Common/Documents/IDocumentGetLinkExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,26 @@ public static class IDocumentGetLinkExtensions
/// </returns>
public static string GetLink(this IDocument document, bool includeHost = false) =>
IExecutionContext.Current.GetLink(document, includeHost);

/// <summary>
/// Gets a link for the specified document using the document destination.
/// Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="document">The document to generate a link for.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the document path. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
/// </param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static string GetLink(this IDocument document, string queryAndFragment, bool includeHost = false) =>
IExecutionContext.Current.GetLink(document, queryAndFragment, includeHost);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public static string GetLink(this IExecutionState executionState) =>
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <remarks>
/// To add a query and/or fragment to the document link, use <see cref="IDocumentGetLinkExtensions.GetLink(IDocument, string, bool)"/>.
/// </remarks>
/// <param name="executionState">The execution context.</param>
/// <param name="document">The document to generate a link for.</param>
/// <param name="includeHost">
Expand Down Expand Up @@ -59,19 +62,42 @@ public static string GetLink(
this IExecutionState executionState,
IMetadata metadata,
string key,
bool includeHost = false) =>
executionState.GetLink(metadata, key, null, includeHost);

/// <summary>
/// Gets a link for the specified metadata using the specified metadata value and the default settings from the
/// configuration. This version should be used inside modules to ensure
/// consistent link generation. Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="executionState">The execution state.</param>
/// <param name="metadata">The metadata or document to generate a link for.</param>
/// <param name="key">The key at which a <see cref="NormalizedPath"/> can be found for generating the link.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the URL from the metadata value. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
/// </param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static string GetLink(
this IExecutionState executionState,
IMetadata metadata,
string key,
string queryAndFragment,
bool includeHost = false)
{
if (metadata?.ContainsKey(key) == true)
{
// Return the actual URI if it's absolute
if (LinkGenerator.TryGetAbsoluteHttpUri(metadata.GetString(key), out string absoluteUri))
{
return absoluteUri;
}

// Otherwise try to process the path
NormalizedPath path = metadata.GetPath(key);
return path.IsNull ? null : executionState.GetLink(path, includeHost);
string path = metadata.GetString(key);
return path is null ? null : executionState.GetLink(LinkGenerator.AddQueryAndFragment(path, queryAndFragment), includeHost);
}
return null;
}
Expand Down
46 changes: 46 additions & 0 deletions src/core/Statiq.Common/Util/LinkGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,5 +170,51 @@ public static bool TryGetAbsoluteHttpUri(string str, out string absoluteUri)
absoluteUri = null;
return false;
}

/// <summary>
/// Adds a query and/or fragment to a URL or path.
/// </summary>
/// <param name="path">The path or URL.</param>
/// <param name="queryAndFragment">
/// The query and/or fragment to add. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <returns>The path or URL with an appended query and/or fragment.</returns>
public static string AddQueryAndFragment(string path, string queryAndFragment)
{
if (!string.IsNullOrEmpty(queryAndFragment))
{
// If we have a query and fragment, make sure it starts with ? or #
if (queryAndFragment[0] == '?' || queryAndFragment[0] == '#')
{
return path + queryAndFragment;
}
return path + "?" + queryAndFragment;
}
return path;
}

/// <summary>
/// Adds a query and/or fragment to a path.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="queryAndFragment">
/// The query and/or fragment to add. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <returns>The path with an appended query and/or fragment.</returns>
public static NormalizedPath AddQueryAndFragment(NormalizedPath path, string queryAndFragment)
{
if (!string.IsNullOrEmpty(queryAndFragment))
{
// If we have a query and fragment, make sure it starts with ? or #
if (queryAndFragment[0] == '?' || queryAndFragment[0] == '#')
{
return new NormalizedPath(path.FullPath + queryAndFragment);
}
return new NormalizedPath(path.FullPath + "?" + queryAndFragment);
}
return path;
}
}
}
143 changes: 133 additions & 10 deletions src/extensions/Statiq.Razor/IHtmlHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument document, bool includeHost, IDictionary<object, object> htmlAttributes)
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
bool includeHost,
IDictionary<object, object> htmlAttributes)
{
document.ThrowIfNull(nameof(document));
return htmlHelper.DocumentLink(document, document.GetTitle(), includeHost, htmlAttributes);
Expand All @@ -88,7 +92,7 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor.</param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
Expand All @@ -103,12 +107,39 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the document path. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string queryAndFragment,
string linkText) =>
htmlHelper.DocumentLink(document, queryAndFragment, linkText, false);

/// <summary>
/// Gets an anchor HTML element for the specified document using the document destination.
/// Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="htmlAttributes">HTML attributes to add to the link.</param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument document, string linkText, IDictionary<object, object> htmlAttributes) =>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string linkText,
IDictionary<object, object> htmlAttributes) =>
htmlHelper.DocumentLink(document, linkText, false, htmlAttributes);

/// <summary>
Expand All @@ -119,15 +150,44 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the document path. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="htmlAttributes">HTML attributes to add to the link.</param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string queryAndFragment,
string linkText,
IDictionary<object, object> htmlAttributes) =>
htmlHelper.DocumentLink(document, queryAndFragment, linkText, false, htmlAttributes);

/// <summary>
/// Gets an anchor HTML element for the specified document using the document destination.
/// Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
/// </param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument document, string linkText, bool includeHost) =>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string linkText,
bool includeHost) =>
htmlHelper.DocumentLink(document, linkText, includeHost, null);

/// <summary>
Expand All @@ -138,7 +198,64 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the document path. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
/// </param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string queryAndFragment,
string linkText,
bool includeHost) =>
htmlHelper.DocumentLink(document, queryAndFragment, linkText, includeHost, null);

/// <summary>
/// Gets an anchor HTML element for the specified document using the document destination.
/// Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
/// </param>
/// <param name="htmlAttributes">HTML attributes to add to the link.</param>
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string linkText,
bool includeHost,
IDictionary<object, object> htmlAttributes) =>
htmlHelper.DocumentLink(document, null, linkText, includeHost, htmlAttributes);

/// <summary>
/// Gets an anchor HTML element for the specified document using the document destination.
/// Note that you can optionally include the host or not depending
/// on if you want to generate host-specific links. By default, the host is not included so that
/// sites work the same on any server including the preview server.
/// </summary>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="document">The document to generate an anchor element for.</param>
/// <param name="queryAndFragment">
/// Appends a query and/or fragment to the document path. If a value is provided for this parameter
/// and it does not start with "?" or "#" then it will be assumed a query and a "?" will be prefixed.
/// </param>
/// <param name="linkText">The title to use for the anchor, or null to use the document title.</param>
/// <param name="includeHost">
/// If set to <c>true</c> the host configured in the output settings will
/// be included in the link, otherwise the host will be omitted and only the root path will be included (default).
Expand All @@ -147,18 +264,24 @@ public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument d
/// <returns>
/// A string representation of the path suitable for a web link.
/// </returns>
public static IHtmlContent DocumentLink(this IHtmlHelper htmlHelper, IDocument document, string linkText, bool includeHost, IDictionary<object, object> htmlAttributes)
public static IHtmlContent DocumentLink(
this IHtmlHelper htmlHelper,
IDocument document,
string queryAndFragment,
string linkText,
bool includeHost,
IDictionary<object, object> htmlAttributes)
{
htmlHelper.ThrowIfNull(nameof(htmlHelper));
document.ThrowIfNull(nameof(document));

TagBuilder tagBuilder = new TagBuilder("a");
tagBuilder.InnerHtml.SetContent(linkText);
tagBuilder.InnerHtml.SetContent(linkText ?? document.GetTitle());
if (htmlAttributes is object)
{
tagBuilder.MergeAttributes(htmlAttributes);
}
tagBuilder.MergeAttribute("href", IExecutionContext.Current.GetLink(document, includeHost));
tagBuilder.MergeAttribute("href", IExecutionContext.Current.GetLink(document, queryAndFragment, includeHost));
return tagBuilder;
}
}
Expand Down

0 comments on commit 0391a88

Please sign in to comment.