diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs index 579e4863f..bb418951b 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveBlock.cs @@ -94,5 +94,8 @@ protected bool PropBool(params string[] keys) protected void EmitError(ParserContext context, string message) => context.EmitError(Line + 1, 1, Directive.Length + 4 , message); + protected void EmitWarning(ParserContext context, string message) => + context.EmitWarning(Line + 1, 1, Directive.Length + 4 , message); + } diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index 806976a90..f75525c89 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -79,13 +79,14 @@ protected override void Write(HtmlRenderer renderer, DirectiveBlock directiveBlo private void WriteImage(HtmlRenderer renderer, ImageBlock block) { - var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static") + var imageUrl = + block.ImageUrl != null && + (block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")) ? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}" : block.ImageUrl; var slice = Image.Create(new ImageViewModel { - Classes = block.Classes, - CrossReferenceName = block.CrossReferenceName, + Label = block.Label, Align = block.Align, Alt = block.Alt, Height = block.Height, @@ -99,13 +100,12 @@ private void WriteImage(HtmlRenderer renderer, ImageBlock block) private void WriteFigure(HtmlRenderer renderer, ImageBlock block) { - var imageUrl = block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static") + var imageUrl = block.ImageUrl != null && (block.ImageUrl.StartsWith("/_static") || block.ImageUrl.StartsWith("_static")) ? $"{block.Build.UrlPathPrefix}/{block.ImageUrl.TrimStart('/')}" : block.ImageUrl; var slice = Slices.Directives.Figure.Create(new ImageViewModel { - Classes = block.Classes, - CrossReferenceName = block.CrossReferenceName, + Label = block.Label, Align = block.Align, Alt = block.Alt, Height = block.Height, diff --git a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs index 947c5697c..d8304a8f0 100644 --- a/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/ImageBlock.cs @@ -46,29 +46,59 @@ public class ImageBlock(DirectiveBlockParser parser, Dictionary /// public string? Target { get; set; } - /// - /// A space-separated list of CSS classes to add to the image. - /// - public string? Classes { get; protected set; } - /// /// A reference target for the admonition (see cross-referencing). /// - public string? CrossReferenceName { get; private set; } + public string? Label { get; private set; } + + public string? ImageUrl { get; private set; } + + public bool Found { get; private set; } - public string ImageUrl { get; private set; } = default!; public override void FinalizeAndValidate(ParserContext context) { - ImageUrl = Arguments ?? string.Empty; //todo validate - Classes = Properties.GetValueOrDefault("class"); - CrossReferenceName = Properties.GetValueOrDefault("name"); - Alt = Properties.GetValueOrDefault("alt"); - Height = Properties.GetValueOrDefault("height"); - Width = Properties.GetValueOrDefault("width"); - Scale = Properties.GetValueOrDefault("scale"); - Align = Properties.GetValueOrDefault("align"); - Target = Properties.GetValueOrDefault("target"); + Label = Prop("label", "name"); + Alt = Prop("alt"); + Align = Prop("align"); + + Height = Prop("height", "h"); + Width = Prop("width", "w"); + + Scale = Prop("scale"); + Target = Prop("target"); + + ExtractImageUrl(context); + + } + + private void ExtractImageUrl(ParserContext context) + { + var imageUrl = Arguments; + if (string.IsNullOrWhiteSpace(imageUrl)) + { + EmitError(context , $"{Directive} requires an argument."); + return; + } + + if (Uri.TryCreate(imageUrl, UriKind.Absolute, out var uri) && uri.Scheme.StartsWith("http")) + { + EmitWarning(context, $"{Directive} is using an external URI: {uri} "); + Found = true; + ImageUrl = imageUrl; + return; + } + + var includeFrom = context.Path.Directory!.FullName; + if (imageUrl.StartsWith('/')) + includeFrom = context.Parser.SourcePath.FullName; + + ImageUrl = imageUrl; + var imagePath = Path.Combine(includeFrom, imageUrl.TrimStart('/')); + if (context.Build.ReadFileSystem.File.Exists(imageUrl)) + Found = true; + else + EmitError(context, $"`{imageUrl}` does not exist. resolved to `{imagePath}"); } } diff --git a/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs b/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs index edd35510c..5052eb910 100644 --- a/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/IncludeBlock.cs @@ -33,13 +33,17 @@ public class IncludeBlock(DirectiveBlockParser parser, Dictionary -
+
@Model.Alt
-

[CONTENT]ΒΆ

+

[CONTENT]ΒΆ

\ No newline at end of file diff --git a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs index 70377c8c5..759fdc30f 100644 --- a/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs +++ b/src/Elastic.Markdown/Slices/Directives/_ViewModels.cs @@ -2,7 +2,6 @@ // 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 System.Text; -using Elastic.Markdown.Myst.Directives; namespace Elastic.Markdown.Slices.Directives; @@ -45,8 +44,7 @@ public class IncludeViewModel public class ImageViewModel { - public required string? CrossReferenceName { get; init; } - public required string? Classes { get; init; } + public required string? Label { get; init; } public required string? Align { get; init; } public required string? Alt { get; init; } public required string? Height { get; init; } diff --git a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs index 187967dd7..a8a482349 100644 --- a/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/DirectiveBaseTests.cs @@ -64,7 +64,7 @@ protected DirectiveTest(ITestOutputHelper output, [LanguageInjection("markdown") }); var file = FileSystem.FileInfo.New("docs/source/index.md"); - var root = FileSystem.DirectoryInfo.New(Paths.Root.FullName); + var root = file.Directory!; Collector = new TestDiagnosticsCollector(logger); var context = new BuildContext { diff --git a/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs b/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs index 1f878b4e9..14d5d080e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs @@ -1,22 +1,29 @@ // Licensed to Elasticsearch B.V under one or more agreements. // 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.Diagnostics; using Elastic.Markdown.Myst.Directives; using FluentAssertions; -using Xunit; using Xunit.Abstractions; namespace Elastic.Markdown.Tests.Directives; public class ImageBlockTests(ITestOutputHelper output) : DirectiveTest(output, """ -```{image} /_static/img/observability.png +```{image} img/observability.png :alt: Elasticsearch :width: 250px ``` """ ) { + public override Task InitializeAsync() + { + FileSystem.AddFile(@"img/observability.png", ""); + return base.InitializeAsync(); + } + [Fact] public void ParsesBlock() => Block.Should().NotBeNull(); @@ -25,7 +32,14 @@ public void ParsesBreakPoint() { Block!.Alt.Should().Be("Elasticsearch"); Block!.Width.Should().Be("250px"); - Block!.ImageUrl.Should().Be("/_static/img/observability.png"); + Block!.ImageUrl.Should().Be("img/observability.png"); + } + + [Fact] + public void ImageIsFoundSoNoErrorIsEmitted() + { + Block!.Found.Should().BeTrue(); + Collector.Diagnostics.Count.Should().Be(0); } } @@ -45,7 +59,11 @@ Relaxing at the beach 🏝 🌊 😎 public void ParsesBlock() => Block.Should().NotBeNull(); [Fact] - public void ParsesBreakPoint() + public void WarnsOnExternalUri() { + Block!.Found.Should().BeTrue(); + + Collector.Diagnostics.Should().HaveCount(1) + .And.OnlyContain(d => d.Severity == Severity.Warning); } }