Headings and links

edit

Heading identifiers

UseAutoIdentifiers gives each heading an id computed from its text, so that a URL can link to a section (page.html#getting-started). A heading with the same text as a previous one gets a number. A link whose text is the text of a heading links to that heading, without a URL:

var pipeline = new MarkdownPipelineBuilder()
    .UseAutoIdentifiers()
    .Build();

var html = MarkdownConverter.ToHtml("# Getting started\n\n## Notes\n\n## Notes\n\nSee [Getting started].", pipeline);
Assert.Equal("""
    <h1 id="getting-started">Getting started</h1>
    <h2 id="notes">Notes</h2>
    <h2 id="notes-1">Notes</h2>
    <p>See <a href="#getting-started">Getting started</a>.</p>

    """.ReplaceLineEndings("\n"), html);

A {#id} attribute replaces the computed identifier.

The AutoIdentifierOptions flags (namespace Meziantou.Framework.Markdown.Extensions.AutoIdentifiers) change how the identifier is computed:

OptionEffect
DefaultAutoLink and AllowOnlyAscii
AutoLinkLinks [Heading text] to the heading
AllowOnlyAsciiRemoves the accents and the other non-ASCII characters (Été becomes ete, 日本語 text becomes text)
GitHubComputes the identifiers as GitHub does, which keeps the non-ASCII letters
NoneNone of the above

Use GitHub when the documents are also displayed on GitHub, so that the links to their sections work on both:

var markdown = "# Été 2024!";

var defaultPipeline = new MarkdownPipelineBuilder().UseAutoIdentifiers().Build();
Assert.Equal("<h1 id=\"ete-2024\">Été 2024!</h1>\n", MarkdownConverter.ToHtml(markdown, defaultPipeline));

var gitHubPipeline = new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub).Build();
Assert.Equal("<h1 id=\"été-2024\">Été 2024!</h1>\n", MarkdownConverter.ToHtml(markdown, gitHubPipeline));

UseAdvancedExtensions enables this extension with the default options. To use other options with it, call UseAutoIdentifiers first; see Enable the common extensions at once.

CommonMark only links URLs written between < and >. UseAutoLinks also links the text that starts with http://, https://, ftp://, mailto: or www.. A www. link gets the http:// scheme, or https:// with UseHttpsForWWWLinks:

var markdown = "Visit www.example.com or https://example.org.";

var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
Assert.Equal("<p>Visit <a href=\"http://www.example.com\">www.example.com</a> or <a href=\"https://example.org\">https://example.org</a>.</p>\n", MarkdownConverter.ToHtml(markdown, pipeline));

var httpsPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions { UseHttpsForWWWLinks = true }).Build();
Assert.Equal("<p>Visit <a href=\"https://www.example.com\">www.example.com</a> or <a href=\"https://example.org\">https://example.org</a>.</p>\n", MarkdownConverter.ToHtml(markdown, httpsPipeline));

The options of AutoLinkOptions (namespace Meziantou.Framework.Markdown.Extensions.AutoLinks):

OptionDefaultEffect
UseHttpsForWWWLinksfalsePrefixes www. links with https:// instead of http://
AllowDomainWithoutPeriodfalseLinks domains without a period, such as https://localhost
OpenInNewWindowfalseAdds target="_blank" to the links
ValidPreviousCharacters*_~(The characters, other than whitespace, that can precede a link

Rel attributes

UseReferralLinks adds rel values to every link, for instance to tell search engines not to follow the links of user content, or to keep the linked pages from accessing yours:

var pipeline = new MarkdownPipelineBuilder()
    .UseReferralLinks("nofollow", "noopener", "noreferrer")
    .Build();

var html = MarkdownConverter.ToHtml("[Example](https://example.com)", pipeline);
Assert.Equal("<p><a href=\"https://example.com\" rel=\"nofollow noopener noreferrer\">Example</a></p>\n", html);

UseJiraLinks links the references to Jira issues, such as PROJ-123. The project key is made of uppercase ASCII letters and digits and starts with a letter; it is followed by - and the issue number. The reference must be preceded by a space, a ( or the start of the text, and followed by a space, a ) or the end of the text:

var pipeline = new MarkdownPipelineBuilder()
    .UseJiraLinks(new JiraLinkOptions("https://jira.example.com"))
    .Build();

var html = MarkdownConverter.ToHtml("Fixed by PROJ-123 (see also PROJ-99)", pipeline);
Assert.Equal("<p>Fixed by <a href=\"https://jira.example.com/browse/PROJ-123\" target=\"_blank\">PROJ-123</a> (see also <a href=\"https://jira.example.com/browse/PROJ-99\" target=\"_blank\">PROJ-99</a>)</p>\n", html);

The URL is BaseUrl, then BasePath (/browse by default), then the reference. Jira links open in a new window (target="_blank") unless OpenInNewWindow is false.

Embedded media

UseMediaLinks turns the images whose URL is a video or an audio file into <video> and <audio> players, and the images that link to YouTube, Vimeo, Yandex Music or Odnoklassniki into the player of the site, in an <iframe>:

var pipeline = new MarkdownPipelineBuilder()
    .UseMediaLinks(new MediaOptions { Width = "640", Height = "360" })
    .Build();

var video = MarkdownConverter.ToHtml("![Demo](https://example.com/demo.mp4)", pipeline);
Assert.Equal("<p><video width=\"640\" height=\"360\" controls=\"\"><source type=\"video/mp4\" src=\"https://example.com/demo.mp4\"></source></video></p>\n", video);

var youTube = MarkdownConverter.ToHtml("![Talk](https://www.youtube.com/watch?v=dQw4w9WgXcQ)", pipeline);
Assert.Equal("<p><iframe src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" class=\"youtube\" width=\"640\" height=\"360\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", youTube);

The options of MediaOptions (namespace Meziantou.Framework.Markdown.Extensions.MediaLinks):

OptionDefaultEffect
Width, Height500, 281The size of the players
AddControlsPropertytrueAdds the controls attribute to <video> and <audio>
ClassThe class of the players
ExtensionToMimeTypeCommon audio and video formatsThe file extensions that are played, and their MIME type
HostsThe sites aboveThe sites whose links become an <iframe>; HostProviderBuilder.Create creates one for another site

Non-ASCII URLs

By default, the non-ASCII characters of URLs are percent-encoded. UseNonAsciiNoEscape writes them as they are:

var markdown = "[Summer](https://example.com/été)";

Assert.Equal("<p><a href=\"https://example.com/%C3%A9t%C3%A9\">Summer</a></p>\n", MarkdownConverter.ToHtml(markdown));

var pipeline = new MarkdownPipelineBuilder().UseNonAsciiNoEscape().Build();
Assert.Equal("<p><a href=\"https://example.com/été\">Summer</a></p>\n", MarkdownConverter.ToHtml(markdown, pipeline));