mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-09 13:56:56 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5528023158 | ||
|
|
f93b9d79d9 | ||
|
|
d53fd0e870 | ||
|
|
c488aca96c | ||
|
|
9b3f442765 | ||
|
|
7b6d659bbd | ||
|
|
bc8ba4fecb | ||
|
|
d87bb7292d | ||
|
|
118d28f886 | ||
|
|
3e0c72f043 | ||
|
|
f2590e7b80 | ||
|
|
d1233ffe66 | ||
|
|
ab8e85b06e | ||
|
|
90bc15c016 | ||
|
|
7f604bef30 | ||
|
|
54783b8f65 | ||
|
|
ad0770a594 | ||
|
|
90365bfeee | ||
|
|
c35f7fff17 | ||
|
|
fdaef77474 | ||
|
|
733c028311 | ||
|
|
bc41b0c2a3 | ||
|
|
a8de2087d8 | ||
|
|
2cff6c5194 | ||
|
|
5e4a917dbd | ||
|
|
aff8a6823a | ||
|
|
b8a3c270cc | ||
|
|
68659f4037 | ||
|
|
e92a8097d0 | ||
|
|
57fad6fc1a | ||
|
|
260f4d5acc | ||
|
|
102d02a6c1 | ||
|
|
5ae8ab7a74 | ||
|
|
eb28f76588 | ||
|
|
d0311b4cea | ||
|
|
a11899a350 | ||
|
|
40781737c3 | ||
|
|
455f8f333d | ||
|
|
98a060f2a3 | ||
|
|
49cf59b819 | ||
|
|
310a55c724 | ||
|
|
f734e91568 | ||
|
|
090e6d791a | ||
|
|
41bdb0f0ab | ||
|
|
b27ef11240 | ||
|
|
dfa2c94b88 | ||
|
|
89330f3524 | ||
|
|
1a1bbecc46 | ||
|
|
68bd3074b2 | ||
|
|
e486903687 | ||
|
|
8e22754db4 | ||
|
|
93d88ab994 | ||
|
|
000393f46a | ||
|
|
a5796890e1 | ||
|
|
c19ba5b0eb | ||
|
|
03390e4f71 | ||
|
|
42bd65caaf | ||
|
|
b7ae04bdba | ||
|
|
391f376fa2 | ||
|
|
f9e96bc9c9 | ||
|
|
c75a11ec32 | ||
|
|
fd226d53e9 | ||
|
|
7132584996 | ||
|
|
f48331d6c7 | ||
|
|
6549d3b726 | ||
|
|
d434f00355 | ||
|
|
b62a12d32d | ||
|
|
3c7edaa82d | ||
|
|
fcb56fb037 | ||
|
|
50bc6cadfc | ||
|
|
201aa4ef73 |
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@@ -11,24 +11,11 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install .NET 6.0, and 8.0
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
|
||||
- name: Build, Test, Pack, Publish
|
||||
shell: bash
|
||||
run: |
|
||||
dotnet tool install -g dotnet-releaser --configfile .github/workflows/nuget_org_only.config
|
||||
dotnet-releaser run --nuget-token "${{secrets.NUGET_TOKEN}}" --github-token "${{secrets.GITHUB_TOKEN}}" src/dotnet-releaser.toml
|
||||
uses: xoofx/.github/.github/workflows/dotnet.yml@main
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0
|
||||
8.0
|
||||
9.0
|
||||
secrets:
|
||||
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Extensions and Parsers
|
||||
|
||||
Markdig was [implemented in such a way](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
|
||||
Markdig was [implemented in such a way](http://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
|
||||
|
||||
The basic mechanism for extension of Markdig is the `IMarkdownExtension` interface, which allows any implementing class to be registered with the pipeline builder and thus to directly modify the collections of `BlockParser` and `InlineParser` objects which end up in the pipeline.
|
||||
|
||||
|
||||
16
readme.md
16
readme.md
@@ -1,4 +1,4 @@
|
||||
# Markdig [](https://github.com/lunet-io/markdig/actions) [](https://coveralls.io/github/xoofx/markdig?branch=master) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
# Markdig [](https://github.com/xoofx/markdig/actions/workflows/ci.yml) [](https://coveralls.io/github/xoofx/markdig?branch=master) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
|
||||
<img align="right" width="160px" height="160px" src="img/markdig.png">
|
||||
|
||||
@@ -48,7 +48,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
|
||||
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
|
||||
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
|
||||
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
|
||||
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
|
||||
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://mermaid.js.org) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
|
||||
- [**YAML Front Matter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the front matter and to discard it from the HTML output (typically used for previewing without the front matter in MarkdownEditor)
|
||||
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
|
||||
- Starting with Markdig version `0.20.0+`, Markdig is compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
|
||||
@@ -70,7 +70,7 @@ If you are looking for support for an old .NET Framework 3.5 or 4.0, you can dow
|
||||
|
||||
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
|
||||
|
||||
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
|
||||
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
|
||||
|
||||
## Download
|
||||
|
||||
@@ -144,12 +144,12 @@ AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
|
||||
- Markdig is roughly **x100 times faster than MarkdownSharp**
|
||||
- **20% faster than the reference cmark C implementation**
|
||||
|
||||
## Sponsors
|
||||
|
||||
## Donate
|
||||
Supports this project with a monthly donation and help me continue improving it. \[[Become a sponsor](https://github.com/sponsors/xoofx)\]
|
||||
|
||||
If you are using this library and find it useful for your project, please consider a donation for it!
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
[<img src="https://github.com/lilith.png?size=200" width="64px;" style="border-radius: 50%" alt="lilith"/>](https://github.com/lilith) Lilith River, author of [Imageflow Server, an easy on-demand
|
||||
image editing, optimization, and delivery server](https://github.com/imazen/imageflow-server)
|
||||
|
||||
## Credits
|
||||
|
||||
@@ -164,4 +164,4 @@ Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markd
|
||||
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
|
||||
## Author
|
||||
|
||||
Alexandre MUTEL aka [xoofx](http://xoofx.com)
|
||||
Alexandre MUTEL aka [xoofx](http://xoofx.github.io)
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
<StartupObject>Markdig.Tests.Program</StartupObject>
|
||||
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.dll</SpecExecutable>
|
||||
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net8.0\SpecFileGen.timestamp</SpecTimestamp>
|
||||
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll</SpecExecutable>
|
||||
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp</SpecTimestamp>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="NUnit" Version="4.0.1" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -317,4 +317,73 @@ $$
|
||||
Assert.That(paragraph.Inline.Span.Start == paragraph.Inline.FirstChild.Span.Start);
|
||||
Assert.That(paragraph.Inline.Span.End == paragraph.Inline.LastChild.Span.End);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGridTableShortLine()
|
||||
{
|
||||
var input = @"
|
||||
+--+
|
||||
| |
|
||||
+-";
|
||||
|
||||
var expected = @"<table>
|
||||
<col style=""width:100%"" />
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
";
|
||||
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefinitionListInListItemWithBlankLine()
|
||||
{
|
||||
var input = @"
|
||||
-
|
||||
|
||||
term
|
||||
: definition
|
||||
";
|
||||
|
||||
var expected = @"<ul>
|
||||
<li>
|
||||
<dl>
|
||||
<dt>term</dt>
|
||||
<dd>definition</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
";
|
||||
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseDefinitionLists().Build());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlertWithinAlertOrNestedBlock()
|
||||
{
|
||||
var input = @"
|
||||
>[!NOTE]
|
||||
[!NOTE]
|
||||
The second one is not a note.
|
||||
|
||||
>>[!NOTE]
|
||||
Also not a note.
|
||||
";
|
||||
|
||||
var expected = @"<div class=""markdown-alert markdown-alert-note"">
|
||||
<p class=""markdown-alert-title""><svg viewBox=""0 0 16 16"" version=""1.1"" width=""16"" height=""16"" aria-hidden=""true""><path d=""M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z""></path></svg>Note</p>
|
||||
<p>[!NOTE]
|
||||
The second one is not a note.</p>
|
||||
</div>
|
||||
<blockquote>
|
||||
<blockquote>
|
||||
<p>[!NOTE]
|
||||
Also not a note.</p>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
";
|
||||
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build());
|
||||
}
|
||||
}
|
||||
|
||||
179
src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs
Normal file
179
src/Markdig.Tests/Specs/AlertBlockSpecs.generated.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
|
||||
// --------------------------------
|
||||
// Alert Blocks
|
||||
// --------------------------------
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests.Specs.AlertBlocks
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestExtensionsAlertBlocks
|
||||
{
|
||||
// # Extensions
|
||||
//
|
||||
// This section describes the different extensions supported:
|
||||
//
|
||||
// ## Alert Blocks
|
||||
//
|
||||
// This is supporting the [GitHub Alert blocks](https://github.com/orgs/community/discussions/16925)
|
||||
[Test]
|
||||
public void ExtensionsAlertBlocks_Example001()
|
||||
{
|
||||
// Example 1
|
||||
// Section: Extensions / Alert Blocks
|
||||
//
|
||||
// The following Markdown:
|
||||
// > [!NOTE]
|
||||
// > Highlights information that users should take into account, even when skimming.
|
||||
//
|
||||
// > [!TIP]
|
||||
// > Optional information to help a user be more successful.
|
||||
//
|
||||
// > [!IMPORTANT]
|
||||
// > Crucial information necessary for users to succeed.
|
||||
//
|
||||
// > [!WARNING]
|
||||
// > Critical content demanding immediate user attention due to potential risks.
|
||||
//
|
||||
// > [!CAUTION]
|
||||
// > Negative potential consequences of an action.
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <div class="markdown-alert markdown-alert-note">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
// <p>Highlights information that users should take into account, even when skimming.</p>
|
||||
// </div>
|
||||
// <div class="markdown-alert markdown-alert-tip">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>Tip</p>
|
||||
// <p>Optional information to help a user be more successful.</p>
|
||||
// </div>
|
||||
// <div class="markdown-alert markdown-alert-important">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
|
||||
// <p>Crucial information necessary for users to succeed.</p>
|
||||
// </div>
|
||||
// <div class="markdown-alert markdown-alert-warning">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p>
|
||||
// <p>Critical content demanding immediate user attention due to potential risks.</p>
|
||||
// </div>
|
||||
// <div class="markdown-alert markdown-alert-caution">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Caution</p>
|
||||
// <p>Negative potential consequences of an action.</p>
|
||||
// </div>
|
||||
|
||||
TestParser.TestSpec("> [!NOTE] \n> Highlights information that users should take into account, even when skimming.\n\n> [!TIP]\n> Optional information to help a user be more successful.\n\n> [!IMPORTANT] \n> Crucial information necessary for users to succeed.\n\n> [!WARNING] \n> Critical content demanding immediate user attention due to potential risks.\n\n> [!CAUTION]\n> Negative potential consequences of an action.", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-tip\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z\"></path></svg>Tip</p>\n<p>Optional information to help a user be more successful.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-important\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Important</p>\n<p>Crucial information necessary for users to succeed.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-warning\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Warning</p>\n<p>Critical content demanding immediate user attention due to potential risks.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-caution\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Caution</p>\n<p>Negative potential consequences of an action.</p>\n</div>", "advanced", context: "Example 1\nSection Extensions / Alert Blocks\n");
|
||||
}
|
||||
|
||||
// Example with code blocks and mix formatting:
|
||||
[Test]
|
||||
public void ExtensionsAlertBlocks_Example002()
|
||||
{
|
||||
// Example 2
|
||||
// Section: Extensions / Alert Blocks
|
||||
//
|
||||
// The following Markdown:
|
||||
// > [!NOTE]
|
||||
// > Highlights information that users should take into account, even when skimming.
|
||||
// > Testing rendering for multiple lines
|
||||
// > ```csharp
|
||||
// > var test = "I can also add code to panels
|
||||
// > ```
|
||||
// > `Inline code testing`
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <div class="markdown-alert markdown-alert-note">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
// <p>Highlights information that users should take into account, even when skimming.
|
||||
// Testing rendering for multiple lines</p>
|
||||
// <pre><code class="language-csharp">var test = "I can also add code to panels
|
||||
// </code></pre>
|
||||
// <p><code>Inline code testing</code></p>
|
||||
// </div>
|
||||
|
||||
TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> Testing rendering for multiple lines\n> ```csharp\n> var test = \"I can also add code to panels\n> ```\n> `Inline code testing`", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines</p>\n<pre><code class=\"language-csharp\">var test = "I can also add code to panels\n</code></pre>\n<p><code>Inline code testing</code></p>\n</div>", "advanced", context: "Example 2\nSection Extensions / Alert Blocks\n");
|
||||
}
|
||||
|
||||
// Multiline:
|
||||
[Test]
|
||||
public void ExtensionsAlertBlocks_Example003()
|
||||
{
|
||||
// Example 3
|
||||
// Section: Extensions / Alert Blocks
|
||||
//
|
||||
// The following Markdown:
|
||||
// > [!NOTE]
|
||||
// > Highlights information that users should take into account, even when skimming.
|
||||
// >
|
||||
// > Testing rendering for multiple lines
|
||||
// >
|
||||
// > `Inline code testing`
|
||||
// >
|
||||
// > Other line
|
||||
// >
|
||||
// > > Nested quote
|
||||
// > >
|
||||
// > > Final nested quote line
|
||||
// >
|
||||
// > Final line of alert
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <div class="markdown-alert markdown-alert-note">
|
||||
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
// <p>Highlights information that users should take into account, even when skimming.</p>
|
||||
// <p>Testing rendering for multiple lines</p>
|
||||
// <p><code>Inline code testing</code></p>
|
||||
// <p>Other line</p>
|
||||
// <blockquote>
|
||||
// <p>Nested quote</p>
|
||||
// <p>Final nested quote line</p>
|
||||
// </blockquote>
|
||||
// <p>Final line of alert</p>
|
||||
// </div>
|
||||
|
||||
TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> \n> Testing rendering for multiple lines\n> \n> `Inline code testing`\n> \n> Other line\n> \n> > Nested quote\n> >\n> > Final nested quote line\n> \n> Final line of alert", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.</p>\n<p>Testing rendering for multiple lines</p>\n<p><code>Inline code testing</code></p>\n<p>Other line</p>\n<blockquote>\n<p>Nested quote</p>\n<p>Final nested quote line</p>\n</blockquote>\n<p>Final line of alert</p>\n</div>", "advanced", context: "Example 3\nSection Extensions / Alert Blocks\n");
|
||||
}
|
||||
|
||||
// An alert inline (e.g `[!NOTE]`) must come first in a quote block, and must be followed by optional spaces with a new line. If no new lines are found, it will not be considered as an alert block.
|
||||
//
|
||||
// Followed by space and new line:
|
||||
[Test]
|
||||
public void ExtensionsAlertBlocks_Example004()
|
||||
{
|
||||
// Example 4
|
||||
// Section: Extensions / Alert Blocks
|
||||
//
|
||||
// The following Markdown:
|
||||
// > [!NOTE] This is invalid because no new line
|
||||
// > Highlights information that users should take into account, even when skimming.
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <blockquote>
|
||||
// <p>[!NOTE] This is invalid because no new line
|
||||
// Highlights information that users should take into account, even when skimming.</p>
|
||||
// </blockquote>
|
||||
|
||||
TestParser.TestSpec("> [!NOTE] This is invalid because no new line\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 4\nSection Extensions / Alert Blocks\n");
|
||||
}
|
||||
|
||||
// Must come first in a quote block:
|
||||
[Test]
|
||||
public void ExtensionsAlertBlocks_Example005()
|
||||
{
|
||||
// Example 5
|
||||
// Section: Extensions / Alert Blocks
|
||||
//
|
||||
// The following Markdown:
|
||||
// > This is not a [!NOTE]
|
||||
// > Highlights information that users should take into account, even when skimming.
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <blockquote>
|
||||
// <p>This is not a [!NOTE]
|
||||
// Highlights information that users should take into account, even when skimming.</p>
|
||||
// </blockquote>
|
||||
|
||||
TestParser.TestSpec("> This is not a [!NOTE]\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 5\nSection Extensions / Alert Blocks\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/Markdig.Tests/Specs/AlertBlockSpecs.md
Normal file
127
src/Markdig.Tests/Specs/AlertBlockSpecs.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Extensions
|
||||
|
||||
This section describes the different extensions supported:
|
||||
|
||||
## Alert Blocks
|
||||
|
||||
This is supporting the [GitHub Alert blocks](https://github.com/orgs/community/discussions/16925)
|
||||
|
||||
```````````````````````````````` example
|
||||
> [!NOTE]
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
|
||||
> [!TIP]
|
||||
> Optional information to help a user be more successful.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Crucial information necessary for users to succeed.
|
||||
|
||||
> [!WARNING]
|
||||
> Critical content demanding immediate user attention due to potential risks.
|
||||
|
||||
> [!CAUTION]
|
||||
> Negative potential consequences of an action.
|
||||
.
|
||||
<div class="markdown-alert markdown-alert-note">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
<p>Highlights information that users should take into account, even when skimming.</p>
|
||||
</div>
|
||||
<div class="markdown-alert markdown-alert-tip">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>Tip</p>
|
||||
<p>Optional information to help a user be more successful.</p>
|
||||
</div>
|
||||
<div class="markdown-alert markdown-alert-important">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
|
||||
<p>Crucial information necessary for users to succeed.</p>
|
||||
</div>
|
||||
<div class="markdown-alert markdown-alert-warning">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p>
|
||||
<p>Critical content demanding immediate user attention due to potential risks.</p>
|
||||
</div>
|
||||
<div class="markdown-alert markdown-alert-caution">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Caution</p>
|
||||
<p>Negative potential consequences of an action.</p>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
Example with code blocks and mix formatting:
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
> [!NOTE]
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
> Testing rendering for multiple lines
|
||||
> ```csharp
|
||||
> var test = "I can also add code to panels
|
||||
> ```
|
||||
> `Inline code testing`
|
||||
.
|
||||
<div class="markdown-alert markdown-alert-note">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
<p>Highlights information that users should take into account, even when skimming.
|
||||
Testing rendering for multiple lines</p>
|
||||
<pre><code class="language-csharp">var test = "I can also add code to panels
|
||||
</code></pre>
|
||||
<p><code>Inline code testing</code></p>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
Multiline:
|
||||
|
||||
```````````````````````````````` example
|
||||
> [!NOTE]
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
>
|
||||
> Testing rendering for multiple lines
|
||||
>
|
||||
> `Inline code testing`
|
||||
>
|
||||
> Other line
|
||||
>
|
||||
> > Nested quote
|
||||
> >
|
||||
> > Final nested quote line
|
||||
>
|
||||
> Final line of alert
|
||||
.
|
||||
<div class="markdown-alert markdown-alert-note">
|
||||
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
|
||||
<p>Highlights information that users should take into account, even when skimming.</p>
|
||||
<p>Testing rendering for multiple lines</p>
|
||||
<p><code>Inline code testing</code></p>
|
||||
<p>Other line</p>
|
||||
<blockquote>
|
||||
<p>Nested quote</p>
|
||||
<p>Final nested quote line</p>
|
||||
</blockquote>
|
||||
<p>Final line of alert</p>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
An alert inline (e.g `[!NOTE]`) must come first in a quote block, and must be followed by optional spaces with a new line. If no new lines are found, it will not be considered as an alert block.
|
||||
|
||||
Followed by space and new line:
|
||||
|
||||
```````````````````````````````` example
|
||||
> [!NOTE] This is invalid because no new line
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
.
|
||||
<blockquote>
|
||||
<p>[!NOTE] This is invalid because no new line
|
||||
Highlights information that users should take into account, even when skimming.</p>
|
||||
</blockquote>
|
||||
````````````````````````````````
|
||||
|
||||
Must come first in a quote block:
|
||||
|
||||
```````````````````````````````` example
|
||||
> This is not a [!NOTE]
|
||||
> Highlights information that users should take into account, even when skimming.
|
||||
.
|
||||
<blockquote>
|
||||
<p>This is not a [!NOTE]
|
||||
Highlights information that users should take into account, even when skimming.</p>
|
||||
</blockquote>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Markdig.Tests.Specs.Diagrams
|
||||
//
|
||||
// ## Mermaid diagrams
|
||||
//
|
||||
// Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
|
||||
// Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
|
||||
[Test]
|
||||
public void ExtensionsMermaidDiagrams_Example001()
|
||||
{
|
||||
@@ -34,14 +34,14 @@ namespace Markdig.Tests.Specs.Diagrams
|
||||
// ```
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <div class="mermaid">graph TD;
|
||||
// <pre class="mermaid">graph TD;
|
||||
// A-->B;
|
||||
// A-->C;
|
||||
// B-->D;
|
||||
// C-->D;
|
||||
// </div>
|
||||
// </pre>
|
||||
|
||||
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<div class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</div>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
|
||||
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<pre class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</pre>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Adds support for diagrams extension:
|
||||
|
||||
## Mermaid diagrams
|
||||
|
||||
Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
|
||||
Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
|
||||
|
||||
```````````````````````````````` example
|
||||
```mermaid
|
||||
@@ -15,12 +15,12 @@ graph TD;
|
||||
C-->D;
|
||||
```
|
||||
.
|
||||
<div class="mermaid">graph TD;
|
||||
<pre class="mermaid">graph TD;
|
||||
A-->B;
|
||||
A-->C;
|
||||
B-->D;
|
||||
C-->D;
|
||||
</div>
|
||||
</pre>
|
||||
````````````````````````````````
|
||||
|
||||
## nomnoml diagrams
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace Markdig.Tests.Specs.Math
|
||||
{
|
||||
// ## Math Block
|
||||
//
|
||||
// The match block can spawn on multiple lines by having a $$ starting on a line.
|
||||
// The math block can spawn on multiple lines by having a $$ starting on a line.
|
||||
// It is working as a fenced code block.
|
||||
[Test]
|
||||
public void ExtensionsMathBlock_Example017()
|
||||
|
||||
@@ -127,7 +127,7 @@ $$ math $$ starting at a line
|
||||
|
||||
## Math Block
|
||||
|
||||
The match block can spawn on multiple lines by having a $$ starting on a line.
|
||||
The math block can spawn on multiple lines by having a $$ starting on a line.
|
||||
It is working as a fenced code block.
|
||||
|
||||
```````````````````````````````` example
|
||||
|
||||
@@ -81,6 +81,22 @@ public class TestLinkHelper
|
||||
Assert.AreEqual(' ', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleMultiline()
|
||||
{
|
||||
var text = new StringSlice("'this\ris\r\na\ntitle'");
|
||||
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
|
||||
Assert.AreEqual("this\ris\r\na\ntitle", title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleMultilineWithSpaceAndBackslash()
|
||||
{
|
||||
var text = new StringSlice("'a\n\\ \\\ntitle'");
|
||||
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
|
||||
Assert.AreEqual("a\n\\ \\\ntitle", title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUrlAndTitle()
|
||||
{
|
||||
@@ -230,6 +246,13 @@ public class TestLinkHelper
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestlLinkReferenceDefinitionInvalid()
|
||||
{
|
||||
var text = new StringSlice("[foo]: /url (title) x\n");
|
||||
Assert.False(LinkHelper.TryParseLinkReferenceDefinition(ref text, out _, out _, out _, out _, out _, out _));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoLinkUrlSimple()
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ public class TestMediaLinks
|
||||
[Test]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/6BUptHVuvyI\" class=\"youtubeshort\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
@@ -33,7 +34,7 @@ public class TestMediaLinks
|
||||
public void TestBuiltInHosts(string markdown, string expected)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline());
|
||||
Assert.AreEqual(html, expected);
|
||||
Assert.AreEqual(expected, html);
|
||||
}
|
||||
|
||||
[TestCase("",
|
||||
@@ -43,7 +44,7 @@ public class TestMediaLinks
|
||||
public void TestBuiltInHostsWithRelativePaths(string markdown, string expected)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline());
|
||||
Assert.AreEqual(html, expected);
|
||||
Assert.AreEqual(expected, html);
|
||||
}
|
||||
|
||||
private class TestHostProvider : IHostProvider
|
||||
|
||||
@@ -9,6 +9,13 @@ namespace Markdig.Tests;
|
||||
[TestFixture]
|
||||
public class TestPlayParser
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void TestInvalidSetext()
|
||||
{
|
||||
TestParser.TestSpec("test\n===n", "<p>test\n===n</p>", "advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBugWithEmphasisAndTable()
|
||||
{
|
||||
@@ -39,6 +46,14 @@ public class TestPlayParser
|
||||
Assert.AreEqual("/yoyo", link?.Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkWithMultipleBackslashesInTitle()
|
||||
{
|
||||
var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')");
|
||||
var link = doc.Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.AreEqual(@"\\127.0.0.1", link?.Title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBug2()
|
||||
{
|
||||
|
||||
@@ -160,6 +160,17 @@ literal ( 0, 8) 8-8
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasis4()
|
||||
{
|
||||
Check("**foo*", @"
|
||||
paragraph ( 0, 0) 0-5
|
||||
literal ( 0, 0) 0-0
|
||||
emphasis ( 0, 1) 1-5
|
||||
literal ( 0, 2) 2-4
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasisFalse()
|
||||
{
|
||||
@@ -522,13 +533,17 @@ literal ( 1, 6) 8-9
|
||||
[Test]
|
||||
public void TestAbbreviations()
|
||||
{
|
||||
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML", @"
|
||||
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML\r\n\r\nHTML abbreviation at the beginning of a line", @"
|
||||
paragraph ( 2, 0) 38-102
|
||||
container ( 2, 0) 38-102
|
||||
literal ( 2, 0) 38-66
|
||||
abbreviation ( 2,29) 67-70
|
||||
literal ( 2,33) 71-98
|
||||
abbreviation ( 2,61) 99-102
|
||||
paragraph ( 4, 0) 107-150
|
||||
container ( 4, 0) 107-150
|
||||
abbreviation ( 4, 0) 107-110
|
||||
literal ( 4, 4) 111-150
|
||||
", "abbreviations");
|
||||
}
|
||||
|
||||
@@ -698,14 +713,22 @@ literal ( 0, 2) 2-3
|
||||
[Test]
|
||||
public void TestMathematicsInline()
|
||||
{
|
||||
// 01 23456789AB
|
||||
Check("0\n012 $abcd$", @"
|
||||
paragraph ( 0, 0) 0-11
|
||||
// 01 23456789ABCDEF
|
||||
Check("0\n012 $abcd$ 321", @"
|
||||
paragraph ( 0, 0) 0-15
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
literal ( 1, 0) 2-5
|
||||
math ( 1, 4) 6-11
|
||||
attributes ( 0, 0) 0--1
|
||||
literal ( 1,10) 12-15
|
||||
", "mathematics");
|
||||
|
||||
// 012345678
|
||||
Check("$ abcd $", @"
|
||||
paragraph ( 0, 0) 0-7
|
||||
math ( 0, 0) 0-7
|
||||
attributes ( 0, 0) 0--1
|
||||
", "mathematics");
|
||||
}
|
||||
|
||||
@@ -788,6 +811,29 @@ literal ( 4, 2) 13-13
|
||||
", "pipetables");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPipeTable3()
|
||||
{
|
||||
// 01234 5678 9ABCD
|
||||
Check("|a|b\n-|-\n0|1|\n", @"
|
||||
table ( 0, 0) 0-12
|
||||
tablerow ( 0, 1) 1-3
|
||||
tablecell ( 0, 1) 1-1
|
||||
paragraph ( 0, 1) 1-1
|
||||
literal ( 0, 1) 1-1
|
||||
tablecell ( 0, 3) 3-3
|
||||
paragraph ( 0, 3) 3-3
|
||||
literal ( 0, 3) 3-3
|
||||
tablerow ( 2, 0) 9-11
|
||||
tablecell ( 2, 0) 9-9
|
||||
paragraph ( 2, 0) 9-9
|
||||
literal ( 2, 0) 9-9
|
||||
tablecell ( 2, 2) 11-11
|
||||
paragraph ( 2, 2) 11-11
|
||||
literal ( 2, 2) 11-11
|
||||
", "pipetables");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIndentedCode()
|
||||
{
|
||||
|
||||
@@ -107,7 +107,7 @@ public class TestYamlFrontMatterExtension
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Pass("No exception parsing and iterating through YAML front matter block lines");
|
||||
// No exception parsing and iterating through YAML front matter block lines
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public class AbbreviationParser : BlockParser
|
||||
{
|
||||
var literal = (LiteralInline)processor.Inline!;
|
||||
var originalLiteral = literal;
|
||||
var originalSpanEnd = literal.Span.End;
|
||||
|
||||
ContainerInline? container = null;
|
||||
|
||||
@@ -171,7 +172,7 @@ public class AbbreviationParser : BlockParser
|
||||
// Process the remaining literal
|
||||
literal = new LiteralInline()
|
||||
{
|
||||
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
|
||||
Span = new SourceSpan(abbrInline.Span.End + 1, originalSpanEnd),
|
||||
Line = line,
|
||||
Column = column + match.Length,
|
||||
};
|
||||
|
||||
33
src/Markdig/Extensions/Alerts/AlertBlock.cs
Normal file
33
src/Markdig/Extensions/Alerts/AlertBlock.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Alerts;
|
||||
|
||||
/// <summary>
|
||||
/// A block representing an alert quote block.
|
||||
/// </summary>
|
||||
public class AlertBlock : QuoteBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of this block.
|
||||
/// </summary>
|
||||
/// <param name="kind"></param>
|
||||
public AlertBlock(StringSlice kind) : base(null)
|
||||
{
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the kind of the alert block (e.g `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`)
|
||||
/// </summary>
|
||||
public StringSlice Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the trivia space after the kind.
|
||||
/// </summary>
|
||||
public StringSlice TriviaSpaceAfterKind { get; set; }
|
||||
}
|
||||
87
src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs
Normal file
87
src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Alerts;
|
||||
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="AlertBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="HtmlObjectRenderer{AlertBlock}" />
|
||||
public class AlertBlockRenderer : HtmlObjectRenderer<AlertBlock>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of this renderer.
|
||||
/// </summary>
|
||||
public AlertBlockRenderer()
|
||||
{
|
||||
RenderKind = DefaultRenderKind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets of sets a delegate to render the kind of the alert.
|
||||
/// </summary>
|
||||
public Action<HtmlRenderer, StringSlice> RenderKind { get; set; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Write(HtmlRenderer renderer, AlertBlock obj)
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
if (renderer.EnableHtmlForBlock)
|
||||
{
|
||||
renderer.Write("<div");
|
||||
renderer.WriteAttributes(obj);
|
||||
renderer.WriteLine('>');
|
||||
}
|
||||
|
||||
RenderKind(renderer, obj.Kind);
|
||||
|
||||
var savedImplicitParagraph = renderer.ImplicitParagraph;
|
||||
renderer.ImplicitParagraph = false;
|
||||
renderer.WriteChildren(obj);
|
||||
renderer.ImplicitParagraph = savedImplicitParagraph;
|
||||
if (renderer.EnableHtmlForBlock)
|
||||
{
|
||||
renderer.WriteLine("</div>");
|
||||
}
|
||||
|
||||
renderer.EnsureLine();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Renders the kind of the alert.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The HTML renderer.</param>
|
||||
/// <param name="kind">The kind of the alert to render</param>
|
||||
public static void DefaultRenderKind(HtmlRenderer renderer, StringSlice kind)
|
||||
{
|
||||
if (kind.Length >= 16)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Span<char> upperKind = stackalloc char[kind.Length];
|
||||
kind.AsSpan().ToUpperInvariant(upperKind);
|
||||
string? html = upperKind switch
|
||||
{
|
||||
"NOTE" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>",
|
||||
"TIP" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z\"></path></svg>Tip</p>",
|
||||
"IMPORTANT" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Important</p>",
|
||||
"WARNING" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Warning</p>",
|
||||
"CAUTION" => "<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Caution</p>",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (html is not null)
|
||||
{
|
||||
renderer.WriteLine(html);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/Markdig/Extensions/Alerts/AlertExtension.cs
Normal file
44
src/Markdig/Extensions/Alerts/AlertExtension.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers.Inlines;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
|
||||
namespace Markdig.Extensions.Alerts;
|
||||
|
||||
/// <summary>
|
||||
/// Extension for adding alerts to a Markdown pipeline.
|
||||
/// </summary>
|
||||
public class AlertExtension : IMarkdownExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate to render the kind of the alert.
|
||||
/// </summary>
|
||||
public Action<HtmlRenderer, StringSlice>? RenderKind { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
var inlineParser = pipeline.InlineParsers.Find<AlertInlineParser>();
|
||||
if (inlineParser == null)
|
||||
{
|
||||
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new AlertInlineParser());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var blockRenderer = renderer.ObjectRenderers.FindExact<AlertBlockRenderer>();
|
||||
if (blockRenderer == null)
|
||||
{
|
||||
renderer.ObjectRenderers.InsertBefore<QuoteBlockRenderer>(new AlertBlockRenderer()
|
||||
{
|
||||
RenderKind = RenderKind ?? AlertBlockRenderer.DefaultRenderKind
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/Markdig/Extensions/Alerts/AlertInlineParser.cs
Normal file
128
src/Markdig/Extensions/Alerts/AlertInlineParser.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.Alerts;
|
||||
|
||||
/// <summary>
|
||||
/// An inline parser for an alert inline (e.g. `[!NOTE]`).
|
||||
/// </summary>
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class AlertInlineParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AlertInlineParser"/> class.
|
||||
/// </summary>
|
||||
public AlertInlineParser()
|
||||
{
|
||||
OpeningCharacters = ['['];
|
||||
}
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// We expect the alert to be the first child of a quote block. Example:
|
||||
// > [!NOTE]
|
||||
// > This is a note
|
||||
if (processor.Block is not ParagraphBlock paragraphBlock || paragraphBlock.Parent is not QuoteBlock quoteBlock || paragraphBlock.Inline?.FirstChild != null
|
||||
|| quoteBlock is AlertBlock || quoteBlock.Parent is not MarkdownDocument)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var saved = slice;
|
||||
var c = slice.NextChar();
|
||||
if (c != '!')
|
||||
{
|
||||
slice = saved;
|
||||
return false;
|
||||
}
|
||||
|
||||
c = slice.NextChar(); // Skip !
|
||||
|
||||
var start = slice.Start;
|
||||
var end = start;
|
||||
while (c.IsAlpha())
|
||||
{
|
||||
end = slice.Start;
|
||||
c = slice.NextChar();
|
||||
}
|
||||
|
||||
// We need at least one character
|
||||
if (c != ']' || start == end)
|
||||
{
|
||||
slice = saved;
|
||||
return false;
|
||||
}
|
||||
|
||||
var alertType = new StringSlice(slice.Text, start, end);
|
||||
c = slice.NextChar(); // Skip ]
|
||||
|
||||
start = slice.Start;
|
||||
while (true)
|
||||
{
|
||||
if (c == '\0' || c == '\n' || c == '\r')
|
||||
{
|
||||
end = slice.Start;
|
||||
if (c == '\r')
|
||||
{
|
||||
c = slice.NextChar(); // Skip \r
|
||||
if (c == '\0' || c == '\n')
|
||||
{
|
||||
end = slice.Start;
|
||||
if (c == '\n')
|
||||
{
|
||||
slice.NextChar(); // Skip \n
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
slice.NextChar(); // Skip \n
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (!c.IsSpaceOrTab())
|
||||
{
|
||||
slice = saved;
|
||||
return false;
|
||||
}
|
||||
|
||||
c = slice.NextChar();
|
||||
}
|
||||
|
||||
var alertBlock = new AlertBlock(alertType)
|
||||
{
|
||||
Span = quoteBlock.Span,
|
||||
TriviaSpaceAfterKind = new StringSlice(slice.Text, start, end),
|
||||
Line = quoteBlock.Line,
|
||||
Column = quoteBlock.Column,
|
||||
};
|
||||
|
||||
alertBlock.GetAttributes().AddClass("markdown-alert");
|
||||
alertBlock.GetAttributes().AddClass($"markdown-alert-{alertType.ToString().ToLowerInvariant()}");
|
||||
|
||||
// Replace the quote block with the alert block
|
||||
var parentQuoteBlock = quoteBlock.Parent!;
|
||||
var indexOfQuoteBlock = parentQuoteBlock.IndexOf(quoteBlock);
|
||||
parentQuoteBlock[indexOfQuoteBlock] = alertBlock;
|
||||
|
||||
while (quoteBlock.Count > 0)
|
||||
{
|
||||
var block = quoteBlock[0];
|
||||
quoteBlock.RemoveAt(0);
|
||||
alertBlock.Add(block);
|
||||
}
|
||||
|
||||
// Workaround to replace the parent container
|
||||
// Experimental API, so we are keeping it internal for now until we are sure it's the way we want to go
|
||||
processor.ReplaceParentContainer(quoteBlock, alertBlock);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Markdig.Extensions.Alerts;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
@@ -24,10 +26,22 @@ public class BootstrapExtension : IMarkdownExtension
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer is HtmlRenderer htmlRenderer)
|
||||
{
|
||||
var alertRenderer = htmlRenderer.ObjectRenderers.OfType<AlertBlockRenderer>().FirstOrDefault();
|
||||
if (alertRenderer == null)
|
||||
{
|
||||
alertRenderer = new AlertBlockRenderer();
|
||||
renderer.ObjectRenderers.InsertBefore<QuoteBlockRenderer>(new AlertBlockRenderer());
|
||||
}
|
||||
|
||||
alertRenderer.RenderKind = (_, _) => { };
|
||||
}
|
||||
}
|
||||
|
||||
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
|
||||
{
|
||||
Span<char> upperKind = new char[16];
|
||||
foreach (var node in document.Descendants())
|
||||
{
|
||||
if (node.IsInline)
|
||||
@@ -43,6 +57,28 @@ public class BootstrapExtension : IMarkdownExtension
|
||||
{
|
||||
node.GetAttributes().AddClass("table");
|
||||
}
|
||||
else if (node is AlertBlock alertBlock) // Needs to be before QuoteBlock
|
||||
{
|
||||
var attributes = node.GetAttributes();
|
||||
attributes.AddClass("alert");
|
||||
attributes.AddProperty("role", "alert");
|
||||
if (alertBlock.Kind.Length <= upperKind.Length)
|
||||
{
|
||||
alertBlock.Kind.AsSpan().ToUpperInvariant(upperKind);
|
||||
attributes.AddClass(upperKind.Slice(0, alertBlock.Kind.Length) switch
|
||||
{
|
||||
"NOTE" => "alert-primary",
|
||||
"TIP" => "alert-success",
|
||||
"IMPORTANT" => "alert-info",
|
||||
"WARNING" => "alert-warning",
|
||||
"CAUTION" => "alert-danger",
|
||||
_ => "alert-dark",
|
||||
});
|
||||
}
|
||||
|
||||
var lastParagraph = alertBlock.Descendants().OfType<ParagraphBlock>().LastOrDefault();
|
||||
lastParagraph?.GetAttributes().AddClass("mb-0");
|
||||
}
|
||||
else if (node is QuoteBlock)
|
||||
{
|
||||
node.GetAttributes().AddClass("blockquote");
|
||||
|
||||
@@ -30,7 +30,11 @@ public class CustomContainerExtension : IMarkdownExtension
|
||||
{
|
||||
if (delimiterCount == 2 && emphasisChar == ':')
|
||||
{
|
||||
return new CustomContainerInline();
|
||||
return new CustomContainerInline
|
||||
{
|
||||
DelimiterChar = ':',
|
||||
DelimiterCount = 2
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -105,13 +105,20 @@ public class DefinitionListParser : BlockParser
|
||||
{
|
||||
var index = previousParent.IndexOf(paragraphBlock) - 1;
|
||||
if (index < 0) return null;
|
||||
var lastBlock = previousParent[index];
|
||||
if (lastBlock is BlankLineBlock)
|
||||
switch (previousParent[index])
|
||||
{
|
||||
lastBlock = previousParent[index - 1];
|
||||
previousParent.RemoveAt(index);
|
||||
case DefinitionList definitionList:
|
||||
return definitionList;
|
||||
|
||||
case BlankLineBlock:
|
||||
if (index > 0 && previousParent[index - 1] is DefinitionList definitionList2)
|
||||
{
|
||||
previousParent.RemoveAt(index);
|
||||
return definitionList2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return lastBlock as DefinitionList;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override BlockState TryContinue(BlockProcessor processor, Block block)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
@@ -22,9 +22,8 @@ public class DiagramExtension : IMarkdownExtension
|
||||
if (renderer is HtmlRenderer htmlRenderer)
|
||||
{
|
||||
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>()!;
|
||||
// TODO: Add other well known diagram languages
|
||||
codeRenderer.BlocksAsDiv.Add("mermaid");
|
||||
codeRenderer.BlockMapping["mermaid"] = "pre";
|
||||
codeRenderer.BlocksAsDiv.Add("nomnoml");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ public class FootnoteParser : BlockParser
|
||||
{
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
Column = processor.Column,
|
||||
Span = new SourceSpan(processor.Start, processor.Line.End),
|
||||
};
|
||||
|
||||
// Maintain a list of all footnotes at document level
|
||||
@@ -74,6 +76,7 @@ public class FootnoteParser : BlockParser
|
||||
{
|
||||
CreateLinkInline = CreateLinkToFootnote,
|
||||
Line = processor.LineIndex,
|
||||
Column = saved,
|
||||
Span = new SourceSpan(start, processor.Start - 2), // account for ]:
|
||||
LabelSpan = labelSpan,
|
||||
Label = label
|
||||
|
||||
@@ -152,7 +152,7 @@ public class MathInlineParser : InlineParser
|
||||
// Create a new MathInline
|
||||
var inline = new MathInline()
|
||||
{
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.End)),
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
|
||||
Line = line,
|
||||
Column = column,
|
||||
Delimiter = match,
|
||||
|
||||
@@ -58,6 +58,7 @@ public class HostProviderBuilder
|
||||
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
|
||||
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["YouTubeShort"] = Create("www.youtube.com", YouTubeShort, iframeClass: "youtubeshort"),
|
||||
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
|
||||
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
|
||||
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
|
||||
@@ -92,6 +93,19 @@ public class HostProviderBuilder
|
||||
);
|
||||
}
|
||||
|
||||
private static string? YouTubeShort(Uri uri)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath;
|
||||
bool isYouTubeShort = uriPath.StartsWith("/shorts/", StringComparison.OrdinalIgnoreCase);
|
||||
if (!isYouTubeShort)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var shortId = uriPath.Substring("/shorts/".Length).Split('?').FirstOrDefault(); // the format might be "/shorts/6BUptHVuvyI?feature=share"
|
||||
return BuildYouTubeIframeUrl(shortId, null);
|
||||
}
|
||||
|
||||
private static string? YouTubeShortened(Uri uri)
|
||||
{
|
||||
return BuildYouTubeIframeUrl(
|
||||
|
||||
@@ -135,6 +135,7 @@ public class GridTableParser : BlockParser
|
||||
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
|
||||
{
|
||||
var lineStart = line.Start;
|
||||
var lineEnd = line.End;
|
||||
isHeaderRow = line.PeekChar() == '=' || line.PeekChar(2) == '=';
|
||||
hasRowSpan = false;
|
||||
foreach (var columnSlice in columns)
|
||||
@@ -142,7 +143,7 @@ public class GridTableParser : BlockParser
|
||||
if (columnSlice.CurrentCell != null)
|
||||
{
|
||||
line.Start = lineStart + columnSlice.Start + 1;
|
||||
line.End = lineStart + columnSlice.End - 1;
|
||||
line.End = Math.Min(lineStart + columnSlice.End - 1, lineEnd);
|
||||
line.Trim();
|
||||
if (line.IsEmptyOrWhitespace() || !IsRowSeparator(line))
|
||||
{
|
||||
@@ -256,7 +257,7 @@ public class GridTableParser : BlockParser
|
||||
{
|
||||
sliceForCell.End = line.Start + columnEnd - 1;
|
||||
}
|
||||
else if (line.PeekCharExtra(line.End) == '|')
|
||||
else if (line.PeekCharExtra(line.End - line.Start) == '|')
|
||||
{
|
||||
sliceForCell.End = line.End - 1;
|
||||
}
|
||||
|
||||
@@ -280,6 +280,8 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
|
||||
tableState.EndOfLines.Add(endOfTable);
|
||||
}
|
||||
|
||||
int lastPipePos = 0;
|
||||
|
||||
// Cell loop
|
||||
// Reconstruct the table from the delimiters
|
||||
TableRow? row = null;
|
||||
@@ -302,6 +304,12 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
|
||||
if (pipeSeparator != null && (delimiter.PreviousSibling is null || delimiter.PreviousSibling is LineBreakInline))
|
||||
{
|
||||
delimiter.Remove();
|
||||
if (table.Span.IsEmpty)
|
||||
{
|
||||
table.Span = delimiter.Span;
|
||||
table.Line = delimiter.Line;
|
||||
table.Column = delimiter.Column;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -354,6 +362,7 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
|
||||
// If the delimiter is a pipe, we need to remove it from the tree
|
||||
// so that previous loop looking for a parent will not go further on subsequent cells
|
||||
delimiter.Remove();
|
||||
lastPipePos = delimiter.Span.End;
|
||||
}
|
||||
|
||||
// We trim whitespace at the beginning and ending of the cell
|
||||
@@ -421,6 +430,11 @@ public class PipeTableParser : InlineParser, IPostInlineProcessor
|
||||
}
|
||||
}
|
||||
|
||||
if (lastPipePos > table.Span.End)
|
||||
{
|
||||
table.UpdateSpanEnd(lastPipePos);
|
||||
}
|
||||
|
||||
// Once we are done with the cells, we can remove all end of lines in the table tree
|
||||
foreach (var endOfLine in tableState.EndOfLines)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ internal sealed class FastStringWriter : TextWriter
|
||||
public override string NewLine
|
||||
{
|
||||
get => _newLine;
|
||||
set => _newLine = value ?? Environment.NewLine;
|
||||
set => base.NewLine = _newLine = value ?? Environment.NewLine;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -545,87 +545,70 @@ public static class LinkHelper
|
||||
enclosingCharacter = c;
|
||||
var closingQuote = c == '(' ? ')' : c;
|
||||
bool hasEscape = false;
|
||||
// -1: undefined
|
||||
// 0: has only spaces
|
||||
// 1: has other characters
|
||||
int hasOnlyWhiteSpacesSinceLastLine = -1;
|
||||
while (true)
|
||||
bool isLineBlank = false; // the first line is never blank
|
||||
while ((c = text.NextChar()) != '\0')
|
||||
{
|
||||
c = text.NextChar();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
|
||||
if (isLineBlank)
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
hasOnlyWhiteSpacesSinceLastLine = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasEscape)
|
||||
{
|
||||
hasEscape = false;
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
|
||||
if (c == '\r' && text.PeekChar() == '\n')
|
||||
{
|
||||
buffer.Append('\n');
|
||||
text.SkipChar();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\0')
|
||||
{
|
||||
break;
|
||||
isLineBlank = true;
|
||||
}
|
||||
|
||||
if (c == closingQuote)
|
||||
else if (hasEscape)
|
||||
{
|
||||
if (hasEscape)
|
||||
hasEscape = false;
|
||||
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append(closingQuote);
|
||||
hasEscape = false;
|
||||
continue;
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
}
|
||||
else if (c == closingQuote)
|
||||
{
|
||||
// Skip last quote
|
||||
text.SkipChar();
|
||||
goto ReturnValid;
|
||||
title = buffer.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
continue;
|
||||
isLineBlank = false;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
if (c.IsSpaceOrTab())
|
||||
else
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine < 0)
|
||||
if (isLineBlank && !c.IsSpaceOrTab())
|
||||
{
|
||||
hasOnlyWhiteSpacesSinceLastLine = 1;
|
||||
isLineBlank = false;
|
||||
}
|
||||
}
|
||||
else if (c != '\n' && c != '\r' && text.PeekChar() != '\n')
|
||||
{
|
||||
hasOnlyWhiteSpacesSinceLastLine = 0;
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
buffer.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Dispose();
|
||||
title = null;
|
||||
return false;
|
||||
|
||||
ReturnValid:
|
||||
title = buffer.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseTitleTrivia<T>(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator
|
||||
@@ -641,87 +624,70 @@ public static class LinkHelper
|
||||
enclosingCharacter = c;
|
||||
var closingQuote = c == '(' ? ')' : c;
|
||||
bool hasEscape = false;
|
||||
// -1: undefined
|
||||
// 0: has only spaces
|
||||
// 1: has other characters
|
||||
int hasOnlyWhiteSpacesSinceLastLine = -1;
|
||||
while (true)
|
||||
bool isLineBlank = false; // the first line is never blank
|
||||
while ((c = text.NextChar()) != '\0')
|
||||
{
|
||||
c = text.NextChar();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
|
||||
if (isLineBlank)
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
hasOnlyWhiteSpacesSinceLastLine = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasEscape)
|
||||
{
|
||||
hasEscape = false;
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
|
||||
if (c == '\r' && text.PeekChar() == '\n')
|
||||
{
|
||||
buffer.Append('\n');
|
||||
text.SkipChar();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == '\0')
|
||||
{
|
||||
break;
|
||||
isLineBlank = true;
|
||||
}
|
||||
|
||||
if (c == closingQuote)
|
||||
else if (hasEscape)
|
||||
{
|
||||
if (hasEscape)
|
||||
hasEscape = false;
|
||||
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append(closingQuote);
|
||||
hasEscape = false;
|
||||
continue;
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
}
|
||||
else if (c == closingQuote)
|
||||
{
|
||||
// Skip last quote
|
||||
text.SkipChar();
|
||||
goto ReturnValid;
|
||||
title = buffer.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
continue;
|
||||
isLineBlank = false;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
if (c.IsSpaceOrTab())
|
||||
else
|
||||
{
|
||||
if (hasOnlyWhiteSpacesSinceLastLine < 0)
|
||||
if (isLineBlank && !c.IsSpaceOrTab())
|
||||
{
|
||||
hasOnlyWhiteSpacesSinceLastLine = 1;
|
||||
isLineBlank = false;
|
||||
}
|
||||
}
|
||||
else if (c != '\n' && c != '\r' && text.PeekChar() != '\n')
|
||||
{
|
||||
hasOnlyWhiteSpacesSinceLastLine = 0;
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
buffer.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer.Dispose();
|
||||
title = null;
|
||||
return false;
|
||||
|
||||
ReturnValid:
|
||||
title = buffer.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseUrl<T>(T text, [NotNullWhen(true)] out string? link) where T : ICharIterator
|
||||
@@ -758,12 +724,15 @@ public static class LinkHelper
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
hasEscape = false;
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
continue;
|
||||
@@ -774,8 +743,6 @@ public static class LinkHelper
|
||||
break;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
buffer.Append(c);
|
||||
|
||||
} while (c != '\0');
|
||||
@@ -814,20 +781,21 @@ public static class LinkHelper
|
||||
|
||||
if (!isAutoLink)
|
||||
{
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
hasEscape = false;
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
}
|
||||
|
||||
if (IsEndOfUri(c, isAutoLink))
|
||||
@@ -905,12 +873,15 @@ public static class LinkHelper
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
hasEscape = false;
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
continue;
|
||||
@@ -921,8 +892,6 @@ public static class LinkHelper
|
||||
break;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
buffer.Append(c);
|
||||
|
||||
} while (c != '\0');
|
||||
@@ -961,20 +930,21 @@ public static class LinkHelper
|
||||
|
||||
if (!isAutoLink)
|
||||
{
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
hasEscape = false;
|
||||
if (!c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
else if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
}
|
||||
|
||||
if (IsEndOfUri(c, isAutoLink))
|
||||
@@ -1159,7 +1129,7 @@ public static class LinkHelper
|
||||
c = text.NextChar();
|
||||
}
|
||||
|
||||
if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n')
|
||||
if (c != '\0' && c != '\n' && c != '\r')
|
||||
{
|
||||
// If we were able to parse the url but the title doesn't end with space,
|
||||
// we are still returning a valid definition
|
||||
@@ -1299,7 +1269,7 @@ public static class LinkHelper
|
||||
c = text.NextChar();
|
||||
}
|
||||
|
||||
if (c != '\0' && c != '\n' && c != '\r' && text.PeekChar() != '\n')
|
||||
if (c != '\0' && c != '\n' && c != '\r')
|
||||
{
|
||||
// If we were able to parse the url but the title doesn't end with space,
|
||||
// we are still returning a valid definition
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0;net8.0</TargetFrameworks>
|
||||
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Extensions.Abbreviations;
|
||||
using Markdig.Extensions.Alerts;
|
||||
using Markdig.Extensions.AutoIdentifiers;
|
||||
using Markdig.Extensions.AutoLinks;
|
||||
using Markdig.Extensions.Bootstrap;
|
||||
@@ -34,6 +35,7 @@ using Markdig.Extensions.Yaml;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Parsers.Inlines;
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig;
|
||||
|
||||
@@ -74,6 +76,7 @@ public static class MarkdownExtensions
|
||||
public static MarkdownPipelineBuilder UseAdvancedExtensions(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
return pipeline
|
||||
.UseAlertBlocks()
|
||||
.UseAbbreviations()
|
||||
.UseAutoIdentifiers()
|
||||
.UseCitations()
|
||||
@@ -94,6 +97,18 @@ public static class MarkdownExtensions
|
||||
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses this extension to enable alert blocks.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="renderKind">Replace the default renderer for the kind with a custom renderer</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseAlertBlocks(this MarkdownPipelineBuilder pipeline, Action<HtmlRenderer, StringSlice>? renderKind = null)
|
||||
{
|
||||
pipeline.Extensions.ReplaceOrAdd<AlertExtension>(new AlertExtension() { RenderKind = renderKind });
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses this extension to enable autolinks from text `http://`, `https://`, `ftp://`, `mailto:`, `www.xxx.yyy`
|
||||
/// </summary>
|
||||
@@ -552,6 +567,9 @@ public static class MarkdownExtensions
|
||||
case "advanced":
|
||||
pipeline.UseAdvancedExtensions();
|
||||
break;
|
||||
case "alerts":
|
||||
pipeline.UseAlertBlocks();
|
||||
break;
|
||||
case "pipetables":
|
||||
pipeline.UsePipeTables();
|
||||
break;
|
||||
|
||||
@@ -29,6 +29,8 @@ public class InlineProcessor
|
||||
private readonly List<StringLineGroup.LineOffset> lineOffsets = [];
|
||||
private int previousSliceOffset;
|
||||
private int previousLineIndexForSliceOffset;
|
||||
internal ContainerBlock? PreviousContainerToReplace;
|
||||
internal ContainerBlock? NewContainerToReplace;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineProcessor" /> class.
|
||||
@@ -203,6 +205,24 @@ public class InlineProcessor
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace a parent container. This method is experimental and should be used with caution.
|
||||
/// </summary>
|
||||
/// <param name="previousParentContainer">The previous parent container to replace</param>
|
||||
/// <param name="newParentContainer">The new parent container</param>
|
||||
/// <exception cref="InvalidOperationException">If a new parent container has been already setup.</exception>
|
||||
internal void ReplaceParentContainer(ContainerBlock previousParentContainer, ContainerBlock newParentContainer)
|
||||
{
|
||||
// Limitation for now, only one parent container can be replaced.
|
||||
if (PreviousContainerToReplace != null)
|
||||
{
|
||||
throw new InvalidOperationException("A block is already being replaced");
|
||||
}
|
||||
|
||||
PreviousContainerToReplace = previousParentContainer;
|
||||
NewContainerToReplace = newParentContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the inline of the specified <see cref="LeafBlock"/>.
|
||||
/// </summary>
|
||||
@@ -211,6 +231,9 @@ public class InlineProcessor
|
||||
{
|
||||
if (leafBlock is null) ThrowHelper.ArgumentNullException_leafBlock();
|
||||
|
||||
PreviousContainerToReplace = null;
|
||||
NewContainerToReplace = null;
|
||||
|
||||
// clear parser states
|
||||
Array.Clear(ParserStates, 0, ParserStates.Length);
|
||||
|
||||
|
||||
@@ -302,14 +302,13 @@ public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
|
||||
var openDelimitercount = openDelimiter.DelimiterCount;
|
||||
var closeDelimitercount = closeDelimiter.DelimiterCount;
|
||||
|
||||
emphasis!.Span.Start = openDelimiter.Span.Start;
|
||||
emphasis!.Span.Start = openDelimiter.Span.Start + openDelimitercount - delimiterDelta;
|
||||
emphasis.Line = openDelimiter.Line;
|
||||
emphasis.Column = openDelimiter.Column;
|
||||
emphasis.Column = openDelimiter.Column + openDelimitercount - delimiterDelta;
|
||||
emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta;
|
||||
|
||||
openDelimiter.Content.Start += delimiterDelta;
|
||||
openDelimiter.Span.Start += delimiterDelta;
|
||||
openDelimiter.Column += delimiterDelta;
|
||||
openDelimiter.Span.End -= delimiterDelta;
|
||||
openDelimiter.Content.End -= delimiterDelta;
|
||||
closeDelimiter.Content.Start += delimiterDelta;
|
||||
closeDelimiter.Span.Start += delimiterDelta;
|
||||
closeDelimiter.Column += delimiterDelta;
|
||||
|
||||
@@ -137,6 +137,9 @@ public class LinkInlineParser : InlineParser
|
||||
if (linkRef.CreateLinkInline != null)
|
||||
{
|
||||
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
|
||||
link.Span = new SourceSpan(parent.Span.Start, endPosition);
|
||||
link.Line = parent.Line;
|
||||
link.Column = parent.Column;
|
||||
}
|
||||
|
||||
// Create a default link if the callback was not found
|
||||
@@ -145,8 +148,8 @@ public class LinkInlineParser : InlineParser
|
||||
// Inline Link
|
||||
var linkInline = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(linkRef.Url),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
Url = HtmlHelper.Unescape(linkRef.Url, removeBackSlash: false),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title, removeBackSlash: false),
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = linkRef.UrlSpan,
|
||||
@@ -256,8 +259,8 @@ public class LinkInlineParser : InlineParser
|
||||
// Inline Link
|
||||
link = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(url),
|
||||
Title = HtmlHelper.Unescape(title),
|
||||
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
|
||||
Title = HtmlHelper.Unescape(title, removeBackSlash: false),
|
||||
IsImage = openParent.IsImage,
|
||||
LabelSpan = openParent.LabelSpan,
|
||||
UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
|
||||
@@ -382,11 +385,11 @@ public class LinkInlineParser : InlineParser
|
||||
return new LinkInline()
|
||||
{
|
||||
TriviaBeforeUrl = wsBeforeLink,
|
||||
Url = HtmlHelper.Unescape(url),
|
||||
Url = HtmlHelper.Unescape(url, removeBackSlash: false),
|
||||
UnescapedUrl = unescapedUrl,
|
||||
UrlHasPointyBrackets = urlHasPointyBrackets,
|
||||
TriviaAfterUrl = wsAfterLink,
|
||||
Title = HtmlHelper.Unescape(title),
|
||||
Title = HtmlHelper.Unescape(title, removeBackSlash: false),
|
||||
UnescapedTitle = unescapedTitle,
|
||||
TitleEnclosingCharacter = titleEnclosingCharacter,
|
||||
TriviaAfterTitle = wsAfterTitle,
|
||||
|
||||
@@ -170,6 +170,40 @@ public static class MarkdownParser
|
||||
if (leafBlock.ProcessInlines)
|
||||
{
|
||||
inlineProcessor.ProcessInlineLeaf(leafBlock);
|
||||
|
||||
// Experimental code to handle a replacement of a parent container
|
||||
// Not satisfied with this code, so we are keeping it internal for now
|
||||
if (inlineProcessor.PreviousContainerToReplace != null)
|
||||
{
|
||||
if (container == inlineProcessor.PreviousContainerToReplace)
|
||||
{
|
||||
item = new ContainerItem(inlineProcessor.NewContainerToReplace!) { Index = item.Index };
|
||||
container = item.Container;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool parentBlockFound = false;
|
||||
for (int i = blockCount - 2; i >= 0; i--)
|
||||
{
|
||||
ref var parentBlock = ref blocks[i];
|
||||
if (parentBlock.Container == inlineProcessor.PreviousContainerToReplace)
|
||||
{
|
||||
parentBlock = new ContainerItem(inlineProcessor.NewContainerToReplace!) { Index = parentBlock.Index };
|
||||
parentBlockFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parentBlockFound)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot find the parent block to replace");
|
||||
}
|
||||
}
|
||||
|
||||
inlineProcessor.PreviousContainerToReplace = null;
|
||||
inlineProcessor.NewContainerToReplace = null;
|
||||
}
|
||||
|
||||
if (leafBlock.RemoveAfterProcessInlines)
|
||||
{
|
||||
container.RemoveAt(item.Index);
|
||||
|
||||
@@ -171,13 +171,9 @@ public class ParagraphBlockParser : BlockParser
|
||||
{
|
||||
count = line.CountAndSkipChar(headingChar);
|
||||
|
||||
if (line.IsEmpty)
|
||||
{
|
||||
return headingChar;
|
||||
}
|
||||
|
||||
while (line.NextChar().IsSpaceOrTab())
|
||||
while (line.CurrentChar.IsSpaceOrTab())
|
||||
{
|
||||
line.NextChar();
|
||||
}
|
||||
|
||||
if (line.IsEmpty)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Parsers;
|
||||
@@ -13,8 +13,6 @@ namespace Markdig.Renderers.Html;
|
||||
/// <seealso cref="HtmlObjectRenderer{CodeBlock}" />
|
||||
public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
|
||||
{
|
||||
private HashSet<string>? _blocksAsDiv;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CodeBlockRenderer"/> class.
|
||||
/// </summary>
|
||||
@@ -25,23 +23,32 @@ public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
|
||||
/// <summary>
|
||||
/// Gets a map of fenced code block infos that should be rendered as div blocks instead of pre/code blocks.
|
||||
/// </summary>
|
||||
public HashSet<string> BlocksAsDiv => _blocksAsDiv ??= new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public HashSet<string> BlocksAsDiv { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a map of custom block mapping to render as custom blocks instead of pre/code blocks.
|
||||
/// For example defining {"mermaid", "pre"} will render a block with info `mermaid` as a `pre` block but without the code HTML element.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> BlockMapping { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected override void Write(HtmlRenderer renderer, CodeBlock obj)
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
|
||||
if (_blocksAsDiv is not null && (obj as FencedCodeBlock)?.Info is string info && _blocksAsDiv.Contains(info))
|
||||
if ((obj as FencedCodeBlock)?.Info is string info && (BlocksAsDiv.Contains(info) || BlockMapping.ContainsKey(info)))
|
||||
{
|
||||
var infoPrefix = (obj.Parser as FencedCodeBlockParser)?.InfoPrefix ??
|
||||
FencedCodeBlockParser.DefaultInfoPrefix;
|
||||
|
||||
var htmlBlock = BlockMapping.TryGetValue(info, out var blockType) ? blockType : "div";
|
||||
|
||||
// We are replacing the HTML attribute `language-mylang` by `mylang` only for a div block
|
||||
// NOTE that we are allocating a closure here
|
||||
|
||||
if (renderer.EnableHtmlForBlock)
|
||||
{
|
||||
renderer.Write("<div")
|
||||
renderer.WriteRaw('<');
|
||||
renderer.Write(htmlBlock)
|
||||
.WriteAttributes(obj.TryGetAttributes(),
|
||||
cls => cls.StartsWith(infoPrefix, StringComparison.Ordinal) ? cls.Substring(infoPrefix.Length) : cls)
|
||||
.WriteRaw('>');
|
||||
@@ -51,7 +58,7 @@ public class CodeBlockRenderer : HtmlObjectRenderer<CodeBlock>
|
||||
|
||||
if (renderer.EnableHtmlForBlock)
|
||||
{
|
||||
renderer.WriteLine("</div>");
|
||||
renderer.Write("</").Write(htmlBlock).WriteLine(">");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -28,15 +28,15 @@ public abstract class MarkdownObjectRenderer<TRenderer, TObject> : IMarkdownObje
|
||||
|
||||
public virtual void Write(RendererBase renderer, MarkdownObject obj)
|
||||
{
|
||||
var htmlRenderer = (TRenderer)renderer;
|
||||
var typedRenderer = (TRenderer)renderer;
|
||||
var typedObj = (TObject)obj;
|
||||
|
||||
if (_tryWriters is not null && TryWrite(htmlRenderer, typedObj))
|
||||
if (_tryWriters is not null && TryWrite(typedRenderer, typedObj))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Write(htmlRenderer, typedObj);
|
||||
Write(typedRenderer, typedObj);
|
||||
}
|
||||
|
||||
private bool TryWrite(TRenderer renderer, TObject obj)
|
||||
|
||||
@@ -19,7 +19,7 @@ public class QuoteBlock : ContainerBlock
|
||||
/// Initializes a new instance of the <see cref="QuoteBlock"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parser">The parser used to create this block.</param>
|
||||
public QuoteBlock(BlockParser parser) : base(parser)
|
||||
public QuoteBlock(BlockParser? parser) : base(parser)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class Program
|
||||
static readonly Spec[] Specs = new[]
|
||||
{
|
||||
new Spec("CommonMarkSpecs", "CommonMark.md", ""),
|
||||
new Spec("Alert Blocks", "AlertBlockSpecs.md", "advanced"),
|
||||
new Spec("Pipe Tables", "PipeTableSpecs.md", "pipetables|advanced"),
|
||||
new Spec("GFM Pipe Tables", "PipeTableGfmSpecs.md", "gfm-pipetables"),
|
||||
new Spec("Footnotes", "FootnotesSpecs.md", "footnotes|advanced"),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"version": "9.0.100",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
This file is licensed under the BSD-Clause 2 license. 
|
||||
See the license.txt file in the project root for more information.</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue"><Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Autolink/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Inlines/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
Reference in New Issue
Block a user