Implemented block quotes
This commit is contained in:
142
Render.go
142
Render.go
@ -10,30 +10,29 @@ var (
|
||||
headerEnd = []string{"</h1>", "</h2>", "</h3>", "</h4>", "</h5>", "</h6>"}
|
||||
)
|
||||
|
||||
type renderer struct {
|
||||
out strings.Builder
|
||||
paragraphLevel int
|
||||
quoteLevel int
|
||||
}
|
||||
|
||||
// Render creates HTML from the supplied markdown text.
|
||||
func Render(markdown string) string {
|
||||
var (
|
||||
out = strings.Builder{}
|
||||
paragraph = strings.Builder{}
|
||||
r renderer
|
||||
i = 0
|
||||
lineStart = 0
|
||||
)
|
||||
|
||||
flush := func() {
|
||||
if paragraph.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
out.WriteString("<p>")
|
||||
writeText(&out, paragraph.String())
|
||||
out.WriteString("</p>")
|
||||
paragraph.Reset()
|
||||
}
|
||||
|
||||
for {
|
||||
if i > len(markdown) {
|
||||
flush()
|
||||
return out.String()
|
||||
r.closeParagraphs()
|
||||
|
||||
for range r.quoteLevel {
|
||||
r.out.WriteString("</blockquote>")
|
||||
}
|
||||
|
||||
return r.out.String()
|
||||
}
|
||||
|
||||
if i != len(markdown) && markdown[i] != '\n' {
|
||||
@ -45,33 +44,74 @@ func Render(markdown string) string {
|
||||
lineStart = i + 1
|
||||
i++
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "#"):
|
||||
flush()
|
||||
space := strings.IndexByte(line, ' ')
|
||||
|
||||
if space > 0 && space <= 6 {
|
||||
out.WriteString(headerStart[space-1])
|
||||
writeText(&out, line[space+1:])
|
||||
out.WriteString(headerEnd[space-1])
|
||||
}
|
||||
|
||||
default:
|
||||
if len(line) == 0 {
|
||||
flush()
|
||||
continue
|
||||
}
|
||||
|
||||
if paragraph.Len() > 0 {
|
||||
paragraph.WriteByte(' ')
|
||||
}
|
||||
|
||||
paragraph.WriteString(line)
|
||||
}
|
||||
r.processLine(line)
|
||||
}
|
||||
}
|
||||
|
||||
func writeText(out *strings.Builder, text string) {
|
||||
func (r *renderer) processLine(line string) {
|
||||
newQuoteLevel := 0
|
||||
|
||||
for strings.HasPrefix(line, ">") {
|
||||
line = strings.TrimSpace(line[1:])
|
||||
newQuoteLevel++
|
||||
}
|
||||
|
||||
if newQuoteLevel > r.quoteLevel {
|
||||
r.closeParagraphs()
|
||||
|
||||
for range newQuoteLevel - r.quoteLevel {
|
||||
r.out.WriteString("<blockquote>")
|
||||
}
|
||||
} else if newQuoteLevel < r.quoteLevel {
|
||||
r.closeParagraphs()
|
||||
|
||||
for range r.quoteLevel - newQuoteLevel {
|
||||
r.out.WriteString("</blockquote>")
|
||||
}
|
||||
}
|
||||
|
||||
r.quoteLevel = newQuoteLevel
|
||||
|
||||
if strings.HasPrefix(line, "#") {
|
||||
r.closeParagraphs()
|
||||
space := strings.IndexByte(line, ' ')
|
||||
|
||||
if space > 0 && space <= 6 {
|
||||
r.out.WriteString(headerStart[space-1])
|
||||
r.writeText(line[space+1:])
|
||||
r.out.WriteString(headerEnd[space-1])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
r.closeParagraphs()
|
||||
return
|
||||
}
|
||||
|
||||
if r.paragraphLevel == 0 {
|
||||
r.out.WriteString("<p>")
|
||||
r.paragraphLevel++
|
||||
r.writeText(line)
|
||||
return
|
||||
}
|
||||
|
||||
r.out.WriteByte(' ')
|
||||
r.writeText(line)
|
||||
}
|
||||
|
||||
// closeParagraphs closes open paragraphs.
|
||||
func (r *renderer) closeParagraphs() {
|
||||
for range r.paragraphLevel {
|
||||
r.out.WriteString("</p>")
|
||||
}
|
||||
|
||||
r.paragraphLevel = 0
|
||||
}
|
||||
|
||||
// writeText converts inline markdown to HTML.
|
||||
func (r *renderer) writeText(markdown string) {
|
||||
var (
|
||||
i = 0
|
||||
tokenStart = 0
|
||||
@ -85,16 +125,16 @@ func writeText(out *strings.Builder, text string) {
|
||||
)
|
||||
|
||||
for {
|
||||
if i == len(text) {
|
||||
out.WriteString(html.EscapeString(text[tokenStart:]))
|
||||
if i == len(markdown) {
|
||||
r.out.WriteString(html.EscapeString(markdown[tokenStart:]))
|
||||
return
|
||||
}
|
||||
|
||||
c := text[i]
|
||||
c := markdown[i]
|
||||
|
||||
switch c {
|
||||
case '[':
|
||||
out.WriteString(html.EscapeString(text[tokenStart:i]))
|
||||
r.out.WriteString(html.EscapeString(markdown[tokenStart:i]))
|
||||
tokenStart = i
|
||||
textStart = i
|
||||
case ']':
|
||||
@ -109,14 +149,14 @@ func writeText(out *strings.Builder, text string) {
|
||||
parentheses--
|
||||
|
||||
if parentheses == 0 && textStart >= 0 && textEnd >= 0 && urlStart >= 0 {
|
||||
linkText := text[textStart+1 : textEnd]
|
||||
linkURL := text[urlStart+1 : i]
|
||||
linkText := markdown[textStart+1 : textEnd]
|
||||
linkURL := markdown[urlStart+1 : i]
|
||||
|
||||
out.WriteString("<a href=\"")
|
||||
out.WriteString(formatURL(linkURL))
|
||||
out.WriteString("\">")
|
||||
out.WriteString(html.EscapeString(linkText))
|
||||
out.WriteString("</a>")
|
||||
r.out.WriteString("<a href=\"")
|
||||
r.out.WriteString(sanitizeURL(linkURL))
|
||||
r.out.WriteString("\">")
|
||||
r.out.WriteString(html.EscapeString(linkText))
|
||||
r.out.WriteString("</a>")
|
||||
|
||||
textStart = -1
|
||||
textEnd = -1
|
||||
@ -130,7 +170,7 @@ func writeText(out *strings.Builder, text string) {
|
||||
}
|
||||
}
|
||||
|
||||
func formatURL(linkURL string) string {
|
||||
func sanitizeURL(linkURL string) string {
|
||||
if strings.HasPrefix(strings.ToLower(linkURL), "javascript:") {
|
||||
return ""
|
||||
}
|
||||
|
Reference in New Issue
Block a user