Skip to content

Commit 81059d9

Browse files
authored
Add footnotes (#2275)
* Add footnotes * Add footnotes to nav * Fix footnotes rendering * Edits * Remove debug HttpContext parameters * Remove decoration * Fix CSS * Format CSS * Formatting * Improve CSS * Visual tweaks * Fix tests * Margins * Prettify CSS * Edit multiple example * Fix docs and clickable area
1 parent 9aaf192 commit 81059d9

File tree

9 files changed

+654
-7
lines changed

9 files changed

+654
-7
lines changed

docs/_docset.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ toc:
9797
- file: definition-lists.md
9898
- file: example_blocks.md
9999
- file: file_inclusion.md
100+
- file: footnotes.md
100101
- file: frontmatter.md
101102
- file: icons.md
102103
- file: images.md

docs/syntax/footnotes.md

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Footnotes
2+
3+
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.
4+
5+
## Plain paragraph test
6+
7+
This is a plain footnote test.[^plain] No directives involved.
8+
9+
[^plain]: This footnote is in a plain paragraph, outside any directive.
10+
11+
## Basic footnotes
12+
13+
::::{tab-set}
14+
15+
:::{tab-item} Output
16+
17+
Here's a simple footnote[^1] and another one.[^2]
18+
19+
You can also use named identifiers[^my-note] which can be more descriptive in your source files.
20+
21+
:::
22+
23+
:::{tab-item} Markdown
24+
25+
```markdown
26+
Here's a simple footnote[^1] and another one.[^2]
27+
28+
You can also use named identifiers[^my-note] which can be more descriptive in your source files.
29+
30+
[^1]: This is the first footnote.
31+
[^2]: This is the second footnote.
32+
[^my-note]: This footnote uses a named identifier instead of a number.
33+
```
34+
35+
:::
36+
37+
::::
38+
39+
[^1]: This is the first footnote.
40+
[^2]: This is the second footnote.
41+
[^my-note]: This footnote uses a named identifier instead of a number.
42+
43+
## Multiple references
44+
45+
You can reference the same footnote multiple times throughout your document.
46+
47+
::::{tab-set}
48+
49+
:::{tab-item} Output
50+
51+
First reference to the concept.[^concept] Some more text here.
52+
53+
...
54+
55+
Second reference to the same concept.[^concept]
56+
57+
:::
58+
59+
:::{tab-item} Markdown
60+
61+
```markdown
62+
First reference to the concept.[^concept] Some more text here.
63+
64+
...
65+
66+
Second reference to the same concept.[^concept]
67+
68+
[^concept]: This explains an important concept that's referenced multiple times.
69+
```
70+
71+
:::
72+
73+
::::
74+
75+
[^concept]: This explains an important concept that's referenced multiple times.
76+
77+
## Complex footnotes
78+
79+
Footnotes can contain multiple paragraphs, lists, blockquotes, and code blocks. Subsequent content must be indented to be included in the footnote.
80+
81+
::::{tab-set}
82+
83+
:::{tab-item} Output
84+
85+
This has a complex footnote.[^complex]
86+
87+
:::
88+
89+
:::{tab-item} Markdown
90+
91+
```markdown
92+
This has a complex footnote.[^complex]
93+
94+
[^complex]: This footnote has multiple elements.
95+
96+
It has multiple paragraphs with detailed explanations.
97+
98+
> This is a blockquote inside the footnote.
99+
> It can span multiple lines.
100+
101+
- List item one
102+
- List item two
103+
- List item three
104+
105+
You can even include code:
106+
107+
```python
108+
def example():
109+
return "Hello from footnote"
110+
```
111+
```
112+
113+
:::
114+
115+
::::
116+
117+
[^complex]: This footnote has multiple elements.
118+
119+
It has multiple paragraphs with detailed explanations.
120+
121+
> This is a blockquote inside the footnote.
122+
> It can span multiple lines.
123+
124+
- List item one
125+
- List item two
126+
- List item three
127+
128+
You can even include code:
129+
130+
```python
131+
def example():
132+
return "Hello from footnote"
133+
```
134+
135+
## Footnote placement
136+
137+
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.
138+
139+
::::{tab-set}
140+
141+
:::{tab-item} Output
142+
143+
Here's text with a footnote.[^early]
144+
145+
More content here, and another footnote.[^late]
146+
147+
Even more content in between.
148+
149+
:::
150+
151+
:::{tab-item} Markdown
152+
153+
```markdown
154+
Here's text with a footnote.[^early]
155+
156+
[^early]: This footnote is defined right after the reference.
157+
158+
More content here, and another footnote.[^late]
159+
160+
Even more content in between.
161+
162+
[^late]: This footnote is defined later in the document.
163+
```
164+
165+
:::
166+
167+
::::
168+
169+
[^early]: This footnote is defined right after the reference.
170+
[^late]: This footnote is defined later in the document.
171+
172+
## Best practices
173+
174+
### Use descriptive identifiers
175+
176+
While you can use simple numbers like `[^1]`, descriptive identifiers like `[^api-note]` make your source more maintainable.
177+
178+
### Keep footnotes focused
179+
180+
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.
181+
182+
### Consider alternatives
183+
184+
Before adding footnotes, consider whether:
185+
- The information is important enough to be in the main text.
186+
- A link to external documentation would be more appropriate.
187+
- An admonition (note, warning, etc.) would be clearer.
188+
189+
### Numbering
190+
191+
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.

docs/syntax/frontmatter.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ In the frontmatter block, you can define the following fields:
88

99
```yaml
1010
---
11-
navigation_title: This is the navigation title <1>
12-
description: This is a description of the page <2>
11+
navigation_title: This is the navigation title. <1>
12+
description: This is a description of the page. <2>
1313
applies_to: <3>
1414
serverless: all
1515
products: <4>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* Footnotes styling */
2+
3+
.footnotes h4 {
4+
@apply mt-8 mb-10;
5+
}
6+
7+
.footnotes ol {
8+
/* Extra top margin so the gap after the "Footnotes" heading
9+
is more prominent than the gap between individual items. */
10+
@apply mt-4 list-decimal pl-6;
11+
}
12+
13+
.footnotes li {
14+
/* Tighter spacing between items than between heading and list. */
15+
@apply text-ink-light mt-2 text-sm leading-relaxed;
16+
}
17+
18+
.footnotes li p {
19+
@apply inline;
20+
}
21+
22+
.footnotes li p:not(:last-child) {
23+
@apply mb-3;
24+
}
25+
26+
/* Footnote reference (superscript link in text) */
27+
.footnote-ref {
28+
@apply text-blue-elastic hover:text-blue-elastic-100 font-semibold no-underline;
29+
text-decoration: none !important;
30+
display: inline-block;
31+
padding: 0.15em 0.2em;
32+
margin: -0.15em 0;
33+
border-radius: 0.15em;
34+
}
35+
36+
.footnote-ref sup {
37+
@apply font-sans;
38+
text-decoration: none !important;
39+
}
40+
41+
/* Back reference (return arrow in footnote) */
42+
.footnote-back-ref {
43+
@apply text-blue-elastic hover:text-blue-elastic-100 no-underline;
44+
font-family: monospace;
45+
text-decoration: none !important;
46+
vertical-align: super;
47+
font-size: x-small;
48+
}
49+
50+
/* Multiple back references */
51+
.footnote-back-ref + .footnote-back-ref {
52+
@apply ml-1;
53+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@import './markdown/dropdown.css';
1717
@import './markdown/table.css';
1818
@import './markdown/definition-list.css';
19+
@import './markdown/footnotes.css';
1920
@import './markdown/images.css';
2021
@import './markdown/math.css';
2122
@import './markdown/image-carousel.css';

src/Elastic.Markdown/IO/MarkdownFile.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,27 @@ public static string CreateHtml(MarkdownDocument document)
367367
var h1 = document.Descendants<HeadingBlock>().FirstOrDefault(h => h.Level == 1);
368368
if (h1 is not null)
369369
_ = document.Remove(h1);
370-
return document.ToHtml(MarkdownParser.Pipeline);
370+
371+
var html = document.ToHtml(MarkdownParser.Pipeline);
372+
return InsertFootnotesHeading(html);
373+
}
374+
375+
private static string InsertFootnotesHeading(string html)
376+
{
377+
const string footnotesContainer = "<div class=\"footnotes\">";
378+
379+
var containerIndex = html.IndexOf(footnotesContainer, StringComparison.Ordinal);
380+
if (containerIndex < 0)
381+
return html;
382+
383+
var hrIndex = html.IndexOf("<hr", containerIndex, StringComparison.Ordinal);
384+
if (hrIndex < 0)
385+
return html;
386+
387+
var endOfHr = html.IndexOf('>', hrIndex);
388+
if (endOfHr < 0)
389+
return html;
390+
391+
return html.Insert(endOfHr + 1, "\n<h4>Footnotes</h4>");
371392
}
372393
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public void Setup(MarkdownPipelineBuilder pipeline)
3535
// Insert the parser before any other parsers
3636
_ = pipeline.BlockParsers.InsertBefore<ThematicBreakParser>(new DirectiveBlockParser());
3737
}
38+
3839
_ = pipeline.BlockParsers.Replace<ParagraphBlockParser>(new DirectiveParagraphParser());
3940

4041
// Plug the inline parser for CustomContainerInline

src/Elastic.Markdown/Myst/MarkdownParser.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ private static MarkdownPipeline MinimalPipeline
132132
return field;
133133
var builder = new MarkdownPipelineBuilder()
134134
.UseYamlFrontMatter()
135+
.UseFootnotes() // Must match Pipeline to avoid inconsistent footnote handling
135136
.UseInlineAnchors()
136137
.UseHeadingsWithSlugs()
137138
.UseDirectives();
@@ -153,6 +154,7 @@ public static MarkdownPipeline Pipeline
153154
var builder = new MarkdownPipelineBuilder()
154155
.UseInlineAnchors()
155156
.UsePreciseSourceLocation()
157+
.UseFootnotes() // Must be before UseDiagnosticLinks to ensure FootnoteLinkParser is inserted correctly
156158
.UseDiagnosticLinks()
157159
.UseHeadingsWithSlugs()
158160
.UseEmphasisExtras(EmphasisExtraOptions.Default)
@@ -165,10 +167,10 @@ public static MarkdownPipeline Pipeline
165167
.UseYamlFrontMatter()
166168
.UseGridTables()
167169
.UsePipeTables()
168-
.UseDirectives()
169-
.UseDefinitionLists()
170-
.UseEnhancedCodeBlocks()
171-
.UseHtmxLinkInlineRenderer()
170+
.UseDirectives()
171+
.UseDefinitionLists()
172+
.UseEnhancedCodeBlocks()
173+
.UseHtmxLinkInlineRenderer()
172174
.DisableHtml()
173175
.UseSpaceNormalizer()
174176
.UseHardBreaks();

0 commit comments

Comments
 (0)