diff --git a/docs/docs.go b/docs/docs.go index 18be2dd5..389e0df0 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5889,6 +5889,7 @@ const docTemplate = `{ "contact_email", "description", "name", + "permalink", "short_description", "site_url" ], @@ -5905,6 +5906,11 @@ const docTemplate = `{ "type": "string", "maxLength": 128 }, + "permalink": { + "type": "integer", + "maximum": 3, + "minimum": 0 + }, "short_description": { "type": "string", "maxLength": 255 @@ -5921,6 +5927,7 @@ const docTemplate = `{ "contact_email", "description", "name", + "permalink", "short_description", "site_url" ], @@ -5937,6 +5944,11 @@ const docTemplate = `{ "type": "string", "maxLength": 128 }, + "permalink": { + "type": "integer", + "maximum": 3, + "minimum": 0 + }, "short_description": { "type": "string", "maxLength": 255 diff --git a/docs/swagger.json b/docs/swagger.json index e8d96d62..a91beb70 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5877,6 +5877,7 @@ "contact_email", "description", "name", + "permalink", "short_description", "site_url" ], @@ -5893,6 +5894,11 @@ "type": "string", "maxLength": 128 }, + "permalink": { + "type": "integer", + "maximum": 3, + "minimum": 0 + }, "short_description": { "type": "string", "maxLength": 255 @@ -5909,6 +5915,7 @@ "contact_email", "description", "name", + "permalink", "short_description", "site_url" ], @@ -5925,6 +5932,11 @@ "type": "string", "maxLength": 128 }, + "permalink": { + "type": "integer", + "maximum": 3, + "minimum": 0 + }, "short_description": { "type": "string", "maxLength": 255 diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 0cdd69bf..6ec43e18 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1122,6 +1122,10 @@ definitions: name: maxLength: 128 type: string + permalink: + maximum: 3 + minimum: 0 + type: integer short_description: maxLength: 255 type: string @@ -1132,6 +1136,7 @@ definitions: - contact_email - description - name + - permalink - short_description - site_url type: object @@ -1146,6 +1151,10 @@ definitions: name: maxLength: 128 type: string + permalink: + maximum: 3 + minimum: 0 + type: integer short_description: maxLength: 255 type: string @@ -1156,6 +1165,7 @@ definitions: - contact_email - description - name + - permalink - short_description - site_url type: object diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 26bef9a4..2ac840c7 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -187,6 +187,17 @@ backend: other: "Your answer has been deleted" your_comment_was_deleted: other: "Your comment has been deleted" + dates: + long_date: "Jan 2" + long_date_with_year: "Jan 2, 2006" + long_date_with_time: "Jan 2, 2006 at 15:04" + now: now + x_seconds_ago: "{{count}}s ago" + x_minutes_ago: "{{count}}m ago" + x_hours_ago: "{{count}}h ago" + hour: hour + day: day + # The following fields are used for interface presentation(Front-end) ui: how_to_format: diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 1493e29e..05f570f0 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -171,6 +171,14 @@ backend: other: "你的答案已被删除" your_comment_was_deleted: other: "你的评论已被删除" + dates: + long_date: "01月02日" + long_date_with_year: "2006年01月02日" + long_date_with_time: "2006年01月02日 15:04" + now: 刚刚 + x_seconds_ago: '{{count}} 秒前' + x_minutes_ago: '{{count}} 分钟前' + x_hours_ago: '{{count}} 小时前' # The following fields are used for interface presentation(Front-end) ui: how_to_format: diff --git a/internal/base/server/http.go b/internal/base/server/http.go index 18b75e37..d6e7611f 100644 --- a/internal/base/server/http.go +++ b/internal/base/server/http.go @@ -1,9 +1,14 @@ package server import ( + "github.com/answerdev/answer/internal/schema" + "github.com/answerdev/answer/pkg/converter" "html/template" "io/fs" + "math" "os" + "strconv" + "strings" "time" brotli "github.com/anargu/gin-brotli" @@ -64,8 +69,62 @@ func NewHTTPServer(debug bool, "translator": func(la i18n.Language, data string) string { return translator.GlobalTrans.Tr(la, data) }, - "translatorTimeFormat": func(la i18n.Language, timestamp int64) string { - return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") + "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, "dates.long_date_with_time") + return time.Unix(timestamp, 0).Format(trans) + }, + "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, "dates.now") + } + + if between > 1 && between < 60 { + trans = translator.GlobalTrans.Tr(la, "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, "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 / 60)) + trans = translator.GlobalTrans.Tr(la, "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, "dates.long_date") + return time.Unix(timestamp, 0).Format(trans) + } + + trans = translator.GlobalTrans.Tr(la, "dates.long_date_with_year") + return time.Unix(timestamp, 0).Format(trans) + }, + "wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} { + return map[string]interface{}{ + "comments": comments, + "language": la, + "timezone": tz, + } }, }) diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index 4abec41b..d78f6a73 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -90,14 +90,12 @@ func (tc *TemplateController) Index(ctx *gin.Context) { tc.Page404(ctx) return } + siteInfo := tc.SiteInfo(ctx) siteInfo.Canonical = fmt.Sprintf("%s", siteInfo.General.SiteUrl) - ctx.HTML(http.StatusOK, "question.html", gin.H{ - "siteinfo": siteInfo, - "scriptPath": tc.scriptPath, - "cssPath": tc.cssPath, - "data": data, - "page": templaterender.Paginator(page, req.PageSize, count), + tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{ + "data": data, + "page": templaterender.Paginator(page, req.PageSize, count), }) } @@ -115,12 +113,10 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) { } siteInfo := tc.SiteInfo(ctx) siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl) - ctx.HTML(http.StatusOK, "question.html", gin.H{ - "siteinfo": siteInfo, - "scriptPath": tc.scriptPath, - "cssPath": tc.cssPath, - "data": data, - "page": templaterender.Paginator(page, req.PageSize, count), + + tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{ + "data": data, + "page": templaterender.Paginator(page, req.PageSize, count), }) } @@ -194,15 +190,12 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) { siteInfo.JsonLD = `` } - 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, + tc.html(ctx, http.StatusOK, "question-detail.html", siteInfo, gin.H{ + "id": id, + "answerid": answerid, + "detail": detail, + "answers": answers, + "comments": comments, }) } @@ -223,14 +216,12 @@ func (tc *TemplateController) TagList(ctx *gin.Context) { return } page := templaterender.Paginator(req.Page, req.PageSize, data.Count) + siteInfo := tc.SiteInfo(ctx) siteInfo.Canonical = fmt.Sprintf("%s/tags", siteInfo.General.SiteUrl) - ctx.HTML(http.StatusOK, "tags.html", gin.H{ - "scriptPath": tc.scriptPath, - "cssPath": tc.cssPath, - "page": page, - "data": data, - "siteinfo": siteInfo, + tc.html(ctx, http.StatusOK, "tags.html", siteInfo, gin.H{ + "page": page, + "data": data, }) } @@ -260,15 +251,13 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) { return } page := templaterender.Paginator(nowPage, req.PageSize, questionCount) + siteInfo := tc.SiteInfo(ctx) siteInfo.Canonical = fmt.Sprintf("%s/tags/%s", siteInfo.General.SiteUrl, tag) - ctx.HTML(http.StatusOK, "tag-detail.html", gin.H{ + tc.html(ctx, http.StatusOK, "tag-detail.html", siteInfo, gin.H{ "tag": taginifo, "questionList": questionList, "questionCount": questionCount, - "scriptPath": tc.scriptPath, - "cssPath": tc.cssPath, - "siteinfo": siteInfo, "page": page, }) } @@ -300,14 +289,9 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) { siteInfo := tc.SiteInfo(ctx) siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username) - - ctx.HTML(http.StatusOK, "homepage.html", gin.H{ - "siteinfo": siteInfo, - "userinfo": userinfo, - "scriptPath": tc.scriptPath, - "cssPath": tc.cssPath, - "language": handler.GetLang(ctx), - "bio": template.HTML(userinfo.Info.BioHTML), + tc.html(ctx, http.StatusOK, "homepage.html", siteInfo, gin.H{ + "userinfo": userinfo, + "bio": template.HTML(userinfo.Info.BioHTML), }) } @@ -318,3 +302,13 @@ func (tc *TemplateController) Page404(ctx *gin.Context) { "cssPath": tc.cssPath, }) } + +func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteInfo *schema.TemplateSiteInfoResp, data gin.H) { + data["siteinfo"] = siteInfo + data["scriptPath"] = tc.scriptPath + data["cssPath"] = tc.cssPath + data["language"] = handler.GetLang(ctx) + data["timezone"] = siteInfo.Interface.TimeZone + + ctx.HTML(code, tpl, data) +} diff --git a/ui/template/comment.html b/ui/template/comment.html index 44792cb0..5866104d 100644 --- a/ui/template/comment.html +++ b/ui/template/comment.html @@ -1,5 +1,5 @@ {{define "comment"}} -{{range .}} +{{range .comments}}