refactor: rely heavier on the templating engine
style: fix comments spacing on mobile
This commit is contained in:
parent
c0df07abad
commit
3b79efc83c
@ -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;
|
||||||
|
@ -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,14 +210,9 @@ 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),
|
|
||||||
"timestamp": questionTimestamp,
|
|
||||||
"author": questionAuthor,
|
|
||||||
"authorURL": questionAuthorURL,
|
|
||||||
"answers": answers,
|
"answers": answers,
|
||||||
"imagePolicy": imagePolicy,
|
"imagePolicy": imagePolicy,
|
||||||
"shortenedBody": questionBodyParent.Text()[0:50],
|
|
||||||
"theme": c.MustGet("theme").(string),
|
"theme": c.MustGet("theme").(string),
|
||||||
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
"currentUrl": fmt.Sprintf("%s/questions/%s/%s", os.Getenv("APP_URL"), questionId, questionTitle),
|
||||||
})
|
})
|
||||||
|
17
src/types/answer.go
Normal file
17
src/types/answer.go
Normal 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
8
src/types/comment.go
Normal 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
12
src/types/question.go
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user