refactor: rely heavier on the templating engine

style: fix comments spacing on mobile
This commit is contained in:
httpjamesm 2022-12-29 13:06:14 -05:00
parent c0df07abad
commit 3b79efc83c
7 changed files with 151 additions and 46 deletions

View File

@ -102,6 +102,10 @@ img {
height: 2rem; height: 2rem;
} }
.comments {
margin-top: 1rem;
}
.comments-parent { .comments-parent {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -9,6 +9,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"anonymousoverflow/src/types"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
@ -39,10 +41,14 @@ func ViewQuestion(c *gin.Context) {
panic(err) panic(err)
} }
newFilteredQuestion := types.FilteredQuestion{}
questionTextParent := doc.Find("h1.fs-headline1") questionTextParent := doc.Find("h1.fs-headline1")
questionText := questionTextParent.Children().First().Text() questionText := questionTextParent.Children().First().Text()
newFilteredQuestion.Title = questionText
questionPostLayout := doc.Find("div.post-layout").First() questionPostLayout := doc.Find("div.post-layout").First()
questionBodyParent := doc.Find("div.s-prose") questionBodyParent := doc.Find("div.s-prose")
@ -52,7 +58,22 @@ func ViewQuestion(c *gin.Context) {
panic(err) panic(err)
} }
questionBodyParentHTML = utils.FindAndReturnComments(questionBodyParentHTML, questionPostLayout) newFilteredQuestion.Body = template.HTML(questionBodyParentHTML)
questionBodyText := questionBodyParent.Text()
// remove all whitespace to create the shortened body desc
shortenedBody := strings.TrimSpace(questionBodyText)
// remove all newlines
shortenedBody = strings.ReplaceAll(shortenedBody, "\n", " ")
// get the first 50 chars
shortenedBody = shortenedBody[:50]
newFilteredQuestion.ShortenedBody = shortenedBody
comments := utils.FindAndReturnComments(questionBodyParentHTML, questionPostLayout)
// parse any code blocks and highlight them // parse any code blocks and highlight them
answerCodeBlocks := questionCodeBlockRegex.FindAllString(questionBodyParentHTML, -1) answerCodeBlocks := questionCodeBlockRegex.FindAllString(questionBodyParentHTML, -1)
@ -87,6 +108,8 @@ func ViewQuestion(c *gin.Context) {
} }
}) })
newFilteredQuestion.Timestamp = questionTimestamp
userDetails := questionMetadata.Find("div.user-details") userDetails := questionMetadata.Find("div.user-details")
questionAuthor := "" questionAuthor := ""
@ -111,9 +134,14 @@ func ViewQuestion(c *gin.Context) {
} }
}) })
answers := []template.HTML{} newFilteredQuestion.AuthorName = questionAuthor
newFilteredQuestion.AuthorURL = questionAuthorURL
answers := []types.FilteredAnswer{}
doc.Find("div.answer").Each(func(i int, s *goquery.Selection) { doc.Find("div.answer").Each(func(i int, s *goquery.Selection) {
newFilteredAnswer := types.FilteredAnswer{}
postLayout := s.Find("div.post-layout") postLayout := s.Find("div.post-layout")
voteCell := postLayout.Find("div.votecell") voteCell := postLayout.Find("div.votecell")
answerCell := postLayout.Find("div.answercell") answerCell := postLayout.Find("div.answercell")
@ -122,13 +150,8 @@ func ViewQuestion(c *gin.Context) {
voteCount := html.EscapeString(voteCell.Find("div.js-vote-count").Text()) voteCount := html.EscapeString(voteCell.Find("div.js-vote-count").Text())
if s.HasClass("accepted-answer") { newFilteredAnswer.Upvotes = voteCount
// add <div class="answer-meta accepted">Accepted Answer</div> to the top of the answer newFilteredAnswer.IsAccepted = s.HasClass("accepted-answer")
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta accepted">Accepted Answer - %s Upvotes</div>`, voteCount) + answerBodyHTML
} else {
// add <div class="answer-meta">%s Upvotes</div> to the top of the answer
answerBodyHTML = fmt.Sprintf(`<div class="answer-meta">%s Upvotes</div>`, voteCount) + answerBodyHTML
}
answerFooter := s.Find("div.mt24") answerFooter := s.Find("div.mt24")
@ -156,8 +179,9 @@ func ViewQuestion(c *gin.Context) {
answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text()) answerTimestamp = html.EscapeString(s.Find("span.relativetime").Text())
}) })
// append <div class="answer-author">Answered %s by %s</div> to the bottom of the answer newFilteredAnswer.AuthorName = answerAuthorName
answerBodyHTML += fmt.Sprintf(`<div class="answer-author-parent"><div class="answer-author">Answered %s by <a href="https://stackoverflow.com/%s" target="_blank" rel="noopener noreferrer">%s</a></div></div>`, answerTimestamp, answerAuthorURL, answerAuthorName) newFilteredAnswer.AuthorURL = answerAuthorURL
newFilteredAnswer.Timestamp = answerTimestamp
// parse any code blocks and highlight them // parse any code blocks and highlight them
answerCodeBlocks := codeBlockRegex.FindAllString(answerBodyHTML, -1) answerCodeBlocks := codeBlockRegex.FindAllString(answerBodyHTML, -1)
@ -171,9 +195,12 @@ func ViewQuestion(c *gin.Context) {
answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1) answerBodyHTML = strings.Replace(answerBodyHTML, codeBlock, highlightedCodeBlock, 1)
} }
answerBodyHTML = utils.FindAndReturnComments(answerBodyHTML, postLayout) comments = utils.FindAndReturnComments(answerBodyHTML, postLayout)
answers = append(answers, template.HTML(answerBodyHTML)) newFilteredAnswer.Comments = comments
newFilteredAnswer.Body = template.HTML(answerBodyHTML)
answers = append(answers, newFilteredAnswer)
}) })
imagePolicy := "'self' https:" imagePolicy := "'self' https:"
@ -183,16 +210,11 @@ func ViewQuestion(c *gin.Context) {
} }
c.HTML(200, "question.html", gin.H{ c.HTML(200, "question.html", gin.H{
"title": questionText, "question": newFilteredQuestion,
"body": template.HTML(questionBodyParentHTML), "answers": answers,
"timestamp": questionTimestamp, "imagePolicy": imagePolicy,
"author": questionAuthor, "theme": c.MustGet("theme").(string),
"authorURL": questionAuthorURL, "currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
"answers": answers,
"imagePolicy": imagePolicy,
"shortenedBody": questionBodyParent.Text()[0:50],
"theme": c.MustGet("theme").(string),
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
}) })
} }

