Skip to content
Merged
1 change: 1 addition & 0 deletions docs/_docset.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
project: 'doc-builder'
max_toc_depth: 2
# indicates this documentation set is not linkable by assembler.
Expand Down Expand Up @@ -97,6 +97,7 @@
- file: definition-lists.md
- file: example_blocks.md
- file: file_inclusion.md
- file: footnotes.md
- file: frontmatter.md
- file: icons.md
- file: images.md
Expand Down
183 changes: 183 additions & 0 deletions docs/syntax/footnotes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Footnotes

Footnotes allow you to add notes and references without cluttering the main text. They're automatically numbered and linked, providing an elegant way to include supplementary information, citations, or explanations.

## Plain paragraph test

This is a plain footnote test[^plain]. No directives involved.

[^plain]: This footnote is in a plain paragraph, outside any directive.

## Basic footnotes

::::{tab-set}

:::{tab-item} Output

Here's a simple footnote[^1] and another one[^2].

You can also use named identifiers[^my-note] which can be more descriptive in your source files.

:::

:::{tab-item} Markdown

```markdown
Here's a simple footnote[^1] and another one[^2].

You can also use named identifiers[^my-note] which can be more descriptive in your source files.

[^1]: This is the first footnote.
[^2]: This is the second footnote.
[^my-note]: This footnote uses a named identifier instead of a number.
```

:::

::::

[^1]: This is the first footnote.
[^2]: This is the second footnote.
[^my-note]: This footnote uses a named identifier instead of a number.

## Multiple references

You can reference the same footnote multiple times throughout your document.

::::{tab-set}

:::{tab-item} Output

First reference to the concept[^concept]. Some more text here. Second reference to the same concept[^concept].

:::

:::{tab-item} Markdown

```markdown
First reference to the concept[^concept]. Some more text here. Second reference to the same concept[^concept].

[^concept]: This explains an important concept that's referenced multiple times.
```

:::

::::

[^concept]: This explains an important concept that's referenced multiple times.

## Complex footnotes

Footnotes can contain multiple paragraphs, lists, blockquotes, and code blocks. Subsequent content must be indented to be included in the footnote.

::::{tab-set}

:::{tab-item} Output

This has a complex footnote[^complex].

:::

:::{tab-item} Markdown

```markdown
This has a complex footnote[^complex].

[^complex]: This footnote has multiple elements.

It has multiple paragraphs with detailed explanations.

> This is a blockquote inside the footnote.
> It can span multiple lines.

- List item one
- List item two
- List item three

You can even include code:

```python
def example():
return "Hello from footnote"
```
```

:::

::::

[^complex]: This footnote has multiple elements.

It has multiple paragraphs with detailed explanations.

> This is a blockquote inside the footnote.
> It can span multiple lines.

- List item one
- List item two
- List item three

You can even include code:

```python
def example():
return "Hello from footnote"
```

## Footnote placement

Footnote definitions should be placed at the document level (not inside directives like tab-sets, admonitions, or other containers). Footnote references can be used anywhere in your document, including inside directives. The footnote content will always be rendered at the bottom of the page.

::::{tab-set}

:::{tab-item} Output

Here's text with a footnote[^early].

More content here, and another footnote[^late].

Even more content in between.

:::

:::{tab-item} Markdown

```markdown
Here's text with a footnote[^early].

[^early]: This footnote is defined right after the reference.

More content here, and another footnote[^late].

Even more content in between.

[^late]: This footnote is defined later in the document.
```

:::

::::

[^early]: This footnote is defined right after the reference.
[^late]: This footnote is defined later in the document.

## Best practices

### Use descriptive identifiers

While you can use simple numbers like `[^1]`, descriptive identifiers like `[^api-note]` make your source more maintainable.

### Keep footnotes focused

Each footnote should contain a single, focused piece of information. If you find yourself writing very long footnotes, consider whether that content belongs in the main text.

### Consider alternatives

Before adding footnotes, consider whether:
- The information is important enough to be in the main text.
- A link to external documentation would be more appropriate.
- An admonition (note, warning, etc.) would be clearer.

### Numbering

Footnotes are automatically numbered in order of first reference, regardless of the identifier you use in your source. This means `[^zebra]` appearing before `[^apple]` will be numbered as footnote 1.
49 changes: 49 additions & 0 deletions src/Elastic.Documentation.Site/Assets/markdown/footnotes.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Footnotes styling */

