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 
Answered %s by %s
to the bottom of the answer answerBodyHTML += fmt.Sprintf(`
Answered at %s by %s
`, 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)
(.+?)
`) + +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 }} +