hanayo/modules/bbcode/bbcode.go

230 lines
6.1 KiB
Go

// Package bbcode implements BBCode compiling for Hanayo.
package bbcode
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/frustra/bbcode"
"github.com/microcosm-cc/bluemonday"
)
var bbcodeCompiler = func() bbcode.Compiler {
compiler := bbcode.NewCompiler(true, true)
compiler.SetTag("list", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "ul"
style := node.GetOpeningTag().Value
switch style {
case "a":
out.Attrs["style"] = "list-style-type: lower-alpha;"
case "A":
out.Attrs["style"] = "list-style-type: upper-alpha;"
case "i":
out.Attrs["style"] = "list-style-type: lower-roman;"
case "I":
out.Attrs["style"] = "list-style-type: upper-roman;"
case "1":
out.Attrs["style"] = "list-style-type: decimal;"
default:
out.Attrs["style"] = "list-style-type: disc;"
}
if len(node.Children) == 0 {
out.AppendChild(bbcode.NewHTMLTag(""))
} else {
node.Info = []*bbcode.HTMLTag{out, out}
tags := node.Info.([]*bbcode.HTMLTag)
for _, child := range node.Children {
curr := tags[1]
curr.AppendChild(node.Compiler.CompileTree(child))
}
if len(tags[1].Children) > 0 {
last := tags[1].Children[len(tags[1].Children)-1]
if len(last.Children) > 0 && last.Children[len(last.Children)-1].Name == "br" {
last.Children[len(last.Children)-1] = bbcode.NewHTMLTag("")
}
} else {
tags[1].AppendChild(bbcode.NewHTMLTag(""))
}
}
return out, false
})
compiler.SetTag("*", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
parent := node.Parent
for parent != nil {
if parent.ID == bbcode.OPENING_TAG && parent.GetOpeningTag().Name == "list" {
out := bbcode.NewHTMLTag("")
out.Name = "li"
tags := parent.Info.([]*bbcode.HTMLTag)
if len(tags[1].Children) > 0 {
last := tags[1].Children[len(tags[1].Children)-1]
if len(last.Children) > 0 && last.Children[len(last.Children)-1].Name == "br" {
last.Children[len(last.Children)-1] = bbcode.NewHTMLTag("")
}
} else {
tags[1].AppendChild(bbcode.NewHTMLTag(""))
}
tags[1] = out
tags[0].AppendChild(out)
if len(parent.Children) == 0 {
out.AppendChild(bbcode.NewHTMLTag(""))
} else {
for _, child := range node.Children {
curr := tags[1]
curr.AppendChild(node.Compiler.CompileTree(child))
}
}
if node.ClosingTag != nil {
tag := bbcode.NewHTMLTag(node.ClosingTag.Raw)
bbcode.InsertNewlines(tag)
out.AppendChild(tag)
}
return nil, false
}
parent = parent.Parent
}
return bbcode.DefaultTagCompiler(node)
})
compiler.SetTag("youtube", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
var youtubeID string
content := bbcode.CompileText(node)
youtubeLink, err := url.Parse(content)
if err != nil {
youtubeID = content
} else {
youtubeID = youtubeLink.Query().Get("v")
if youtubeID == "" {
youtubeID = content
}
}
tag := bbcode.NewHTMLTag("")
tag.Name = "iframe"
tag.Attrs = map[string]string{
"style": "width: 100%; max-height: 100%;",
"src": "https://www.youtube.com/embed/" + youtubeID,
"frameborder": "0",
"allowfullscreen": "",
}
tag.AppendChild(nil)
container := bbcode.NewHTMLTag("")
container.Name = "div"
container.Attrs["class"] = "youtube video container"
container.AppendChild(tag)
return container, false
})
compiler.SetTag("left", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: left;"
return out, true
})
compiler.SetTag("right", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = "text-align: right;"
return out, true
})
compiler.SetTag("container", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
args := node.GetOpeningTag().Args
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["style"] = ""
out.Attrs["class"] = ""
if _, err := strconv.Atoi(args["width"]); err == nil {
out.Attrs["style"] += "width: " + args["width"] + "px;"
}
if args["compact"] != "" {
out.Attrs["class"] += "compact-container "
}
if args["center"] != "" {
out.Attrs["style"] += "margin: 0 auto;"
}
return out, true
})
compiler.SetTag("hr", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "div"
out.Attrs["class"] = "ui divider"
out.AppendChild(nil)
return out, false
})
compiler.SetTag("email", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "a"
val := node.GetOpeningTag().Value
if val == "" {
out.Attrs["href"] = "mailto:" + bbcode.CompileText(node)
out.AppendChild(bbcode.NewHTMLTag(bbcode.CompileText(node)))
return out, false
}
out.Attrs["href"] = "mailto:" + val
return out, true
})
compiler.SetTag("size", func(node *bbcode.BBCodeNode) (*bbcode.HTMLTag, bool) {
out := bbcode.NewHTMLTag("")
out.Name = "span"
if size, err := strconv.Atoi(node.GetOpeningTag().Value); err == nil && size > 0 {
if size > 15 {
size = 15
}
out.Attrs["style"] = fmt.Sprintf("font-size: %dpt; line-height: %[1]dpt;", size*6)
}
return out, true
})
return compiler
}()
var emojis = []string{
"peppy",
"barney",
"akerino",
"foka",
"kappy",
"creepypeppy",
"peppyfiero",
"djpeppy",
"kappa",
}
var emojiReplacer = func() *strings.Replacer {
var list []string
for _, e := range emojis {
list = append(list, ":"+e+":", "[img=/static/emotes/"+e+".png]:"+e+":[/img]")
}
return strings.NewReplacer(list...)
}()
// Compile takes some BBCode and converts it into safe HTML output.
func Compile(s string) string {
s = emojiReplacer.Replace(s)
s = strings.TrimSpace(s)
return mondaySanitise(bbcodeCompiler.Compile(s))
}
var policy = func() *bluemonday.Policy {
p := bluemonday.UGCPolicy()
p.AllowAttrs("style", "class").Globally()
p.AllowElements("iframe")
p.AllowAttrs("style", "src", "frameborder", "allowfullscreen").OnElements("iframe")
return p
}()
func mondaySanitise(source string) string {
return policy.Sanitize(source)
}