17
src/types/answer.go Normal file
View File

@ -0,0 +1,17 @@
package types
import "html/template"
type FilteredAnswer struct {
Upvotes string
IsAccepted bool
AuthorName string
AuthorURL string
Timestamp string
Body template.HTML
Comments []FilteredComment
}

8
src/types/comment.go Normal file
View File

@ -0,0 +1,8 @@
package types
type FilteredComment struct {
Text string
Timestamp string
AuthorName string
AuthorURL string
}

12
src/types/question.go Normal file
View File

@ -0,0 +1,12 @@
package types
import "html/template"
type FilteredQuestion struct {
Title string
Body template.HTML
Timestamp string
AuthorName string
AuthorURL string
ShortenedBody string
}

View File

@ -1,17 +1,12 @@
package utils package utils
import ( import (
"fmt" "anonymousoverflow/src/types"
"html"
"strings"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
) )
func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (outHtml string) { func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (comments []types.FilteredComment) {
outHtml = inHtml
comments := []string{}
commentsComponent := postLayout.Find("div.js-post-comments-component") commentsComponent := postLayout.Find("div.js-post-comments-component")
@ -39,21 +34,22 @@ func FindAndReturnComments(inHtml string, postLayout *goquery.Selection) (outHtm
return return
} }
commentAuthorURL = html.EscapeString(commentAuthor.AttrOr("href", "")) commentAuthorURL = commentAuthor.AttrOr("href", "")
} }
commentTimestamp := html.EscapeString(commentBody.Find("span.relativetime-clean").Text()) commentTimestamp := commentBody.Find("span.relativetime-clean").Text()
comment := fmt.Sprintf(`<div class="comment-parent"><div class="comment"><div class="comment-body">%s</div><div class="comment-author">Commented %s by <a href="https://stackoverflow.com%s" target="_blank" rel="noopener noreferrer">%s</a>.</div></div></div>`, commentCopy, commentTimestamp, commentAuthorURL, html.EscapeString(commentAuthor.Text())) newFilteredComment := types.FilteredComment{
Text: commentCopy,
Timestamp: commentTimestamp,
AuthorName: commentAuthor.Text(),
AuthorURL: commentAuthorURL,
}
comments = append(comments, comment) comments = append(comments, newFilteredComment)
}) })
if len(comments) > 0 {
outHtml = inHtml + fmt.Sprintf(`<details class="comments"><summary>Show <b>%d comments</b></summary><div class="comments-parent">%s</div></details>`, len(comments), strings.Join(comments, ""))
}
return return
} }

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="{{ .theme }}"> <html data-theme="{{ .theme }}">
<head> <head>
<title>{{ .title }}</title> <title>{{ .question.Title }}</title>
<link rel="stylesheet" href="/static/question.css" /> <link rel="stylesheet" href="/static/question.css" />
<link rel="stylesheet" href="/static/globals.css" /> <link rel="stylesheet" href="/static/globals.css" />
<link rel="stylesheet" href="/static/syntax.css" /> <link rel="stylesheet" href="/static/syntax.css" />
@ -13,7 +13,7 @@
<link rel="icon" href="/static/codecircles.png" /> <link rel="icon" href="/static/codecircles.png" />
<meta name="theme-color" content="#8CFFC1" /> <meta name="theme-color" content="#8CFFC1" />
<meta name="og:image" content="/static/codecircles.png" /> <meta name="og:image" content="/static/codecircles.png" />
<meta name="description" content="{{ .shortenedBody }}..." /> <meta name="description" content="{{ .question.ShortenedBody }}..." />
</head> </head>
<body> <body>
<div class="parent"> <div class="parent">
@ -34,23 +34,69 @@
</div> </div>
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h1>{{ .title }}</h1> <h1>{{ .question.Title }}</h1>
<p class="timestamp"> <p class="timestamp">
Asked {{ .timestamp }} by Asked {{ .question.Timestamp }} by
<a <a
href="https://stackoverflow.com{{ .authorURL }}" href="https://stackoverflow.com{{ .question.AuthorURL }}"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
>{{ .author }}</a >{{ .question.AuthorName }}</a
>. >.
</p> </p>
</div> </div>
<div class="card-body">{{ .body }}</div> <div class="card-body">{{ .question.Body }}</div>
</div> </div>
<hr class="post-divider" /> <hr class="post-divider" />
<h2>Answers</h2> <h2>Answers</h2>
{{ range $answer := .answers }} {{ range $answer := .answers }}
<div class="answer">{{ $answer }}</div> <div class="answer">
<div
class="answer-meta{{ if $answer.IsAccepted }} accepted{{end}}"
>
{{ if $answer.IsAccepted }} Accepted - {{ end }}
{{$answer.Upvotes}} Votes
</div>
{{ $answer.Body }}
<div class="answer-author-parent">
<div class="answer-author">
Answered {{ $answer.Timestamp }} by
<a
href="https://stackoverflow.com{{ $answer.AuthorURL }}"
target="_blank"
rel="noopener noreferrer"
>{{ $answer.AuthorName }}</a
>
</div>
</div>
{{ if $answer.Comments }}
<details class="comments">
<summary>
Show <b>{{ (len $answer.Comments) }} comments</b>
</summary>
<div class="comments-parent">
{{ range $comment := $answer.Comments }}
<div class="comment-parent">
<div class="comment">
<div class="comment-body">
{{ $comment.Text }}
</div>
<div class="comment-author">
Commented {{ $comment.Timestamp }} by
<a
href="https://stackoverflow.com{{ $comment.AuthorURL }}"
target="_blank"
rel="noopener noreferrer"
>{{ $comment.AuthorName }}</a
>.
</div>
</div>
</div>
{{end}}
</div>
</details>
{{end}}
</div>
<hr class="answer-divider" /> <hr class="answer-divider" />
{{ end }} {{ end }}
</div> </div>