Merge branch 'feat/0.7.0/seo' into test

This commit is contained in:
aichy126 2022-12-14 17:34:46 +08:00
commit 04f048b829
12 changed files with 270 additions and 142 deletions

View File

@ -4865,6 +4865,26 @@ const docTemplate = `{
}
}
},
"/custom.css": {
"get": {
"description": "get site robots information",
"produces": [
"application/json"
],
"tags": [
"site"
],
"summary": "get site robots information",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/installation/base-info": {
"post": {
"description": "init base info",
@ -6944,7 +6964,7 @@ const docTemplate = `{
"login": {
"$ref": "#/definitions/schema.SiteLoginResp"
},
"siteseo": {
"site_seo": {
"$ref": "#/definitions/schema.SiteSeoReq"
},
"theme": {

View File

@ -4853,6 +4853,26 @@
}
}
},
"/custom.css": {
"get": {
"description": "get site robots information",
"produces": [
"application/json"
],
"tags": [
"site"
],
"summary": "get site robots information",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
}
}
}
},
"/installation/base-info": {
"post": {
"description": "init base info",
@ -6932,7 +6952,7 @@
"login": {
"$ref": "#/definitions/schema.SiteLoginResp"
},
"siteseo": {
"site_seo": {
"$ref": "#/definitions/schema.SiteSeoReq"
},
"theme": {

View File

@ -1292,7 +1292,7 @@ definitions:
$ref: '#/definitions/schema.SiteInterfaceResp'
login:
$ref: '#/definitions/schema.SiteLoginResp'
siteseo:
site_seo:
$ref: '#/definitions/schema.SiteSeoReq'
theme:
$ref: '#/definitions/schema.SiteThemeResp'
@ -4814,6 +4814,19 @@ paths:
summary: vote up
tags:
- Activity
/custom.css:
get:
description: get site robots information
produces:
- application/json
responses:
"200":
description: OK
schema:
type: string
summary: get site robots information
tags:
- site
/installation/base-info:
post:
consumes:

View File

@ -3,25 +3,13 @@ package server
import (
"html/template"
"io/fs"
"math"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/day"
"github.com/answerdev/answer/pkg/htmltext"
brotli "github.com/anargu/gin-brotli"
"github.com/answerdev/answer/internal/base/middleware"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/router"
"github.com/answerdev/answer/ui"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/i18n"
)
// NewHTTPServer new http server.
@ -44,6 +32,16 @@ func NewHTTPServer(debug bool,
r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage)
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
dev := os.Getenv("DEVCODE")
if dev != "" {
r.SetFuncMap(funcMap)
r.LoadHTMLGlob("../../ui/template/*")
} else {
html, _ := fs.Sub(ui.Template, "template")
htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
r.SetHTMLTemplate(htmlTemplate)
}
viewRouter.Register(r)
rootGroup := r.Group("")
@ -70,116 +68,6 @@ func NewHTTPServer(debug bool,
cmsauthV1.Use(authUserMiddleware.CmsAuth())
answerRouter.RegisterAnswerCmsAPIRouter(cmsauthV1)
funcMap := template.FuncMap{
"replaceHTMLTag": func(src string, tags ...string) string {
p := `(?U)<(\d+)>.+</(\d+)>`
re := regexp.MustCompile(p)
ms := re.FindAllStringSubmatch(src, -1)
for _, mi := range ms {
if mi[1] == mi[2] {
i, err := strconv.Atoi(mi[1])
if err != nil || len(tags) < i {
break
}
src = strings.ReplaceAll(src, mi[0], tags[i-1])
}
}
return src
},
"join": func(sep string, elems ...string) string {
return strings.Join(elems, sep)
},
"templateHTML": func(data string) template.HTML {
return template.HTML(data)
},
"translator": func(la i18n.Language, data string, params ...interface{}) string {
trans := translator.GlobalTrans.Tr(la, data)
if len(params) > 0 && len(params)%2 == 0 {
for i := 0; i < len(params); i += 2 {
k := converter.InterfaceToString(params[i])
v := converter.InterfaceToString(params[i+1])
trans = strings.ReplaceAll(trans, "{{ "+k+" }}", v)
}
}
return trans
},
"timeFormatISO": func(tz string, timestamp int64) string {
_, _ = time.LoadLocation(tz)
return time.Unix(timestamp, 0).Format("2006-01-02T15:04:05.000Z")
},
"translatorTimeFormatLongDate": func(la i18n.Language, tz string, timestamp int64) string {
trans := translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_time")
return day.Format(timestamp, trans, tz)
},
"translatorTimeFormat": func(la i18n.Language, tz string, timestamp int64) string {
var (
now = time.Now().Unix()
between int64 = 0
trans string
)
_, _ = time.LoadLocation(tz)
if now > timestamp {
between = now - timestamp
}
if between <= 1 {
return translator.GlobalTrans.Tr(la, "ui.dates.now")
}
if between > 1 && between < 60 {
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_seconds_ago")
return strings.ReplaceAll(trans, "{{count}}", converter.IntToString(between))
}
if between >= 60 && between < 3600 {
min := math.Floor(float64(between / 60))
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_minutes_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(min, 'f', 0, 64))
}
if between >= 3600 && between < 3600*24 {
h := math.Floor(float64(between / 3600))
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_hours_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(h, 'f', 0, 64))
}
if between >= 3600*24 &&
between < 3600*24*366 &&
time.Unix(timestamp, 0).Format("2006") == time.Unix(now, 0).Format("2006") {
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date")
return day.Format(timestamp, trans, tz)
}
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_year")
return day.Format(timestamp, trans, tz)
},
"wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} {
return map[string]interface{}{
"comments": comments,
"language": la,
"timezone": tz,
}
},
"urlTitle": func(title string) string {
return htmltext.UrlTitle(title)
},
}
r.SetFuncMap(funcMap)
dev := os.Getenv("DEVCODE")
if dev != "" {
r.LoadHTMLGlob("../../ui/template/*")
} else {
html, _ := fs.Sub(ui.Template, "template")
htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
r.SetHTMLTemplate(htmlTemplate)
}
templateRouter.RegisterTemplateRouter(rootGroup)
return r
}

View File

@ -0,0 +1,117 @@
package server
import (
"html/template"
"math"
"regexp"
"strconv"
"strings"
"time"
"github.com/answerdev/answer/internal/base/translator"
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/pkg/converter"
"github.com/answerdev/answer/pkg/day"
"github.com/answerdev/answer/pkg/htmltext"
"github.com/segmentfault/pacman/i18n"
)
var funcMap = template.FuncMap{
"replaceHTMLTag": func(src string, tags ...string) string {
p := `(?U)<(\d+)>.+</(\d+)>`
re := regexp.MustCompile(p)
ms := re.FindAllStringSubmatch(src, -1)
for _, mi := range ms {
if mi[1] == mi[2] {
i, err := strconv.Atoi(mi[1])
if err != nil || len(tags) < i {
break
}
src = strings.ReplaceAll(src, mi[0], tags[i-1])
}
}
return src
},
"join": func(sep string, elems ...string) string {
return strings.Join(elems, sep)
},
"templateHTML": func(data string) template.HTML {
return template.HTML(data)
},
"translator": func(la i18n.Language, data string, params ...interface{}) string {
trans := translator.GlobalTrans.Tr(la, data)
if len(params) > 0 && len(params)%2 == 0 {
for i := 0; i < len(params); i += 2 {
k := converter.InterfaceToString(params[i])
v := converter.InterfaceToString(params[i+1])
trans = strings.ReplaceAll(trans, "{{ "+k+" }}", v)
}
}
return trans
},
"timeFormatISO": func(tz string, timestamp int64) string {
_, _ = time.LoadLocation(tz)
return time.Unix(timestamp, 0).Format("2006-01-02T15:04:05.000Z")
},
"translatorTimeFormatLongDate": func(la i18n.Language, tz string, timestamp int64) string {
trans := translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_time")
return day.Format(timestamp, trans, tz)
},
"translatorTimeFormat": func(la i18n.Language, tz string, timestamp int64) string {
var (
now = time.Now().Unix()
between int64 = 0
trans string
)
_, _ = time.LoadLocation(tz)
if now > timestamp {
between = now - timestamp
}
if between <= 1 {
return translator.GlobalTrans.Tr(la, "ui.dates.now")
}
if between > 1 && between < 60 {
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_seconds_ago")
return strings.ReplaceAll(trans, "{{count}}", converter.IntToString(between))
}
if between >= 60 && between < 3600 {
min := math.Floor(float64(between / 60))
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_minutes_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(min, 'f', 0, 64))
}
if between >= 3600 && between < 3600*24 {
h := math.Floor(float64(between / 3600))
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_hours_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(h, 'f', 0, 64))
}
if between >= 3600*24 &&
between < 3600*24*366 &&
time.Unix(timestamp, 0).Format("2006") == time.Unix(now, 0).Format("2006") {
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date")
return day.Format(timestamp, trans, tz)
}
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_year")
return day.Format(timestamp, trans, tz)
},
"wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} {
return map[string]interface{}{
"comments": comments,
"language": la,
"timezone": tz,
}
},
"urlTitle": func(title string) string {
return htmltext.UrlTitle(title)
},
}

