mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/0.7.0/seo' into test
This commit is contained in:
commit
d36ad2db81
24
docs/docs.go
24
docs/docs.go
|
@ -6871,7 +6871,6 @@ const docTemplate = `{
|
|||
"required": [
|
||||
"contact_email",
|
||||
"name",
|
||||
"permalink",
|
||||
"site_url"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -6887,11 +6886,6 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"short_description": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
|
@ -6907,7 +6901,6 @@ const docTemplate = `{
|
|||
"required": [
|
||||
"contact_email",
|
||||
"name",
|
||||
"permalink",
|
||||
"site_url"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -6923,11 +6916,6 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"short_description": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
|
@ -7064,9 +7052,15 @@ const docTemplate = `{
|
|||
"schema.SiteSeoReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permalink",
|
||||
"robots"
|
||||
],
|
||||
"properties": {
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"robots": {
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -7075,9 +7069,15 @@ const docTemplate = `{
|
|||
"schema.SiteSeoResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permalink",
|
||||
"robots"
|
||||
],
|
||||
"properties": {
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"robots": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -6859,7 +6859,6 @@
|
|||
"required": [
|
||||
"contact_email",
|
||||
"name",
|
||||
"permalink",
|
||||
"site_url"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -6875,11 +6874,6 @@
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"short_description": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
|
@ -6895,7 +6889,6 @@
|
|||
"required": [
|
||||
"contact_email",
|
||||
"name",
|
||||
"permalink",
|
||||
"site_url"
|
||||
],
|
||||
"properties": {
|
||||
|
@ -6911,11 +6904,6 @@
|
|||
"type": "string",
|
||||
"maxLength": 128
|
||||
},
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"short_description": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
|
@ -7052,9 +7040,15 @@
|
|||
"schema.SiteSeoReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permalink",
|
||||
"robots"
|
||||
],
|
||||
"properties": {
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"robots": {
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -7063,9 +7057,15 @@
|
|||
"schema.SiteSeoResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"permalink",
|
||||
"robots"
|
||||
],
|
||||
"properties": {
|
||||
"permalink": {
|
||||
"type": "integer",
|
||||
"maximum": 3,
|
||||
"minimum": 0
|
||||
},
|
||||
"robots": {
|
||||
"type": "string"
|
||||
}
|
||||
|
|
|
@ -1247,10 +1247,6 @@ definitions:
|
|||
name:
|
||||
maxLength: 128
|
||||
type: string
|
||||
permalink:
|
||||
maximum: 3
|
||||
minimum: 0
|
||||
type: integer
|
||||
short_description:
|
||||
maxLength: 255
|
||||
type: string
|
||||
|
@ -1260,7 +1256,6 @@ definitions:
|
|||
required:
|
||||
- contact_email
|
||||
- name
|
||||
- permalink
|
||||
- site_url
|
||||
type: object
|
||||
schema.SiteGeneralResp:
|
||||
|
@ -1274,10 +1269,6 @@ definitions:
|
|||
name:
|
||||
maxLength: 128
|
||||
type: string
|
||||
permalink:
|
||||
maximum: 3
|
||||
minimum: 0
|
||||
type: integer
|
||||
short_description:
|
||||
maxLength: 255
|
||||
type: string
|
||||
|
@ -1287,7 +1278,6 @@ definitions:
|
|||
required:
|
||||
- contact_email
|
||||
- name
|
||||
- permalink
|
||||
- site_url
|
||||
type: object
|
||||
schema.SiteInfoResp:
|
||||
|
@ -1375,16 +1365,26 @@ definitions:
|
|||
type: object
|
||||
schema.SiteSeoReq:
|
||||
properties:
|
||||
permalink:
|
||||
maximum: 3
|
||||
minimum: 0
|
||||
type: integer
|
||||
robots:
|
||||
type: string
|
||||
required:
|
||||
- permalink
|
||||
- robots
|
||||
type: object
|
||||
schema.SiteSeoResp:
|
||||
properties:
|
||||
permalink:
|
||||
maximum: 3
|
||||
minimum: 0
|
||||
type: integer
|
||||
robots:
|
||||
type: string
|
||||
required:
|
||||
- permalink
|
||||
- robots
|
||||
type: object
|
||||
schema.SiteThemeReq:
|
||||
|
|
|
@ -75,6 +75,12 @@ func (tc *TemplateController) SiteInfo(ctx *gin.Context) *schema.TemplateSiteInf
|
|||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
resp.SiteSeo, err = tc.siteInfoService.GetSiteSeo(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
resp.Year = fmt.Sprintf("%d", time.Now().Year())
|
||||
return resp
|
||||
}
|
||||
|
@ -101,7 +107,7 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
|
|||
siteInfo.Canonical = fmt.Sprintf("%s", siteInfo.General.SiteUrl)
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.General.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = ""
|
||||
|
@ -109,6 +115,7 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
|
|||
"data": data,
|
||||
"useTitle": UrlUseTitle,
|
||||
"page": templaterender.Paginator(page, req.PageSize, count),
|
||||
"path": "questions",
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -130,7 +137,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
|||
siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl)
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.General.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = fmt.Sprintf("Questions - %s", siteInfo.General.Name)
|
||||
|
@ -141,7 +148,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp) (jump bool, url string) {
|
||||
func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp, correctTitle bool) (jump bool, url string) {
|
||||
id := ctx.Param("id")
|
||||
title := ctx.Param("title")
|
||||
titleIsAnswerID := false
|
||||
|
@ -154,7 +161,7 @@ func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *sc
|
|||
}
|
||||
|
||||
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
||||
if siteInfo.General.PermaLink == schema.PermaLinkQuestionID {
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
|
||||
//not have title
|
||||
if titleIsAnswerID || len(title) == 0 {
|
||||
return false, ""
|
||||
|
@ -162,7 +169,7 @@ func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *sc
|
|||
return true, url
|
||||
} else {
|
||||
//have title
|
||||
if len(title) > 0 && !titleIsAnswerID {
|
||||
if len(title) > 0 && !titleIsAnswerID && correctTitle {
|
||||
return false, ""
|
||||
}
|
||||
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
|
||||
|
@ -178,20 +185,27 @@ func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *sc
|
|||
// QuestionInfo question and answers info
|
||||
func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
title := ctx.Param("title")
|
||||
answerid := ctx.Param("answerid")
|
||||
|
||||
siteInfo := tc.SiteInfo(ctx)
|
||||
jump, jumpurl := tc.QuestionInfo301Jump(ctx, siteInfo)
|
||||
if jump {
|
||||
ctx.Redirect(http.StatusMovedPermanently, jumpurl)
|
||||
return
|
||||
}
|
||||
correctTitle := false
|
||||
|
||||
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
|
||||
if err != nil {
|
||||
tc.Page404(ctx)
|
||||
return
|
||||
}
|
||||
encodeTitle := htmltext.UrlTitle(detail.Title)
|
||||
if encodeTitle == title {
|
||||
correctTitle = true
|
||||
}
|
||||
|
||||
siteInfo := tc.SiteInfo(ctx)
|
||||
jump, jumpurl := tc.QuestionInfo301Jump(ctx, siteInfo, correctTitle)
|
||||
if jump {
|
||||
ctx.Redirect(http.StatusMovedPermanently, jumpurl)
|
||||
return
|
||||
}
|
||||
|
||||
// answers
|
||||
answerReq := &schema.AnswerListReq{
|
||||
|
@ -217,9 +231,8 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
|||
tc.Page404(ctx)
|
||||
return
|
||||
}
|
||||
encodeTitle := htmltext.UrlTitle(detail.Title)
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
|
||||
if siteInfo.General.PermaLink == schema.PermaLinkQuestionID {
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
|
||||
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
|
||||
}
|
||||
jsonLD := &schema.QAPageJsonLD{}
|
||||
|
@ -236,13 +249,15 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
|
|||
answerList := make([]*schema.SuggestedAnswerItem, 0)
|
||||
for _, answer := range answers {
|
||||
if answer.Adopted == schema.AnswerAdoptedEnable {
|
||||
jsonLD.MainEntity.AcceptedAnswer.Type = "Answer"
|
||||
jsonLD.MainEntity.AcceptedAnswer.Text = answer.HTML
|
||||
jsonLD.MainEntity.AcceptedAnswer.UpvoteCount = answer.VoteCount
|
||||
jsonLD.MainEntity.AcceptedAnswer.URL = fmt.Sprintf("%s/%s", siteInfo.Canonical, answer.ID)
|
||||
jsonLD.MainEntity.AcceptedAnswer.Author.Type = "Person"
|
||||
jsonLD.MainEntity.AcceptedAnswer.Author.Name = answer.UserInfo.DisplayName
|
||||
|
||||
acceptedAnswerItem := &schema.AcceptedAnswerItem{}
|
||||
acceptedAnswerItem.Type = "Answer"
|
||||
acceptedAnswerItem.Text = answer.HTML
|
||||
acceptedAnswerItem.DateCreated = time.Unix(answer.CreateTime, 0)
|
||||
acceptedAnswerItem.UpvoteCount = answer.VoteCount
|
||||
acceptedAnswerItem.URL = fmt.Sprintf("%s/%s", siteInfo.Canonical, answer.ID)
|
||||
acceptedAnswerItem.Author.Type = "Person"
|
||||
acceptedAnswerItem.Author.Name = answer.UserInfo.DisplayName
|
||||
jsonLD.MainEntity.AcceptedAnswer = acceptedAnswerItem
|
||||
} else {
|
||||
item := &schema.SuggestedAnswerItem{}
|
||||
item.Type = "Answer"
|
||||
|
@ -326,7 +341,7 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
|
|||
siteInfo.Keywords = taginifo.DisplayName
|
||||
|
||||
UrlUseTitle := false
|
||||
if siteInfo.General.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
|
||||
UrlUseTitle = true
|
||||
}
|
||||
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
|
||||
|
@ -399,6 +414,10 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
|
|||
data["description"] = siteInfo.Description
|
||||
data["language"] = handler.GetLang(ctx)
|
||||
data["timezone"] = siteInfo.Interface.TimeZone
|
||||
_, ok := data["path"]
|
||||
if !ok {
|
||||
data["path"] = ""
|
||||
}
|
||||
|
||||
ctx.HTML(code, tpl, data)
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@ type SiteGeneralReq struct {
|
|||
Description string `validate:"omitempty,gt=3,lte=2000" form:"description" json:"description"`
|
||||
SiteUrl string `validate:"required,gt=1,lte=512,url" form:"site_url" json:"site_url"`
|
||||
ContactEmail string `validate:"required,gt=1,lte=512,email" form:"contact_email" json:"contact_email"`
|
||||
PermaLink int `validate:"required,lte=3,gte=0" form:"permalink" json:"permalink"`
|
||||
}
|
||||
|
||||
type SiteSeoReq struct {
|
||||
Robots string `validate:"required" form:"robots" json:"robots"`
|
||||
PermaLink int `validate:"required,lte=3,gte=0" form:"permalink" json:"permalink"`
|
||||
Robots string `validate:"required" form:"robots" json:"robots"`
|
||||
}
|
||||
|
||||
func (r *SiteGeneralReq) FormatSiteUrl() {
|
||||
|
@ -142,6 +142,7 @@ type TemplateSiteInfoResp struct {
|
|||
General *SiteGeneralResp `json:"general"`
|
||||
Interface *SiteInterfaceResp `json:"interface"`
|
||||
Branding *SiteBrandingResp `json:"branding"`
|
||||
SiteSeo *SiteSeoReq `json:"siteseo"`
|
||||
Title string
|
||||
Year string
|
||||
Canonical string
|
||||
|
|
|
@ -24,16 +24,17 @@ type QAPageJsonLD struct {
|
|||
Type string `json:"@type"`
|
||||
Name string `json:"name"`
|
||||
} `json:"author"`
|
||||
AcceptedAnswer AcceptedAnswerItem `json:"acceptedAnswer"`
|
||||
AcceptedAnswer *AcceptedAnswerItem `json:"acceptedAnswer,omitempty"`
|
||||
SuggestedAnswer []*SuggestedAnswerItem `json:"suggestedAnswer"`
|
||||
} `json:"mainEntity"`
|
||||
}
|
||||
|
||||
type AcceptedAnswerItem struct {
|
||||
Type string `json:"@type"`
|
||||
Text string `json:"text"`
|
||||
UpvoteCount int `json:"upvoteCount"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"@type"`
|
||||
Text string `json:"text"`
|
||||
DateCreated time.Time `json:"dateCreated"`
|
||||
UpvoteCount int `json:"upvoteCount"`
|
||||
URL string `json:"url"`
|
||||
Author struct {
|
||||
Type string `json:"@type"`
|
||||
Name string `json:"name"`
|
||||
|
|
|
@ -98,6 +98,15 @@ func (s *SiteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// GetSiteSeo get site seo
|
||||
func (s *SiteInfoCommonService) GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoReq, err error) {
|
||||
resp = &schema.SiteSeoReq{}
|
||||
if err = s.getSiteInfoByType(ctx, constant.SiteTypeSeo, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *SiteInfoCommonService) getSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
|
||||
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
|
||||
if err != nil {
|
||||
|
|
|
@ -2,26 +2,26 @@
|
|||
<ul class="d-inline-flex mb-0 pagination pagination-sm">
|
||||
{{ if ne .page.Currpage 1 }}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{$.page.Prevpage}}"><span aria-hidden="true">{{translator $.language "ui.pagination.prev"}}</span><span
|
||||
<a class="page-link" href="{{$.path}}?page={{$.page.Prevpage}}"><span aria-hidden="true">{{translator $.language "ui.pagination.prev"}}</span><span
|
||||
class="visually-hidden">{{translator $.language "ui.pagination.prev"}}</span></a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ range $value := .page.Pages }}
|
||||
{{ if eq $.page.Currpage $value }}
|
||||
<li class="page-item active">
|
||||
<span class="page-link" href="?page={{$value}}">{{$value}}
|
||||
<span class="page-link" href="{{$.path}}?page={{$value}}">{{$value}}
|
||||
<span class="visually-hidden">(current)</span>
|
||||
</span>
|
||||
</li>
|
||||
{{ else }}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{$value}}">{{$value}}</a>
|
||||
<a class="page-link" href="{{$.path}}?page={{$value}}">{{$value}}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if lt $.page.Currpage $.page.Totalpages }}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{$.page.Nextpage}}"><span aria-hidden="true">{{translator $.language "ui.pagination.next"}}</span><span
|
||||
<a class="page-link" href="{{$.path}}?page={{$.page.Nextpage}}"><span aria-hidden="true">{{translator $.language "ui.pagination.next"}}</span><span
|
||||
class="visually-hidden">{{translator $.language "ui.pagination.next"}}</span></a>
|
||||
</li>
|
||||
{{ end }}
|
||||
|
|
Loading…
Reference in New Issue