Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions docs/syntax/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,38 +60,40 @@ Here is the same image used inline ![Elasticsearch](/syntax/images/observability

### Inline image titles

Titles are optional making this the minimal syntax required
Titles are optional making this the minimal syntax required:

```markdown
![Elasticsearch](/syntax/images/observability.png)
```

Including a title can be done by supplying it as an optional argument.
For inline images, the alt text always overrides any title specified in the Markdown. This ensures consistent accessibility where both the `alt` and `title` attributes contain the same descriptive text.

```markdown
![Elasticsearch](/syntax/images/observability.png "elasticsearch")
![Elasticsearch](/syntax/images/observability.png "Different title")
```

### Inline image sizing
![Elasticsearch](/syntax/images/observability.png "Different title")


Inline images are supplied at the end through the title argument.
### Inline image sizing

This is done to maintain maximum compatibility with markdown parsers
and previewers.
Image sizing is specified through the title argument. You can specify just the size without needing to provide a redundant title:

```markdown
![alt](img.png "title =WxH")
![alt](img.png "title =W")
![alt](img.png "=WxH")
![alt](img.png "=W")
```

In this case, the alt text will be used as both the `alt` and `title` attributes, and the size parameters will be applied.

`W` and `H` can be either an absolute number in pixels or a number followed by `%` to indicate relative sizing.

If `H` is omitted `W` is used as the height as well.

```markdown
![alt](img.png "title =250x330")
![alt](img.png "title =50%x40%")
![alt](img.png "title =50%")
![alt](img.png "=250x330")
![alt](img.png "=50%x40%")
![alt](img.png "=50%")
```


Expand Down Expand Up @@ -138,7 +140,7 @@ The image carousel directive builds upon the image directive.

:::{image} images/applies.png
:alt: Second image description
:title: Second image title
### Title is optional - alt text will be used as title if not specified
:::

::::
Expand Down
4 changes: 3 additions & 1 deletion src/Elastic.Markdown/Myst/Directives/Image/ImageBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ public override void FinalizeAndValidate(ParserContext context)
{
Label = Prop("label", "name");
Alt = Prop("alt")?.ReplaceSubstitutions(context) ?? string.Empty;
Title = Prop("title")?.ReplaceSubstitutions(context);
// Use Alt as Title if no explicit Title is provided
var explicitTitle = Prop("title")?.ReplaceSubstitutions(context);
Title = string.IsNullOrEmpty(explicitTitle) ? Alt : explicitTitle;

Align = Prop("align");
Height = Prop("height", "h");
Expand Down
34 changes: 33 additions & 1 deletion src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ protected override void Write(HtmlRenderer renderer, LinkInline link)
{
if (renderer.EnableHtmlForInline && !link.IsImage)
{
// ReSharper disable once UnusedVariable
if (link.GetData(nameof(ParserContext.CurrentUrlPath)) is not string currentUrl)
{
base.Write(renderer, link);
Expand Down Expand Up @@ -70,9 +69,42 @@ protected override void Write(HtmlRenderer renderer, LinkInline link)

_ = renderer.Write("</a>");
}
else if (link.IsImage)
{
// Handle inline images with ALT override logic
WriteImage(renderer, link);
}
else
base.Write(renderer, link);
}

private static void WriteImage(HtmlRenderer renderer, LinkInline link)
{
_ = renderer.Write("<img src=\"");
_ = renderer.WriteEscapeUrl(link.GetDynamicUrl?.Invoke() ?? link.Url);
_ = renderer.Write('"');

// Write alt text using WriteChildren to ensure substitutions are processed
if (link.FirstChild != null)
{
_ = renderer.Write(" alt=\"");
renderer.WriteChildren(link);
_ = renderer.Write('"');
}

// Write any additional attributes (like width/height from styling instructions)
_ = renderer.WriteAttributes(link);

// Set title to alt text for inline images (after any substitutions are processed)
if (link.FirstChild != null)
{
_ = renderer.Write(" title=\"");
renderer.WriteChildren(link);
_ = renderer.Write('"');
}

_ = renderer.Write(" />");
}
}

public static class CustomLinkInlineRendererExtensions
Expand Down
2 changes: 1 addition & 1 deletion tests/Elastic.Markdown.Tests/Inline/SubstitutionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ public class ReplaceInImageTitle(ITestOutputHelper output) : InlineTest(output,
[Fact]
public void OnlySeesGlobalVariable() =>
Html.Should().NotContain("title=\"{{hello-world}}\"")
.And.Contain("title=\"Hello World\"");
.And.Contain("title=\"Observability\"");
}

public class MutationOperatorTest(ITestOutputHelper output) : InlineTest(output,
Expand Down
8 changes: 4 additions & 4 deletions tests/authoring/Blocks/ImageBlocks.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type ``static path to image`` () =
[<Fact>]
let ``validate src is anchored`` () =
markdown |> convertsToContainingHtml """
<img loading="lazy" alt="Elasticsearch" src="/img/observability.png" style="width: 250px;" class="screenshot">
<img loading="lazy" title="Elasticsearch" alt="Elasticsearch" src="/img/observability.png" style="width: 250px;" class="screenshot">
"""

type ``supports --url-path-prefix`` () =
Expand All @@ -45,13 +45,13 @@ type ``supports --url-path-prefix`` () =
[<Fact>]
let ``validate image src contains prefix`` () =
docs |> convertsToContainingHtml """
<img loading="lazy" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
<img loading="lazy" title="Elasticsearch" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
"""

[<Fact>]
let ``validate image src contains prefix when referenced relatively`` () =
docs |> converts "folder/relative.md" |> containsHtml """
<img loading="lazy" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
<img loading="lazy" title="Elasticsearch" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
"""

[<Fact>]
Expand All @@ -73,7 +73,7 @@ type ``image ref out of scope`` () =
[<Fact>]
let ``validate image src contains prefix and is anchored to documentation scope root`` () =
docs |> convertsToContainingHtml """
<img loading="lazy" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
<img loading="lazy" title="Elasticsearch" alt="Elasticsearch" src="/docs/img/observability.png" style="width: 250px;" class="screenshot">
"""

[<Fact>]
Expand Down
12 changes: 6 additions & 6 deletions tests/authoring/Inline/InlineAppliesTo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ``static path to image`` () =
[<Fact>]
let ``validate HTML: generates link and alt attr`` () =
markdown |> convertsToHtml """
<p><img src="/_static/img/observability.png" alt="Elasticsearch" /></p>
<p><img src="/_static/img/observability.png" alt="Elasticsearch" title="Elasticsearch" /></p>
"""

type ``relative path to image`` () =
Expand All @@ -26,7 +26,7 @@ type ``relative path to image`` () =
[<Fact>]
let ``validate HTML: preserves relative path`` () =
markdown |> convertsToHtml """
<p><img src="/_static/img/observability.png" alt="Elasticsearch" /></p>
<p><img src="/_static/img/observability.png" alt="Elasticsearch" title="Elasticsearch" /></p>
"""

type ``supplying a tittle`` () =
Expand All @@ -37,7 +37,7 @@ type ``supplying a tittle`` () =
[<Fact>]
let ``validate HTML: includes title`` () =
markdown |> convertsToHtml """
<p><img src="/_static/img/observability.png" alt="Elasticsearch" title="Hello world" /></p>
<p><img src="/_static/img/observability.png" alt="Elasticsearch" title="Elasticsearch" /></p>
"""

type ``supplying a tittle with width and height`` () =
Expand All @@ -48,7 +48,7 @@ type ``supplying a tittle with width and height`` () =
[<Fact>]
let ``validate HTML: does not include width and height in title`` () =
markdown |> convertsToHtml """
<p><img src="/obs.png" width="250px" height="400px" alt="o" title="Title"/></p>
<p><img src="/obs.png" width="250px" height="400px" alt="o" title="o"/></p>
"""

type ``supplying a tittle with width and height in percentage`` () =
Expand All @@ -59,7 +59,7 @@ type ``supplying a tittle with width and height in percentage`` () =
[<Fact>]
let ``validate HTML: does not include width and height in title`` () =
markdown |> convertsToHtml """
<p><img src="/obs.png" width="50%" height="40%" alt="o" title="Title"/></p>
<p><img src="/obs.png" width="50%" height="40%" alt="o" title="o"/></p>
"""
type ``supplying a tittle with width only`` () =
static let markdown = Setup.Markdown """
Expand All @@ -69,5 +69,5 @@ type ``supplying a tittle with width only`` () =
[<Fact>]
let ``validate HTML: sets height to width if not supplied`` () =
markdown |> convertsToHtml """
<p><img src="/obs.png" width="30%" height="30%" alt="o" title="Title"/></p>
<p><img src="/obs.png" width="30%" height="30%" alt="o" title="o"/></p>
"""
Loading