diff --git a/go.mod b/go.mod index 121a398..f660b95 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.19 require ( github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.8.2 // indirect github.com/go-playground/locales v0.14.0 // indirect diff --git a/go.sum b/go.sum index 49f79fc..5b25cc9 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,15 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= diff --git a/public/syntax.css b/public/syntax.css new file mode 100644 index 0000000..320e3ed --- /dev/null +++ b/public/syntax.css @@ -0,0 +1,60 @@ +/* Background */ .bg { color: #f8f8f2; background-color: #272822; } +/* Error */ .err { color: #960050; background-color: #1e0010 } +/* LineTableTD */ .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } +/* LineTable */ .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } +/* LineHighlight */ .hl { background-color: #3c3d38 } +/* LineNumbersTable */ .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* LineNumbers */ .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* Line */ .line { display: flex; } +/* Keyword */ .k { color: #66d9ef } +/* KeywordConstant */ .kc { color: #66d9ef } +/* KeywordDeclaration */ .kd { color: #66d9ef } +/* KeywordNamespace */ .kn { color: #f92672 } +/* KeywordPseudo */ .kp { color: #66d9ef } +/* KeywordReserved */ .kr { color: #66d9ef } +/* KeywordType */ .kt { color: #66d9ef } +/* NameAttribute */ .na { color: #a6e22e } +/* NameClass */ .nc { color: #a6e22e } +/* NameConstant */ .no { color: #66d9ef } +/* NameDecorator */ .nd { color: #a6e22e } +/* NameException */ .ne { color: #a6e22e } +/* NameFunction */ .nf { color: #a6e22e } +/* NameOther */ .nx { color: #a6e22e } +/* NameTag */ .nt { color: #f92672 } +/* Literal */ .l { color: #ae81ff } +/* LiteralDate */ .ld { color: #e6db74 } +/* LiteralString */ .s { color: #e6db74 } +/* LiteralStringAffix */ .sa { color: #e6db74 } +/* LiteralStringBacktick */ .sb { color: #e6db74 } +/* LiteralStringChar */ .sc { color: #e6db74 } +/* LiteralStringDelimiter */ .dl { color: #e6db74 } +/* LiteralStringDoc */ .sd { color: #e6db74 } +/* LiteralStringDouble */ .s2 { color: #e6db74 } +/* LiteralStringEscape */ .se { color: #ae81ff } +/* LiteralStringHeredoc */ .sh { color: #e6db74 } +/* LiteralStringInterpol */ .si { color: #e6db74 } +/* LiteralStringOther */ .sx { color: #e6db74 } +/* LiteralStringRegex */ .sr { color: #e6db74 } +/* LiteralStringSingle */ .s1 { color: #e6db74 } +/* LiteralStringSymbol */ .ss { color: #e6db74 } +/* LiteralNumber */ .m { color: #ae81ff } +/* LiteralNumberBin */ .mb { color: #ae81ff } +/* LiteralNumberFloat */ .mf { color: #ae81ff } +/* LiteralNumberHex */ .mh { color: #ae81ff } +/* LiteralNumberInteger */ .mi { color: #ae81ff } +/* LiteralNumberIntegerLong */ .il { color: #ae81ff } +/* LiteralNumberOct */ .mo { color: #ae81ff } +/* Operator */ .o { color: #f92672 } +/* OperatorWord */ .ow { color: #f92672 } +/* Comment */ .c { color: #75715e } +/* CommentHashbang */ .ch { color: #75715e } +/* CommentMultiline */ .cm { color: #75715e } +/* CommentSingle */ .c1 { color: #75715e } +/* CommentSpecial */ .cs { color: #75715e } +/* CommentPreproc */ .cp { color: #75715e } +/* CommentPreprocFile */ .cpf { color: #75715e } +/* GenericDeleted */ .gd { color: #f92672 } +/* GenericEmph */ .ge { font-style: italic } +/* GenericInserted */ .gi { color: #a6e22e } +/* GenericStrong */ .gs { font-weight: bold } +/* GenericSubheading */ .gu { color: #75715e } diff --git a/src/routes/question.go b/src/routes/question.go index cd65a08..f2ee434 100644 --- a/src/routes/question.go +++ b/src/routes/question.go @@ -1,9 +1,11 @@ package routes import ( + "anonymousoverflow/src/utils" "fmt" "html/template" "os" + "regexp" "strings" "github.com/PuerkitoBio/goquery" @@ -11,6 +13,8 @@ import ( "github.com/go-resty/resty/v2" ) +var codeBlockRegex = regexp.MustCompile(`(?s)
(.+?)<\/code><\/pre>`)
+
func ViewQuestion(c *gin.Context) {
client := resty.New()
@@ -137,7 +141,17 @@ func ViewQuestion(c *gin.Context) {
// append (.+?)
`)
+
+func HighlightSyntaxViaContent(content string) (htmlOut string) {
+ content = html.UnescapeString(content)
+
+ fallbackOut := content
+
+ // identify the language
+ lexer := lexers.Analyse(content)
+ if lexer == nil {
+ // unable to identify, so just return the wrapped content
+ htmlOut = fallbackOut
+ return
+ }
+
+ style := styles.Get("xcode")
+ if style == nil {
+ style = styles.Fallback
+ }
+
+ formatter := formatters.Get("html")
+ if formatter == nil {
+ formatter = formatters.Fallback
+ }
+
+ iterator, err := lexer.Tokenise(nil, content)
+ if err != nil {
+ htmlOut = fallbackOut
+ return
+ }
+
+ b := bytes.NewBufferString("")
+ w := io.Writer(b)
+
+ err = formatter.Format(w, style, iterator)
+ if err != nil {
+ htmlOut = fallbackOut
+ return
+ }
+
+ // parse only the ...
part
+ htmlOut = b.String()
+ htmlOut = plainFormattedCodeRegex.FindString(htmlOut)
+
+ htmlOut = StripBlockTags(htmlOut)
+
+ // remove
+ htmlOut = strings.Replace(htmlOut, "", "", -1)
+
+ return
+}
+
+func StripBlockTags(content string) (result string) {
+ // strip all "" tags
+ content = strings.Replace(content, "", "", -1)
+ content = strings.Replace(content, "
", "", -1)
+ // and the
+ content = strings.Replace(content, "", "", -1)
+ content = strings.Replace(content, "
", "", -1)
+
+ result = content
+
+ return
+}
diff --git a/templates/question.html b/templates/question.html
index c7a11b0..a2d0aac 100644
--- a/templates/question.html
+++ b/templates/question.html
@@ -4,6 +4,7 @@
{{ .title }}
+
to the bottom of the answer
answerBodyHTML += fmt.Sprintf(` `, answerTimestamp, answerAuthorURL, answerAuthorName)
- // get the timestamp and author
+ // parse any code blocks and highlight them
+ answerCodeBlocks := codeBlockRegex.FindAllString(answerBodyHTML, -1)
+ for _, codeBlock := range answerCodeBlocks {
+ codeBlock = utils.StripBlockTags(codeBlock)
+
+ // syntax highlight
+ highlightedCodeBlock := utils.HighlightSyntaxViaContent(codeBlock)
+
+ // replace the code block with the highlighted code block
+ answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1)
+ }
answers = append(answers, template.HTML(answerBodyHTML))
})
diff --git a/src/utils/syntax.go b/src/utils/syntax.go
new file mode 100644
index 0000000..e4cdf58
--- /dev/null
+++ b/src/utils/syntax.go
@@ -0,0 +1,78 @@
+package utils
+
+import (
+ "bytes"
+ "html"
+ "io"
+ "regexp"
+ "strings"
+
+ "github.com/alecthomas/chroma/formatters"
+ "github.com/alecthomas/chroma/lexers"
+ "github.com/alecthomas/chroma/styles"
+)
+
+var plainFormattedCodeRegex = regexp.MustCompile(`(?s)