mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/ui-1.0.4' into feat/ui-1.1.0
This commit is contained in:
commit
e8246433a2
4
Makefile
4
Makefile
|
@ -1,6 +1,6 @@
|
||||||
.PHONY: build clean ui
|
.PHONY: build clean ui
|
||||||
|
|
||||||
VERSION=1.0.2
|
VERSION=1.0.3
|
||||||
BIN=answer
|
BIN=answer
|
||||||
DIR_SRC=./cmd/answer
|
DIR_SRC=./cmd/answer
|
||||||
DOCKER_CMD=docker
|
DOCKER_CMD=docker
|
||||||
|
@ -39,6 +39,6 @@ install-ui-packages:
|
||||||
@corepack prepare pnpm@v7.12.2 --activate
|
@corepack prepare pnpm@v7.12.2 --activate
|
||||||
|
|
||||||
ui:
|
ui:
|
||||||
@cd ui && pnpm install && pnpm build && cd -
|
@cd ui && pnpm install && pnpm build && sed -i 's/%AnswerVersion%/'$(VERSION)'/g' ./build/index.html && cd -
|
||||||
|
|
||||||
all: clean build
|
all: clean build
|
||||||
|
|
|
@ -181,7 +181,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
|
||||||
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo)
|
reportAdminService := report_admin.NewReportAdminService(reportRepo, userCommon, commonRepo, answerRepo, questionRepo, commentCommonRepo, reportHandle, configRepo)
|
||||||
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
||||||
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
|
||||||
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon)
|
userAdminService := user_admin.NewUserAdminService(userAdminRepo, userRoleRelService, authService, userCommon, userActiveActivityRepo)
|
||||||
userAdminController := controller_admin.NewUserAdminController(userAdminService)
|
userAdminController := controller_admin.NewUserAdminController(userAdminService)
|
||||||
reasonRepo := reason.NewReasonRepo(configRepo)
|
reasonRepo := reason.NewReasonRepo(configRepo)
|
||||||
reasonService := reason2.NewReasonService(reasonRepo)
|
reasonService := reason2.NewReasonService(reasonRepo)
|
||||||
|
|
|
@ -6948,10 +6948,6 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"schema.SiteBrandingReq": {
|
"schema.SiteBrandingReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"logo",
|
|
||||||
"square_icon"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -6973,10 +6969,6 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"schema.SiteBrandingResp": {
|
"schema.SiteBrandingResp": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"logo",
|
|
||||||
"square_icon"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -6936,10 +6936,6 @@
|
||||||
},
|
},
|
||||||
"schema.SiteBrandingReq": {
|
"schema.SiteBrandingReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"logo",
|
|
||||||
"square_icon"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -6961,10 +6957,6 @@
|
||||||
},
|
},
|
||||||
"schema.SiteBrandingResp": {
|
"schema.SiteBrandingResp": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
|
||||||
"logo",
|
|
||||||
"square_icon"
|
|
||||||
],
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"favicon": {
|
"favicon": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
|
@ -1241,9 +1241,6 @@ definitions:
|
||||||
square_icon:
|
square_icon:
|
||||||
maxLength: 512
|
maxLength: 512
|
||||||
type: string
|
type: string
|
||||||
required:
|
|
||||||
- logo
|
|
||||||
- square_icon
|
|
||||||
type: object
|
type: object
|
||||||
schema.SiteBrandingResp:
|
schema.SiteBrandingResp:
|
||||||
properties:
|
properties:
|
||||||
|
@ -1259,9 +1256,6 @@ definitions:
|
||||||
square_icon:
|
square_icon:
|
||||||
maxLength: 512
|
maxLength: 512
|
||||||
type: string
|
type: string
|
||||||
required:
|
|
||||||
- logo
|
|
||||||
- square_icon
|
|
||||||
type: object
|
type: object
|
||||||
schema.SiteCustomCssHTMLReq:
|
schema.SiteCustomCssHTMLReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -15,7 +15,6 @@ require (
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/goccy/go-json v0.9.11
|
github.com/goccy/go-json v0.9.11
|
||||||
github.com/golang/mock v1.4.4
|
github.com/golang/mock v1.4.4
|
||||||
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
|
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/google/wire v0.5.0
|
github.com/google/wire v0.5.0
|
||||||
github.com/gosimple/slug v1.13.1
|
github.com/gosimple/slug v1.13.1
|
||||||
|
@ -24,6 +23,7 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5
|
github.com/jinzhu/now v1.1.5
|
||||||
github.com/lib/pq v1.10.7
|
github.com/lib/pq v1.10.7
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.21
|
||||||
github.com/mojocn/base64Captcha v1.3.5
|
github.com/mojocn/base64Captcha v1.3.5
|
||||||
github.com/ory/dockertest/v3 v3.9.1
|
github.com/ory/dockertest/v3 v3.9.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
@ -55,6 +55,7 @@ require (
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
|
github.com/aymerick/douceur v0.2.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||||
github.com/containerd/continuity v0.3.0 // indirect
|
github.com/containerd/continuity v0.3.0 // indirect
|
||||||
github.com/docker/cli v20.10.14+incompatible // indirect
|
github.com/docker/cli v20.10.14+incompatible // indirect
|
||||||
|
@ -71,6 +72,7 @@ require (
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -82,6 +82,8 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
|
||||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
@ -256,8 +258,6 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c h1:iyaGYbCmcYK0Ja9a3OUa2Fo+EaN0cbLu0eKpBwPFzc8=
|
|
||||||
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
@ -301,6 +301,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
@ -476,6 +478,8 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
|
156
i18n/de_DE.yaml
156
i18n/de_DE.yaml
|
@ -2,65 +2,65 @@
|
||||||
backend:
|
backend:
|
||||||
base:
|
base:
|
||||||
success:
|
success:
|
||||||
other: "Success."
|
other: "Erfolgreich."
|
||||||
unknown:
|
unknown:
|
||||||
other: "Unknown error."
|
other: "Unbekannter Fehler."
|
||||||
request_format_error:
|
request_format_error:
|
||||||
other: "Request format is not valid."
|
other: "Request format is not valid."
|
||||||
unauthorized_error:
|
unauthorized_error:
|
||||||
other: "Unauthorized."
|
other: "Nicht autorisiert."
|
||||||
database_error:
|
database_error:
|
||||||
other: "Data server error."
|
other: "Data server error."
|
||||||
role:
|
role:
|
||||||
name:
|
name:
|
||||||
user:
|
user:
|
||||||
other: "User"
|
other: "Nutzer"
|
||||||
admin:
|
admin:
|
||||||
other: "Admin"
|
other: "Admin"
|
||||||
moderator:
|
moderator:
|
||||||
other: "Moderator"
|
other: "Moderator"
|
||||||
description:
|
description:
|
||||||
user:
|
user:
|
||||||
other: "Default with no special access."
|
other: "Standard ohne speziellen Zugriff."
|
||||||
admin:
|
admin:
|
||||||
other: "Have the full power to access the site."
|
other: "Have the full power to access the site."
|
||||||
moderator:
|
moderator:
|
||||||
other: "Has access to all posts except admin settings."
|
other: "Hat Zugriff auf alle Beiträge außer Admin-Einstellungen."
|
||||||
email:
|
email:
|
||||||
other: "Email"
|
other: "E-Mail"
|
||||||
password:
|
password:
|
||||||
other: "Password"
|
other: "Passwort"
|
||||||
email_or_password_wrong_error:
|
email_or_password_wrong_error:
|
||||||
other: "Email and password do not match."
|
other: "E-Mail und Password stimmen nicht überein."
|
||||||
error:
|
error:
|
||||||
admin:
|
admin:
|
||||||
email_or_password_wrong:
|
email_or_password_wrong:
|
||||||
other: Email and password do not match.
|
other: E-Mail und Password stimmen nicht überein.
|
||||||
answer:
|
answer:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Answer do not found."
|
other: "Answer do not found."
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "No permission to delete."
|
other: "Keine Berechtigung zum Löschen."
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "Keine Berechtigung zum Aktualisieren."
|
||||||
comment:
|
comment:
|
||||||
edit_without_permission:
|
edit_without_permission:
|
||||||
other: "Comment are not allowed to edit."
|
other: "Kommentar kann nicht bearbeitet werden."
|
||||||
not_found:
|
not_found:
|
||||||
other: "Comment not found."
|
other: "Kommentar wurde nicht gefunden."
|
||||||
email:
|
email:
|
||||||
duplicate:
|
duplicate:
|
||||||
other: "Email already exists."
|
other: "E-Mail existiert bereits."
|
||||||
need_to_be_verified:
|
need_to_be_verified:
|
||||||
other: "Email should be verified."
|
other: "E-Mail muss überprüft werden."
|
||||||
verify_url_expired:
|
verify_url_expired:
|
||||||
other: "Email verified URL has expired, please resend the email."
|
other: "Email verified URL has expired, please resend the email."
|
||||||
lang:
|
lang:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Language file not found."
|
other: "Sprachdatei nicht gefunden."
|
||||||
object:
|
object:
|
||||||
captcha_verification_failed:
|
captcha_verification_failed:
|
||||||
other: "Captcha wrong."
|
other: "Captcha ist falsch."
|
||||||
disallow_follow:
|
disallow_follow:
|
||||||
other: "You are not allowed to follow."
|
other: "You are not allowed to follow."
|
||||||
disallow_vote:
|
disallow_vote:
|
||||||
|
@ -70,22 +70,22 @@ backend:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Object not found."
|
other: "Object not found."
|
||||||
verification_failed:
|
verification_failed:
|
||||||
other: "Verification failed."
|
other: "Verifizierung fehlgeschlagen."
|
||||||
email_or_password_incorrect:
|
email_or_password_incorrect:
|
||||||
other: "Email and password do not match."
|
other: "E-Mail und Password stimmen nicht überein."
|
||||||
old_password_verification_failed:
|
old_password_verification_failed:
|
||||||
other: "The old password verification failed"
|
other: "The old password verification failed"
|
||||||
new_password_same_as_previous_setting:
|
new_password_same_as_previous_setting:
|
||||||
other: "The new password is the same as the previous one."
|
other: "Das neue Passwort ist das gleiche wie das vorherige Passwort."
|
||||||
question:
|
question:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Question not found."
|
other: "Frage nicht gefunden."
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "No permission to delete."
|
other: "Keine Berechtigung zum Löschen."
|
||||||
cannot_close:
|
cannot_close:
|
||||||
other: "No permission to close."
|
other: "No permission to close."
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "Keine Berechtigung zum Aktualisieren."
|
||||||
rank:
|
rank:
|
||||||
fail_to_meet_the_condition:
|
fail_to_meet_the_condition:
|
||||||
other: "Rank fail to meet the condition."
|
other: "Rank fail to meet the condition."
|
||||||
|
@ -96,7 +96,7 @@ backend:
|
||||||
other: "Report not found."
|
other: "Report not found."
|
||||||
tag:
|
tag:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Tag not found."
|
other: "Schlagwort nicht gefunden."
|
||||||
recommend_tag_not_found:
|
recommend_tag_not_found:
|
||||||
other: "Recommend Tag is not exist."
|
other: "Recommend Tag is not exist."
|
||||||
recommend_tag_enter:
|
recommend_tag_enter:
|
||||||
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -181,7 +184,7 @@ backend:
|
||||||
name:
|
name:
|
||||||
other: "spam"
|
other: "spam"
|
||||||
desc:
|
desc:
|
||||||
other: "This question has been asked before and already has an answer."
|
other: "Diese Frage ist bereits gestellt worden und hat bereits eine Antwort."
|
||||||
guideline:
|
guideline:
|
||||||
name:
|
name:
|
||||||
other: "a community-specific reason"
|
other: "a community-specific reason"
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -230,24 +240,24 @@ ui:
|
||||||
desc: >-
|
desc: >-
|
||||||
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>></code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>></code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
||||||
pagination:
|
pagination:
|
||||||
prev: Prev
|
prev: Zurück
|
||||||
next: Next
|
next: Weiter
|
||||||
page_title:
|
page_title:
|
||||||
question: Question
|
question: Frage
|
||||||
questions: Questions
|
questions: Fragen
|
||||||
tag: Tag
|
tag: Schlagwort
|
||||||
tags: Tags
|
tags: Schlagwörter
|
||||||
tag_wiki: tag wiki
|
tag_wiki: tag wiki
|
||||||
edit_tag: Edit Tag
|
edit_tag: Edit Tag
|
||||||
ask_a_question: Add Question
|
ask_a_question: Add Question
|
||||||
edit_question: Edit Question
|
edit_question: Edit Question
|
||||||
edit_answer: Edit Answer
|
edit_answer: Edit Answer
|
||||||
search: Search
|
search: Suche
|
||||||
posts_containing: Posts containing
|
posts_containing: Posts containing
|
||||||
settings: Settings
|
settings: Einstellungen
|
||||||
notifications: Notifications
|
notifications: Notifications
|
||||||
login: Log In
|
login: Anmelden
|
||||||
sign_up: Sign Up
|
sign_up: Registrieren
|
||||||
account_recovery: Account Recovery
|
account_recovery: Account Recovery
|
||||||
account_activation: Account Activation
|
account_activation: Account Activation
|
||||||
confirm_email: Confirm Email
|
confirm_email: Confirm Email
|
||||||
|
@ -367,7 +377,7 @@ ui:
|
||||||
unordered_list:
|
unordered_list:
|
||||||
text: Bulleted List
|
text: Bulleted List
|
||||||
table:
|
table:
|
||||||
text: Table
|
text: Tabelle
|
||||||
heading: Heading
|
heading: Heading
|
||||||
cell: Cell
|
cell: Cell
|
||||||
close_modal:
|
close_modal:
|
||||||
|
@ -586,13 +596,13 @@ ui:
|
||||||
empty: Name cannot be empty.
|
empty: Name cannot be empty.
|
||||||
range: Name up to 30 characters.
|
range: Name up to 30 characters.
|
||||||
email:
|
email:
|
||||||
label: Email
|
label: E-Mail
|
||||||
msg:
|
msg:
|
||||||
empty: Email cannot be empty.
|
empty: E-Mail-Feld darf nicht leer sein.
|
||||||
password:
|
password:
|
||||||
label: Password
|
label: Passwort
|
||||||
msg:
|
msg:
|
||||||
empty: Password cannot be empty.
|
empty: Passwort-Feld darf nicht leer sein.
|
||||||
different: The passwords entered on both sides are inconsistent
|
different: The passwords entered on both sides are inconsistent
|
||||||
account_forgot:
|
account_forgot:
|
||||||
page_title: Forgot Your Password
|
page_title: Forgot Your Password
|
||||||
|
@ -600,7 +610,7 @@ ui:
|
||||||
send_success: >-
|
send_success: >-
|
||||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
||||||
email:
|
email:
|
||||||
label: Email
|
label: E-Mail
|
||||||
msg:
|
msg:
|
||||||
empty: Email cannot be empty.
|
empty: Email cannot be empty.
|
||||||
change_email:
|
change_email:
|
||||||
|
@ -674,24 +684,24 @@ ui:
|
||||||
radio: "Answers to your questions, comments, and more"
|
radio: "Answers to your questions, comments, and more"
|
||||||
account:
|
account:
|
||||||
heading: Account
|
heading: Account
|
||||||
change_email_btn: Change email
|
change_email_btn: E-Mail-Adresse ändern
|
||||||
change_pass_btn: Change password
|
change_pass_btn: Passwort ändern
|
||||||
change_email_info: >-
|
change_email_info: >-
|
||||||
We've sent an email to that address. Please follow the confirmation instructions.
|
We've sent an email to that address. Please follow the confirmation instructions.
|
||||||
email:
|
email:
|
||||||
label: Email
|
label: E-Mail
|
||||||
msg: Email cannot be empty.
|
msg: E-Mail-Feld darf nicht leer sein.
|
||||||
password_title: Password
|
password_title: Passwort
|
||||||
current_pass:
|
current_pass:
|
||||||
label: Current Password
|
label: Aktuelles Passwort
|
||||||
msg:
|
msg:
|
||||||
empty: Current Password cannot be empty.
|
empty: Current Password cannot be empty.
|
||||||
length: The length needs to be between 8 and 32.
|
length: The length needs to be between 8 and 32.
|
||||||
different: The two entered passwords do not match.
|
different: The two entered passwords do not match.
|
||||||
new_pass:
|
new_pass:
|
||||||
label: New Password
|
label: Neues Passwort
|
||||||
pass_confirm:
|
pass_confirm:
|
||||||
label: Confirm New Password
|
label: Neues Passwort bestätigen
|
||||||
interface:
|
interface:
|
||||||
heading: Interface
|
heading: Interface
|
||||||
lang:
|
lang:
|
||||||
|
@ -747,10 +757,10 @@ ui:
|
||||||
tip_question_deleted: This post has been deleted
|
tip_question_deleted: This post has been deleted
|
||||||
tip_answer_deleted: This answer has been deleted
|
tip_answer_deleted: This answer has been deleted
|
||||||
btns:
|
btns:
|
||||||
confirm: Confirm
|
confirm: Bestätigen
|
||||||
cancel: Cancel
|
cancel: Abbrechen
|
||||||
save: Save
|
save: Speichern
|
||||||
delete: Delete
|
delete: Löschen
|
||||||
login: Log in
|
login: Log in
|
||||||
signup: Sign up
|
signup: Sign up
|
||||||
logout: Log out
|
logout: Log out
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -871,7 +886,7 @@ ui:
|
||||||
placeholder: root
|
placeholder: root
|
||||||
msg: Username cannot be empty.
|
msg: Username cannot be empty.
|
||||||
db_password:
|
db_password:
|
||||||
label: Password
|
label: Passwort
|
||||||
placeholder: root
|
placeholder: root
|
||||||
msg: Password cannot be empty.
|
msg: Password cannot be empty.
|
||||||
db_host:
|
db_host:
|
||||||
|
@ -991,13 +1006,14 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
latest: Latest
|
latest: Latest
|
||||||
check_failed: Check failed
|
check_failed: Check failed
|
||||||
"yes": "Yes"
|
"yes": "Ja"
|
||||||
"no": "No"
|
"no": "Nein"
|
||||||
not_allowed: Not allowed
|
not_allowed: Not allowed
|
||||||
allowed: Allowed
|
allowed: Allowed
|
||||||
enabled: Enabled
|
enabled: Enabled
|
||||||
|
@ -1045,7 +1061,7 @@ ui:
|
||||||
users:
|
users:
|
||||||
title: Users
|
title: Users
|
||||||
name: Name
|
name: Name
|
||||||
email: Email
|
email: E-Mail
|
||||||
reputation: Reputation
|
reputation: Reputation
|
||||||
created_at: Created Time
|
created_at: Created Time
|
||||||
delete_at: Deleted Time
|
delete_at: Deleted Time
|
||||||
|
@ -1184,7 +1200,7 @@ ui:
|
||||||
ssl: SSL
|
ssl: SSL
|
||||||
none: None
|
none: None
|
||||||
smtp_port:
|
smtp_port:
|
||||||
label: SMTP Port
|
label: SMTP-Port
|
||||||
msg: SMTP port must be number 1 ~ 65535.
|
msg: SMTP port must be number 1 ~ 65535.
|
||||||
text: The port to your mail server.
|
text: The port to your mail server.
|
||||||
smtp_username:
|
smtp_username:
|
||||||
|
|
|
@ -509,6 +509,8 @@ ui:
|
||||||
label: Revision
|
label: Revision
|
||||||
answer:
|
answer:
|
||||||
label: Answer
|
label: Answer
|
||||||
|
feedback:
|
||||||
|
characters: content must be at least 6 characters in length.
|
||||||
edit_summary:
|
edit_summary:
|
||||||
label: Edit Summary
|
label: Edit Summary
|
||||||
placeholder: >-
|
placeholder: >-
|
||||||
|
@ -748,7 +750,7 @@ ui:
|
||||||
text: User interface language. It will change when you refresh the page.
|
text: User interface language. It will change when you refresh the page.
|
||||||
my_logins:
|
my_logins:
|
||||||
title: My Logins
|
title: My Logins
|
||||||
lable: Log in or sign up on this site using these accounts.
|
label: Log in or sign up on this site using these accounts.
|
||||||
modal_title: Remove Login
|
modal_title: Remove Login
|
||||||
modal_content: Are you sure you want to remove this login from your account?
|
modal_content: Are you sure you want to remove this login from your account?
|
||||||
modal_confirm_btn: Remove
|
modal_confirm_btn: Remove
|
||||||
|
@ -790,6 +792,7 @@ ui:
|
||||||
<p>Are you sure you want to add another answer?</p><p>You could use the
|
<p>Are you sure you want to add another answer?</p><p>You could use the
|
||||||
edit link to refine and improve your existing answer, instead.</p>
|
edit link to refine and improve your existing answer, instead.</p>
|
||||||
empty: Answer cannot be empty.
|
empty: Answer cannot be empty.
|
||||||
|
characters: content must be at least 6 characters in length.
|
||||||
reopen:
|
reopen:
|
||||||
title: Reopen this post
|
title: Reopen this post
|
||||||
content: Are you sure you want to reopen?
|
content: Are you sure you want to reopen?
|
||||||
|
@ -1012,7 +1015,11 @@ ui:
|
||||||
db_failed: Database connection failed
|
db_failed: Database connection failed
|
||||||
db_failed_desc: >-
|
db_failed_desc: >-
|
||||||
This either means that the database information in your <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean your host’s database server is down.
|
This either means that the database information in your <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean your host’s database server is down.
|
||||||
|
counts:
|
||||||
|
views: views
|
||||||
|
votes: votes
|
||||||
|
answers: answers
|
||||||
|
accepted: Accepted
|
||||||
page_404:
|
page_404:
|
||||||
desc: "Unfortunately, this page doesn't exist."
|
desc: "Unfortunately, this page doesn't exist."
|
||||||
back_home: Back to homepage
|
back_home: Back to homepage
|
||||||
|
@ -1050,7 +1057,7 @@ ui:
|
||||||
title: Admin
|
title: Admin
|
||||||
dashboard:
|
dashboard:
|
||||||
title: Dashboard
|
title: Dashboard
|
||||||
welcome: Welcome to {{site_name}} Admin!
|
welcome: Welcome to Answer Admin!
|
||||||
site_statistics: Site Statistics
|
site_statistics: Site Statistics
|
||||||
questions: "Questions:"
|
questions: "Questions:"
|
||||||
answers: "Answers:"
|
answers: "Answers:"
|
||||||
|
|
1074
i18n/es_ES.yaml
1074
i18n/es_ES.yaml
File diff suppressed because it is too large
Load Diff
952
i18n/fr_FR.yaml
952
i18n/fr_FR.yaml
File diff suppressed because it is too large
Load Diff
124
i18n/it_IT.yaml
124
i18n/it_IT.yaml
|
@ -14,18 +14,18 @@ backend:
|
||||||
role:
|
role:
|
||||||
name:
|
name:
|
||||||
user:
|
user:
|
||||||
other: "User"
|
other: "Utente"
|
||||||
admin:
|
admin:
|
||||||
other: "Admin"
|
other: "Amministratore"
|
||||||
moderator:
|
moderator:
|
||||||
other: "Moderator"
|
other: "Moderatore"
|
||||||
description:
|
description:
|
||||||
user:
|
user:
|
||||||
other: "Default with no special access."
|
other: "Predefinito senza alcun accesso speciale."
|
||||||
admin:
|
admin:
|
||||||
other: "Have the full power to access the site."
|
other: "Avere il pieno potere di accedere al sito."
|
||||||
moderator:
|
moderator:
|
||||||
other: "Has access to all posts except admin settings."
|
other: "Ha accesso a tutti i post tranne le impostazioni di amministratore."
|
||||||
email:
|
email:
|
||||||
other: "email"
|
other: "email"
|
||||||
password:
|
password:
|
||||||
|
@ -40,9 +40,9 @@ backend:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Risposta non trovata"
|
other: "Risposta non trovata"
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "No permission to delete."
|
other: "Permesso per cancellare mancante."
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "Nessun permesso per l'aggiornamento."
|
||||||
comment:
|
comment:
|
||||||
edit_without_permission:
|
edit_without_permission:
|
||||||
other: "Non si hanno di privilegi sufficienti per modificare il commento"
|
other: "Non si hanno di privilegi sufficienti per modificare il commento"
|
||||||
|
@ -81,11 +81,11 @@ backend:
|
||||||
not_found:
|
not_found:
|
||||||
other: "domanda non trovata"
|
other: "domanda non trovata"
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "No permission to delete."
|
other: "Permesso per cancellare mancante."
|
||||||
cannot_close:
|
cannot_close:
|
||||||
other: "No permission to close."
|
other: "Nessun permesso per chiudere."
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "Nessun permesso per l'aggiornamento."
|
||||||
rank:
|
rank:
|
||||||
fail_to_meet_the_condition:
|
fail_to_meet_the_condition:
|
||||||
other: "Condizioni non valide per il grado"
|
other: "Condizioni non valide per il grado"
|
||||||
|
@ -98,23 +98,26 @@ backend:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Etichetta non trovata"
|
other: "Etichetta non trovata"
|
||||||
recommend_tag_not_found:
|
recommend_tag_not_found:
|
||||||
other: "Recommend Tag is not exist."
|
other: "Il Tag consigliato non esiste."
|
||||||
recommend_tag_enter:
|
recommend_tag_enter:
|
||||||
other: "Please enter at least one required tag."
|
other: "Inserisci almeno un tag."
|
||||||
not_contain_synonym_tags:
|
not_contain_synonym_tags:
|
||||||
other: "Should not contain synonym tags."
|
other: "Non deve contenere tag sinonimi."
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "Nessun permesso per l'aggiornamento."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "Non puoi impostare il sinonimo del tag corrente come se stesso."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "tema non trovato"
|
other: "tema non trovato"
|
||||||
revision:
|
revision:
|
||||||
review_underway:
|
review_underway:
|
||||||
other: "Can't edit currently, there is a version in the review queue."
|
other: "Non è possibile modificare al momento, c'è una versione nella coda di revisione."
|
||||||
no_permission:
|
no_permission:
|
||||||
other: "No permission to Revision."
|
other: "Nessun permesso per la revisione."
|
||||||
user:
|
user:
|
||||||
email_or_password_wrong:
|
email_or_password_wrong:
|
||||||
other:
|
other:
|
||||||
|
@ -128,75 +131,82 @@ backend:
|
||||||
username_duplicate:
|
username_duplicate:
|
||||||
other: "utente già in uso"
|
other: "utente già in uso"
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Inserimento dell'Avatar non riuscito."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "Non puoi modificare il tuo ruolo."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Al momento il sito non è aperto per la registrazione"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
other: "spam"
|
other: "posta indesiderata"
|
||||||
desc:
|
desc:
|
||||||
other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
|
other: "Questo articolo è una pubblicità o vandalismo. Non è utile o rilevante all'argomento corrente."
|
||||||
rude:
|
rude:
|
||||||
name:
|
name:
|
||||||
other: "scortese o violento"
|
other: "scortese o violento"
|
||||||
desc:
|
desc:
|
||||||
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
other: "Una persona ragionevole trova questo contenuto inappropriato a un discorso rispettoso."
|
||||||
duplicate:
|
duplicate:
|
||||||
name:
|
name:
|
||||||
other: "duplicato"
|
other: "duplicato"
|
||||||
desc:
|
desc:
|
||||||
other: "This question has been asked before and already has an answer."
|
other: "Questa domanda è già stata posta e ha già una risposta."
|
||||||
not_answer:
|
not_answer:
|
||||||
name:
|
name:
|
||||||
other: "non è una risposta"
|
other: "non è una risposta"
|
||||||
desc:
|
desc:
|
||||||
other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
|
other: "Questo è stato pubblicato come una risposta, ma non tenta di rispondere alla domanda. Dovrebbe forse essere una modifica, un commento, un'altra domanda, o cancellata del tutto."
|
||||||
not_need:
|
not_need:
|
||||||
name:
|
name:
|
||||||
other: "non più necessario"
|
other: "non più necessario"
|
||||||
desc:
|
desc:
|
||||||
other: "This comment is outdated, conversational or not relevant to this post."
|
other: "Questo commento è obsoleto, conversazionale o non pertinente per questo post."
|
||||||
other:
|
other:
|
||||||
name:
|
name:
|
||||||
other: "altro"
|
other: "altro"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires staff attention for another reason not listed above."
|
other: "Questo articolo richiede una supervisione dello staff per altre ragioni non listate sopra."
|
||||||
question:
|
question:
|
||||||
close:
|
close:
|
||||||
duplicate:
|
duplicate:
|
||||||
name:
|
name:
|
||||||
other: "spam"
|
other: "posta indesiderata"
|
||||||
desc:
|
desc:
|
||||||
other: "This question has been asked before and already has an answer."
|
other: "Questa domanda è già stata posta e ha già una risposta."
|
||||||
guideline:
|
guideline:
|
||||||
name:
|
name:
|
||||||
other: "motivo legato alla community"
|
other: "motivo legato alla community"
|
||||||
desc:
|
desc:
|
||||||
other: "This question doesn't meet a community guideline."
|
other: "Questa domanda non soddisfa le linee guida della comunità."
|
||||||
multiple:
|
multiple:
|
||||||
name:
|
name:
|
||||||
other: "richiede maggiori dettagli o chiarezza"
|
other: "richiede maggiori dettagli o chiarezza"
|
||||||
desc:
|
desc:
|
||||||
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
other: "Questa domanda attualmente include più domande in uno. Dovrebbe concentrarsi su un solo problema."
|
||||||
other:
|
other:
|
||||||
name:
|
name:
|
||||||
other: "altro"
|
other: "altro"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "Questo articolo richiede un'altro motivo non listato sopra."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -206,7 +216,7 @@ backend:
|
||||||
update_answer:
|
update_answer:
|
||||||
other: "risposta aggiornata"
|
other: "risposta aggiornata"
|
||||||
accept_answer:
|
accept_answer:
|
||||||
other: "risposta accepted"
|
other: "risposta accettata"
|
||||||
comment_question:
|
comment_question:
|
||||||
other: "domanda commentata"
|
other: "domanda commentata"
|
||||||
comment_answer:
|
comment_answer:
|
||||||
|
@ -226,21 +236,21 @@ backend:
|
||||||
#The following fields are used for interface presentation(Front-end)
|
#The following fields are used for interface presentation(Front-end)
|
||||||
ui:
|
ui:
|
||||||
how_to_format:
|
how_to_format:
|
||||||
title: How to Format
|
title: Come formattare
|
||||||
desc: >-
|
desc: >-
|
||||||
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>></code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url. om)</code></pre></li><li><p class="mb-2">mette i rendimenti tra i paragrafi</p></li><li><p class="mb-2"><em>_italic_</em> o **<strong>grassetto</strong>**</p></li><li><p class="mb-2">trattino di codice per 4 spazi</p></li><li><p class="mb-2">preventivo inserendo <code>></code> all'inizio della riga</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">crea recinzioni di codice con backticks <code>`</code></p><pre class="mb-0">````<code><br/>codice qui<br/>`````</code></pre></li></ul>
|
||||||
pagination:
|
pagination:
|
||||||
prev: Prev
|
prev: Prec
|
||||||
next: Next
|
next: Successivo
|
||||||
page_title:
|
page_title:
|
||||||
question: Question
|
question: Domanda
|
||||||
questions: Questions
|
questions: Domande
|
||||||
tag: Tag
|
tag: Tag
|
||||||
tags: Tags
|
tags: Tags
|
||||||
tag_wiki: tag wiki
|
tag_wiki: tag wiki
|
||||||
edit_tag: Edit Tag
|
edit_tag: Modifica Tag
|
||||||
ask_a_question: Add Question
|
ask_a_question: Aggiungi una domanda
|
||||||
edit_question: Edit Question
|
edit_question: Modifica Domanda
|
||||||
edit_answer: Edit Answer
|
edit_answer: Edit Answer
|
||||||
search: Search
|
search: Search
|
||||||
posts_containing: Posts containing
|
posts_containing: Posts containing
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
backend:
|
backend:
|
||||||
base:
|
base:
|
||||||
success:
|
success:
|
||||||
other: "Success."
|
other: "Sucesso."
|
||||||
unknown:
|
unknown:
|
||||||
other: "Unknown error."
|
other: "Erro desconhecido."
|
||||||
request_format_error:
|
request_format_error:
|
||||||
other: "Request format is not valid."
|
other: "Formato de solicitação não é válido."
|
||||||
unauthorized_error:
|
unauthorized_error:
|
||||||
other: "Unauthorized."
|
other: "Não autorizado."
|
||||||
database_error:
|
database_error:
|
||||||
other: "Data server error."
|
other: "Erro no servidor de dados."
|
||||||
role:
|
role:
|
||||||
name:
|
name:
|
||||||
user:
|
user:
|
||||||
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "Theme not found."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
414
i18n/zh_CN.yaml
414
i18n/zh_CN.yaml
|
@ -2,15 +2,15 @@
|
||||||
backend:
|
backend:
|
||||||
base:
|
base:
|
||||||
success:
|
success:
|
||||||
other: "成功"
|
other: "成功。"
|
||||||
unknown:
|
unknown:
|
||||||
other: "未知错误"
|
other: "未知错误。"
|
||||||
request_format_error:
|
request_format_error:
|
||||||
other: "请求格式错误"
|
other: "请求格式错误。"
|
||||||
unauthorized_error:
|
unauthorized_error:
|
||||||
other: "未登录"
|
other: "未授权。"
|
||||||
database_error:
|
database_error:
|
||||||
other: "数据服务异常"
|
other: "数据服务器错误。"
|
||||||
role:
|
role:
|
||||||
name:
|
name:
|
||||||
user:
|
user:
|
||||||
|
@ -23,38 +23,38 @@ backend:
|
||||||
user:
|
user:
|
||||||
other: "默认没有特殊访问权限。"
|
other: "默认没有特殊访问权限。"
|
||||||
admin:
|
admin:
|
||||||
other: "拥有进入网站的全部权限。"
|
other: "拥有管理网站的全部权限。"
|
||||||
moderator:
|
moderator:
|
||||||
other: "有权访问所有的帖子,无法进入管理员设置页面。"
|
other: "拥有访问除管理员设置以外的所有权限。"
|
||||||
email:
|
email:
|
||||||
other: "邮箱"
|
other: "邮箱"
|
||||||
password:
|
password:
|
||||||
other: "密码"
|
other: "密码"
|
||||||
email_or_password_wrong_error:
|
email_or_password_wrong_error:
|
||||||
other: "邮箱或密码错误"
|
other: "邮箱和密码不匹配。"
|
||||||
error:
|
error:
|
||||||
admin:
|
admin:
|
||||||
email_or_password_wrong:
|
email_or_password_wrong:
|
||||||
other: 邮箱或密码错误
|
other: 邮箱和密码不匹配。
|
||||||
answer:
|
answer:
|
||||||
not_found:
|
not_found:
|
||||||
other: "答案未找到"
|
other: "没有找到答案。"
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "无删除权限"
|
other: "没有删除权限。"
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "无修改权限"
|
other: "没有更新权限。"
|
||||||
comment:
|
comment:
|
||||||
edit_without_permission:
|
edit_without_permission:
|
||||||
other: "不允许编辑评论"
|
other: "不允许编辑评论。"
|
||||||
not_found:
|
not_found:
|
||||||
other: "评论未找到"
|
other: "评论未找到。"
|
||||||
email:
|
email:
|
||||||
duplicate:
|
duplicate:
|
||||||
other: "邮箱已经存在"
|
other: "邮箱已经存在。"
|
||||||
need_to_be_verified:
|
need_to_be_verified:
|
||||||
other: "邮箱需要验证"
|
other: "邮箱需要验证。"
|
||||||
verify_url_expired:
|
verify_url_expired:
|
||||||
other: "邮箱验证的网址已过期,请重新发送邮件"
|
other: "邮箱验证的网址已过期,请重新发送邮件。"
|
||||||
lang:
|
lang:
|
||||||
not_found:
|
not_found:
|
||||||
other: "语言未找到"
|
other: "语言未找到"
|
||||||
|
@ -106,7 +106,10 @@ backend:
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "没有更新标签权限。"
|
other: "没有更新标签权限。"
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "你无法将当前标签的同义词设置为当前标签自己"
|
other: "您不能将当前标签的同义词设置为本身。"
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "发件人名称不能是电子邮件地址。"
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "主题未找到"
|
other: "主题未找到"
|
||||||
|
@ -129,75 +132,81 @@ backend:
|
||||||
other: "用户名已被使用"
|
other: "用户名已被使用"
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "头像设置错误"
|
other: "头像设置错误"
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "读取配置失败"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "数据连接异常!"
|
|
||||||
create_table_failed:
|
|
||||||
other: "创建表失败"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "无法创建配置文件"
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "你无法修改自己的角色"
|
other: "您不能修改自己的角色。"
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "目前该网站尚未开放注册"
|
other: "目前该站点未开放注册"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "读取配置失败"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "数据库连接失败"
|
||||||
|
create_table_failed:
|
||||||
|
other: "创建表失败"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "无法创建 config.yaml 文件。"
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
other: "垃圾信息"
|
other: "垃圾信息"
|
||||||
desc:
|
desc:
|
||||||
other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
|
other: "这个帖子是一个广告,或是破坏性行为。它对当前的主题没有用处,也不相关。"
|
||||||
rude:
|
rude:
|
||||||
name:
|
name:
|
||||||
other: "粗鲁或辱骂的"
|
other: "粗鲁或辱骂的"
|
||||||
desc:
|
desc:
|
||||||
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
other: "一个有理智的人都会认为这种内容不适合进行尊重性的讨论。"
|
||||||
duplicate:
|
duplicate:
|
||||||
name:
|
name:
|
||||||
other: "重复信息"
|
other: "重复信息"
|
||||||
desc:
|
desc:
|
||||||
other: "This question has been asked before and already has an answer."
|
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||||
not_answer:
|
not_answer:
|
||||||
name:
|
name:
|
||||||
other: "不是答案"
|
other: "不是答案"
|
||||||
desc:
|
desc:
|
||||||
other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
|
other: "此帖子是作为一个答案发布的,但它并没有试图回答这个问题。总之,它可能应该是个编辑,评论,另一个问题或者被删除。"
|
||||||
not_need:
|
not_need:
|
||||||
name:
|
name:
|
||||||
other: "不再需要"
|
other: "不再需要"
|
||||||
desc:
|
desc:
|
||||||
other: "This comment is outdated, conversational or not relevant to this post."
|
other: "此评论已过时,对话或与此帖子无关。"
|
||||||
other:
|
other:
|
||||||
name:
|
name:
|
||||||
other: "其他原因"
|
other: "其他原因"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires staff attention for another reason not listed above."
|
other: "此帖子需要工作人员关注,因为是上述所列以外的其他理由。"
|
||||||
question:
|
question:
|
||||||
close:
|
close:
|
||||||
duplicate:
|
duplicate:
|
||||||
name:
|
name:
|
||||||
other: "垃圾信息"
|
other: "垃圾信息"
|
||||||
desc:
|
desc:
|
||||||
other: "This question has been asked before and already has an answer."
|
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||||
guideline:
|
guideline:
|
||||||
name:
|
name:
|
||||||
other: "社区特定原因"
|
other: "社区特定原因"
|
||||||
desc:
|
desc:
|
||||||
other: "This question doesn't meet a community guideline."
|
other: "此问题不符合社区准则。"
|
||||||
multiple:
|
multiple:
|
||||||
name:
|
name:
|
||||||
other: "需要细节或澄清"
|
other: "需要细节或澄清"
|
||||||
desc:
|
desc:
|
||||||
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
other: "此问题目前涵盖多个问题。它应该只集中在一个问题上。"
|
||||||
other:
|
other:
|
||||||
name:
|
name:
|
||||||
other: "其他原因"
|
other: "其他原因"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "这个帖子需要上面没有列出的另一个原因。"
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "提问于"
|
||||||
|
answered:
|
||||||
|
other: "回答于"
|
||||||
|
modified:
|
||||||
|
other: "修改于"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -207,7 +216,7 @@ backend:
|
||||||
update_answer:
|
update_answer:
|
||||||
other: "更新了答案"
|
other: "更新了答案"
|
||||||
accept_answer:
|
accept_answer:
|
||||||
other: "接受了答案"
|
other: "已接受的回答"
|
||||||
comment_question:
|
comment_question:
|
||||||
other: "评论了问题"
|
other: "评论了问题"
|
||||||
comment_answer:
|
comment_answer:
|
||||||
|
@ -229,15 +238,7 @@ ui:
|
||||||
how_to_format:
|
how_to_format:
|
||||||
title: 如何设定文本格式
|
title: 如何设定文本格式
|
||||||
desc: >-
|
desc: >-
|
||||||
<ul class="mb-0"><li><p class="mb-2">添加链接:</p><pre
|
<ul class="mb-0"><li><p class="mb-2">添加链接:</p><pre class="mb-2"><code><https://url.com><br/><br/>[标题](https://url.com)</code></pre></li><li><p class="mb-2">段落之间使用空行分隔</p></li><li><p class="mb-2"><em>_斜体_</em> 或者 **<strong>粗体</strong>**</p></li><li><p class="mb-2">使用 4 个空格缩进代码</p></li><li><p class="mb-2">在行首添加<code>></code>表示引用</p></li><li><p class="mb-2">反引号进行转义 <code>`像 _这样_`</code></p></li><li><p class="mb-2">使用<code>```</code>创建代码块</p><pre class="mb-0"><code>```<br/>// 这是代码<br/>```</code></pre></li></ul>
|
||||||
class="mb-2"><code><https://url.com><br/><br/>[标题](https://url.com)</code></pre></li><li><p
|
|
||||||
class="mb-2">段落之间使用空行分隔</p></li><li><p class="mb-2"><em>_斜体_</em> 或者
|
|
||||||
**<strong>粗体</strong>**</p></li><li><p class="mb-2">使用 4
|
|
||||||
个空格缩进代码</p></li><li><p
|
|
||||||
class="mb-2">在行首添加<code>></code>表示引用</p></li><li><p class="mb-2">反引号进行转义
|
|
||||||
<code>`像 _这样_`</code></p></li><li><p
|
|
||||||
class="mb-2">使用<code>```</code>创建代码块</p><pre class="mb-0"><code>```<br/>//
|
|
||||||
这是代码<br/>```</code></pre></li></ul>
|
|
||||||
pagination:
|
pagination:
|
||||||
prev: 上一页
|
prev: 上一页
|
||||||
next: 下一页
|
next: 下一页
|
||||||
|
@ -266,7 +267,7 @@ ui:
|
||||||
install: Answer 安装
|
install: Answer 安装
|
||||||
upgrade: Answer 升级
|
upgrade: Answer 升级
|
||||||
maintenance: 网站维护
|
maintenance: 网站维护
|
||||||
users: Users
|
users: 用户
|
||||||
notifications:
|
notifications:
|
||||||
title: 通知
|
title: 通知
|
||||||
inbox: 收件箱
|
inbox: 收件箱
|
||||||
|
@ -290,7 +291,7 @@ ui:
|
||||||
class_diagram: 类图
|
class_diagram: 类图
|
||||||
state_diagram: 状态图
|
state_diagram: 状态图
|
||||||
entity_relationship_diagram: ER 图
|
entity_relationship_diagram: ER 图
|
||||||
user_defined_diagram: User defined diagram
|
user_defined_diagram: 用户自定义图表
|
||||||
gantt_chart: 甘特图
|
gantt_chart: 甘特图
|
||||||
pie_chart: 饼图
|
pie_chart: 饼图
|
||||||
code:
|
code:
|
||||||
|
@ -339,7 +340,7 @@ ui:
|
||||||
only_image: 只能上传图片文件。
|
only_image: 只能上传图片文件。
|
||||||
max_size: 图片文件大小不能超过 4 MB。
|
max_size: 图片文件大小不能超过 4 MB。
|
||||||
desc:
|
desc:
|
||||||
label: 图片描述(可选)
|
label: 描述(可选)
|
||||||
tab_url: 网络图片
|
tab_url: 网络图片
|
||||||
form_url:
|
form_url:
|
||||||
fields:
|
fields:
|
||||||
|
@ -410,13 +411,13 @@ ui:
|
||||||
range: 不能超过 35 个字符
|
range: 不能超过 35 个字符
|
||||||
slug_name:
|
slug_name:
|
||||||
label: URL 固定链接
|
label: URL 固定链接
|
||||||
desc: '必须由 "a-z", "0-9", "+ # - ." 组成'
|
desc: '必须使用字符集 "a-z"、"0-9"、"+ # - ."'
|
||||||
msg:
|
msg:
|
||||||
empty: 不能为空
|
empty: 不能为空
|
||||||
range: 不能超过 35 个字符
|
range: 不能超过 35 个字符
|
||||||
character: 包含非法字符
|
character: 包含非法字符
|
||||||
desc:
|
desc:
|
||||||
label: 标签描述(可选)
|
label: 描述(可选)
|
||||||
btn_cancel: 取消
|
btn_cancel: 取消
|
||||||
btn_submit: 提交
|
btn_submit: 提交
|
||||||
tag_info:
|
tag_info:
|
||||||
|
@ -564,8 +565,7 @@ ui:
|
||||||
placeholder: 搜索
|
placeholder: 搜索
|
||||||
footer:
|
footer:
|
||||||
build_on: >-
|
build_on: >-
|
||||||
Built on <1> Answer </1>- the open-source software that powers Q&A
|
基于<1>Answer</1>--为问答社区提供动力的开源软件。<br />Made with love © {{cc}}.
|
||||||
communities<br />Made with love © 2022 Answer
|
|
||||||
upload_img:
|
upload_img:
|
||||||
name: 更改图片
|
name: 更改图片
|
||||||
loading: 加载中...
|
loading: 加载中...
|
||||||
|
@ -616,7 +616,7 @@ ui:
|
||||||
btn_cancel: 取消
|
btn_cancel: 取消
|
||||||
btn_update: 更新电子邮件地址
|
btn_update: 更新电子邮件地址
|
||||||
send_success: >-
|
send_success: >-
|
||||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
如果账户与<strong>{{mail}}</strong>相匹配,您应该很快就会收到一封电子邮件,说明如何重置您的密码。
|
||||||
email:
|
email:
|
||||||
label: 新邮箱
|
label: 新邮箱
|
||||||
msg:
|
msg:
|
||||||
|
@ -645,8 +645,8 @@ ui:
|
||||||
account: 账号
|
account: 账号
|
||||||
interface: 界面
|
interface: 界面
|
||||||
profile:
|
profile:
|
||||||
heading: Profile
|
heading: 个人资料
|
||||||
btn_name: Save
|
btn_name: 保存
|
||||||
display_name:
|
display_name:
|
||||||
label: 昵称
|
label: 昵称
|
||||||
msg: 昵称不能为空
|
msg: 昵称不能为空
|
||||||
|
@ -664,7 +664,7 @@ ui:
|
||||||
custom: 自定义
|
custom: 自定义
|
||||||
btn_refresh: 刷新
|
btn_refresh: 刷新
|
||||||
custom_text: 您可以上传您的图片。
|
custom_text: 您可以上传您的图片。
|
||||||
default: System
|
default: 系统
|
||||||
msg: 请上传头像
|
msg: 请上传头像
|
||||||
bio:
|
bio:
|
||||||
label: 关于我 (可选)
|
label: 关于我 (可选)
|
||||||
|
@ -676,12 +676,12 @@ ui:
|
||||||
label: 位置 (可选)
|
label: 位置 (可选)
|
||||||
placeholder: "城市, 国家"
|
placeholder: "城市, 国家"
|
||||||
notification:
|
notification:
|
||||||
heading: Notifications
|
heading: 通知
|
||||||
email:
|
email:
|
||||||
label: 邮件通知
|
label: 邮件通知
|
||||||
radio: "你的提问有新的回答,评论,和其他"
|
radio: "你的提问有新的回答,评论,和其他"
|
||||||
account:
|
account:
|
||||||
heading: Account
|
heading: 账号
|
||||||
change_email_btn: 更改邮箱
|
change_email_btn: 更改邮箱
|
||||||
change_pass_btn: 更改密码
|
change_pass_btn: 更改密码
|
||||||
change_email_info: >-
|
change_email_info: >-
|
||||||
|
@ -701,7 +701,7 @@ ui:
|
||||||
pass_confirm:
|
pass_confirm:
|
||||||
label: 确认新密码
|
label: 确认新密码
|
||||||
interface:
|
interface:
|
||||||
heading: Interface
|
heading: 界面
|
||||||
lang:
|
lang:
|
||||||
label: 界面语言
|
label: 界面语言
|
||||||
text: 设置用户界面语言,在刷新页面后生效。
|
text: 设置用户界面语言,在刷新页面后生效。
|
||||||
|
@ -709,7 +709,7 @@ ui:
|
||||||
update: 更新成功
|
update: 更新成功
|
||||||
update_password: 更改密码成功。
|
update_password: 更改密码成功。
|
||||||
flag_success: 感谢您的标记,我们会尽快处理。
|
flag_success: 感谢您的标记,我们会尽快处理。
|
||||||
forbidden_operate_self: Forbidden to operate on yourself
|
forbidden_operate_self: 禁止自己操作
|
||||||
review: 您的修订将在审核通过后显示。
|
review: 您的修订将在审核通过后显示。
|
||||||
related_question:
|
related_question:
|
||||||
title: 相关问题
|
title: 相关问题
|
||||||
|
@ -735,16 +735,16 @@ ui:
|
||||||
write_answer:
|
write_answer:
|
||||||
title: 你的回答
|
title: 你的回答
|
||||||
btn_name: 提交你的回答
|
btn_name: 提交你的回答
|
||||||
add_another_answer: Add another answer
|
add_another_answer: 添加另一个答案
|
||||||
confirm_title: 继续回答
|
confirm_title: 继续回答
|
||||||
continue: 继续
|
continue: 继续
|
||||||
confirm_info: >-
|
confirm_info: >-
|
||||||
<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
|
<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
|
||||||
empty: 回答内容不能为空。
|
empty: 回答内容不能为空。
|
||||||
reopen:
|
reopen:
|
||||||
title: Reopen this post
|
title: 重新打开这个帖子
|
||||||
content: Are you sure you want to reopen?
|
content: 确定要重新打开吗?
|
||||||
success: This post has been reopened
|
success: 这个帖子已被重新打开
|
||||||
delete:
|
delete:
|
||||||
title: 删除
|
title: 删除
|
||||||
question: >-
|
question: >-
|
||||||
|
@ -808,6 +808,11 @@ ui:
|
||||||
confirm_new_email: 你的电子邮箱已更新
|
confirm_new_email: 你的电子邮箱已更新
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?
|
抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: 退订
|
||||||
|
success_title: 取消订阅成功
|
||||||
|
success_desc: 您已成功地从此订阅者列表中移除,并且将不会再收到我们的任何电子邮件。
|
||||||
|
link: 更改设置
|
||||||
question:
|
question:
|
||||||
following_tags: 已关注的标签
|
following_tags: 已关注的标签
|
||||||
edit: 编辑
|
edit: 编辑
|
||||||
|
@ -870,9 +875,9 @@ ui:
|
||||||
done: 完成
|
done: 完成
|
||||||
config_yaml_error: 无法创建配置文件
|
config_yaml_error: 无法创建配置文件
|
||||||
lang:
|
lang:
|
||||||
label: Please Choose a Language
|
label: 请选择一种语言
|
||||||
db_type:
|
db_type:
|
||||||
label: Database Engine
|
label: 数据库引擎
|
||||||
db_username:
|
db_username:
|
||||||
label: 用户名
|
label: 用户名
|
||||||
placeholder: root
|
placeholder: root
|
||||||
|
@ -887,17 +892,17 @@ ui:
|
||||||
msg: 数据库地址不能为空
|
msg: 数据库地址不能为空
|
||||||
db_name:
|
db_name:
|
||||||
label: 数据库名
|
label: 数据库名
|
||||||
placeholder: answer
|
placeholder: 回答
|
||||||
msg: 数据库名称不能为空。
|
msg: 数据库名称不能为空。
|
||||||
db_file:
|
db_file:
|
||||||
label: Database File
|
label: 数据库文件
|
||||||
placeholder: /data/answer.db
|
placeholder: /data/answer.db
|
||||||
msg: 数据库文件不能为空。
|
msg: 数据库文件不能为空。
|
||||||
config_yaml:
|
config_yaml:
|
||||||
title: 创建 config.yaml
|
title: 创建 config.yaml
|
||||||
label: 已创建 config.yaml 文件。
|
label: 已创建 config.yaml 文件。
|
||||||
desc: >-
|
desc: >-
|
||||||
You can create the <1>config.yaml</1> file manually in the <1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
您可以手动在 <1>/var/wwww/xxx/</1> 目录中创建<1>config.yaml</1> 文件并粘贴以下文本。
|
||||||
info: "完成后,点击“下一步”按钮。"
|
info: "完成后,点击“下一步”按钮。"
|
||||||
site_information: 站点信息
|
site_information: 站点信息
|
||||||
admin_account: 管理员账户
|
admin_account: 管理员账户
|
||||||
|
@ -906,52 +911,52 @@ ui:
|
||||||
msg: 站点名称不能为空。
|
msg: 站点名称不能为空。
|
||||||
site_url:
|
site_url:
|
||||||
label: 站点地址(URL)
|
label: 站点地址(URL)
|
||||||
text: The address of your site.
|
text: 此网站的地址。
|
||||||
msg:
|
msg:
|
||||||
empty: 站点URL不能为空。
|
empty: 站点URL不能为空。
|
||||||
incorrect: 站点URL格式不正确。
|
incorrect: 站点URL格式不正确。
|
||||||
contact_email:
|
contact_email:
|
||||||
label: 联系邮箱
|
label: 联系邮箱
|
||||||
text: Email address of key contact responsible for this site.
|
text: 负责本网站的主要联系人的电子邮件地址。
|
||||||
msg:
|
msg:
|
||||||
empty: Contact Email cannot be empty.
|
empty: 联系人邮箱地址不能为空。
|
||||||
incorrect: Contact Email incorrect format.
|
incorrect: 联系人邮箱地址不正确。
|
||||||
admin_name:
|
admin_name:
|
||||||
label: Name
|
label: 昵称
|
||||||
msg: Name cannot be empty.
|
msg: 昵称不能为空。
|
||||||
admin_password:
|
admin_password:
|
||||||
label: Password
|
label: 密码
|
||||||
text: >-
|
text: >-
|
||||||
You will need this password to log in. Please store it in a secure location.
|
您需要此密码才能登录。请将其存储在一个安全的位置。
|
||||||
msg: Password cannot be empty.
|
msg: 密码不能为空。
|
||||||
admin_email:
|
admin_email:
|
||||||
label: Email
|
label: 邮箱
|
||||||
text: You will need this email to log in.
|
text: 您需要此电子邮件才能登录。
|
||||||
msg:
|
msg:
|
||||||
empty: Email cannot be empty.
|
empty: 邮箱不能为空。
|
||||||
incorrect: Email incorrect format.
|
incorrect: 邮箱格式不正确。
|
||||||
ready_title: Your Answer is Ready!
|
ready_title: 你的答案已经准备好了!
|
||||||
ready_desc: >-
|
ready_desc: >-
|
||||||
If you ever feel like changing more settings, visit <1>admin section</1>; find it in the site menu.
|
如果你想改变更多的设置,请访问<1>管理员部分</1>;在网站菜单中找到它。
|
||||||
good_luck: "Have fun, and good luck!"
|
good_luck: "玩得愉快,祝您好运!"
|
||||||
warn_title: Warning
|
warn_title: 警告
|
||||||
warn_desc: >-
|
warn_desc: >-
|
||||||
The file <1>config.yaml</1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
|
文件<1>config.yaml</1>已存在。如果您需要重置此文件中的任何配置项,请先删除它。
|
||||||
install_now: You may try <1>installing now</1>.
|
install_now: 您可以尝试<1>现在安装</1>。
|
||||||
installed: 已安裝
|
installed: 已安裝
|
||||||
installed_desc: >-
|
installed_desc: >-
|
||||||
You appear to have already installed. To reinstall please clear your old database tables first.
|
您似乎已经安装过了。要重新安装,请先清除旧的数据库表。
|
||||||
db_failed: 数据连接异常!
|
db_failed: 数据连接异常!
|
||||||
db_failed_desc: >-
|
db_failed_desc: >-
|
||||||
This either means that the database information in your <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean your host’s database server is down.
|
这或者意味着数据库信息在 <1>config.yaml</1> 文件不正确,或者无法与数据库服务器建立联系。这可能意味着您的主机数据库服务器已关闭。
|
||||||
page_404:
|
page_404:
|
||||||
desc: 页面不存在
|
desc: "很抱歉,此页面不存在。"
|
||||||
back_home: 回到主页
|
back_home: 回到主页
|
||||||
page_50X:
|
page_50X:
|
||||||
desc: 服务器遇到了一个错误,无法完成你的请求。
|
desc: 服务器遇到了一个错误,无法完成你的请求。
|
||||||
back_home: 回到主页
|
back_home: 回到主页
|
||||||
page_maintenance:
|
page_maintenance:
|
||||||
desc: "We are under maintenance, we’ll be back soon."
|
desc: "我们正在进行维护,我们将很快回来。"
|
||||||
nav_menus:
|
nav_menus:
|
||||||
dashboard: 后台管理
|
dashboard: 后台管理
|
||||||
contents: 内容管理
|
contents: 内容管理
|
||||||
|
@ -969,17 +974,17 @@ ui:
|
||||||
tos: 服务条款
|
tos: 服务条款
|
||||||
privacy: 隐私政策
|
privacy: 隐私政策
|
||||||
seo: SEO
|
seo: SEO
|
||||||
customize: Customize
|
customize: 自定义
|
||||||
themes: Themes
|
themes: 主题
|
||||||
css-html: CSS/HTML
|
css-html: CSS/HTML
|
||||||
login: Login
|
login: 登录
|
||||||
website_welcome: 欢迎来到 {{site_name}}
|
website_welcome: 欢迎来到 {{site_name}}
|
||||||
admin:
|
admin:
|
||||||
admin_header:
|
admin_header:
|
||||||
title: 后台管理
|
title: 后台管理
|
||||||
dashboard:
|
dashboard:
|
||||||
title: 后台管理
|
title: 后台管理
|
||||||
welcome: 欢迎来到 {{site_name}} 后台管理!
|
welcome: 欢迎来到 Answer 后台管理!
|
||||||
site_statistics: 站点统计
|
site_statistics: 站点统计
|
||||||
questions: "问题:"
|
questions: "问题:"
|
||||||
answers: "回答:"
|
answers: "回答:"
|
||||||
|
@ -999,6 +1004,7 @@ ui:
|
||||||
answer_links: 回答链接
|
answer_links: 回答链接
|
||||||
documents: 文档
|
documents: 文档
|
||||||
feedback: 用户反馈
|
feedback: 用户反馈
|
||||||
|
support: 帮助
|
||||||
review: 审查
|
review: 审查
|
||||||
config: 配置
|
config: 配置
|
||||||
update_to: 更新到
|
update_to: 更新到
|
||||||
|
@ -1023,11 +1029,11 @@ ui:
|
||||||
btn_cancel: 取消
|
btn_cancel: 取消
|
||||||
btn_submit: 提交
|
btn_submit: 提交
|
||||||
normal_name: 正常
|
normal_name: 正常
|
||||||
normal_desc: 正常状态的用户可以提问和回答。
|
normal_desc: 普通用户可以提问和回答。
|
||||||
suspended_name: 封禁
|
suspended_name: 封禁
|
||||||
suspended_desc: 被封禁的用户将无法登录。
|
suspended_desc: 被封禁的用户将无法登录。
|
||||||
deleted_name: 删除
|
deleted_name: 删除
|
||||||
deleted_desc: 删除用户的个人信息,认证等等。
|
deleted_desc: "删除个人资料和身份验证关联。"
|
||||||
inactive_name: 不活跃
|
inactive_name: 不活跃
|
||||||
inactive_desc: 不活跃的用户必须重新验证邮箱。
|
inactive_desc: 不活跃的用户必须重新验证邮箱。
|
||||||
confirm_title: 删除此用户
|
confirm_title: 删除此用户
|
||||||
|
@ -1038,11 +1044,11 @@ ui:
|
||||||
status_modal:
|
status_modal:
|
||||||
title: "更改 {{ type }} 状态为..."
|
title: "更改 {{ type }} 状态为..."
|
||||||
normal_name: 正常
|
normal_name: 正常
|
||||||
normal_desc: 所有用户都可以访问
|
normal_desc: 所有用户都可以访问的普通帖子。
|
||||||
closed_name: 关闭
|
closed_name: 关闭
|
||||||
closed_desc: 不能回答,但仍然可以编辑、投票和评论。
|
closed_desc: "关闭的问题不能回答,但仍然可以编辑、投票和评论。"
|
||||||
deleted_name: 删除
|
deleted_name: 删除
|
||||||
deleted_desc: 所有获得/损失的声望将会恢复。
|
deleted_desc: 获得和丧失的所有信誉积分将被恢复。
|
||||||
btn_cancel: 取消
|
btn_cancel: 取消
|
||||||
btn_submit: 提交
|
btn_submit: 提交
|
||||||
btn_next: 下一步
|
btn_next: 下一步
|
||||||
|
@ -1077,32 +1083,32 @@ ui:
|
||||||
change_status: 更改状态
|
change_status: 更改状态
|
||||||
change_role: 更改角色
|
change_role: 更改角色
|
||||||
show_logs: 显示日志
|
show_logs: 显示日志
|
||||||
add_user: Add user
|
add_user: 添加用户
|
||||||
new_password_modal:
|
new_password_modal:
|
||||||
title: Set new password
|
title: 设置新密码
|
||||||
form:
|
form:
|
||||||
fields:
|
fields:
|
||||||
password:
|
password:
|
||||||
label: Password
|
label: 密码
|
||||||
text: The user will be logged out and need to login again.
|
text: 用户将被注销,需要再次登录。
|
||||||
msg: Password must be at 8 - 32 characters in length.
|
msg: 密码的长度必须是8-32个字符。
|
||||||
btn_cancel: Cancel
|
btn_cancel: 取消
|
||||||
btn_submit: Submit
|
btn_submit: 提交
|
||||||
user_modal:
|
user_modal:
|
||||||
title: Add new user
|
title: 添加新用户
|
||||||
form:
|
form:
|
||||||
fields:
|
fields:
|
||||||
display_name:
|
display_name:
|
||||||
label: Display Name
|
label: 昵称
|
||||||
msg: display_name must be at 4 - 30 characters in length.
|
msg: 昵称的长度必须是4-30个字符。
|
||||||
email:
|
email:
|
||||||
label: Email
|
label: 邮箱
|
||||||
msg: Email is not valid.
|
msg: 电子邮箱无效。
|
||||||
password:
|
password:
|
||||||
label: Password
|
label: 密码
|
||||||
msg: Password must be at 8 - 32 characters in length.
|
msg: 密码的长度必须是8-32个字符。
|
||||||
btn_cancel: Cancel
|
btn_cancel: 取消
|
||||||
btn_submit: Submit
|
btn_submit: 提交
|
||||||
questions:
|
questions:
|
||||||
page_title: 问题
|
page_title: 问题
|
||||||
normal: 正常
|
normal: 正常
|
||||||
|
@ -1116,7 +1122,7 @@ ui:
|
||||||
action: 操作
|
action: 操作
|
||||||
change: 更改
|
change: 更改
|
||||||
filter:
|
filter:
|
||||||
placeholder: "Filter by title, question:id"
|
placeholder: "按标题过滤,问题:id"
|
||||||
answers:
|
answers:
|
||||||
page_title: 回答
|
page_title: 回答
|
||||||
normal: 正常
|
normal: 正常
|
||||||
|
@ -1128,21 +1134,31 @@ ui:
|
||||||
action: 操作
|
action: 操作
|
||||||
change: 更改
|
change: 更改
|
||||||
filter:
|
filter:
|
||||||
placeholder: "Filter by title, answer:id"
|
placeholder: "按标题筛选,答案:id"
|
||||||
general:
|
general:
|
||||||
page_title: 一般
|
page_title: 一般
|
||||||
name:
|
name:
|
||||||
label: 站点名称
|
label: 站点名称
|
||||||
msg: 不能为空
|
msg: 不能为空
|
||||||
text: 站点的名称,作为站点的标题(HTML 的 title 标签)。
|
text: "站点的名称,作为站点的标题(HTML 的 title 标签)。"
|
||||||
|
site_url:
|
||||||
|
label: 网站网址
|
||||||
|
msg: 网站网址不能为空。
|
||||||
|
validate: 请输入一个有效的 URL。
|
||||||
|
text: 此网站的地址。
|
||||||
short_desc:
|
short_desc:
|
||||||
label: 简短的站点标语 (可选)
|
label: 简短网站描述(可选)
|
||||||
msg: 不能为空
|
msg: 简短网站描述不能为空。
|
||||||
text: 简短的标语,作为网站主页的标题(HTML 的 title 标签)。
|
text: "简短的标语,作为网站主页的标题(Html 的 title 标签)。"
|
||||||
desc:
|
desc:
|
||||||
label: 网站描述 (可选)
|
label: 网站描述 (可选)
|
||||||
msg: 不能为空
|
msg: 网站描述不能为空。
|
||||||
text: 使用一句话描述本站,作为网站的描述(HTML 的 meta 标签)。
|
text: "使用一句话描述本站,作为网站的描述(Html 的 meta 标签)。"
|
||||||
|
contact_email:
|
||||||
|
label: 联系人邮箱
|
||||||
|
msg: 联系人邮箱不能为空。
|
||||||
|
validate: 联系人邮箱无效。
|
||||||
|
text: 负责本网站的主要联系人的电子邮件地址。
|
||||||
interface:
|
interface:
|
||||||
page_title: 界面
|
page_title: 界面
|
||||||
logo:
|
logo:
|
||||||
|
@ -1158,9 +1174,9 @@ ui:
|
||||||
msg: 不能为空
|
msg: 不能为空
|
||||||
text: 设置用户界面语言,在刷新页面后生效。
|
text: 设置用户界面语言,在刷新页面后生效。
|
||||||
time_zone:
|
time_zone:
|
||||||
label: Timezone
|
label: 时区
|
||||||
msg: Timezone cannot be empty.
|
msg: 时区不能为空。
|
||||||
text: Choose a city in the same timezone as you.
|
text: 选择一个与您相同时区的城市。
|
||||||
smtp:
|
smtp:
|
||||||
page_title: SMTP
|
page_title: SMTP
|
||||||
from_email:
|
from_email:
|
||||||
|
@ -1196,97 +1212,97 @@ ui:
|
||||||
text: 提供用于接收测试邮件的邮箱地址。
|
text: 提供用于接收测试邮件的邮箱地址。
|
||||||
msg: 地址无效
|
msg: 地址无效
|
||||||
smtp_authentication:
|
smtp_authentication:
|
||||||
label: Enable authentication
|
label: 启用身份验证
|
||||||
title: SMTP Authentication
|
title: SMTP身份验证
|
||||||
msg: 不能为空
|
msg: 不能为空
|
||||||
"yes": "是"
|
"yes": "是"
|
||||||
"no": "否"
|
"no": "否"
|
||||||
branding:
|
branding:
|
||||||
page_title: Branding
|
page_title: 品牌
|
||||||
logo:
|
logo:
|
||||||
label: Logo
|
label: 图标
|
||||||
msg: Logo cannot be empty.
|
msg: 图标不能为空。
|
||||||
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
|
text: 在你的网站左上方的Logo图标。使用一个高度为56,长宽比大于3:1的宽长方形图像。如果留空,将显示网站标题文本。
|
||||||
mobile_logo:
|
mobile_logo:
|
||||||
label: Mobile Logo (optional)
|
label: 移动端图标(可选)
|
||||||
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
|
text: 在你的网站的移动版上使用的标志。使用一个高度为56的宽矩形图像。如果留空,将使用 "Logo"设置中的图像。
|
||||||
square_icon:
|
square_icon:
|
||||||
label: Square Icon
|
label: 方形图标
|
||||||
msg: Square icon cannot be empty.
|
msg: 方形图标不能为空。
|
||||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
text: 用作元数据图标的基础的图像。最好是大于512x512。
|
||||||
favicon:
|
favicon:
|
||||||
label: Favicon (optional)
|
label: 收藏夹图标(可选)
|
||||||
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
|
text: 网站的图标。要在 CDN 正常工作,它必须是 png。 将调整大小到32x32。如果留空,将使用“方形图标”。
|
||||||
legal:
|
legal:
|
||||||
page_title: Legal
|
page_title: 法律条款
|
||||||
terms_of_service:
|
terms_of_service:
|
||||||
label: Terms of Service
|
label: 服务条款
|
||||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
text: "您可以在此添加服务内容的条款。如果您已经在别处托管了文档,请在这里提供完整的URL。"
|
||||||
privacy_policy:
|
privacy_policy:
|
||||||
label: Privacy Policy
|
label: 隐私条款
|
||||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
text: "您可以在此添加隐私政策内容。如果您已经在别处托管了文档,请在这里提供完整的URL。"
|
||||||
write:
|
write:
|
||||||
page_title: Write
|
page_title: 编辑
|
||||||
recommend_tags:
|
recommend_tags:
|
||||||
label: Recommend Tags
|
label: 推荐标签
|
||||||
text: "Please input tag slug above, one tag per line."
|
text: "请输入以上标签,每行一个标签。"
|
||||||
required_tag:
|
required_tag:
|
||||||
title: Required Tag
|
title: 必需的标签
|
||||||
label: Set recommend tag as required
|
label: 根据需要设置推荐标签
|
||||||
text: "Every new question must have at least one recommend tag."
|
text: "每个新问题必须至少有一个推荐标签。"
|
||||||
reserved_tags:
|
reserved_tags:
|
||||||
label: Reserved Tags
|
label: 保留标签
|
||||||
text: "Reserved tags can only be added to a post by moderator."
|
text: "保留的标签只能由版主添加到一个帖子中。"
|
||||||
seo:
|
seo:
|
||||||
page_title: SEO
|
page_title: 搜索引擎优化
|
||||||
permalink:
|
permalink:
|
||||||
label: Permalink
|
label: 固定链接
|
||||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
text: 自定义URL结构可以提高可用性,以及你的链接的向前兼容性。
|
||||||
robots:
|
robots:
|
||||||
label: robots.txt
|
label: robots.txt
|
||||||
text: This will permanently override any related site settings.
|
text: 这将永久覆盖任何相关的网站设置。
|
||||||
themes:
|
themes:
|
||||||
page_title: Themes
|
page_title: 主题
|
||||||
themes:
|
themes:
|
||||||
label: Themes
|
label: 主题
|
||||||
text: Select an existing theme.
|
text: 选择一个现有主题。
|
||||||
navbar_style:
|
navbar_style:
|
||||||
label: Navbar Style
|
label: 导航栏样式
|
||||||
text: Select an existing theme.
|
text: 选择一个现有主题。
|
||||||
primary_color:
|
primary_color:
|
||||||
label: Primary Color
|
label: 主色调
|
||||||
text: Modify the colors used by your themes
|
text: 修改您主题使用的颜色
|
||||||
css_and_html:
|
css_and_html:
|
||||||
page_title: CSS and HTML
|
page_title: CSS 与 HTML
|
||||||
custom_css:
|
custom_css:
|
||||||
label: Custom CSS
|
label: 自定义CSS
|
||||||
text: This will insert as <link>
|
text: 这将在 <link> 之前插入
|
||||||
head:
|
head:
|
||||||
label: Head
|
label: 头部
|
||||||
text: This will insert before </head>
|
text: 这将在 </head> 之前插入
|
||||||
header:
|
header:
|
||||||
label: Header
|
label: 标题
|
||||||
text: This will insert after <body>
|
text: 这将在 <body> 之前插入
|
||||||
footer:
|
footer:
|
||||||
label: Footer
|
label: 页脚
|
||||||
text: This will insert before </html>.
|
text: 这将在 </html> 之前插入
|
||||||
login:
|
login:
|
||||||
page_title: Login
|
page_title: 登录
|
||||||
membership:
|
membership:
|
||||||
title: Membership
|
title: 会员
|
||||||
label: Allow new registrations
|
label: 允许新注册
|
||||||
text: Turn off to prevent anyone from creating a new account.
|
text: 关闭以防止任何人创建新帐户。
|
||||||
private:
|
private:
|
||||||
title: Private
|
title: 非公开的
|
||||||
label: Login required
|
label: 需要登录
|
||||||
text: Only logged in users can access this community.
|
text: 只有登录用户才能访问这个社区。
|
||||||
form:
|
form:
|
||||||
empty: cannot be empty
|
empty: 不能为空
|
||||||
invalid: is invalid
|
invalid: 是无效的
|
||||||
btn_submit: Save
|
btn_submit: 保存
|
||||||
not_found_props: "Required property {{ key }} not found."
|
not_found_props: "所需属性 {{ key }} 未找到。"
|
||||||
page_review:
|
page_review:
|
||||||
review: Review
|
review: 评论
|
||||||
proposed: 提案
|
proposed: 提案
|
||||||
question_edit: 问题编辑
|
question_edit: 问题编辑
|
||||||
answer_edit: 回答编辑
|
answer_edit: 回答编辑
|
||||||
|
@ -1324,11 +1340,11 @@ ui:
|
||||||
comment: 评论
|
comment: 评论
|
||||||
no_data: "空空如也"
|
no_data: "空空如也"
|
||||||
users:
|
users:
|
||||||
title: Users
|
title: 用户
|
||||||
users_with_the_most_reputation: Users with the highest reputation scores
|
users_with_the_most_reputation: 信誉积分最高的用户
|
||||||
users_with_the_most_vote: Users who voted the most
|
users_with_the_most_vote: 投票最多的用户
|
||||||
staffs: Our community staff
|
staffs: 我们的社区工作人员
|
||||||
reputation: reputation
|
reputation: 声望值
|
||||||
votes: votes
|
votes: 投票
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ backend:
|
||||||
success:
|
success:
|
||||||
other: "成功!"
|
other: "成功!"
|
||||||
unknown:
|
unknown:
|
||||||
other: "Unknown error."
|
other: "未知的錯誤。"
|
||||||
request_format_error:
|
request_format_error:
|
||||||
other: "Request format is not valid."
|
other: "請求的格式無效。"
|
||||||
unauthorized_error:
|
unauthorized_error:
|
||||||
other: "Unauthorized."
|
other: "未授權。"
|
||||||
database_error:
|
database_error:
|
||||||
other: "Data server error."
|
other: "Data server error."
|
||||||
role:
|
role:
|
||||||
|
@ -29,13 +29,13 @@ backend:
|
||||||
email:
|
email:
|
||||||
other: "Email"
|
other: "Email"
|
||||||
password:
|
password:
|
||||||
other: "Password"
|
other: "密碼"
|
||||||
email_or_password_wrong_error:
|
email_or_password_wrong_error:
|
||||||
other: "Email and password do not match."
|
other: "電子郵箱和密碼不匹配。"
|
||||||
error:
|
error:
|
||||||
admin:
|
admin:
|
||||||
email_or_password_wrong:
|
email_or_password_wrong:
|
||||||
other: Email and password do not match.
|
other: 電子郵箱和密碼不匹配。
|
||||||
answer:
|
answer:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Answer do not found."
|
other: "Answer do not found."
|
||||||
|
@ -70,22 +70,22 @@ backend:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Object not found."
|
other: "Object not found."
|
||||||
verification_failed:
|
verification_failed:
|
||||||
other: "Verification failed."
|
other: "驗證失敗。"
|
||||||
email_or_password_incorrect:
|
email_or_password_incorrect:
|
||||||
other: "Email and password do not match."
|
other: "電子郵箱和密碼不匹配。"
|
||||||
old_password_verification_failed:
|
old_password_verification_failed:
|
||||||
other: "The old password verification failed"
|
other: "舊密碼驗證失敗"
|
||||||
new_password_same_as_previous_setting:
|
new_password_same_as_previous_setting:
|
||||||
other: "The new password is the same as the previous one."
|
other: "新密碼與先前的一樣。"
|
||||||
question:
|
question:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Question not found."
|
other: "找不到問題。"
|
||||||
cannot_deleted:
|
cannot_deleted:
|
||||||
other: "No permission to delete."
|
other: "沒有刪除的權限。"
|
||||||
cannot_close:
|
cannot_close:
|
||||||
other: "No permission to close."
|
other: "沒有關閉的權限。"
|
||||||
cannot_update:
|
cannot_update:
|
||||||
other: "No permission to update."
|
other: "沒有更新的權限。"
|
||||||
rank:
|
rank:
|
||||||
fail_to_meet_the_condition:
|
fail_to_meet_the_condition:
|
||||||
other: "Rank fail to meet the condition."
|
other: "Rank fail to meet the condition."
|
||||||
|
@ -107,9 +107,12 @@ backend:
|
||||||
other: "No permission to update."
|
other: "No permission to update."
|
||||||
cannot_set_synonym_as_itself:
|
cannot_set_synonym_as_itself:
|
||||||
other: "You cannot set the synonym of the current tag as itself."
|
other: "You cannot set the synonym of the current tag as itself."
|
||||||
|
smtp:
|
||||||
|
config_from_name_cannot_be_email:
|
||||||
|
other: "The From Name cannot be a email address."
|
||||||
theme:
|
theme:
|
||||||
not_found:
|
not_found:
|
||||||
other: "Theme not found."
|
other: "未找到主題。"
|
||||||
revision:
|
revision:
|
||||||
review_underway:
|
review_underway:
|
||||||
other: "Can't edit currently, there is a version in the review queue."
|
other: "Can't edit currently, there is a version in the review queue."
|
||||||
|
@ -129,21 +132,21 @@ backend:
|
||||||
other: "Username is already in use."
|
other: "Username is already in use."
|
||||||
set_avatar:
|
set_avatar:
|
||||||
other: "Avatar set failed."
|
other: "Avatar set failed."
|
||||||
config:
|
|
||||||
read_config_failed:
|
|
||||||
other: "Read config failed"
|
|
||||||
database:
|
|
||||||
connection_failed:
|
|
||||||
other: "Database connection failed"
|
|
||||||
create_table_failed:
|
|
||||||
other: "Create table failed"
|
|
||||||
install:
|
|
||||||
create_config_failed:
|
|
||||||
other: "Can’t create the config.yaml file."
|
|
||||||
cannot_update_your_role:
|
cannot_update_your_role:
|
||||||
other: "You cannot modify your role."
|
other: "You cannot modify your role."
|
||||||
not_allowed_registration:
|
not_allowed_registration:
|
||||||
other: "Currently the site is not open for registration"
|
other: "Currently the site is not open for registration"
|
||||||
|
config:
|
||||||
|
read_config_failed:
|
||||||
|
other: "Read config failed"
|
||||||
|
database:
|
||||||
|
connection_failed:
|
||||||
|
other: "Database connection failed"
|
||||||
|
create_table_failed:
|
||||||
|
other: "Create table failed"
|
||||||
|
install:
|
||||||
|
create_config_failed:
|
||||||
|
other: "Can’t create the config.yaml file."
|
||||||
report:
|
report:
|
||||||
spam:
|
spam:
|
||||||
name:
|
name:
|
||||||
|
@ -197,6 +200,13 @@ backend:
|
||||||
other: "something else"
|
other: "something else"
|
||||||
desc:
|
desc:
|
||||||
other: "This post requires another reason not listed above."
|
other: "This post requires another reason not listed above."
|
||||||
|
operation_type:
|
||||||
|
asked:
|
||||||
|
other: "asked"
|
||||||
|
answered:
|
||||||
|
other: "answered"
|
||||||
|
modified:
|
||||||
|
other: "modified"
|
||||||
notification:
|
notification:
|
||||||
action:
|
action:
|
||||||
update_question:
|
update_question:
|
||||||
|
@ -801,6 +811,11 @@ ui:
|
||||||
confirm_new_email: Your email has been updated.
|
confirm_new_email: Your email has been updated.
|
||||||
confirm_new_email_invalid: >-
|
confirm_new_email_invalid: >-
|
||||||
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
|
||||||
|
unsubscribe:
|
||||||
|
page_title: Unsubscribe
|
||||||
|
success_title: Unsubscribe Successful
|
||||||
|
success_desc: You have been successfully removed from this subscriber list and won’t receive any further emails from us.
|
||||||
|
link: Change settings
|
||||||
question:
|
question:
|
||||||
following_tags: Following Tags
|
following_tags: Following Tags
|
||||||
edit: Edit
|
edit: Edit
|
||||||
|
@ -991,6 +1006,7 @@ ui:
|
||||||
answer_links: Answer Links
|
answer_links: Answer Links
|
||||||
documents: Documents
|
documents: Documents
|
||||||
feedback: Feedback
|
feedback: Feedback
|
||||||
|
support: Support
|
||||||
review: Review
|
review: Review
|
||||||
config: Config
|
config: Config
|
||||||
update_to: Update to
|
update_to: Update to
|
||||||
|
|
|
@ -21,7 +21,7 @@ type RespBody struct {
|
||||||
// TrMsg translate the reason cause as a message
|
// TrMsg translate the reason cause as a message
|
||||||
func (r *RespBody) TrMsg(lang i18n.Language) *RespBody {
|
func (r *RespBody) TrMsg(lang i18n.Language) *RespBody {
|
||||||
if len(r.Message) == 0 {
|
if len(r.Message) == 0 {
|
||||||
r.Message = translator.GlobalTrans.Tr(lang, r.Reason)
|
r.Message = translator.Tr(lang, r.Reason)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,3 +106,12 @@ func CheckLanguageIsValid(lang string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tr use language to translate data. If this language translation is not available, return default english translation.
|
||||||
|
func Tr(lang i18n.Language, data string) string {
|
||||||
|
translation := GlobalTrans.Tr(lang, data)
|
||||||
|
if translation == data {
|
||||||
|
return GlobalTrans.Tr(i18n.DefaultLanguage, data)
|
||||||
|
}
|
||||||
|
return translation
|
||||||
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ func createDefaultValidator(la i18n.Language) *validator.Validate {
|
||||||
validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) {
|
validate.RegisterTagNameFunc(func(fld reflect.StructField) (res string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
res = translator.GlobalTrans.Tr(la, res)
|
res = translator.Tr(la, res)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if jsonTag := fld.Tag.Get("json"); len(jsonTag) > 0 {
|
if jsonTag := fld.Tag.Get("json"); len(jsonTag) > 0 {
|
||||||
|
@ -168,7 +168,7 @@ func (m *MyValidator) Check(value interface{}) (errFields []*FormErrorField, err
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
for _, errField := range errFields {
|
for _, errField := range errFields {
|
||||||
errField.ErrorMsg = translator.GlobalTrans.Tr(m.Lang, errField.ErrorMsg)
|
errField.ErrorMsg = translator.Tr(m.Lang, errField.ErrorMsg)
|
||||||
}
|
}
|
||||||
return errFields, err
|
return errFields, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package controller
|
||||||
import (
|
import (
|
||||||
"github.com/answerdev/answer/internal/base/handler"
|
"github.com/answerdev/answer/internal/base/handler"
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
|
"github.com/answerdev/answer/internal/schema"
|
||||||
"github.com/answerdev/answer/internal/service/uploader"
|
"github.com/answerdev/answer/internal/service/uploader"
|
||||||
|
"github.com/answerdev/answer/pkg/converter"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/segmentfault/pacman/errors"
|
"github.com/segmentfault/pacman/errors"
|
||||||
)
|
)
|
||||||
|
@ -63,3 +65,21 @@ func (uc *UploadController) UploadFile(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
handler.HandleResponse(ctx, err, url)
|
handler.HandleResponse(ctx, err, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostRender render post content
|
||||||
|
// @Summary render post content
|
||||||
|
// @Description render post content
|
||||||
|
// @Tags Upload
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param data body schema.PostRenderReq true "PostRenderReq"
|
||||||
|
// @Success 200 {object} handler.RespBody
|
||||||
|
// @Router /answer/api/v1/post/render [post]
|
||||||
|
func (uc *UploadController) PostRender(ctx *gin.Context) {
|
||||||
|
req := &schema.PostRenderReq{}
|
||||||
|
if handler.BindAndCheck(ctx, req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler.HandleResponse(ctx, nil, converter.Markdown2HTML(req.Content))
|
||||||
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||||
if !captchaPass {
|
if !captchaPass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "captcha_code",
|
ErrorField: "captcha_code",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
@ -124,7 +124,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
||||||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "e_mail",
|
ErrorField: "e_mail",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailOrPasswordWrong), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailOrPasswordWrong), errFields)
|
||||||
return
|
return
|
||||||
|
@ -151,7 +151,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
||||||
if !captchaPass {
|
if !captchaPass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "captcha_code",
|
ErrorField: "captcha_code",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
@ -236,7 +236,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
||||||
if !captchaPass {
|
if !captchaPass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "captcha_code",
|
ErrorField: "captcha_code",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
@ -245,7 +245,8 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
||||||
resp, errFields, err := uc.userService.UserRegisterByEmail(ctx, req)
|
resp, errFields, err := uc.userService.UserRegisterByEmail(ctx, req)
|
||||||
if len(errFields) > 0 {
|
if len(errFields) > 0 {
|
||||||
for _, field := range errFields {
|
for _, field := range errFields {
|
||||||
field.ErrorMsg = translator.GlobalTrans.Tr(handler.GetLang(ctx), field.ErrorMsg)
|
field.ErrorMsg = translator.
|
||||||
|
Tr(handler.GetLang(ctx), field.ErrorMsg)
|
||||||
}
|
}
|
||||||
handler.HandleResponse(ctx, err, errFields)
|
handler.HandleResponse(ctx, err, errFields)
|
||||||
} else {
|
} else {
|
||||||
|
@ -312,7 +313,7 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
|
||||||
if !captchaPass {
|
if !captchaPass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "captcha_code",
|
ErrorField: "captcha_code",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
@ -350,7 +351,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||||
if !oldPassVerification {
|
if !oldPassVerification {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "old_pass",
|
ErrorField: "old_pass",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.OldPasswordVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.OldPasswordVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.OldPasswordVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.OldPasswordVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
@ -358,7 +359,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
||||||
if req.OldPass == req.Pass {
|
if req.OldPass == req.Pass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "pass",
|
ErrorField: "pass",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.NewPasswordSameAsPreviousSetting),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.NewPasswordSameAsPreviousSetting),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.NewPasswordSameAsPreviousSetting), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.NewPasswordSameAsPreviousSetting), errFields)
|
||||||
return
|
return
|
||||||
|
@ -386,7 +387,7 @@ func (uc *UserController) UserUpdateInfo(ctx *gin.Context) {
|
||||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||||
errFields, err := uc.userService.UpdateInfo(ctx, req)
|
errFields, err := uc.userService.UpdateInfo(ctx, req)
|
||||||
for _, field := range errFields {
|
for _, field := range errFields {
|
||||||
field.ErrorMsg = translator.GlobalTrans.Tr(handler.GetLang(ctx), field.ErrorMsg)
|
field.ErrorMsg = translator.Tr(handler.GetLang(ctx), field.ErrorMsg)
|
||||||
}
|
}
|
||||||
handler.HandleResponse(ctx, err, errFields)
|
handler.HandleResponse(ctx, err, errFields)
|
||||||
}
|
}
|
||||||
|
@ -491,7 +492,7 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
|
||||||
if !captchaPass {
|
if !captchaPass {
|
||||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "captcha_code",
|
ErrorField: "captcha_code",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
|
||||||
})
|
})
|
||||||
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
|
||||||
return
|
return
|
||||||
|
|
|
@ -77,7 +77,7 @@ type InitBaseInfoReq struct {
|
||||||
SiteName string `validate:"required,gt=0,lte=30" json:"site_name"`
|
SiteName string `validate:"required,gt=0,lte=30" json:"site_name"`
|
||||||
SiteURL string `validate:"required,gt=0,lte=512,url" json:"site_url"`
|
SiteURL string `validate:"required,gt=0,lte=512,url" json:"site_url"`
|
||||||
ContactEmail string `validate:"required,email,gt=0,lte=500" json:"contact_email"`
|
ContactEmail string `validate:"required,email,gt=0,lte=500" json:"contact_email"`
|
||||||
AdminName string `validate:"required,gt=4,lte=30" json:"name"`
|
AdminName string `validate:"required,gt=3,lte=30" json:"name"`
|
||||||
AdminPassword string `validate:"required,gte=8,lte=32" json:"password"`
|
AdminPassword string `validate:"required,gte=8,lte=32" json:"password"`
|
||||||
AdminEmail string `validate:"required,email,gt=0,lte=500" json:"email"`
|
AdminEmail string `validate:"required,email,gt=0,lte=500" json:"email"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (ar *ActivityRepo) GetActivity(ctx context.Context, session *xorm.Session,
|
||||||
func (ar *ActivityRepo) GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error) {
|
func (ar *ActivityRepo) GetUserIDObjectIDActivitySum(ctx context.Context, userID, objectID string) (int, error) {
|
||||||
sum := &entity.ActivityRankSum{}
|
sum := &entity.ActivityRankSum{}
|
||||||
_, err := ar.data.DB.Table(entity.Activity{}.TableName()).
|
_, err := ar.data.DB.Table(entity.Activity{}.TableName()).
|
||||||
Select("sum(rank) as rank").
|
Select("sum(`rank`) as `rank`").
|
||||||
Where("user_id =?", userID).
|
Where("user_id =?", userID).
|
||||||
And("object_id = ?", objectID).
|
And("object_id = ?", objectID).
|
||||||
And("cancelled =0").
|
And("cancelled =0").
|
||||||
|
@ -113,7 +113,7 @@ func (ar *ActivityRepo) AddActivity(ctx context.Context, activity *entity.Activi
|
||||||
func (ar *ActivityRepo) GetUsersWhoHasGainedTheMostReputation(
|
func (ar *ActivityRepo) GetUsersWhoHasGainedTheMostReputation(
|
||||||
ctx context.Context, startTime, endTime time.Time, limit int) (rankStat []*entity.ActivityUserRankStat, err error) {
|
ctx context.Context, startTime, endTime time.Time, limit int) (rankStat []*entity.ActivityUserRankStat, err error) {
|
||||||
rankStat = make([]*entity.ActivityUserRankStat, 0)
|
rankStat = make([]*entity.ActivityUserRankStat, 0)
|
||||||
session := ar.data.DB.Select("user_id, SUM(rank) AS rank_amount").Table("activity")
|
session := ar.data.DB.Select("user_id, SUM(`rank`) AS rank_amount").Table("activity")
|
||||||
session.Where("has_rank = 1 AND cancelled = 0")
|
session.Where("has_rank = 1 AND cancelled = 0")
|
||||||
session.Where("created_at >= ?", startTime)
|
session.Where("created_at >= ?", startTime)
|
||||||
session.Where("created_at <= ?", endTime)
|
session.Where("created_at <= ?", endTime)
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (tr *tagCommonRepo) GetTagListByName(ctx context.Context, name string, hasR
|
||||||
cond := &entity.Tag{}
|
cond := &entity.Tag{}
|
||||||
session := tr.data.DB.Where("")
|
session := tr.data.DB.Where("")
|
||||||
if name != "" {
|
if name != "" {
|
||||||
session.Where("slug_name LIKE ?", name+"%")
|
session.Where("slug_name LIKE ? or display_name LIKE ?", name+"%", name+"%")
|
||||||
} else {
|
} else {
|
||||||
session.UseBool("recommend")
|
session.UseBool("recommend")
|
||||||
cond.Recommend = true
|
cond.Recommend = true
|
||||||
|
|
|
@ -217,6 +217,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
||||||
|
|
||||||
// upload file
|
// upload file
|
||||||
r.POST("/file", a.uploadController.UploadFile)
|
r.POST("/file", a.uploadController.UploadFile)
|
||||||
|
r.POST("/post/render", a.uploadController.PostRender)
|
||||||
|
|
||||||
// activity
|
// activity
|
||||||
r.GET("/activity/timeline", a.activityController.GetObjectTimeline)
|
r.GET("/activity/timeline", a.activityController.GetObjectTimeline)
|
||||||
|
|
|
@ -246,9 +246,9 @@ type QuestionPageReq struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QuestionPageRespOperationTypeAsked = "question.operation_type.asked"
|
QuestionPageRespOperationTypeAsked = "asked"
|
||||||
QuestionPageRespOperationTypeAnswered = "question.operation_type.answered"
|
QuestionPageRespOperationTypeAnswered = "answered"
|
||||||
QuestionPageRespOperationTypeModified = "question.operation_type.modified"
|
QuestionPageRespOperationTypeModified = "modified"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuestionPageResp struct {
|
type QuestionPageResp struct {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package schema
|
||||||
|
|
||||||
|
// PostRenderReq post render request
|
||||||
|
type PostRenderReq struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
|
@ -46,9 +46,9 @@ type SiteInterfaceReq struct {
|
||||||
|
|
||||||
// SiteBrandingReq site branding request
|
// SiteBrandingReq site branding request
|
||||||
type SiteBrandingReq struct {
|
type SiteBrandingReq struct {
|
||||||
Logo string `validate:"required,gt=0,lte=512" form:"logo" json:"logo"`
|
Logo string `validate:"omitempty,gt=0,lte=512" form:"logo" json:"logo"`
|
||||||
MobileLogo string `validate:"omitempty,gt=0,lte=512" form:"mobile_logo" json:"mobile_logo"`
|
MobileLogo string `validate:"omitempty,gt=0,lte=512" form:"mobile_logo" json:"mobile_logo"`
|
||||||
SquareIcon string `validate:"required,gt=0,lte=512" form:"square_icon" json:"square_icon"`
|
SquareIcon string `validate:"omitempty,gt=0,lte=512" form:"square_icon" json:"square_icon"`
|
||||||
Favicon string `validate:"omitempty,gt=0,lte=512" form:"favicon" json:"favicon"`
|
Favicon string `validate:"omitempty,gt=0,lte=512" form:"favicon" json:"favicon"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ type SiteThemeResp struct {
|
||||||
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
|
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
|
||||||
la := handler.GetLangByCtx(ctx)
|
la := handler.GetLangByCtx(ctx)
|
||||||
for _, option := range s.ThemeOptions {
|
for _, option := range s.ThemeOptions {
|
||||||
tr := translator.GlobalTrans.Tr(la, option.Value)
|
tr := translator.Tr(la, option.Value)
|
||||||
// if tr is equal the option value means not found translation, so use the original label
|
// if tr is equal the option value means not found translation, so use the original label
|
||||||
if tr != option.Value {
|
if tr != option.Value {
|
||||||
option.Label = tr
|
option.Label = tr
|
||||||
|
|
|
@ -228,7 +228,7 @@ type UserEmailLogin struct {
|
||||||
// UserRegisterReq user register request
|
// UserRegisterReq user register request
|
||||||
type UserRegisterReq struct {
|
type UserRegisterReq struct {
|
||||||
// name
|
// name
|
||||||
Name string `validate:"required,gt=4,lte=30" json:"name"`
|
Name string `validate:"required,gt=3,lte=30" json:"name"`
|
||||||
// email
|
// email
|
||||||
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" `
|
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" `
|
||||||
// password
|
// password
|
||||||
|
@ -277,7 +277,7 @@ type UpdateInfoRequest struct {
|
||||||
// display_name
|
// display_name
|
||||||
DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"`
|
DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"`
|
||||||
// username
|
// username
|
||||||
Username string `validate:"omitempty,gt=0,lte=30" json:"username"`
|
Username string `validate:"omitempty,gt=3,lte=30" json:"username"`
|
||||||
// avatar
|
// avatar
|
||||||
Avatar AvatarInfo `json:"avatar"`
|
Avatar AvatarInfo `json:"avatar"`
|
||||||
// bio
|
// bio
|
||||||
|
@ -300,12 +300,13 @@ type AvatarInfo struct {
|
||||||
|
|
||||||
func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||||
if len(u.Username) > 0 {
|
if len(u.Username) > 0 {
|
||||||
|
errFields := make([]*validator.FormErrorField, 0)
|
||||||
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||||
match := re.MatchString(u.Username)
|
match := re.MatchString(u.Username)
|
||||||
if !match {
|
if !match {
|
||||||
errField := &validator.FormErrorField{
|
errField := &validator.FormErrorField{
|
||||||
ErrorField: "username",
|
ErrorField: "username",
|
||||||
ErrorMsg: err.Error(),
|
ErrorMsg: reason.UsernameInvalid,
|
||||||
}
|
}
|
||||||
errFields = append(errFields, errField)
|
errFields = append(errFields, errField)
|
||||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
|
lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
|
||||||
item.NotificationAction = translator.GlobalTrans.Tr(lang, item.NotificationAction)
|
item.NotificationAction = translator.Tr(lang, item.NotificationAction)
|
||||||
item.ID = notificationInfo.ID
|
item.ID = notificationInfo.ID
|
||||||
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
||||||
if notificationInfo.IsRead == schema.NotificationRead {
|
if notificationInfo.IsRead == schema.NotificationRead {
|
||||||
|
|
|
@ -6,9 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/answerdev/answer/internal/base/constant"
|
"github.com/answerdev/answer/internal/base/constant"
|
||||||
"github.com/answerdev/answer/internal/base/handler"
|
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/base/translator"
|
|
||||||
"github.com/answerdev/answer/internal/service/activity_common"
|
"github.com/answerdev/answer/internal/service/activity_common"
|
||||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||||
"github.com/answerdev/answer/internal/service/config"
|
"github.com/answerdev/answer/internal/service/config"
|
||||||
|
@ -250,13 +248,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
|
||||||
func (qs *QuestionCommon) FormatQuestionsPage(
|
func (qs *QuestionCommon) FormatQuestionsPage(
|
||||||
ctx context.Context, questionList []*entity.Question, loginUserID string, orderCond string) (
|
ctx context.Context, questionList []*entity.Question, loginUserID string, orderCond string) (
|
||||||
formattedQuestions []*schema.QuestionPageResp, err error) {
|
formattedQuestions []*schema.QuestionPageResp, err error) {
|
||||||
language := handler.GetLangByCtx(ctx)
|
|
||||||
askedOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeAsked)
|
|
||||||
answeredOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeAnswered)
|
|
||||||
modifiedOp := translator.GlobalTrans.Tr(language, schema.QuestionPageRespOperationTypeModified)
|
|
||||||
|
|
||||||
formattedQuestions = make([]*schema.QuestionPageResp, 0)
|
formattedQuestions = make([]*schema.QuestionPageResp, 0)
|
||||||
|
|
||||||
questionIDs := make([]string, 0)
|
questionIDs := make([]string, 0)
|
||||||
userIDs := make([]string, 0)
|
userIDs := make([]string, 0)
|
||||||
for _, questionInfo := range questionList {
|
for _, questionInfo := range questionList {
|
||||||
|
@ -300,20 +292,20 @@ func (qs *QuestionCommon) FormatQuestionsPage(
|
||||||
|
|
||||||
// if order condition is newest or nobody edited or nobody answered, only show question author
|
// if order condition is newest or nobody edited or nobody answered, only show question author
|
||||||
if orderCond == schema.QuestionOrderCondNewest || (!haveEdited && !haveAnswered) {
|
if orderCond == schema.QuestionOrderCondNewest || (!haveEdited && !haveAnswered) {
|
||||||
t.OperationType = askedOp
|
t.OperationType = schema.QuestionPageRespOperationTypeAsked
|
||||||
t.OperatedAt = questionInfo.CreatedAt.Unix()
|
t.OperatedAt = questionInfo.CreatedAt.Unix()
|
||||||
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.UserID}
|
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.UserID}
|
||||||
} else {
|
} else {
|
||||||
// if no one
|
// if no one
|
||||||
if haveEdited {
|
if haveEdited {
|
||||||
t.OperationType = modifiedOp
|
t.OperationType = schema.QuestionPageRespOperationTypeModified
|
||||||
t.OperatedAt = questionInfo.UpdatedAt.Unix()
|
t.OperatedAt = questionInfo.UpdatedAt.Unix()
|
||||||
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.LastEditUserID}
|
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.LastEditUserID}
|
||||||
}
|
}
|
||||||
|
|
||||||
if haveAnswered {
|
if haveAnswered {
|
||||||
if t.LastAnsweredAt.Unix() > t.OperatedAt {
|
if t.LastAnsweredAt.Unix() > t.OperatedAt {
|
||||||
t.OperationType = answeredOp
|
t.OperationType = schema.QuestionPageRespOperationTypeAnswered
|
||||||
t.OperatedAt = t.LastAnsweredAt.Unix()
|
t.OperatedAt = t.LastAnsweredAt.Unix()
|
||||||
t.Operator = &schema.QuestionPageRespOperator{ID: t.LastAnsweredUserID}
|
t.Operator = &schema.QuestionPageRespOperator{ID: t.LastAnsweredUserID}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,8 +140,8 @@ func (qs *QuestionService) CloseMsgList(ctx context.Context, lang i18n.Language)
|
||||||
return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
return nil, errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
|
||||||
}
|
}
|
||||||
for _, t := range resp {
|
for _, t := range resp {
|
||||||
t.Name = translator.GlobalTrans.Tr(lang, t.Name)
|
t.Name = translator.Tr(lang, t.Name)
|
||||||
t.Description = translator.GlobalTrans.Tr(lang, t.Description)
|
t.Description = translator.Tr(lang, t.Description)
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que
|
||||||
errorlist := make([]*validator.FormErrorField, 0)
|
errorlist := make([]*validator.FormErrorField, 0)
|
||||||
errorlist = append(errorlist, &validator.FormErrorField{
|
errorlist = append(errorlist, &validator.FormErrorField{
|
||||||
ErrorField: "tags",
|
ErrorField: "tags",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||||
})
|
})
|
||||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||||
return errorlist, err
|
return errorlist, err
|
||||||
|
@ -176,7 +176,7 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que
|
||||||
errorlist := make([]*validator.FormErrorField, 0)
|
errorlist := make([]*validator.FormErrorField, 0)
|
||||||
errorlist = append(errorlist, &validator.FormErrorField{
|
errorlist = append(errorlist, &validator.FormErrorField{
|
||||||
ErrorField: "tags",
|
ErrorField: "tags",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||||
})
|
})
|
||||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||||
return errorlist, err
|
return errorlist, err
|
||||||
|
@ -213,7 +213,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
||||||
errorlist := make([]*validator.FormErrorField, 0)
|
errorlist := make([]*validator.FormErrorField, 0)
|
||||||
errorlist = append(errorlist, &validator.FormErrorField{
|
errorlist = append(errorlist, &validator.FormErrorField{
|
||||||
ErrorField: "tags",
|
ErrorField: "tags",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||||
})
|
})
|
||||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||||
return errorlist, err
|
return errorlist, err
|
||||||
|
@ -226,7 +226,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
||||||
errorlist := make([]*validator.FormErrorField, 0)
|
errorlist := make([]*validator.FormErrorField, 0)
|
||||||
errorlist = append(errorlist, &validator.FormErrorField{
|
errorlist = append(errorlist, &validator.FormErrorField{
|
||||||
ErrorField: "tags",
|
ErrorField: "tags",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||||
})
|
})
|
||||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||||
return errorlist, err
|
return errorlist, err
|
||||||
|
@ -539,7 +539,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
||||||
errorlist := make([]*validator.FormErrorField, 0)
|
errorlist := make([]*validator.FormErrorField, 0)
|
||||||
errorlist = append(errorlist, &validator.FormErrorField{
|
errorlist = append(errorlist, &validator.FormErrorField{
|
||||||
ErrorField: "tags",
|
ErrorField: "tags",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||||
})
|
})
|
||||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||||
return errorlist, err
|
return errorlist, err
|
||||||
|
|
|
@ -74,8 +74,8 @@ func (rs *ReportService) GetReportTypeList(ctx context.Context, lang i18n.Langua
|
||||||
err = errors.BadRequest(reason.UnknownError)
|
err = errors.BadRequest(reason.UnknownError)
|
||||||
}
|
}
|
||||||
for _, t := range resp {
|
for _, t := range resp {
|
||||||
t.Name = translator.GlobalTrans.Tr(lang, t.Name)
|
t.Name = translator.Tr(lang, t.Name)
|
||||||
t.Description = translator.GlobalTrans.Tr(lang, t.Description)
|
t.Description = translator.Tr(lang, t.Description)
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,13 +72,13 @@ func (rs *RoleService) GetRoleMapping(ctx context.Context) (roleMapping map[int]
|
||||||
func (rs *RoleService) translateRole(ctx context.Context, role *entity.Role) {
|
func (rs *RoleService) translateRole(ctx context.Context, role *entity.Role) {
|
||||||
switch role.Name {
|
switch role.Name {
|
||||||
case roleUserName:
|
case roleUserName:
|
||||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameUser)
|
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameUser)
|
||||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionUser)
|
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionUser)
|
||||||
case roleAdminName:
|
case roleAdminName:
|
||||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameAdmin)
|
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameAdmin)
|
||||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionAdmin)
|
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionAdmin)
|
||||||
case roleModeratorName:
|
case roleModeratorName:
|
||||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameModerator)
|
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameModerator)
|
||||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionModerator)
|
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionModerator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -496,7 +496,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
||||||
thisObjTagNameList := make([]string, 0)
|
thisObjTagNameList := make([]string, 0)
|
||||||
thisObjTagIDList := make([]string, 0)
|
thisObjTagIDList := make([]string, 0)
|
||||||
for _, t := range objectTagData.Tags {
|
for _, t := range objectTagData.Tags {
|
||||||
t.SlugName = strings.ToLower(t.SlugName)
|
// t.SlugName = strings.ToLower(t.SlugName)
|
||||||
thisObjTagNameList = append(thisObjTagNameList, t.SlugName)
|
thisObjTagNameList = append(thisObjTagNameList, t.SlugName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,13 +508,13 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
||||||
|
|
||||||
tagInDbMapping := make(map[string]*entity.Tag)
|
tagInDbMapping := make(map[string]*entity.Tag)
|
||||||
for _, tag := range tagListInDb {
|
for _, tag := range tagListInDb {
|
||||||
tagInDbMapping[tag.SlugName] = tag
|
tagInDbMapping[strings.ToLower(tag.SlugName)] = tag
|
||||||
thisObjTagIDList = append(thisObjTagIDList, tag.ID)
|
thisObjTagIDList = append(thisObjTagIDList, tag.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
addTagList := make([]*entity.Tag, 0)
|
addTagList := make([]*entity.Tag, 0)
|
||||||
for _, tag := range objectTagData.Tags {
|
for _, tag := range objectTagData.Tags {
|
||||||
_, ok := tagInDbMapping[tag.SlugName]
|
_, ok := tagInDbMapping[strings.ToLower(tag.SlugName)]
|
||||||
if ok {
|
if ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/answerdev/answer/internal/base/reason"
|
"github.com/answerdev/answer/internal/base/reason"
|
||||||
"github.com/answerdev/answer/internal/entity"
|
"github.com/answerdev/answer/internal/entity"
|
||||||
"github.com/answerdev/answer/internal/schema"
|
"github.com/answerdev/answer/internal/schema"
|
||||||
|
"github.com/answerdev/answer/internal/service/activity"
|
||||||
"github.com/answerdev/answer/internal/service/auth"
|
"github.com/answerdev/answer/internal/service/auth"
|
||||||
"github.com/answerdev/answer/internal/service/role"
|
"github.com/answerdev/answer/internal/service/role"
|
||||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||||
|
@ -38,6 +39,7 @@ type UserAdminService struct {
|
||||||
userRoleRelService *role.UserRoleRelService
|
userRoleRelService *role.UserRoleRelService
|
||||||
authService *auth.AuthService
|
authService *auth.AuthService
|
||||||
userCommonService *usercommon.UserCommon
|
userCommonService *usercommon.UserCommon
|
||||||
|
userActivity activity.UserActiveActivityRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserAdminService new user admin service
|
// NewUserAdminService new user admin service
|
||||||
|
@ -46,12 +48,14 @@ func NewUserAdminService(
|
||||||
userRoleRelService *role.UserRoleRelService,
|
userRoleRelService *role.UserRoleRelService,
|
||||||
authService *auth.AuthService,
|
authService *auth.AuthService,
|
||||||
userCommonService *usercommon.UserCommon,
|
userCommonService *usercommon.UserCommon,
|
||||||
|
userActivity activity.UserActiveActivityRepo,
|
||||||
) *UserAdminService {
|
) *UserAdminService {
|
||||||
return &UserAdminService{
|
return &UserAdminService{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
userRoleRelService: userRoleRelService,
|
userRoleRelService: userRoleRelService,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
userCommonService: userCommonService,
|
userCommonService: userCommonService,
|
||||||
|
userActivity: userActivity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +87,17 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
|
||||||
userInfo.Status = entity.UserStatusAvailable
|
userInfo.Status = entity.UserStatusAvailable
|
||||||
userInfo.MailStatus = entity.EmailStatusAvailable
|
userInfo.MailStatus = entity.EmailStatusAvailable
|
||||||
}
|
}
|
||||||
return us.userRepo.UpdateUserStatus(ctx, userInfo.ID, userInfo.Status, userInfo.MailStatus, userInfo.EMail)
|
|
||||||
|
err = us.userRepo.UpdateUserStatus(ctx, userInfo.ID, userInfo.Status, userInfo.MailStatus, userInfo.EMail)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user reputation is zero means this user is inactive, so try to activate this user.
|
||||||
|
if req.IsNormal() && userInfo.Rank == 0 {
|
||||||
|
return us.userActivity.UserActive(ctx, userInfo.ID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserRole update user role
|
// UpdateUserRole update user role
|
||||||
|
|
|
@ -510,7 +510,7 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
|
||||||
if exist {
|
if exist {
|
||||||
resp = append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
resp = append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||||
ErrorField: "e_mail",
|
ErrorField: "e_mail",
|
||||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.EmailDuplicate),
|
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.EmailDuplicate),
|
||||||
})
|
})
|
||||||
return resp, errors.BadRequest(reason.EmailDuplicate)
|
return resp, errors.BadRequest(reason.EmailDuplicate)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,26 @@ package converter
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/segmentfault/pacman/log"
|
"github.com/segmentfault/pacman/log"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
goldmarkHTML "github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Markdown2HTML convert markdown to html
|
// Markdown2HTML convert markdown to html
|
||||||
func Markdown2HTML(source string) string {
|
func Markdown2HTML(source string) string {
|
||||||
mdConverter := goldmark.New(
|
mdConverter := goldmark.New(
|
||||||
goldmark.WithExtensions(extension.GFM),
|
goldmark.WithExtensions(&DangerousHTMLFilterExtension{}, extension.GFM),
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
parser.WithAutoHeadingID(),
|
parser.WithAutoHeadingID(),
|
||||||
),
|
),
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
html.WithHardWraps(),
|
goldmarkHTML.WithHardWraps(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
@ -28,3 +32,56 @@ func Markdown2HTML(source string) string {
|
||||||
}
|
}
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DangerousHTMLFilterExtension struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DangerousHTMLFilterExtension) Extend(m goldmark.Markdown) {
|
||||||
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(&DangerousHTMLRenderer{
|
||||||
|
Config: goldmarkHTML.NewConfig(),
|
||||||
|
Filter: bluemonday.UGCPolicy(),
|
||||||
|
}, 1),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
type DangerousHTMLRenderer struct {
|
||||||
|
goldmarkHTML.Config
|
||||||
|
Filter *bluemonday.Policy
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
|
||||||
|
func (r *DangerousHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
|
||||||
|
reg.Register(ast.KindRawHTML, r.renderRawHTML)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DangerousHTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
if !entering {
|
||||||
|
return ast.WalkSkipChildren, nil
|
||||||
|
}
|
||||||
|
n := node.(*ast.RawHTML)
|
||||||
|
l := n.Segments.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
segment := n.Segments.At(i)
|
||||||
|
_, _ = w.Write(r.Filter.SanitizeBytes(segment.Value(source)))
|
||||||
|
}
|
||||||
|
return ast.WalkSkipChildren, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DangerousHTMLRenderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
n := node.(*ast.HTMLBlock)
|
||||||
|
if entering {
|
||||||
|
l := n.Lines().Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
line := n.Lines().At(i)
|
||||||
|
r.Writer.SecureWrite(w, r.Filter.SanitizeBytes(line.Value(source)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if n.HasClosure() {
|
||||||
|
closure := n.ClosureLine
|
||||||
|
r.Writer.SecureWrite(w, closure.Value(source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<!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"/><script defer="defer" src="/static/js/main.fde484b3.js"></script><link href="/static/css/main.401dc3ca.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="spin-mask"><noscript><style>#spin-mask{display:none!important}</style></noscript><style>@keyframes _doc-spin{to{transform:rotate(360deg)}}#spin-mask{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#fff;z-index:9999}#spin-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#spin-container .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="spin-container"><div class="spinner"></div></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"/><script defer="defer" src="/static/js/main.cb9bf782.js"></script><link href="/static/css/main.b8d8739f.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="spin-mask"><noscript><style>#spin-mask{display:none!important}</style></noscript><style>@keyframes _doc-spin{to{transform:rotate(360deg)}}#spin-mask{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#fff;z-index:9999}#spin-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#spin-container .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="spin-container"><div class="spinner"></div></div></div></div></body></html>
|
|
@ -22,7 +22,9 @@
|
||||||
"copy-to-clipboard": "^3.3.2",
|
"copy-to-clipboard": "^3.3.2",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
|
"dompurify": "^2.4.3",
|
||||||
"emoji-regex": "^10.2.1",
|
"emoji-regex": "^10.2.1",
|
||||||
|
"html-react-parser": "^3.0.8",
|
||||||
"i18next": "^21.9.0",
|
"i18next": "^21.9.0",
|
||||||
"katex": "^0.16.2",
|
"katex": "^0.16.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
@ -51,6 +53,7 @@
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
|
"@types/dompurify": "^2.4.0",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/lodash": "^4.14.184",
|
"@types/lodash": "^4.14.184",
|
||||||
"@types/marked": "^4.0.6",
|
"@types/marked": "^4.0.6",
|
||||||
|
|
1412
ui/pnpm-lock.yaml
1412
ui/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="generator" content="Answer %AnswerVersion% - https://github.com/answerdev/answer">
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export interface QuestionParams {
|
||||||
title: string;
|
title: string;
|
||||||
url_title?: string;
|
url_title?: string;
|
||||||
content: string;
|
content: string;
|
||||||
html: string;
|
html?: string;
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ export interface AnswerItem {
|
||||||
|
|
||||||
export interface PostAnswerReq {
|
export interface PostAnswerReq {
|
||||||
content: string;
|
content: string;
|
||||||
html: string;
|
html?: string;
|
||||||
question_id: string;
|
question_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect, memo } from 'react';
|
import { useState, useEffect, memo } from 'react';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button, Form } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -7,7 +7,7 @@ import classNames from 'classnames';
|
||||||
import { TextArea, Mentions } from '@/components';
|
import { TextArea, Mentions } from '@/components';
|
||||||
import { usePageUsers } from '@/hooks';
|
import { usePageUsers } from '@/hooks';
|
||||||
|
|
||||||
const Form = ({
|
const Index = ({
|
||||||
className = '',
|
className = '',
|
||||||
value: initialValue = '',
|
value: initialValue = '',
|
||||||
onSendReply,
|
onSendReply,
|
||||||
|
@ -18,7 +18,7 @@ const Form = ({
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const pageUsers = usePageUsers();
|
const pageUsers = usePageUsers();
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||||
|
const [validationErrorMsg, setValidationErrorMsg] = useState('');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialValue) {
|
if (!initialValue) {
|
||||||
return;
|
return;
|
||||||
|
@ -32,6 +32,13 @@ const Form = ({
|
||||||
const handleSelected = (val) => {
|
const handleSelected = (val) => {
|
||||||
setValue(val);
|
setValue(val);
|
||||||
};
|
};
|
||||||
|
const handleSendReply = () => {
|
||||||
|
onSendReply(value).catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
setValidationErrorMsg(ex.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -39,17 +46,27 @@ const Form = ({
|
||||||
className,
|
className,
|
||||||
)}>
|
)}>
|
||||||
<div>
|
<div>
|
||||||
<Mentions pageUsers={pageUsers.getUsers()} onSelected={handleSelected}>
|
<div
|
||||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
className={classNames('custom-form-control', {
|
||||||
</Mentions>
|
'is-invalid': validationErrorMsg,
|
||||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
})}>
|
||||||
|
<Mentions
|
||||||
|
pageUsers={pageUsers.getUsers()}
|
||||||
|
onSelected={handleSelected}>
|
||||||
|
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||||
|
</Mentions>
|
||||||
|
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||||
|
</div>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{validationErrorMsg}
|
||||||
|
</Form.Control.Feedback>
|
||||||
</div>
|
</div>
|
||||||
{type === 'edit' ? (
|
{type === 'edit' ? (
|
||||||
<div className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
<div className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-nowrap "
|
className="text-nowrap "
|
||||||
onClick={() => onSendReply(value)}>
|
onClick={() => handleSendReply()}>
|
||||||
{t('btn_save_edits')}
|
{t('btn_save_edits')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@ -64,7 +81,7 @@ const Form = ({
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-nowrap ms-0 ms-md-2 mt-2 mt-md-0"
|
className="text-nowrap ms-0 ms-md-2 mt-2 mt-md-0"
|
||||||
onClick={() => onSendReply(value)}>
|
onClick={() => handleSendReply()}>
|
||||||
{t('btn_add_comment')}
|
{t('btn_add_comment')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -72,4 +89,4 @@ const Form = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(Form);
|
export default memo(Index);
|
||||||
|
|
|
@ -1,21 +1,30 @@
|
||||||
import { useState, memo } from 'react';
|
import { useState, memo } from 'react';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button, Form } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { TextArea, Mentions } from '@/components';
|
import { TextArea, Mentions } from '@/components';
|
||||||
import { usePageUsers } from '@/hooks';
|
import { usePageUsers } from '@/hooks';
|
||||||
|
|
||||||
const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
const Index = ({ userName, onSendReply, onCancel, mode }) => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const pageUsers = usePageUsers();
|
const pageUsers = usePageUsers();
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||||
|
const [validationErrorMsg, setValidationErrorMsg] = useState('');
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
setValue(e.target.value);
|
setValue(e.target.value);
|
||||||
};
|
};
|
||||||
const handleSelected = (val) => {
|
const handleSelected = (val) => {
|
||||||
setValue(val);
|
setValue(val);
|
||||||
};
|
};
|
||||||
|
const handleSendReply = () => {
|
||||||
|
onSendReply(value).catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
setValidationErrorMsg(ex.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
|
@ -24,18 +33,26 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex mb-1 align-items-start flex-column flex-md-row">
|
<div className="d-flex mb-1 align-items-start flex-column flex-md-row">
|
||||||
<div>
|
<div>
|
||||||
<Mentions
|
<div
|
||||||
pageUsers={pageUsers.getUsers()}
|
className={classNames('custom-form-control', {
|
||||||
onSelected={handleSelected}>
|
'is-invalid': validationErrorMsg,
|
||||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
})}>
|
||||||
</Mentions>
|
<Mentions
|
||||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
pageUsers={pageUsers.getUsers()}
|
||||||
|
onSelected={handleSelected}>
|
||||||
|
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||||
|
</Mentions>
|
||||||
|
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||||
|
</div>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{validationErrorMsg}
|
||||||
|
</Form.Control.Feedback>
|
||||||
</div>
|
</div>
|
||||||
<div className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
<div className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-nowrap"
|
className="text-nowrap"
|
||||||
onClick={() => onSendReply(value)}>
|
onClick={() => handleSendReply()}>
|
||||||
{t('btn_add_comment')}
|
{t('btn_add_comment')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@ -51,4 +68,4 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(Form);
|
export default memo(Index);
|
||||||
|
|
|
@ -10,7 +10,12 @@ import { marked } from 'marked';
|
||||||
import * as Types from '@/common/interface';
|
import * as Types from '@/common/interface';
|
||||||
import { Modal } from '@/components';
|
import { Modal } from '@/components';
|
||||||
import { usePageUsers, useReportModal } from '@/hooks';
|
import { usePageUsers, useReportModal } from '@/hooks';
|
||||||
import { matchedUsers, parseUserInfo, scrollTop, bgFadeOut } from '@/utils';
|
import {
|
||||||
|
matchedUsers,
|
||||||
|
parseUserInfo,
|
||||||
|
scrollToElementTop,
|
||||||
|
bgFadeOut,
|
||||||
|
} from '@/utils';
|
||||||
import { tryNormalLogged } from '@/utils/guard';
|
import { tryNormalLogged } from '@/utils/guard';
|
||||||
import {
|
import {
|
||||||
useQueryComments,
|
useQueryComments,
|
||||||
|
@ -43,7 +48,7 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
const scrollCallback = useCallback((el, co) => {
|
const scrollCallback = useCallback((el, co) => {
|
||||||
if (pageIndex === 0 && co.comment_id === commentId) {
|
if (pageIndex === 0 && co.comment_id === commentId) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollTop(el);
|
scrollToElementTop(el);
|
||||||
bgFadeOut(el);
|
bgFadeOut(el);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
@ -102,13 +107,14 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
const handleSendReply = (item) => {
|
const handleSendReply = (item) => {
|
||||||
const users = matchedUsers(item.value);
|
const users = matchedUsers(item.value);
|
||||||
const userNames = unionBy(users.map((user) => user.userName));
|
const userNames = unionBy(users.map((user) => user.userName));
|
||||||
const html = marked.parse(parseUserInfo(item.value));
|
const commentMarkDown = parseUserInfo(item.value);
|
||||||
if (!item.value || !html) {
|
const html = marked.parse(commentMarkDown);
|
||||||
return;
|
// if (!commentMarkDown || !html) {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
const params = {
|
const params = {
|
||||||
object_id: objectId,
|
object_id: objectId,
|
||||||
original_text: item.value,
|
original_text: commentMarkDown,
|
||||||
mention_username_list: userNames,
|
mention_username_list: userNames,
|
||||||
parsed_text: html,
|
parsed_text: html,
|
||||||
...(item.type === 'reply'
|
...(item.type === 'reply'
|
||||||
|
@ -119,7 +125,7 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (item.type === 'edit') {
|
if (item.type === 'edit') {
|
||||||
updateComment({
|
return updateComment({
|
||||||
...params,
|
...params,
|
||||||
comment_id: item.comment_id,
|
comment_id: item.comment_id,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -134,30 +140,29 @@ const Comment = ({ objectId, mode, commentId }) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
addComment(params).then((res) => {
|
|
||||||
if (item.type === 'reply') {
|
|
||||||
const index = comments.findIndex(
|
|
||||||
(comment) => comment.comment_id === item.comment_id,
|
|
||||||
);
|
|
||||||
comments[index].showReply = false;
|
|
||||||
comments.splice(index + 1, 0, res);
|
|
||||||
setComments([...comments]);
|
|
||||||
} else {
|
|
||||||
setComments([
|
|
||||||
...comments.map((comment) => {
|
|
||||||
if (comment.comment_id === item.comment_id) {
|
|
||||||
comment.showReply = false;
|
|
||||||
}
|
|
||||||
return comment;
|
|
||||||
}),
|
|
||||||
res,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setVisibleComment(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return addComment(params).then((res) => {
|
||||||
|
if (item.type === 'reply') {
|
||||||
|
const index = comments.findIndex(
|
||||||
|
(comment) => comment.comment_id === item.comment_id,
|
||||||
|
);
|
||||||
|
comments[index].showReply = false;
|
||||||
|
comments.splice(index + 1, 0, res);
|
||||||
|
setComments([...comments]);
|
||||||
|
} else {
|
||||||
|
setComments([
|
||||||
|
...comments.map((comment) => {
|
||||||
|
if (comment.comment_id === item.comment_id) {
|
||||||
|
comment.showReply = false;
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
}),
|
||||||
|
res,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisibleComment(false);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id) => {
|
const handleDelete = (id) => {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { FC, memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import classname from 'classnames';
|
||||||
|
|
||||||
|
import { Icon } from '@/components';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: {
|
||||||
|
votes: number;
|
||||||
|
answers: number;
|
||||||
|
views: number;
|
||||||
|
};
|
||||||
|
showVotes?: boolean;
|
||||||
|
showAnswers?: boolean;
|
||||||
|
showViews?: boolean;
|
||||||
|
showAccepted?: boolean;
|
||||||
|
isAccepted?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
const Index: FC<Props> = ({
|
||||||
|
data,
|
||||||
|
showVotes = true,
|
||||||
|
showAnswers = true,
|
||||||
|
showViews = true,
|
||||||
|
isAccepted = false,
|
||||||
|
showAccepted = false,
|
||||||
|
className = '',
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('translation', { keyPrefix: 'counts' });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classname('d-flex align-items-center', className)}>
|
||||||
|
{showVotes && (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<Icon name="hand-thumbs-up-fill me-1" />
|
||||||
|
<span>
|
||||||
|
{data.votes} {t('votes')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showAccepted && (
|
||||||
|
<div className="d-flex align-items-center ms-3 text-success">
|
||||||
|
<Icon name="check-circle-fill me-1" />
|
||||||
|
<span>{t('accepted')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showAnswers && (
|
||||||
|
<div
|
||||||
|
className={`d-flex align-items-center ms-3 ${
|
||||||
|
isAccepted ? 'text-success' : ''
|
||||||
|
}`}>
|
||||||
|
{isAccepted ? (
|
||||||
|
<Icon name="check-circle-fill me-1" />
|
||||||
|
) : (
|
||||||
|
<Icon name="chat-square-text-fill me-1" />
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{data.answers} {t('answers')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showViews && (
|
||||||
|
<span className="summary-stat ms-3">
|
||||||
|
<Icon name="eye-fill" />
|
||||||
|
<em className="fst-normal ms-1">
|
||||||
|
{data.views} {t('views')}
|
||||||
|
</em>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Index);
|
|
@ -8,6 +8,7 @@ import {
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { markdownToHtml } from '@/services';
|
import { markdownToHtml } from '@/services';
|
||||||
|
import { htmlToReact } from '@/utils';
|
||||||
|
|
||||||
import { htmlRender } from './utils';
|
import { htmlRender } from './utils';
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ const Index = ({ value }, ref) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
previewRef.current?.scrollTo(0, scrollTop);
|
previewRef.current?.scrollTo(0, scrollTop);
|
||||||
|
|
||||||
htmlRender(previewRef.current);
|
htmlRender(previewRef.current);
|
||||||
}, [html]);
|
}, [html]);
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
|
@ -49,9 +51,9 @@ const Index = ({ value }, ref) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt">
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
{htmlToReact(html)}
|
||||||
/>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { Pagination } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
|
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { scrollToDocTop } from '@/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
@ -49,7 +51,7 @@ const PageItem = ({ page, currentPage, path }: PageItemProps) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(path);
|
navigate(path);
|
||||||
window.scrollTo(0, 0);
|
scrollToDocTop();
|
||||||
}}>
|
}}>
|
||||||
{page}
|
{page}
|
||||||
</Pagination.Item>
|
</Pagination.Item>
|
||||||
|
@ -91,7 +93,7 @@ const Index: FC<Props> = ({
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(handleParams(currentPage - 1));
|
navigate(handleParams(currentPage - 1));
|
||||||
window.scrollTo(0, 0);
|
scrollToDocTop();
|
||||||
}}>
|
}}>
|
||||||
{t('prev')}
|
{t('prev')}
|
||||||
</Pagination.Prev>
|
</Pagination.Prev>
|
||||||
|
@ -186,7 +188,7 @@ const Index: FC<Props> = ({
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(handleParams(currentPage + 1));
|
navigate(handleParams(currentPage + 1));
|
||||||
window.scrollTo(0, 0);
|
scrollToDocTop();
|
||||||
}}>
|
}}>
|
||||||
{t('next')}
|
{t('next')}
|
||||||
</Pagination.Next>
|
</Pagination.Next>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
import type * as Type from '@/common/interface';
|
import type * as Type from '@/common/interface';
|
||||||
import {
|
import {
|
||||||
Icon,
|
|
||||||
Tag,
|
Tag,
|
||||||
Pagination,
|
Pagination,
|
||||||
FormatTime,
|
FormatTime,
|
||||||
|
@ -14,6 +13,7 @@ import {
|
||||||
BaseUserCard,
|
BaseUserCard,
|
||||||
QueryGroup,
|
QueryGroup,
|
||||||
QuestionListLoader,
|
QuestionListLoader,
|
||||||
|
Counts,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { useQuestionList } from '@/services';
|
import { useQuestionList } from '@/services';
|
||||||
|
|
||||||
|
@ -95,29 +95,15 @@ const QuestionList: FC<Props> = ({ source }) => {
|
||||||
preFix={t(li.operation_type)}
|
preFix={t(li.operation_type)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ms-0 ms-md-3 mt-2 mt-md-0">
|
<Counts
|
||||||
<span>
|
data={{
|
||||||
<Icon name="hand-thumbs-up-fill" />
|
votes: li.vote_count,
|
||||||
<em className="fst-normal ms-1">{li.vote_count}</em>
|
answers: li.answer_count,
|
||||||
</span>
|
views: li.view_count,
|
||||||
<span
|
}}
|
||||||
className={`ms-3 ${
|
isAccepted={li.accepted_answer_id >= 1}
|
||||||
li.accepted_answer_id >= 1 ? 'text-success' : ''
|
className="ms-0 ms-md-3 mt-2 mt-md-0"
|
||||||
}`}>
|
/>
|
||||||
<Icon
|
|
||||||
name={
|
|
||||||
li.accepted_answer_id >= 1
|
|
||||||
? 'check-circle-fill'
|
|
||||||
: 'chat-square-text-fill'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<em className="fst-normal ms-1">{li.answer_count}</em>
|
|
||||||
</span>
|
|
||||||
<span className="summary-stat ms-3">
|
|
||||||
<Icon name="eye-fill" />
|
|
||||||
<em className="fst-normal ms-1">{li.view_count}</em>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="question-tags m-n1">
|
<div className="question-tags m-n1">
|
||||||
{Array.isArray(li.tags)
|
{Array.isArray(li.tags)
|
||||||
|
@ -139,7 +125,7 @@ const QuestionList: FC<Props> = ({ source }) => {
|
||||||
currentPage={curPage}
|
currentPage={curPage}
|
||||||
totalSize={count}
|
totalSize={count}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pathname="/questions"
|
pathname={source === 'questions' ? '/questions' : ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -155,6 +155,7 @@ const TagSelector: FC<IProps> = ({
|
||||||
};
|
};
|
||||||
const handleKeyDown = (e) => {
|
const handleKeyDown = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (!tags) {
|
if (!tags) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -166,13 +167,20 @@ const TagSelector: FC<IProps> = ({
|
||||||
if (keyCode === 40 && currentIndex < tags.length - 1) {
|
if (keyCode === 40 && currentIndex < tags.length - 1) {
|
||||||
setCurrentIndex(currentIndex + 1);
|
setCurrentIndex(currentIndex + 1);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
keyCode === 13 &&
|
if (keyCode === 13 && currentIndex > -1) {
|
||||||
currentIndex > -1 &&
|
|
||||||
currentIndex <= tags.length - 1
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleClick(tags[currentIndex]);
|
|
||||||
|
if (tags.length === 0) {
|
||||||
|
tagModal.onShow(tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentIndex <= tags.length - 1) {
|
||||||
|
handleClick(tags[currentIndex]);
|
||||||
|
if (currentIndex === tags.length - 1 && currentIndex > 0) {
|
||||||
|
setCurrentIndex(currentIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -33,6 +33,7 @@ import PageTags from './PageTags';
|
||||||
import QuestionListLoader from './QuestionListLoader';
|
import QuestionListLoader from './QuestionListLoader';
|
||||||
import TagsLoader from './TagsLoader';
|
import TagsLoader from './TagsLoader';
|
||||||
import WelcomeTitle from './WelcomeTitle';
|
import WelcomeTitle from './WelcomeTitle';
|
||||||
|
import Counts from './Counts';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
@ -72,5 +73,6 @@ export {
|
||||||
QuestionListLoader,
|
QuestionListLoader,
|
||||||
TagsLoader,
|
TagsLoader,
|
||||||
WelcomeTitle,
|
WelcomeTitle,
|
||||||
|
Counts,
|
||||||
};
|
};
|
||||||
export type { EditorRef, JSONSchema, UISchema };
|
export type { EditorRef, JSONSchema, UISchema };
|
||||||
|
|
|
@ -143,21 +143,7 @@ const Ask = () => {
|
||||||
const checkValidated = (): boolean => {
|
const checkValidated = (): boolean => {
|
||||||
const bol = true;
|
const bol = true;
|
||||||
const { title, content, tags, answer } = formData;
|
const { title, content, tags, answer } = formData;
|
||||||
if (!title.value) {
|
if (title.value && Array.from(title.value).length <= 150) {
|
||||||
// bol = false;
|
|
||||||
// formData.title = {
|
|
||||||
// value: '',
|
|
||||||
// isInvalid: true,
|
|
||||||
// errorMsg: t('form.fields.title.msg.empty'),
|
|
||||||
// };
|
|
||||||
} else if (Array.from(title.value).length > 150) {
|
|
||||||
// bol = false;
|
|
||||||
// formData.title = {
|
|
||||||
// value: title.value,
|
|
||||||
// isInvalid: true,
|
|
||||||
// errorMsg: t('form.fields.title.msg.range'),
|
|
||||||
// };
|
|
||||||
} else {
|
|
||||||
formData.title = {
|
formData.title = {
|
||||||
value: title.value,
|
value: title.value,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
|
@ -165,14 +151,7 @@ const Ask = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!content.value) {
|
if (content.value) {
|
||||||
// bol = false;
|
|
||||||
// formData.content = {
|
|
||||||
// value: '',
|
|
||||||
// isInvalid: true,
|
|
||||||
// errorMsg: t('form.fields.body.msg.empty'),
|
|
||||||
// };
|
|
||||||
} else {
|
|
||||||
formData.content = {
|
formData.content = {
|
||||||
value: content.value,
|
value: content.value,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
|
@ -180,29 +159,16 @@ const Ask = () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tags.value.length === 0) {
|
if (Array.isArray(tags.value) && tags.value.length > 0) {
|
||||||
// bol = false;
|
|
||||||
// formData.tags = {
|
|
||||||
// value: [],
|
|
||||||
// isInvalid: true,
|
|
||||||
// errorMsg: t('form.fields.tags.msg.empty'),
|
|
||||||
// };
|
|
||||||
} else {
|
|
||||||
formData.tags = {
|
formData.tags = {
|
||||||
value: tags.value,
|
value: tags.value,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
if (!answer.value) {
|
if (answer.value) {
|
||||||
// bol = false;
|
|
||||||
// formData.answer = {
|
|
||||||
// value: '',
|
|
||||||
// isInvalid: true,
|
|
||||||
// errorMsg: t('form.fields.answer.msg.empty'),
|
|
||||||
// };
|
|
||||||
} else {
|
|
||||||
formData.answer = {
|
formData.answer = {
|
||||||
value: answer.value,
|
value: answer.value,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
|
@ -227,7 +193,6 @@ const Ask = () => {
|
||||||
const params: Type.QuestionParams = {
|
const params: Type.QuestionParams = {
|
||||||
title: formData.title.value,
|
title: formData.title.value,
|
||||||
content: formData.content.value,
|
content: formData.content.value,
|
||||||
html: editorRef.current.getHtml(),
|
|
||||||
tags: formData.tags.value,
|
tags: formData.tags.value,
|
||||||
};
|
};
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
|
@ -261,7 +226,6 @@ const Ask = () => {
|
||||||
postAnswer({
|
postAnswer({
|
||||||
question_id: id,
|
question_id: id,
|
||||||
content: formData.answer.value,
|
content: formData.answer.value,
|
||||||
html: editorRef2.current.getHtml(),
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
navigate(pathFactory.questionLanding(id, params.url_title));
|
navigate(pathFactory.questionLanding(id, params.url_title));
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
FormatTime,
|
FormatTime,
|
||||||
htmlRender,
|
htmlRender,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { scrollTop, bgFadeOut } from '@/utils';
|
import { scrollToElementTop, bgFadeOut } from '@/utils';
|
||||||
import { AnswerItem } from '@/common/interface';
|
import { AnswerItem } from '@/common/interface';
|
||||||
import { acceptanceAnswer } from '@/services';
|
import { acceptanceAnswer } from '@/services';
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ const Index: FC<Props> = ({
|
||||||
if (aid === data.id) {
|
if (aid === data.id) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const element = answerRef.current;
|
const element = answerRef.current;
|
||||||
scrollTop(element);
|
scrollToElementTop(element);
|
||||||
if (!searchParams.get('commentId')) {
|
if (!searchParams.get('commentId')) {
|
||||||
bgFadeOut(answerRef.current);
|
bgFadeOut(answerRef.current);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import classNames from 'classnames';
|
||||||
import { Editor, Modal, TextArea } from '@/components';
|
import { Editor, Modal, TextArea } from '@/components';
|
||||||
import { FormDataType } from '@/common/interface';
|
import { FormDataType } from '@/common/interface';
|
||||||
import { postAnswer } from '@/services';
|
import { postAnswer } from '@/services';
|
||||||
import { guard } from '@/utils';
|
import { guard, handleFormError } from '@/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
|
@ -35,35 +35,60 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
||||||
const [focusType, setFocusType] = useState('');
|
const [focusType, setFocusType] = useState('');
|
||||||
const [editorFocusState, setEditorFocusState] = useState(false);
|
const [editorFocusState, setEditorFocusState] = useState(false);
|
||||||
|
|
||||||
|
const checkValidated = (): boolean => {
|
||||||
|
let bol = true;
|
||||||
|
const { content } = formData;
|
||||||
|
|
||||||
|
if (!content.value || Array.from(content.value.trim()).length < 6) {
|
||||||
|
bol = false;
|
||||||
|
formData.content = {
|
||||||
|
value: content.value,
|
||||||
|
isInvalid: true,
|
||||||
|
errorMsg: t('characters'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
formData.content = {
|
||||||
|
value: content.value,
|
||||||
|
isInvalid: false,
|
||||||
|
errorMsg: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
});
|
||||||
|
return bol;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (!guard.tryNormalLogged(true)) {
|
if (!guard.tryNormalLogged(true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.content.value) {
|
if (!checkValidated()) {
|
||||||
setFormData({
|
|
||||||
content: {
|
|
||||||
value: '',
|
|
||||||
isInvalid: true,
|
|
||||||
errorMsg: t('empty'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
postAnswer({
|
postAnswer({
|
||||||
question_id: data?.qid,
|
question_id: data?.qid,
|
||||||
content: formData.content.value,
|
content: formData.content.value,
|
||||||
html: marked.parse(formData.content.value),
|
html: marked.parse(formData.content.value),
|
||||||
}).then((res) => {
|
})
|
||||||
setShowEditor(false);
|
.then((res) => {
|
||||||
setFormData({
|
setShowEditor(false);
|
||||||
content: {
|
setFormData({
|
||||||
value: '',
|
content: {
|
||||||
isInvalid: false,
|
value: '',
|
||||||
errorMsg: '',
|
isInvalid: false,
|
||||||
},
|
errorMsg: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
callback?.(res.info);
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
const stateData = handleFormError(ex, formData);
|
||||||
|
setFormData({ ...stateData });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
callback?.(res.info);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickBtn = () => {
|
const clickBtn = () => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
|
||||||
import Pattern from '@/common/pattern';
|
import Pattern from '@/common/pattern';
|
||||||
import { Pagination } from '@/components';
|
import { Pagination } from '@/components';
|
||||||
import { loggedUserInfoStore, toastStore } from '@/stores';
|
import { loggedUserInfoStore, toastStore } from '@/stores';
|
||||||
import { scrollTop } from '@/utils';
|
import { scrollToElementTop } from '@/utils';
|
||||||
import { usePageTags, usePageUsers } from '@/hooks';
|
import { usePageTags, usePageUsers } from '@/hooks';
|
||||||
import type {
|
import type {
|
||||||
ListResult,
|
ListResult,
|
||||||
|
@ -80,7 +80,7 @@ const Index = () => {
|
||||||
if (page > 0 || order) {
|
if (page > 0 || order) {
|
||||||
// scroll into view;
|
// scroll into view;
|
||||||
const element = document.getElementById('answerHeader');
|
const element = document.getElementById('answerHeader');
|
||||||
scrollTop(element);
|
scrollToElementTop(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.list.forEach((item) => {
|
res.list.forEach((item) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { handleFormError } from '@/utils';
|
||||||
import { usePageTags } from '@/hooks';
|
import { usePageTags } from '@/hooks';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
import { Editor, EditorRef, Icon } from '@/components';
|
import { Editor, EditorRef, Icon } from '@/components';
|
||||||
|
@ -19,11 +20,11 @@ import {
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
interface FormDataItem {
|
interface FormDataItem {
|
||||||
answer: Type.FormValue<string>;
|
content: Type.FormValue<string>;
|
||||||
description: Type.FormValue<string>;
|
description: Type.FormValue<string>;
|
||||||
}
|
}
|
||||||
const initFormData = {
|
const initFormData = {
|
||||||
answer: {
|
content: {
|
||||||
value: '',
|
value: '',
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
|
@ -35,7 +36,6 @@ const initFormData = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
|
||||||
const { aid = '', qid = '' } = useParams();
|
const { aid = '', qid = '' } = useParams();
|
||||||
const [focusType, setForceType] = useState('');
|
const [focusType, setForceType] = useState('');
|
||||||
|
|
||||||
|
@ -43,6 +43,10 @@ const Index = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { data } = useQueryAnswerInfo(aid);
|
const { data } = useQueryAnswerInfo(aid);
|
||||||
|
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||||
|
|
||||||
|
initFormData.content.value = data?.info.content || '';
|
||||||
|
|
||||||
const { data: revisions = [] } = useQueryRevisions(aid);
|
const { data: revisions = [] } = useQueryRevisions(aid);
|
||||||
|
|
||||||
const editorRef = useRef<EditorRef>({
|
const editorRef = useRef<EditorRef>({
|
||||||
|
@ -51,18 +55,10 @@ const Index = () => {
|
||||||
|
|
||||||
const questionContentRef = useRef<HTMLDivElement>(null);
|
const questionContentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
formData.answer.value = data.info.content;
|
|
||||||
setFormData({ ...formData });
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const handleAnswerChange = (value: string) =>
|
const handleAnswerChange = (value: string) =>
|
||||||
setFormData({
|
setFormData({
|
||||||
...formData,
|
...formData,
|
||||||
answer: { ...formData.answer, value },
|
content: { ...formData.content, value },
|
||||||
});
|
});
|
||||||
const handleSummaryChange = (evt) => {
|
const handleSummaryChange = (evt) => {
|
||||||
const v = evt.currentTarget.value;
|
const v = evt.currentTarget.value;
|
||||||
|
@ -74,18 +70,18 @@ const Index = () => {
|
||||||
|
|
||||||
const checkValidated = (): boolean => {
|
const checkValidated = (): boolean => {
|
||||||
let bol = true;
|
let bol = true;
|
||||||
const { answer } = formData;
|
const { content } = formData;
|
||||||
|
|
||||||
if (!answer.value) {
|
if (!content.value || Array.from(content.value.trim()).length < 6) {
|
||||||
bol = false;
|
bol = false;
|
||||||
formData.answer = {
|
formData.content = {
|
||||||
value: '',
|
value: content.value,
|
||||||
isInvalid: true,
|
isInvalid: true,
|
||||||
errorMsg: '标题不能为空',
|
errorMsg: t('form.fields.answer.feedback.characters'),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
formData.answer = {
|
formData.content = {
|
||||||
value: answer.value,
|
value: content.value,
|
||||||
isInvalid: false,
|
isInvalid: false,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
};
|
};
|
||||||
|
@ -105,29 +101,36 @@ const Index = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: Type.AnswerParams = {
|
const params: Type.AnswerParams = {
|
||||||
content: formData.answer.value,
|
content: formData.content.value,
|
||||||
html: editorRef.current.getHtml(),
|
html: editorRef.current.getHtml(),
|
||||||
question_id: qid,
|
question_id: qid,
|
||||||
id: aid,
|
id: aid,
|
||||||
edit_summary: formData.description.value,
|
edit_summary: formData.description.value,
|
||||||
};
|
};
|
||||||
modifyAnswer(params).then((res) => {
|
modifyAnswer(params)
|
||||||
navigate(
|
.then((res) => {
|
||||||
pathFactory.answerLanding({
|
navigate(
|
||||||
questionId: qid,
|
pathFactory.answerLanding({
|
||||||
slugTitle: data?.question?.url_title,
|
questionId: qid,
|
||||||
answerId: aid,
|
slugTitle: data?.question?.url_title,
|
||||||
}),
|
answerId: aid,
|
||||||
{
|
}),
|
||||||
state: { isReview: res?.wait_for_review },
|
{
|
||||||
},
|
state: { isReview: res?.wait_for_review },
|
||||||
);
|
},
|
||||||
});
|
);
|
||||||
|
})
|
||||||
|
.catch((ex) => {
|
||||||
|
if (ex.isError) {
|
||||||
|
const stateData = handleFormError(ex, formData);
|
||||||
|
setFormData({ ...stateData });
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const handleSelectedRevision = (e) => {
|
const handleSelectedRevision = (e) => {
|
||||||
const index = e.target.value;
|
const index = e.target.value;
|
||||||
const revision = revisions[index];
|
const revision = revisions[index];
|
||||||
formData.answer.value = revision.content.content;
|
formData.content.value = revision.content.content;
|
||||||
setFormData({ ...formData });
|
setFormData({ ...formData });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,7 +193,7 @@ const Index = () => {
|
||||||
<Form.Group controlId="answer" className="mt-3">
|
<Form.Group controlId="answer" className="mt-3">
|
||||||
<Form.Label>{t('form.fields.answer.label')}</Form.Label>
|
<Form.Label>{t('form.fields.answer.label')}</Form.Label>
|
||||||
<Editor
|
<Editor
|
||||||
value={formData.answer.value}
|
value={formData.content.value}
|
||||||
onChange={handleAnswerChange}
|
onChange={handleAnswerChange}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'form-control p-0',
|
'form-control p-0',
|
||||||
|
@ -205,14 +208,14 @@ const Index = () => {
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
/>
|
/>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
value={formData.answer.value}
|
value={formData.content.value}
|
||||||
type="text"
|
type="text"
|
||||||
isInvalid={formData.answer.isInvalid}
|
isInvalid={formData.content.isInvalid}
|
||||||
readOnly
|
readOnly
|
||||||
hidden
|
hidden
|
||||||
/>
|
/>
|
||||||
<Form.Control.Feedback type="invalid">
|
<Form.Control.Feedback type="invalid">
|
||||||
{formData.answer.errorMsg}
|
{formData.content.errorMsg}
|
||||||
</Form.Control.Feedback>
|
</Form.Control.Feedback>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group controlId="edit_summary" className="my-3">
|
<Form.Group controlId="edit_summary" className="my-3">
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { ListGroupItem } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
import { Icon, Tag, FormatTime, BaseUserCard } from '@/components';
|
import { Tag, FormatTime, BaseUserCard, Counts } from '@/components';
|
||||||
import type { SearchResItem } from '@/common/interface';
|
import type { SearchResItem } from '@/common/interface';
|
||||||
import { escapeRemove } from '@/utils';
|
import { escapeRemove } from '@/utils';
|
||||||
|
|
||||||
|
@ -51,23 +51,17 @@ const Index: FC<Props> = ({ data }) => {
|
||||||
className="me-3"
|
className="me-3"
|
||||||
preFix={data.object_type === 'question' ? 'asked' : 'answered'}
|
preFix={data.object_type === 'question' ? 'asked' : 'answered'}
|
||||||
/>
|
/>
|
||||||
<div className="d-flex align-items-center my-2 my-sm-0">
|
|
||||||
<div className="d-flex align-items-center me-3">
|
<Counts
|
||||||
<Icon name="hand-thumbs-up-fill me-1" />
|
className="my-2 my-sm-0"
|
||||||
<span> {data.object?.vote_count}</span>
|
showViews={false}
|
||||||
</div>
|
isAccepted={data.object?.accepted}
|
||||||
<div
|
data={{
|
||||||
className={`d-flex align-items-center ${
|
votes: data.object?.vote_count,
|
||||||
data.object?.accepted ? 'text-success' : ''
|
answers: data.object?.answer_count,
|
||||||
}`}>
|
views: 0,
|
||||||
{data.object?.accepted ? (
|
}}
|
||||||
<Icon name="check-circle-fill me-1" />
|
/>
|
||||||
) : (
|
|
||||||
<Icon name="chat-square-text-fill me-1" />
|
|
||||||
)}
|
|
||||||
<span>{data.object?.answer_count}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data.object?.excerpt && (
|
{data.object?.excerpt && (
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Link, useSearchParams } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { usePageTags } from '@/hooks';
|
import { usePageTags } from '@/hooks';
|
||||||
import { loggedUserInfoStore } from '@/stores';
|
import { loggedUserInfoStore, siteInfoStore } from '@/stores';
|
||||||
import { changeEmailVerify, getLoggedUserInfo } from '@/services';
|
import { changeEmailVerify, getLoggedUserInfo } from '@/services';
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
|
@ -13,6 +13,7 @@ const Index: FC = () => {
|
||||||
const [step, setStep] = useState('loading');
|
const [step, setStep] = useState('loading');
|
||||||
|
|
||||||
const updateUser = loggedUserInfoStore((state) => state.update);
|
const updateUser = loggedUserInfoStore((state) => state.update);
|
||||||
|
const siteName = siteInfoStore((state) => state.siteInfo.name);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const code = searchParams.get('code');
|
const code = searchParams.get('code');
|
||||||
|
@ -38,7 +39,9 @@ const Index: FC = () => {
|
||||||
<Container className="pt-4 mt-2 mb-5">
|
<Container className="pt-4 mt-2 mb-5">
|
||||||
<Row className="justify-content-center">
|
<Row className="justify-content-center">
|
||||||
<Col lg={6}>
|
<Col lg={6}>
|
||||||
<h3 className="text-center mt-3 mb-5">{t('page_title')}</h3>
|
<h3 className="text-center mt-3 mb-5">
|
||||||
|
{t('page_title', { site_name: siteName })}
|
||||||
|
</h3>
|
||||||
{step === 'success' && (
|
{step === 'success' && (
|
||||||
<>
|
<>
|
||||||
<p className="text-center">{t('confirm_new_email')}</p>
|
<p className="text-center">{t('confirm_new_email')}</p>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { FC, memo } from 'react';
|
||||||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Icon, FormatTime, Tag } from '@/components';
|
import { FormatTime, Tag, Counts } from '@/components';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -35,21 +35,16 @@ const Index: FC<Props> = ({ visible, data }) => {
|
||||||
<div className="d-flex align-items-center fs-14 text-secondary mb-2">
|
<div className="d-flex align-items-center fs-14 text-secondary mb-2">
|
||||||
<FormatTime
|
<FormatTime
|
||||||
time={item.create_time}
|
time={item.create_time}
|
||||||
className="me-4"
|
className="me-3"
|
||||||
preFix={t('answered')}
|
preFix={t('answered')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="d-flex align-items-center me-3">
|
<Counts
|
||||||
<Icon name="hand-thumbs-up-fill me-1" />
|
data={{ votes: item?.vote_count, views: 0, answers: 0 }}
|
||||||
<span>{item?.vote_count}</span>
|
showAnswers={false}
|
||||||
</div>
|
showViews={false}
|
||||||
|
showAccepted={item.accepted === 2}
|
||||||
{item.accepted === 2 && (
|
/>
|
||||||
<div className="d-flex align-items-center me-3 text-success">
|
|
||||||
<Icon name="check-circle-fill me-1" />
|
|
||||||
<span>{t('accepted')}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{item.question_info?.tags?.map((tag) => {
|
{item.question_info?.tags?.map((tag) => {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { FC, memo } from 'react';
|
||||||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Icon, FormatTime, Tag, BaseUserCard } from '@/components';
|
import { FormatTime, Tag, BaseUserCard, Counts } from '@/components';
|
||||||
import { pathFactory } from '@/router/pathFactory';
|
import { pathFactory } from '@/router/pathFactory';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -44,35 +44,23 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
||||||
<span className="split-dot" />
|
<span className="split-dot" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormatTime
|
<FormatTime
|
||||||
time={item.create_time}
|
time={
|
||||||
|
tabName === 'bookmarks' ? item.create_time : item.created_at
|
||||||
|
}
|
||||||
className="me-3"
|
className="me-3"
|
||||||
preFix={t('asked')}
|
preFix={t('asked')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="d-flex align-items-center me-3">
|
<Counts
|
||||||
<Icon name="hand-thumbs-up-fill me-1" />
|
isAccepted={Number(item.accepted_answer_id) > 0}
|
||||||
<span>{item.vote_count}</span>
|
data={{
|
||||||
</div>
|
votes: item.vote_count,
|
||||||
|
answers: item.answer_count,
|
||||||
{tabName !== 'answers' && (
|
views: item.view_count,
|
||||||
<div
|
}}
|
||||||
className={`d-flex align-items-center me-3 ${
|
/>
|
||||||
Number(item.accepted_answer_id) > 0 ? 'text-success' : ''
|
|
||||||
}`}>
|
|
||||||
{Number(item.accepted_answer_id) > 0 ? (
|
|
||||||
<Icon name="check-circle-fill me-1" />
|
|
||||||
) : (
|
|
||||||
<Icon name="chat-square-text-fill me-1" />
|
|
||||||
)}
|
|
||||||
<span>{item.answer_count}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="d-flex align-items-center me-3">
|
|
||||||
<Icon name="eye-fill me-1" />
|
|
||||||
<span>{item.view_count}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{item.tags?.map((tag) => {
|
{item.tags?.map((tag) => {
|
||||||
|
|
|
@ -32,9 +32,12 @@ const Index: FC<Props> = ({ data, type }) => {
|
||||||
}>
|
}>
|
||||||
{type === 'answer' ? item.question_info.title : item.title}
|
{type === 'answer' ? item.question_info.title : item.title}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div className="d-inline-block text-secondary ms-3 fs-14">
|
<div className="d-inline-block text-secondary ms-3 fs-14">
|
||||||
<Icon name="hand-thumbs-up-fill" />
|
<Icon name="hand-thumbs-up-fill me-1" />
|
||||||
<span> {item.vote_count}</span>
|
<span>
|
||||||
|
{item.vote_count} {t('votes', { keyPrefix: 'counts' })}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{type === 'question' && (
|
{type === 'question' && (
|
||||||
<div
|
<div
|
||||||
|
@ -47,7 +50,10 @@ const Index: FC<Props> = ({ data, type }) => {
|
||||||
<Icon name="chat-square-text-fill" />
|
<Icon name="chat-square-text-fill" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<span> {item.answer_count}</span>
|
<span>
|
||||||
|
{' '}
|
||||||
|
{item.answer_count} {t('answers', { keyPrefix: 'counts' })}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ const Index = () => {
|
||||||
return (
|
return (
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<div className="form-label">{t('title')}</div>
|
<div className="form-label">{t('title')}</div>
|
||||||
<small className="form-text mt-0">{t('lable')}</small>
|
<small className="form-text mt-0">{t('label')}</small>
|
||||||
|
|
||||||
<div className="d-grid gap-2 mt-3">
|
<div className="d-grid gap-2 mt-3">
|
||||||
{data?.map((item) => {
|
{data?.map((item) => {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
import parse from 'html-react-parser';
|
||||||
|
import * as DOMPurify from 'dompurify';
|
||||||
|
|
||||||
const Diff = require('diff');
|
const Diff = require('diff');
|
||||||
|
|
||||||
|
@ -21,7 +23,7 @@ function formatCount($num: number): string {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollTop(element) {
|
function scrollToElementTop(element) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +38,15 @@ function scrollTop(element) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToDocTop = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const bgFadeOut = (el) => {
|
const bgFadeOut = (el) => {
|
||||||
if (el && !el.classList.contains('bg-fade-out')) {
|
if (el && !el.classList.contains('bg-fade-out')) {
|
||||||
el.classList.add('bg-fade-out');
|
el.classList.add('bg-fade-out');
|
||||||
|
@ -160,14 +171,17 @@ function handleFormError(
|
||||||
) {
|
) {
|
||||||
if (error.list?.length > 0) {
|
if (error.list?.length > 0) {
|
||||||
error.list.forEach((item) => {
|
error.list.forEach((item) => {
|
||||||
data[item.error_field].isInvalid = true;
|
const errorFieldObject = data[item.error_field];
|
||||||
data[item.error_field].errorMsg = item.error_msg;
|
if (errorFieldObject) {
|
||||||
|
errorFieldObject.isInvalid = true;
|
||||||
|
errorFieldObject.errorMsg = item.error_msg;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffText(newText: string, oldText: string): string {
|
function diffText(newText: string, oldText?: string): string {
|
||||||
if (!newText) {
|
if (!newText) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -181,8 +195,6 @@ function diffText(newText: string, oldText: string): string {
|
||||||
?.replace(/<input/gi, '<input');
|
?.replace(/<input/gi, '<input');
|
||||||
}
|
}
|
||||||
const diff = Diff.diffChars(oldText, newText);
|
const diff = Diff.diffChars(oldText, newText);
|
||||||
console.log(diff);
|
|
||||||
|
|
||||||
const result = diff.map((part) => {
|
const result = diff.map((part) => {
|
||||||
if (part.added) {
|
if (part.added) {
|
||||||
if (part.value.replace(/\n/g, '').length <= 0) {
|
if (part.value.replace(/\n/g, '').length <= 0) {
|
||||||
|
@ -214,10 +226,18 @@ function diffText(newText: string, oldText: string): string {
|
||||||
?.replace(/<input/gi, '<input');
|
?.replace(/<input/gi, '<input');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function htmlToReact(html: string) {
|
||||||
|
const cleanedHtml = DOMPurify.sanitize(html, {
|
||||||
|
USE_PROFILES: { html: true },
|
||||||
|
});
|
||||||
|
return parse(cleanedHtml);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
thousandthDivision,
|
thousandthDivision,
|
||||||
formatCount,
|
formatCount,
|
||||||
scrollTop,
|
scrollToElementTop,
|
||||||
|
scrollToDocTop,
|
||||||
bgFadeOut,
|
bgFadeOut,
|
||||||
matchedUsers,
|
matchedUsers,
|
||||||
parseUserInfo,
|
parseUserInfo,
|
||||||
|
@ -228,4 +248,5 @@ export {
|
||||||
labelStyle,
|
labelStyle,
|
||||||
handleFormError,
|
handleFormError,
|
||||||
diffText,
|
diffText,
|
||||||
|
htmlToReact,
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,7 +48,7 @@ class Request {
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const { status, data: respData } = error.response || {};
|
const { status, data: respData } = error.response || {};
|
||||||
const { data = {}, msg = '' } = respData || {};
|
const { data = {}, msg = '', reason = '' } = respData || {};
|
||||||
if (status === 400) {
|
if (status === 400) {
|
||||||
// show error message
|
// show error message
|
||||||
if (data instanceof Object && data.err_type) {
|
if (data instanceof Object && data.err_type) {
|
||||||
|
@ -79,7 +79,13 @@ class Request {
|
||||||
|
|
||||||
if (data instanceof Array && data.length > 0) {
|
if (data instanceof Array && data.length > 0) {
|
||||||
// handle form error
|
// handle form error
|
||||||
return Promise.reject({ isError: true, list: data });
|
return Promise.reject({
|
||||||
|
code: status,
|
||||||
|
msg,
|
||||||
|
reason,
|
||||||
|
isError: true,
|
||||||
|
list: data,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data || Object.keys(data).length <= 0) {
|
if (!data || Object.keys(data).length <= 0) {
|
||||||
|
|
Loading…
Reference in New Issue