View File

@ -81,6 +81,10 @@ func (tc *TemplateController) SiteInfo(ctx *gin.Context) *schema.TemplateSiteInf
log.Error(err)
}
resp.CustomCssHtml, err = tc.siteInfoService.GetSiteCustomCssHTML(ctx)
if err != nil {
log.Error(err)
}
resp.Year = fmt.Sprintf("%d", time.Now().Year())
return resp
}
@ -376,11 +380,12 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
req := &schema.GetOtherUserInfoByUsernameReq{}
req.Username = username
userinfo, err := tc.templateRenderController.UserInfo(ctx, req)
if !userinfo.Has {
if err != nil {
tc.Page404(ctx)
return
}
if err != nil {
if !userinfo.Has {
tc.Page404(ctx)
return
}
@ -414,6 +419,9 @@ 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
data["HeadCode"] = siteInfo.CustomCssHtml.CustomHead
data["HeaderCode"] = siteInfo.CustomCssHtml.CustomHeader
data["FooterCode"] = siteInfo.CustomCssHtml.CustomFooter
_, ok := data["path"]
if !ok {
data["path"] = ""

View File

@ -155,6 +155,22 @@ func (sc *SiteInfoController) GetRobots(ctx *gin.Context) {
ctx.String(http.StatusOK, resp.Robots)
}
// GetRobots get site robots information
// @Summary get site robots information
// @Description get site robots information
// @Tags site
// @Produce json
// @Success 200 {string} txt ""
// @Router /custom.css [get]
func (sc *SiteInfoController) GetCss(ctx *gin.Context) {
resp, err := sc.siteInfoService.GetSiteCustomCssHTML(ctx)
if err != nil {
ctx.String(http.StatusOK, "")
return
}
ctx.String(http.StatusOK, resp.CustomCss)
}
// UpdateSeo update site seo information
// @Summary update site seo information
// @Description update site seo information

View File

@ -32,6 +32,7 @@ func (a *TemplateRouter) RegisterTemplateRouter(r *gin.RouterGroup) {
r.GET("/sitemap/:page", a.templateController.SitemapPage)
r.GET("/robots.txt", a.siteInfoController.GetRobots)
r.GET("/custom.css", a.siteInfoController.GetCss)
r.GET("/", a.templateController.Index)
r.GET("/index", a.templateController.Index)

View File

@ -162,19 +162,20 @@ type SiteInfoResp struct {
Login *SiteLoginResp `json:"login"`
Theme *SiteThemeResp `json:"theme"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
SiteSeo *SiteSeoReq `json:"site_seo"`
SiteSeo *SiteSeoReq `json:"site__seo"`
}
type TemplateSiteInfoResp struct {
General *SiteGeneralResp `json:"general"`
Interface *SiteInterfaceResp `json:"interface"`
Branding *SiteBrandingResp `json:"branding"`
SiteSeo *SiteSeoReq `json:"site_seo"`
Title string
Year string
Canonical string
JsonLD string
Keywords string
Description string
General *SiteGeneralResp `json:"general"`
Interface *SiteInterfaceResp `json:"interface"`
Branding *SiteBrandingResp `json:"branding"`
SiteSeo *SiteSeoReq `json:"site_seo"`
CustomCssHtml *SiteCustomCssHTMLResp `json:"custom_css_html"`
Title string
Year string
Canonical string
JsonLD string
Keywords string
Description string
}
// UpdateSMTPConfigReq get smtp config request

View File

@ -1 +1,44 @@
<!doctype html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Answer</title><script defer="defer" src="/static/js/main.9de9552b.js"></script><link href="/static/css/main.d4180d41.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><style>@keyframes _doc-spin{to{transform:rotate(360deg)}}#doc-spinner{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%)}#doc-spinner .spinner{box-sizing:border-box;display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25rem solid currentColor;border-right-color:transparent;color:rgba(108,117,125,.75);border-radius:50%;animation:.75s linear infinite _doc-spin}</style><div id="doc-spinner"><div class="spinner"></div></div></div></body></html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="/manifest.json" />
<title>Answer</title>
<script defer="defer" src="/static/js/main.9de9552b.js"></script>
<link href="/static/css/main.d4180d41.css" rel="stylesheet">
<link href="/custom.css" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<style>
@keyframes _doc-spin {
to {
transform: rotate(360deg);
}
}
#doc-spinner {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#doc-spinner .spinner {
box-sizing: border-box;
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: -0.125em;
border: 0.25rem solid currentColor;
border-right-color: transparent;
color: rgba(108, 117, 125, 0.75);
border-radius: 50%;
animation: 0.75s linear infinite _doc-spin;
}
</style>
<div id="doc-spinner"><div class="spinner"></div></div>
</div>
</body>
</html>

View File

@ -11,6 +11,6 @@
</footer>
</div>
</body>
{{templateHTML .FooterCode}}
</html>
{{end}}

View File

@ -11,13 +11,14 @@
<link rel="canonical" href="{{.siteinfo.Canonical}}" />
<link rel="manifest" href="/manifest.json"/>
<link href="{{.cssPath}}" rel="stylesheet" />
<link href="/custom.css" rel="stylesheet">
<script defer="defer" src="{{.scriptPath}}"></script>
{{templateHTML .HeadCode}}
{{if $.siteinfo.JsonLD }}{{ .siteinfo.JsonLD | templateHTML}}{{end}}
</head>
<body>
{{templateHTML .HeaderCode}}
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<nav id="header" class="sticky-top navbar navbar-expand-lg navbar-dark">