Footnotes

This commit is contained in:
2026-07-14 18:59:50 +02:00
parent 3f680b5e22
commit 91671995d3
5 changed files with 120 additions and 3 deletions
+5 -2
View File
@@ -26,12 +26,12 @@ The **VARIABLE** is $(variable)
- List item 1 - List item 1
. List item 2 . List item 2
1. Ordered list item 1. Ordered list item [#1]
> This is a sample blockquote > This is a sample blockquote
> Bon apetit! > Bon apetit!
Text under a blockquote Text under a blockquote [#named]
**This *is* bold *and* italics *mixed** TOGETHER* **This *is* bold *and* italics *mixed** TOGETHER*
@@ -40,3 +40,6 @@ Text under a blockquote
| This << | is >> | a ^^ | | This << | is >> | a ^^ |
| table | row | with | | table | row | with |
| some | ***content*** | in | | some | ***content*** | in |
[&1] Ordered list item 2 not included
[&named] This is the definition for a named footnote
+19
View File
@@ -5,7 +5,12 @@ import "core:fmt"
print_html :: proc(token: ^Token) { print_html :: proc(token: ^Token) {
switch token.type { switch token.type {
case TokenType.Root: case TokenType.Root:
has_footnotes := false
for child in token.children { for child in token.children {
if child.type == .FootnoteDef && !has_footnotes {
fmt.print("<hr class=\"footnotes-sep\">\n")
has_footnotes = true
}
print_html(child) print_html(child)
} }
@@ -168,6 +173,20 @@ print_html :: proc(token: ^Token) {
} }
fmt.print("</div>") fmt.print("</div>")
case TokenType.FootnoteRef:
if id, ok := token.value.(string); ok {
fmt.printf("<a href=\"#fn-%s\" id=\"ref-%s\"><sup>%s</sup></a>", id, id, id)
}
case TokenType.FootnoteDef:
if id, ok := token.value.(string); ok {
fmt.printf("<div class=\"footnote\" id=\"fn-%s\"><a href=\"#ref-%s\">^</a> ", id, id)
for child in token.children {
print_html(child)
}
fmt.print("</div>\n")
}
case TokenType.BreakLine: case TokenType.BreakLine:
fmt.print("<br>\n") fmt.print("<br>\n")
+4
View File
@@ -55,6 +55,10 @@ print_token :: proc(token: ^Token, depth: int = 0) {
type_str = "Right" type_str = "Right"
case TokenType.Middle: case TokenType.Middle:
type_str = "Middle" type_str = "Middle"
case TokenType.FootnoteDef:
type_str = "FootnoteDef"
case TokenType.FootnoteRef:
type_str = "FootnoteRef"
} }
value_str := "" value_str := ""
+78
View File
@@ -24,6 +24,8 @@ TokenType :: enum {
Left, Left,
Right, Right,
Middle, Middle,
FootnoteDef,
FootnoteRef,
} }
TokenValue :: union { TokenValue :: union {
@@ -141,6 +143,18 @@ parse_header :: proc(line: string) -> (int, string) {
return lvl, line[lvl + 1:] return lvl, line[lvl + 1:]
} }
parse_footnote_def :: proc(line: string) -> (string, string, bool) {
if strings.has_prefix(line, "[&") {
idx := strings.index_byte(line, ']')
if idx != -1 {
id := line[2:idx]
text := strings.trim_space(line[idx + 1:])
return id, text, true
}
}
return "", "", false
}
StyleToggle :: struct { StyleToggle :: struct {
amount: int, amount: int,
type: TokenType, type: TokenType,
@@ -240,6 +254,46 @@ pass_through_children :: proc(token: ^Token) {
parse_inline_style(token, '-', []StyleToggle{{2, .Strikethrough}}) parse_inline_style(token, '-', []StyleToggle{{2, .Strikethrough}})
} }
parse_inline_footnotes :: proc(token: ^Token) {
if token.children != nil {
for i := 0; i < len(token.children); i += 1 {
parse_inline_footnotes(token.children[i])
}
}
if token.type == .Text {
str, ok := token.value.(string)
if !ok {
return
}
start_idx := strings.index(str, "[#")
if start_idx == -1 {
return
}
end_idx := strings.index_byte(str[start_idx:], ']')
if end_idx == -1 {
return
}
end_idx += start_idx
id := str[start_idx + 2:end_idx]
token.value = nil
if start_idx > 0 {
push_token(token, .Text, str[:start_idx])
}
push_token(token, .FootnoteRef, id)
if end_idx + 1 < len(str) {
rem := push_token(token, .Text, str[end_idx + 1:])
parse_inline_footnotes(rem)
}
}
}
add_breaklines :: proc(token: ^Token) { add_breaklines :: proc(token: ^Token) {
if token.children == nil { if token.children == nil {
return return
@@ -293,6 +347,13 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
continue continue
} }
if id, ftext, ok := parse_footnote_def(line); ok {
current_token = push_token(&root, TokenType.FootnoteDef, id)
push_text(current_token, ftext)
current_token = &root
continue
}
if strings.has_prefix(text, "> ") { if strings.has_prefix(text, "> ") {
if current_token.type != TokenType.Blockquote { if current_token.type != TokenType.Blockquote {
current_token = push_token(&root, TokenType.Blockquote, nil) current_token = push_token(&root, TokenType.Blockquote, nil)
@@ -381,7 +442,24 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
for token in root.children { for token in root.children {
pass_through_children(token) pass_through_children(token)
parse_inline_footnotes(token)
} }
// FOURTH PASS
footnotes: [dynamic]^Token
new_root_children: [dynamic]^Token
for child in root.children {
if child.type == .FootnoteDef {
append(&footnotes, child)
} else {
append(&new_root_children, child)
}
}
for child in footnotes {
append(&new_root_children, child)
}
root.children = new_root_children
return root return root
} }
+13
View File
@@ -59,3 +59,16 @@ tr.table-sep td {
padding: 0; padding: 0;
height: 0; height: 0;
} }
hr.footnotes-sep {
border: 0;
border-top: 1px solid #444;
margin: 2em 0 1em 0;
}
.footnote {
font-size: 14px;
color: #a0a0a0;
margin-bottom: 0.5em;
}
.footnote a {
text-decoration: none;
}