From a50013d8ea217a8df3790586278566a94b2cf4c2 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Mon, 21 Apr 2025 16:54:21 -0500 Subject: [PATCH 1/9] use substitution values in image alt and title text --- .../Myst/Directives/DirectiveHtmlRenderer.cs | 2 ++ src/Elastic.Markdown/Myst/Directives/ImageBlock.cs | 11 +++++++++-- .../Myst/InlineParsers/DiagnosticLinkInlineParser.cs | 11 +++++------ src/Elastic.Markdown/Slices/Directives/_ViewModels.cs | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index 4cca0048d..067c30abe 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -107,6 +107,7 @@ private static void WriteImage(HtmlRenderer renderer, ImageBlock block) Label = block.Label, Align = block.Align, Alt = block.Alt, + Title = block.Title, Height = block.Height, Scale = block.Scale, Target = block.Target, @@ -144,6 +145,7 @@ private static void WriteFigure(HtmlRenderer renderer, ImageBlock block) Label = block.Label, Align = block.Align, Alt = block.Alt, + Title = block.Title, Height = block.Height, Scale = block.Scale, Target = block.Target, diff --git a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs index 8883fec0e..d5fdfcec4 100644 --- a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Markdown.Helpers; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.IO; @@ -20,6 +21,11 @@ public class ImageBlock(DirectiveBlockParser parser, ParserContext context) /// public string? Alt { get; set; } + /// + /// Title text: a short description of the image + /// + public string? Title { get; set; } + /// /// The desired height of the image. Used to reserve space or scale the image vertically. When the “scale” option /// is also specified, they are combined. For example, a height of 200px and a scale of 50 is equivalent to @@ -64,9 +70,10 @@ public class ImageBlock(DirectiveBlockParser parser, ParserContext context) public override void FinalizeAndValidate(ParserContext context) { Label = Prop("label", "name"); - Alt = Prop("alt"); - Align = Prop("align"); + Alt = (Prop("alt") ?? "{undefined}").ReplaceSubstitutions(context); + Title = (Prop("title") ?? "{undefined}").ReplaceSubstitutions(context); + Align = Prop("align"); Height = Prop("height", "h"); Width = Prop("width", "w"); diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 6e7ee5c19..c861592c3 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -63,19 +63,21 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice) ValidateAndProcessLink(link, processor, context); - ParseStylingInstructions(link); + ParseStylingInstructions(link, context); return match; } - private static void ParseStylingInstructions(LinkInline link) + private static void ParseStylingInstructions(LinkInline link, ParserContext context) { if (!link.IsImage) return; + var attributes = link.GetAttributes(); + if (string.IsNullOrWhiteSpace(link.Title) || link.Title.IndexOf('=') < 0) - return; + link.Title = (link.Title ?? "{undefined}").ReplaceSubstitutions(context); var matches = LinkRegexExtensions.MatchTitleStylingInstructions().Match(link.Title); if (!matches.Success) @@ -89,10 +91,7 @@ private static void ParseStylingInstructions(LinkInline link) height = width; else if (!height.EndsWith('%')) height += "px"; - var title = link.Title[..matches.Index]; - link.Title = title; - var attributes = link.GetAttributes(); attributes.AddProperty("width", width); attributes.AddProperty("height", height); } diff --git a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs index bf33ad95c..33adc979d 100644 --- a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs @@ -50,6 +50,7 @@ public class ImageViewModel public required string? Label { get; init; } public required string? Align { get; init; } public required string? Alt { get; init; } + public required string? Title { get; init; } public required string? Height { get; init; } public required string? Scale { get; init; } public required string? Target { get; init; } From b6d66fb107f63cb6ca42930bdcd6605870e8a832 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 22 Apr 2025 09:25:44 -0500 Subject: [PATCH 2/9] reorder imports --- src/Elastic.Markdown/Myst/Directives/ImageBlock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs index d5fdfcec4..07a7e1fca 100644 --- a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs @@ -2,8 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using Elastic.Markdown.Helpers; using Elastic.Markdown.Diagnostics; +using Elastic.Markdown.Helpers; using Elastic.Markdown.IO; namespace Elastic.Markdown.Myst.Directives; From 2c732093ead54469cab23bcb7a0ed94cb1902239 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 22 Apr 2025 11:39:30 -0500 Subject: [PATCH 3/9] fix when to resolve subs --- .../DiagnosticLinkInlineParser.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index c861592c3..d977df7f5 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -75,25 +75,29 @@ private static void ParseStylingInstructions(LinkInline link, ParserContext cont return; var attributes = link.GetAttributes(); + var title = link.Title; - if (string.IsNullOrWhiteSpace(link.Title) || link.Title.IndexOf('=') < 0) - link.Title = (link.Title ?? "{undefined}").ReplaceSubstitutions(context); - - var matches = LinkRegexExtensions.MatchTitleStylingInstructions().Match(link.Title); - if (!matches.Success) + if (string.IsNullOrEmpty(title)) return; - var width = matches.Groups["width"].Value; - if (!width.EndsWith('%')) - width += "px"; - var height = matches.Groups["height"].Value; - if (string.IsNullOrEmpty(height)) - height = width; - else if (!height.EndsWith('%')) - height += "px"; - - attributes.AddProperty("width", width); - attributes.AddProperty("height", height); + var matches = LinkRegexExtensions.MatchTitleStylingInstructions().Match(title); + if (matches.Success) + { + var width = matches.Groups["width"].Value; + if (!width.EndsWith('%')) + width += "px"; + var height = matches.Groups["height"].Value; + if (string.IsNullOrEmpty(height)) + height = width; + else if (!height.EndsWith('%')) + height += "px"; + + attributes.AddProperty("width", width); + attributes.AddProperty("height", height); + + title = title[..matches.Index]; + } + link.Title = (title ?? "{undefined}").ReplaceSubstitutions(context); } private static bool IsInCommentBlock(LinkInline link) => @@ -182,7 +186,7 @@ private static void ProcessCrossLink(LinkInline link, InlineProcessor processor, s => processor.EmitError(link, s), s => processor.EmitWarning(link, s), uri, out var resolvedUri) - ) + ) link.Url = resolvedUri.ToString(); } From 6ff2c7890cf4e0fc672e3fff91c8e86941d91fbb Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 22 Apr 2025 11:39:58 -0500 Subject: [PATCH 4/9] add image with subs to subs test --- .../Inline/SubstitutionTest.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs b/tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs index 947a58ae0..fdc43ac77 100644 --- a/tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs +++ b/tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs @@ -165,3 +165,43 @@ public void OnlySeesGlobalVariable() => public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0); } + +public class ReplaceInImageAlt(ITestOutputHelper output) : InlineTest(output, +""" +--- +sub: + hello-world: Hello World +--- + +# Testing ReplaceInImageAlt + +![{{hello-world}}](_static/img/observability.png) +""" +) +{ + + [Fact] + public void OnlySeesGlobalVariable() => + Html.Should().NotContain("alt=\"{{hello-world}}\"") + .And.Contain("alt=\"Hello World\""); +} + +public class ReplaceInImageTitle(ITestOutputHelper output) : InlineTest(output, +""" +--- +sub: + hello-world: Hello World +--- + +# Testing ReplaceInImageTitle + +![Observability](_static/img/observability.png "{{hello-world}}") +""" +) +{ + + [Fact] + public void OnlySeesGlobalVariable() => + Html.Should().NotContain("title=\"{{hello-world}}\"") + .And.Contain("title=\"Hello World\""); +} From 5748ca0924a19c168f55915e25c6c1b879fb6068 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 20 May 2025 11:52:23 +0200 Subject: [PATCH 5/9] Allow empty alt tag --- .../Myst/Directives/DirectiveHtmlRenderer.cs | 5 +-- .../Myst/Directives/ImageBlock.cs | 6 ++-- .../Slices/Directives/Image.cshtml | 32 +++++++++++++++++-- .../Slices/Directives/_ViewModels.cs | 2 +- tests/authoring/Blocks/ImageBlocks.fs | 14 ++++++++ 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index cfbf48744..4d51675fc 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -13,6 +13,7 @@ using Markdig.Renderers.Html; using Markdig.Syntax; using Markdig.Syntax.Inlines; +using Microsoft.AspNetCore.Html; using RazorSlices; using YamlDotNet.Core; @@ -91,7 +92,7 @@ private static void WriteImage(HtmlRenderer renderer, ImageBlock block) { Label = block.Label, Align = block.Align, - Alt = block.Alt, + Alt = block.Alt ?? string.Empty, Title = block.Title, Height = block.Height, Scale = block.Scale, @@ -129,7 +130,7 @@ private static void WriteFigure(HtmlRenderer renderer, ImageBlock block) { Label = block.Label, Align = block.Align, - Alt = block.Alt, + Alt = block.Alt ?? string.Empty, Title = block.Title, Height = block.Height, Scale = block.Scale, diff --git a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs index bab61d12c..352960aeb 100644 --- a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs @@ -71,8 +71,8 @@ public class ImageBlock(DirectiveBlockParser parser, ParserContext context) public override void FinalizeAndValidate(ParserContext context) { Label = Prop("label", "name"); - Alt = (Prop("alt") ?? "{undefined}").ReplaceSubstitutions(context); - Title = (Prop("title") ?? "{undefined}").ReplaceSubstitutions(context); + Alt = Prop("alt")?.ReplaceSubstitutions(context) ?? string.Empty; + Title = Prop("title")?.ReplaceSubstitutions(context); Align = Prop("align"); Height = Prop("height", "h"); @@ -119,5 +119,3 @@ private void ExtractImageUrl(ParserContext context) } } } - - diff --git a/src/Elastic.Markdown/Slices/Directives/Image.cshtml b/src/Elastic.Markdown/Slices/Directives/Image.cshtml index 88aa1212c..405d616e3 100644 --- a/src/Elastic.Markdown/Slices/Directives/Image.cshtml +++ b/src/Elastic.Markdown/Slices/Directives/Image.cshtml @@ -1,6 +1,20 @@ +@using Microsoft.AspNetCore.Mvc.ModelBinding.Validation @inherits RazorSlice - @Model.Alt + @{ + // Using an if statement because using a ternary operator + // doesn't work. Model.Alt and HtmlString.Empty are not the same type. + // Converting HtmlString.Empty to string makes the alt tag disappear + // in the final HTML. + } + @if (string.IsNullOrEmpty(Model.Alt)) + { + @HtmlString.Empty + } + else + { + @Model.Alt + } [CONTENT] @@ -14,7 +28,21 @@ - @Model.Alt + + @{ + // Using an if statement because using a ternary operator + // doesn't work. Model.Alt and HtmlString.Empty are not the same type. + // Converting HtmlString.Empty to string makes the alt tag disappear + // in the final HTML. + } + @if (string.IsNullOrEmpty(Model.Alt)) + { + @HtmlString.Empty + } + else + { + @Model.Alt + } diff --git a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs index 33adc979d..f9b8ed0b6 100644 --- a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs @@ -49,7 +49,7 @@ public class ImageViewModel { public required string? Label { get; init; } public required string? Align { get; init; } - public required string? Alt { get; init; } + public required string Alt { get; init; } public required string? Title { get; init; } public required string? Height { get; init; } public required string? Scale { get; init; } diff --git a/tests/authoring/Blocks/ImageBlocks.fs b/tests/authoring/Blocks/ImageBlocks.fs index ad492f68a..7231f8d5a 100644 --- a/tests/authoring/Blocks/ImageBlocks.fs +++ b/tests/authoring/Blocks/ImageBlocks.fs @@ -79,3 +79,17 @@ type ``image ref out of scope`` () = [] let ``emits an error image reference is outside of documentation scope`` () = docs |> hasError "./img/observability.png` does not exist. resolved to" + +type ``empty alt attribute`` () = + static let markdown = Setup.Markdown """ +:::{image} img/some-image.png +:alt: +:width: 250px +::: +""" + + [] + let ``validate empty alt attribute`` () = + markdown |> convertsToContainingHtml """ + + """ From edd2e4c8dc0b4f099e18fe030370e58c5f34135f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 20 May 2025 14:21:26 +0200 Subject: [PATCH 6/9] Add title --- src/Elastic.Markdown/Slices/Directives/Image.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Elastic.Markdown/Slices/Directives/Image.cshtml b/src/Elastic.Markdown/Slices/Directives/Image.cshtml index 405d616e3..bd06c4f1c 100644 --- a/src/Elastic.Markdown/Slices/Directives/Image.cshtml +++ b/src/Elastic.Markdown/Slices/Directives/Image.cshtml @@ -9,11 +9,11 @@ } @if (string.IsNullOrEmpty(Model.Alt)) { - @HtmlString.Empty + @HtmlString.Empty } else { - @Model.Alt + @Model.Alt } [CONTENT] @@ -37,11 +37,11 @@ } @if (string.IsNullOrEmpty(Model.Alt)) { - @HtmlString.Empty + @HtmlString.Empty } else { - @Model.Alt + @Model.Alt } From 337e1bf9df378d4e604a234599c3244a3ba1c47f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 20 May 2025 14:25:59 +0200 Subject: [PATCH 7/9] Refactor --- .../Slices/Directives/Image.cshtml | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/Elastic.Markdown/Slices/Directives/Image.cshtml b/src/Elastic.Markdown/Slices/Directives/Image.cshtml index bd06c4f1c..3ffc31854 100644 --- a/src/Elastic.Markdown/Slices/Directives/Image.cshtml +++ b/src/Elastic.Markdown/Slices/Directives/Image.cshtml @@ -1,20 +1,7 @@ @using Microsoft.AspNetCore.Mvc.ModelBinding.Validation @inherits RazorSlice - @{ - // Using an if statement because using a ternary operator - // doesn't work. Model.Alt and HtmlString.Empty are not the same type. - // Converting HtmlString.Empty to string makes the alt tag disappear - // in the final HTML. - } - @if (string.IsNullOrEmpty(Model.Alt)) - { - @HtmlString.Empty - } - else - { - @Model.Alt - } + @(Model.Alt == string.Empty ? HtmlString.Empty : new HtmlString(Model.Alt)) [CONTENT] @@ -28,21 +15,7 @@ - - @{ - // Using an if statement because using a ternary operator - // doesn't work. Model.Alt and HtmlString.Empty are not the same type. - // Converting HtmlString.Empty to string makes the alt tag disappear - // in the final HTML. - } - @if (string.IsNullOrEmpty(Model.Alt)) - { - @HtmlString.Empty - } - else - { - @Model.Alt - } + @(Model.Alt == string.Empty ? HtmlString.Empty : new HtmlString(Model.Alt)) From 838c406e3d71c79bbc82ecfe09e5a3f08a3c8382 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 20 May 2025 14:27:32 +0200 Subject: [PATCH 8/9] Update src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs --- src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index 4d51675fc..66f01d151 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -13,7 +13,6 @@ using Markdig.Renderers.Html; using Markdig.Syntax; using Markdig.Syntax.Inlines; -using Microsoft.AspNetCore.Html; using RazorSlices; using YamlDotNet.Core; From 4774943637cc457f318cada8d1e1b0f57766695d Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 20 May 2025 14:28:25 +0200 Subject: [PATCH 9/9] Update src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs --- .../Myst/InlineParsers/DiagnosticLinkInlineParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index 02499ef80..59231fc9d 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -97,7 +97,7 @@ private static void ParseStylingInstructions(LinkInline link, ParserContext cont title = title[..matches.Index]; } - link.Title = (title ?? "{undefined}").ReplaceSubstitutions(context); + link.Title = title?.ReplaceSubstitutions(context); } private static bool IsInCommentBlock(LinkInline link) =>