130 lines
2.7 KiB
Go

package markdown
import (
"fmt"
"html"
"strings"
)
// MarkdownToHTML converts a basic subset of markdown to HTML, for
// use in the news posts, forums, or user blurbs.
func MarkdownToHTML(s string) string {
// Escape HTML entities first to sanitize user input
s = html.EscapeString(s)
// Handle headers first (line-based)
lines := strings.Split(s, "\n")
for i, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "### ") {
lines[i] = "<h3>" + line[4:] + "</h3>"
} else if strings.HasPrefix(line, "## ") {
lines[i] = "<h2>" + line[3:] + "</h2>"
} else if strings.HasPrefix(line, "# ") {
lines[i] = "<h1>" + line[2:] + "</h1>"
}
}
s = strings.Join(lines, "\n")
// Handle code spans before other formatting
s = replaceCodeSpans(s)
// Handle links [text](url)
s = replaceLinks(s)
// Handle bold and italic
s = replaceBoldItalic(s)
// Handle line breaks last
s = strings.ReplaceAll(s, "\n", "<br>")
return `<div class="md-lib">` + s + "</div>"
}
// MarkdownTemplateFunc is a TemplateFunc-compatible wrapper for MarkdownToHTML
func MarkdownTemplateFunc(args ...any) any {
if len(args) == 0 {
return ""
}
// Convert first argument to string
input := fmt.Sprintf("%v", args[0])
return MarkdownToHTML(input)
}
func replaceCodeSpans(s string) string {
for {
start := strings.Index(s, "`")
if start == -1 {
break
}
end := strings.Index(s[start+1:], "`")
if end == -1 {
break
}
end += start + 1
code := s[start+1 : end]
s = s[:start] + "<code>" + code + "</code>" + s[end+1:]
}
return s
}
func replaceLinks(s string) string {
for {
linkStart := strings.Index(s, "[")
if linkStart == -1 {
break
}
linkEnd := strings.Index(s[linkStart:], "](")
if linkEnd == -1 {
break
}
linkEnd += linkStart
urlStart := linkEnd + 2
urlEnd := strings.Index(s[urlStart:], ")")
if urlEnd == -1 {
break
}
urlEnd += urlStart
text := s[linkStart+1 : linkEnd]
url := s[urlStart:urlEnd]
s = s[:linkStart] + `<a href="` + url + `">` + text + "</a>" + s[urlEnd+1:]
}
return s
}
func replaceBoldItalic(s string) string {
// Handle bold first
for strings.Contains(s, "**") {
first := strings.Index(s, "**")
if first == -1 {
break
}
second := strings.Index(s[first+2:], "**")
if second == -1 {
break
}
second += first + 2
text := s[first+2 : second]
s = s[:first] + "<b>" + text + "</b>" + s[second+2:]
}
// Handle italic
for strings.Contains(s, "*") {
first := strings.Index(s, "*")
if first == -1 {
break
}
second := strings.Index(s[first+1:], "*")
if second == -1 {
break
}
second += first + 1
text := s[first+1 : second]
s = s[:first] + "<i>" + text + "</i>" + s[second+1:]
}
return s
}