130 lines
2.7 KiB
Go
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
|
|
}
|