Skip to content

Commit e19ef30

Browse files
Icons (#1413)
* PoC: Icons * Update docs/syntax/icons.md Co-authored-by: Fabrizio Ferri-Benedetti <[email protected]> * Update docs/syntax/icons.md Co-authored-by: Fabrizio Ferri-Benedetti <[email protected]> * Add aria-label * Use output/markdown pattern in docs * Remove `i`-prefix from icon syntax * Fix docs * Fix docs * Naming * Refactor to role instead of inline parser * cleanup * cleanup * Naming * table styling * Simplify test * Remove obsolete surpress message --------- Co-authored-by: Fabrizio Ferri-Benedetti <[email protected]>
1 parent 73f3e30 commit e19ef30

File tree

522 files changed

+3266
-10
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

522 files changed

+3266
-10
lines changed

docs/_docset.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ toc:
8585
- file: example_blocks.md
8686
- file: file_inclusion.md
8787
- file: frontmatter.md
88+
- file: icons.md
8889
- file: images.md
8990
- file: lists.md
9091
- file: line_breaks.md

docs/syntax/icons.md

Lines changed: 580 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@layer components {
2+
.markdown-content {
3+
.icon {
4+
@apply inline-block align-middle;
5+
transform: translateY(-0.05em);
6+
svg {
7+
width: 1em;
8+
height: 1em;
9+
fill: currentColor;
10+
}
11+
}
12+
}
13+
}

src/Elastic.Documentation.Site/Assets/styles.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import './markdown/list.css';
88
@import './markdown/tabs.css';
99
@import './markdown/code.css';
10+
@import './markdown/icons.css';
1011
@import './copybutton.css';
1112
@import './markdown/admonition.css';
1213
@import './markdown/dropdown.css';

src/Elastic.Markdown/Elastic.Markdown.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@
3636
<ProjectReference Include="..\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj" />
3737
</ItemGroup>
3838

39+
<ItemGroup>
40+
<EmbeddedResource Include="Myst\Roles\Icons\svgs\*.svg" />
41+
</ItemGroup>
42+
3943
</Project>

src/Elastic.Markdown/Myst/Directives/DirectiveParagraphParser.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to Elasticsearch B.V under one or more agreements.
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
4+
45
using Markdig.Parsers;
56
using Markdig.Syntax;
67

src/Elastic.Markdown/Myst/InlineParsers/HeadingBlockWithSlugParser.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information
44

55
using System.Text.RegularExpressions;
6+
using Elastic.Markdown.Myst.Roles.Icons;
67
using Markdig;
78
using Markdig.Helpers;
89
using Markdig.Parsers;
@@ -30,13 +31,17 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { }
3031

3132
public class HeadingBlockWithSlugParser : HeadingBlockParser
3233
{
34+
private static readonly Regex IconSyntax = IconParser.IconRegex();
35+
3336
public override bool Close(BlockProcessor processor, Block block)
3437
{
35-
if (block is not HeadingBlock headerBlock)
38+
if (block is not HeadingBlock headingBlock)
3639
return base.Close(processor, block);
3740

38-
var text = headerBlock.Lines.Lines[0].Slice.AsSpan();
39-
headerBlock.SetData("header", text.ToString());
41+
var text = headingBlock.Lines.Lines[0].Slice.AsSpan();
42+
// Remove icon syntax from the heading text
43+
var cleanText = IconSyntax.Replace(text.ToString(), "").Trim();
44+
headingBlock.SetData("header", cleanText);
4045

4146
if (!HeadingAnchorParser.MatchAnchorLine().IsMatch(text))
4247
return base.Close(processor, block);
@@ -49,13 +54,13 @@ public override bool Close(BlockProcessor processor, Block block)
4954
var anchor = text.Slice(match.Index, match.Length);
5055

5156
var newSlice = new StringSlice(header.ToString());
52-
headerBlock.Lines.Lines[0] = new StringLine(ref newSlice);
57+
headingBlock.Lines.Lines[0] = new StringLine(ref newSlice);
5358

5459
if (header.IndexOf('$') >= 0)
5560
anchor = HeadingAnchorParser.MatchAnchor().Replace(anchor.ToString(), "");
56-
57-
headerBlock.SetData("anchor", anchor.ToString());
58-
headerBlock.SetData("header", header.ToString());
61+
headingBlock.SetData("anchor", anchor.ToString());
62+
// Remove icon syntax from the header text when setting it as data
63+
headingBlock.SetData("header", IconSyntax.Replace(header.ToString(), "").Trim());
5964
return base.Close(processor, block);
6065
}
6166

src/Elastic.Markdown/Myst/MarkdownParser.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using Elastic.Markdown.Myst.Linters;
1515
using Elastic.Markdown.Myst.Renderers;
1616
using Elastic.Markdown.Myst.Roles.AppliesTo;
17-
17+
using Elastic.Markdown.Myst.Roles.Icons;
1818
using Markdig;
1919
using Markdig.Extensions.EmphasisExtras;
2020
using Markdig.Parsers;
@@ -146,6 +146,7 @@ public static MarkdownPipeline Pipeline
146146
.UseHeadingsWithSlugs()
147147
.UseEmphasisExtras(EmphasisExtraOptions.Default)
148148
.UseInlineAppliesTo()
149+
.UseInlineIcons()
149150
.UseSubstitution()
150151
.UseComments()
151152
.UseYamlFrontMatter()
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Diagnostics;
6+
using System.Text.RegularExpressions;
7+
using Elastic.Markdown.Diagnostics;
8+
using Markdig.Parsers;
9+
10+
namespace Elastic.Markdown.Myst.Roles.Icons;
11+
12+
[DebuggerDisplay("{GetType().Name} Line: {Line}, Role: {Role}, Content: {Content}")]
13+
public class IconsRole : RoleLeaf
14+
{
15+
private static readonly IReadOnlyDictionary<string, string> IconMap;
16+
static IconsRole()
17+
{
18+
var assembly = typeof(IconsRole).Assembly;
19+
var iconFolder = $"{assembly.GetName().Name}.Myst.Roles.Icons.svgs.";
20+
IconMap = assembly.GetManifestResourceNames()
21+
.Where(r => r.StartsWith(iconFolder) && r.EndsWith(".svg"))
22+
.ToDictionary(
23+
r => r[iconFolder.Length..].Replace(".svg", string.Empty),
24+
r =>
25+
{
26+
using var stream = assembly.GetManifestResourceStream(r);
27+
if (stream is null)
28+
return string.Empty;
29+
using var reader = new StreamReader(stream);
30+
return reader.ReadToEnd();
31+
}
32+
);
33+
}
34+
35+
public IconsRole(string role, string content, InlineProcessor processor) : base(role, content)
36+
{
37+
38+
if (IconMap.TryGetValue(content, out var svg))
39+
{
40+
Svg = svg;
41+
Name = content;
42+
}
43+
else
44+
processor.EmitError(this, Role.Length + content.Length, $"Unknown icon: {content}");
45+
}
46+
47+
public string? Name { get; }
48+
public string? Svg { get; }
49+
}
50+
51+
public partial class IconParser : RoleParser<IconsRole>
52+
{
53+
[GeneratedRegex(@"\{icon\}`([^`]+)`", RegexOptions.Compiled)]
54+
public static partial Regex IconRegex();
55+
56+
protected override IconsRole CreateRole(string role, string content, InlineProcessor parserContext) =>
57+
new(role, content, parserContext);
58+
59+
protected override bool Matches(ReadOnlySpan<char> role) => role is "{icon}";
60+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
using System.Diagnostics.CodeAnalysis;
6+
using Markdig;
7+
using Markdig.Parsers.Inlines;
8+
using Markdig.Renderers;
9+
using Markdig.Renderers.Html;
10+
using Markdig.Renderers.Html.Inlines;
11+
12+
namespace Elastic.Markdown.Myst.Roles.Icons;
13+
14+
public class IconRoleHtmlRenderer : HtmlObjectRenderer<IconsRole>
15+
{
16+
17+
protected override void Write(HtmlRenderer renderer, IconsRole role)
18+
{
19+
_ = renderer.Write($"<span aria-label=\"Icon for {role.Name}\" class=\"icon icon-{role.Name}\">");
20+
_ = renderer.Write(role.Svg);
21+
_ = renderer.Write("</span>");
22+
}
23+
}
24+
25+
public static class InlineAppliesToExtensions
26+
{
27+
public static MarkdownPipelineBuilder UseInlineIcons(this MarkdownPipelineBuilder pipeline)
28+
{
29+
pipeline.Extensions.AddIfNotAlready<InlineIconExtension>();
30+
return pipeline;
31+
}
32+
}
33+
34+
public class InlineIconExtension : IMarkdownExtension
35+
{
36+
public void Setup(MarkdownPipelineBuilder pipeline) => _ = pipeline.InlineParsers.InsertBefore<CodeInlineParser>(new IconParser());
37+
38+
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) =>
39+
renderer.ObjectRenderers.InsertBefore<CodeInlineRenderer>(new IconRoleHtmlRenderer());
40+
}

0 commit comments

Comments
 (0)