feat: question detail template render

This commit is contained in:
kumfo 2022-12-01 15:30:02 +08:00
parent 21421d0380
commit 9ca6445d3a
8 changed files with 306 additions and 205 deletions

View File

@ -190,7 +190,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
uiRouter := router.NewUIRouter()
authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService)
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService)
templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService)
templateRouter := router.NewTemplateRouter(templateController, templateRenderController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, templateRouter)

View File

@ -130,9 +130,44 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
siteInfo := tc.SiteInfo(ctx)
encodeTitle := url.QueryEscape("title")
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
if err != nil {
tc.Page404(ctx)
return
}
// answers
answerReq := &schema.AnswerList{
QuestionID: id,
Order: "",
Page: 1,
PageSize: 999,
LoginUserID: "",
}
answers, _, err := tc.templateRenderController.AnswerList(ctx, answerReq)
if err != nil {
tc.Page404(ctx)
return
}
// comments
objectIDs := []string{id}
for _, answer := range answers {
objectIDs = append(objectIDs, answer.ID)
}
comments, err := tc.templateRenderController.CommentList(ctx, objectIDs)
if err != nil {
tc.Page404(ctx)
return
}
ctx.HTML(http.StatusOK, "question-detail.html", gin.H{
"id": id,
"answerid": answerid,
"detail": detail,
"answers": answers,
"comments": comments,
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"siteinfo": siteInfo,

View File

@ -0,0 +1,10 @@
package templaterender
import (
"context"
"github.com/answerdev/answer/internal/schema"
)
func (t *TemplateRenderController) AnswerList(ctx context.Context, req *schema.AnswerList) ([]*schema.AnswerInfo, int64, error) {
return t.answerService.SearchList(ctx, req)
}

View File

@ -0,0 +1,38 @@
package templaterender
import (
"context"
"github.com/answerdev/answer/internal/base/pager"
"github.com/answerdev/answer/internal/schema"
)
func (t *TemplateRenderController) CommentList(
ctx context.Context,
objectIDs []string,
) (
comments map[string][]*schema.GetCommentResp,
err error,
) {
comments = make(map[string][]*schema.GetCommentResp, len(objectIDs))
for _, objectID := range objectIDs {
var (
req = &schema.GetCommentWithPageReq{
Page: 1,
PageSize: 3,
ObjectID: objectID,
QueryCond: "vote",
UserID: "",
}
pageModel *pager.PageModel
)
pageModel, err = t.commentService.GetCommentWithPage(ctx, req)
if err != nil {
return
}
li := pageModel.List
comments[objectID] = li.([]*schema.GetCommentResp)
}
return
}

View File

@ -1,6 +1,7 @@
package templaterender
import (
"github.com/answerdev/answer/internal/service/comment"
"math"
"github.com/answerdev/answer/internal/schema"
@ -18,18 +19,23 @@ type TemplateRenderController struct {
questionService *service.QuestionService
userService *service.UserService
tagService *tag.TagService
answerService *service.AnswerService
commentService *comment.CommentService
}
func NewTemplateRenderController(
questionService *service.QuestionService,
userService *service.UserService,
tagService *tag.TagService,
answerService *service.AnswerService,
commentService *comment.CommentService,
) *TemplateRenderController {
return &TemplateRenderController{
questionService: questionService,
userService: userService,
tagService: tagService,
answerService: answerService,
commentService: commentService,
}
}

View File

@ -9,8 +9,6 @@ func (t *TemplateRenderController) Index(ctx *gin.Context, req *schema.QuestionS
return t.questionService.SearchList(ctx, req, req.UserID)
}
func (t *TemplateRenderController) QuestionDetail(ctx *gin.Context) {
func (t *TemplateRenderController) QuestionDetail(ctx *gin.Context, id string) (resp *schema.QuestionInfo, err error) {
return t.questionService.GetQuestion(ctx, id, "", true)
}
func (t *TemplateRenderController) AnswerDetail(ctx *gin.Context) {}

39
ui/template/comment.html Normal file
View File

@ -0,0 +1,39 @@
{{define "comment"}}
{{range .}}
<div class="border-bottom py-2 comment-item border-top">
<div class="d-block">
<div class="fmt fs-14">
{{templateHTML .ParsedText}}
</div>
</div>
<div class="d-flex justify-content-between fs-14">
<div class="d-flex align-items-center link-secondary">
<a href="/users/{{.Username}}">{{.UserDisplayName}}</a><span
class="mx-1">•</span>
<time
class="me-3" datetime="2022-11-24T02:04:49.000Z"
title="Nov 24, 2022 at 10:04">Nov 24
</time>
<button type="button"
class="me-3 btn-no-border p-0 link-secondary btn btn-link btn-sm">
<i class="br bi-hand-thumbs-up-fill"></i>
{{if ne 0 .VoteCount}}
<span class="ms-2">{{.VoteCount}}</span>
{{end}}
</button>
<button type="button"
class="link-secondary m-0 p-0 btn-no-border btn btn-link btn-sm">
Reply
</button>
</div>
<div class="d-block d-md-none dropdown">
<div class="no-toggle dropdown-toggle" id="dropdown-comment"
aria-expanded="false" variant="success">
<i class="br bi-three-dots text-secondary"></i>
</div>
</div>
</div>
</div>
{{end}}
{{end}}

View File

@ -1,205 +1,180 @@
{{template "header" . }}
<div class="pt-4 mt-2 mb-5 questionDetailPage container">
<div class="justify-content-center row">
<div class="mb-5 mb-md-0 col-xxl-7 col-lg-8 col-sm-12">
<div>
<h1 class="h3 mb-3 text-wrap text-break">
<a class="link-dark" href="/questions/10010000000000887">how to make links and how to
make errors ?</a>
</h1>
<div class="d-flex flex-wrap align-items-center fs-14 mb-3 text-secondary">
<time class="me-3" datetime="2022-11-24T02:04:31.000Z"
title="Nov 24, 2022 at 10:04">Asked Nov 24</time><time class="me-3"
datetime="2022-11-28T09:48:16.000Z" title="Nov 28, 2022 at 17:48">Modified 23h
ago</time>
<div class="me-3">Viewed 68</div>
<div class="pt-4 mt-2 mb-5 questionDetailPage container">
<div class="justify-content-center row">
<div class="mb-5 mb-md-0 col-xxl-7 col-lg-8 col-sm-12">
<div>
<h1 class="h3 mb-3 text-wrap text-break">
<a class="link-dark" href="/questions/{{.detail.ID}}">{{.detail.Title}}</a>
</h1>
<div
class="d-flex flex-wrap align-items-center fs-14 mb-3 text-secondary">
<time class="me-3" datetime="2022-11-24T02:04:31.000Z"
title="Nov 24, 2022 at 10:04">Asked Nov 24
</time>
<time class="me-3"
datetime="2022-11-28T09:48:16.000Z"
title="Nov 28, 2022 at 17:48">Modified 23h
ago
</time>
<div class="me-3">Viewed {{.detail.ViewCount}}</div>
</div>
<div class="m-n1">
<a href="/tags/support" class="badge-tag rounded-1 badge-tag-required m-1"><span
class="">support</span></a><a href="/tags/bug"
class="badge-tag rounded-1 badge-tag-required m-1"><span class="">bug</span></a><a
href="/tags/featured" class="badge-tag rounded-1 badge-tag-reserved m-1"><span
class="">featured</span></a><a href="/tags/javascript"
class="badge-tag rounded-1 m-1"><span class="">javascript</span></a><a
href="/tags/go" class="badge-tag rounded-1 m-1"><span class="">go</span></a>
</div>
<article class="fmt text-break text-wrap mt-4">
<p>如下面的例子</p>
<pre><code class="language-typescript">const getStrLength = (target: string | number): number =&gt; {
if (typeof target === "string") {
return target.length; // 这里的`target`被识别为`string`,不用写`(target as string)`
} else {
return target.toString().length;
}
};
</code></pre>
<p>
那要是不能用<code>typeof</code>进行识别的类型,如何写这个<code>if</code>可以在后续内容中省略断言呢?
</p>
<pre><code class="language-typescript">function getSomething(target: Event): number | string {
if (??? MouseEvnet ???) { // 这里怎么写可以省略下面的`(target as MouseEvent)`
console.log(target.clientX);
console.log(target.clientX);
console.log(target.clientX);
.... // 多行代码总不能每次都写`(target as MouseEvent)`吧
return target.clientX;
} else if (??? KeyboardEvent ???) {
return target.key;
} else {
...
}
}
</code></pre>
</article>
<div class="mt-4">
<div role="group" class="btn-group">
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-up-fill"></i></button><button type="button"
disabled="" class="btn btn-outline-dark text-body">
0</button><button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-down-fill"></i>
</button>
</div>
<button type="button" class="btn btn-outline-secondary ms-3">
<i class="br bi-bookmark-fill"></i><span style="padding-left: 10px">0</span>
</button>
</div>
<div class="mt-4 mb-3 row">
<div class="mb-3 mb-md-0 col-lg-5">
</div>
<div class="m-n1">
{{range .detail.Tags}}
<a href="/tags/{{.SlugName}}"
class="badge-tag rounded-1 {{if .Reserved}}badge-tag-reserved{{end}} {{if .Recommend}}badge-tag-required{{end}} m-1">
<span class="">{{.SlugName}}</span>
</a>
{{end}}
</div>
<article class="fmt text-break text-wrap mt-4">
{{templateHTML .detail.HTML}}
</article>
<div class="mt-4">
<div role="group" class="btn-group">
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-up-fill"></i></button>
<button type="button"
disabled="" class="btn btn-outline-dark text-body">
{{.detail.VoteCount}}
</button>
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-down-fill"></i>
</button>
</div>
<button type="button" class="btn btn-outline-secondary ms-3">
<i class="br bi-bookmark-fill"></i><span style="padding-left: 10px">{{.detail.CollectionCount}}</span>
</button>
</div>
<div class="mt-4 mb-3 row">
<div class="mb-3 mb-md-0 col-lg-5">
</div>
<div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/10010000000000887/timeline"><time class="link-secondary fs-14"
datetime="2022-11-24T03:02:51.000Z" title="Nov 24, 2022 at 11:02">edited Nov
24</time></a>
</div>
<div class="col-lg-3">
<div class="d-flex">
<a href="/users/bmckfbksayryy"><img
src="https://answer.dev.segmentfault.com/uploads/avatar/4GmbdScSotb.jpg?s=96"
width="40px" height="40px" class="rounded me-2 d-none d-md-block"
alt="" /><img
src="https://answer.dev.segmentfault.com/uploads/avatar/4GmbdScSotb.jpg?s=48"
width="24px" height="24px" class="rounded me-2 d-block d-md-none"
alt="" /></a>
<div
class="fs-14 text-secondary d-flex flex-row flex-md-column align-items-center align-items-md-start">
<div class="me-1 me-md-0">
<a class="me-1 text-break" href="/users/bmckfbksayryy">shuai</a><span
class="fw-bold" title="Reputation">150</span>
</div>
<a href="/posts/10010000000000887/timeline"><time class="link-secondary"
datetime="2022-11-24T02:04:31.000Z"
title="Nov 24, 2022 at 10:04">asked Nov 24</time></a>
</div>
</div>
</div>
</div>
<div class="comments-wrap">
<div class="border-bottom py-2 comment-item border-top">
<div class="d-block">
<div class="fmt fs-14">
<p>this is a good question</p>
</div>
</div>
<div class="d-flex justify-content-between fs-14">
<div class="d-flex align-items-center link-secondary">
<a href="/users/bmckfbksayryy">shuai</a><span class="mx-1"></span><time
class="me-3" datetime="2022-11-24T02:04:49.000Z"
title="Nov 24, 2022 at 10:04">Nov 24</time><button type="button"
class="me-3 btn-no-border p-0 link-secondary btn btn-link btn-sm">
<i class="br bi-hand-thumbs-up-fill"></i></button><button type="button"
class="link-secondary m-0 p-0 btn-no-border btn btn-link btn-sm">
Reply
</button>
</div>
<div class="d-block d-md-none dropdown">
<div class="no-toggle dropdown-toggle" id="dropdown-comment"
aria-expanded="false" variant="success">
<i class="br bi-three-dots text-secondary"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex align-items-center justify-content-between mt-5 mb-3" id="answerHeader">
<h5 class="mb-0">3 Answers</h5>
</div>
<div id="10020000000000930" class="answer-item py-4">
<article class="fmt">
<ol>
<li>/questions/10010000000000887</li>
<li>
/questions/10010000000000887/questions/10010000000000887
</li>
<li>/questions/10010000000000887</li>
<li>/questions/10010000000000887</li>
</ol>
</article>
<div class="d-flex align-items-center mt-4">
<div class="">
<div role="group" class="btn-group">
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-up-fill"></i></button><button type="button"
disabled="" class="btn btn-outline-dark text-body">
1</button><button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-down-fill"></i>
</button>
</div>
</div>
<button type="button" disabled=""
class="ms-3 active opacity-100 bg-success text-white btn btn-outline-success">
<i class="br bi-check-circle-fill me-2"></i><span>Accepted</span>
</button>
</div>
<div class="mt-4 mb-3 row">
<div class="mb-3 mb-md-0 col">
<div class="d-flex align-items-center">
</div>
</div>
<div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/10010000000000887/10020000000000930/timeline"><time
class="link-secondary fs-14" datetime="2022-11-28T09:48:16.000Z"
title="Nov 28, 2022 at 17:48">edited 23h ago</time></a>
</div>
<div class="col-lg-4">
<div class="d-flex">
<a href="/users/bmxtgtpksxayy"><img
src="/static/media/default-avatar.ac1be9284e893e315871fa5e571cabaf.svg"
width="40px" height="40px" class="rounded me-2 d-none d-md-block"
alt="" /><img
src="/static/media/default-avatar.ac1be9284e893e315871fa5e571cabaf.svg"
width="24px" height="24px" class="rounded me-2 d-block d-md-none"
alt="" /></a>
<div
class="fs-14 text-secondary d-flex flex-row flex-md-column align-items-center align-items-md-start">
<div class="me-1 me-md-0">
<a class="me-1 text-break" href="/users/bmxtgtpksxayy">342564</a><span
class="fw-bold" title="Reputation">34</span>
</div>
<a href="/posts/10010000000000887/10020000000000930/timeline"><time
class="link-secondary" datetime="2022-11-28T09:48:16.000Z"
title="Nov 28, 2022 at 17:48">answered 23h ago</time></a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 mt-lg-0 col-xxl-3 col-lg-4 col-sm-12">
</div>
</div>
<div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/{{.detail.ID}}/timeline">
<time class="link-secondary fs-14"
datetime="2022-11-24T03:02:51.000Z"
title="Nov 24, 2022 at 11:02">edited Nov
24
</time>
</a>
</div>
<div class="col-lg-3">
<div class="d-flex">
<a href="/users/bmckfbksayryy"><img
src="{{.detail.UserInfo.Avatar}}"
width="40px" height="40px"
class="rounded me-2 d-none d-md-block"
alt=""/><img
src="{{.detail.UserInfo.Avatar}}"
width="24px" height="24px"
class="rounded me-2 d-block d-md-none"
alt=""/></a>
<div
class="fs-14 text-secondary d-flex flex-row flex-md-column align-items-center align-items-md-start">
<div class="me-1 me-md-0">
<a class="me-1 text-break"
href="/users/{{.detail.UserInfo.Username}}">{{.detail.UserInfo.DisplayName}}</a><span
class="fw-bold" title="Reputation">{{.detail.UserInfo.Rank}}</span>
</div>
<a href="/posts/{{.detail.ID}}/timeline">
<time class="link-secondary"
datetime="2022-11-24T02:04:31.000Z"
title="Nov 24, 2022 at 10:04">asked Nov 24
</time>
</a>
</div>
</div>
<div style="
</div>
</div>
<div class="comments-wrap">
{{template "comment" index $.comments $.detail.ID }}
</div>
</div>
<div class="d-flex align-items-center justify-content-between mt-5 mb-3"
id="answerHeader">
<h5 class="mb-0">{{.detail.AnswerCount}} Answers</h5>
</div>
{{range .answers}}
<div id="10020000000000930" class="answer-item py-4">
<article class="fmt">
{{templateHTML .HTML}}
</article>
<div class="d-flex align-items-center mt-4">
<div class="">
<div role="group" class="btn-group">
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-up-fill"></i></button>
<button type="button"
disabled="" class="btn btn-outline-dark text-body">
{{.VoteCount}}
</button>
<button type="button" class="btn btn-outline-secondary">
<i class="br bi-hand-thumbs-down-fill"></i>
</button>
</div>
</div>
{{if eq 2 .Adopted}}
<button type="button" disabled=""
class="ms-3 active opacity-100 bg-success text-white btn btn-outline-success">
<i class="br bi-check-circle-fill me-2"></i><span>Accepted</span>
</button>
{{end}}
</div>
<div class="mt-4 mb-3 row">
<div class="mb-3 mb-md-0 col">
<div class="d-flex align-items-center">
</div>
</div>
<div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline">
<time
class="link-secondary fs-14" datetime="2022-11-28T09:48:16.000Z"
title="Nov 28, 2022 at 17:48">edited 23h ago
</time>
</a>
</div>
<div class="col-lg-4">
<div class="d-flex">
<a href="/users/bmxtgtpksxayy"><img
src="{{.UserInfo.Avatar}}"
width="40px" height="40px"
class="rounded me-2 d-none d-md-block"
alt=""/><img
src="{{.UserInfo.Avatar}}"
width="24px" height="24px"
class="rounded me-2 d-block d-md-none"
alt=""/></a>
<div
class="fs-14 text-secondary d-flex flex-row flex-md-column align-items-center align-items-md-start">
<div class="me-1 me-md-0">
<a class="me-1 text-break"
href="/users/{{.UserInfo.Username}}">{{.UserInfo.DisplayName}}</a><span
class="fw-bold" title="Reputation">{{.UserInfo.Rank}}</span>
</div>
<a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline">
<time
class="link-secondary" datetime="2022-11-28T09:48:16.000Z"
title="Nov 28, 2022 at 17:48">answered 23h ago
</time>
</a>
</div>
</div>
</div>
</div>
<div class="comments-wrap">
{{template "comment" index $.comments .ID }}
</div>
</div>
{{end}}
</div>
<div class="mt-5 mt-lg-0 col-xxl-3 col-lg-4 col-sm-12">
</div>
</div>
</div>
<div style="
position: fixed;
top: 90px;
left: 0px;
@ -207,7 +182,7 @@
margin: auto;
z-index: 5;
">
<div class="d-flex justify-content-center"></div>
</div>
<div class="d-flex justify-content-center"></div>
</div>
{{template "footer" .}}