diff --git a/internal/markdown/markdown.go b/internal/markdown/markdown.go index b7fe25e..1d7e9e6 100644 --- a/internal/markdown/markdown.go +++ b/internal/markdown/markdown.go @@ -362,6 +362,7 @@ func init() { md = goldmark.New( goldmark.WithExtensions( extension.GFM, + extension.Footnote, highlighting.NewHighlighting( highlighting.WithStyle("github"), ), @@ -377,6 +378,15 @@ func init() { // Allow task list checkboxes generated by goldmark GFM policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input") policy.AllowAttrs("checked", "disabled").OnElements("input") + // Footnote support + policy.AllowElements("sup", "hr") + policy.AllowAttrs("id").Matching(regexp.MustCompile(`^fnref\d*:\d+$`)).OnElements("sup") + policy.AllowAttrs("id").Matching(regexp.MustCompile(`^fn:\d+$`)).OnElements("li") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^footnote-ref$`)).OnElements("a") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^footnote-backref$`)).OnElements("a") + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^footnotes$`)).OnElements("div") + policy.AllowAttrs("role").Matching(regexp.MustCompile(`^doc-(noteref|backlink|endnotes)$`)).OnElements("a", "div") + policy.AllowAttrs("href").Matching(regexp.MustCompile(`^#fn(ref\d*)?:\d+$`)).OnElements("a") } // ExtractMentions returns unique @usernames found in the raw markdown text. diff --git a/web/static/css/input.css b/web/static/css/input.css index 0c44115..fd393cc 100644 --- a/web/static/css/input.css +++ b/web/static/css/input.css @@ -34,6 +34,22 @@ pre.mermaid { overflow-x: auto; } +/* Footnote styling */ +.prose .footnotes { + border-top: 1px solid var(--color-gray-300); + margin-top: 1.5rem; + padding-top: 1rem; + font-size: 0.875rem; + color: var(--color-gray-600); +} +.prose sup a { + color: var(--color-blue-600); + text-decoration: none; +} +.prose sup a:hover { + text-decoration: underline; +} + /* Task list checkbox styling */ .prose input[type="checkbox"] { margin-right: 0.375rem;