.footnotes h4 {
@apply mt-8 mb-10;
}

.footnotes ol {
/* Extra top margin so the gap after the "Footnotes" heading
is more prominent than the gap between individual items. */
@apply mt-4 list-decimal pl-6;
}

.footnotes li {
/* Tighter spacing between items than between heading and list. */
@apply text-ink-light mt-2 text-sm leading-relaxed;
}

.footnotes li p {
@apply inline;
}

.footnotes li p:not(:last-child) {
@apply mb-3;
}

/* Footnote reference (superscript link in text) */
.footnote-ref {
@apply text-blue-elastic hover:text-blue-elastic-100 font-semibold no-underline;
text-decoration: none !important;
}

.footnote-ref sup {
@apply font-sans;
text-decoration: none !important;
}

/* Back reference (return arrow in footnote) */
.footnote-back-ref {
@apply text-blue-elastic hover:text-blue-elastic-100 no-underline;
font-family: monospace;
text-decoration: none !important;
vertical-align: super;
font-size: x-small;
}

/* Multiple back references */
.footnote-back-ref + .footnote-back-ref {
@apply ml-1;
}
1 change: 1 addition & 0 deletions src/Elastic.Documentation.Site/Assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@import './markdown/dropdown.css';
@import './markdown/table.css';
@import './markdown/definition-list.css';
@import './markdown/footnotes.css';
@import './markdown/images.css';
@import './markdown/math.css';
@import './markdown/image-carousel.css';
Expand Down
23 changes: 22 additions & 1 deletion src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,27 @@ public static string CreateHtml(MarkdownDocument document)
var h1 = document.Descendants<HeadingBlock>().FirstOrDefault(h => h.Level == 1);
if (h1 is not null)
_ = document.Remove(h1);
return document.ToHtml(MarkdownParser.Pipeline);

var html = document.ToHtml(MarkdownParser.Pipeline);
return InsertFootnotesHeading(html);
}

private static string InsertFootnotesHeading(string html)
{
const string footnotesContainer = "<div class=\"footnotes\">";

var containerIndex = html.IndexOf(footnotesContainer, StringComparison.Ordinal);
if (containerIndex < 0)
return html;

var hrIndex = html.IndexOf("<hr", containerIndex, StringComparison.Ordinal);
if (hrIndex < 0)
return html;

var endOfHr = html.IndexOf('>', hrIndex);
if (endOfHr < 0)
return html;

return html.Insert(endOfHr + 1, "\n<h4>Footnotes</h4>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public void Setup(MarkdownPipelineBuilder pipeline)
// Insert the parser before any other parsers
_ = pipeline.BlockParsers.InsertBefore<ThematicBreakParser>(new DirectiveBlockParser());
}

_ = pipeline.BlockParsers.Replace<ParagraphBlockParser>(new DirectiveParagraphParser());

// Plug the inline parser for CustomContainerInline
Expand Down
10 changes: 6 additions & 4 deletions src/Elastic.Markdown/Myst/MarkdownParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ private static MarkdownPipeline MinimalPipeline
return field;
var builder = new MarkdownPipelineBuilder()
.UseYamlFrontMatter()
.UseFootnotes() // Must match Pipeline to avoid inconsistent footnote handling
.UseInlineAnchors()
.UseHeadingsWithSlugs()
.UseDirectives();
Expand All @@ -153,6 +154,7 @@ public static MarkdownPipeline Pipeline
var builder = new MarkdownPipelineBuilder()
.UseInlineAnchors()
.UsePreciseSourceLocation()
.UseFootnotes() // Must be before UseDiagnosticLinks to ensure FootnoteLinkParser is inserted correctly
.UseDiagnosticLinks()
.UseHeadingsWithSlugs()
.UseEmphasisExtras(EmphasisExtraOptions.Default)
Expand All @@ -165,10 +167,10 @@ public static MarkdownPipeline Pipeline
.UseYamlFrontMatter()
.UseGridTables()
.UsePipeTables()
.UseDirectives()
.UseDefinitionLists()
.UseEnhancedCodeBlocks()
.UseHtmxLinkInlineRenderer()
.UseDirectives()
.UseDefinitionLists()
.UseEnhancedCodeBlocks()
.UseHtmxLinkInlineRenderer()
.DisableHtml()
.UseSpaceNormalizer()
.UseHardBreaks();
Expand Down
Loading
Loading