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
|
||||
|
||||
VERSION=1.0.2
|
||||
VERSION=1.0.3
|
||||
BIN=answer
|
||||
DIR_SRC=./cmd/answer
|
||||
DOCKER_CMD=docker
|
||||
|
@ -39,6 +39,6 @@ install-ui-packages:
|
|||
@corepack prepare pnpm@v7.12.2 --activate
|
||||
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
controller_adminReportController := controller_admin.NewReportController(reportAdminService)
|
||||
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)
|
||||
reasonRepo := reason.NewReasonRepo(configRepo)
|
||||
reasonService := reason2.NewReasonService(reasonRepo)
|
||||
|
|
|
@ -6948,10 +6948,6 @@ const docTemplate = `{
|
|||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
|
@ -6973,10 +6969,6 @@ const docTemplate = `{
|
|||
},
|
||||
"schema.SiteBrandingResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
|
|
|
@ -6936,10 +6936,6 @@
|
|||
},
|
||||
"schema.SiteBrandingReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
|
@ -6961,10 +6957,6 @@
|
|||
},
|
||||
"schema.SiteBrandingResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"logo",
|
||||
"square_icon"
|
||||
],
|
||||
"properties": {
|
||||
"favicon": {
|
||||
"type": "string",
|
||||
|
|
|
@ -1241,9 +1241,6 @@ definitions:
|
|||
square_icon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
required:
|
||||
- logo
|
||||
- square_icon
|
||||
type: object
|
||||
schema.SiteBrandingResp:
|
||||
properties:
|
||||
|
@ -1259,9 +1256,6 @@ definitions:
|
|||
square_icon:
|
||||
maxLength: 512
|
||||
type: string
|
||||
required:
|
||||
- logo
|
||||
- square_icon
|
||||
type: object
|
||||
schema.SiteCustomCssHTMLReq:
|
||||
properties:
|
||||
|
|
4
go.mod
4
go.mod
|
@ -15,7 +15,6 @@ require (
|
|||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
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/wire v0.5.0
|
||||
github.com/gosimple/slug v1.13.1
|
||||
|
@ -24,6 +23,7 @@ require (
|
|||
github.com/jinzhu/now v1.1.5
|
||||
github.com/lib/pq v1.10.7
|
||||
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/ory/dockertest/v3 v3.9.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/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // 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/containerd/continuity v0.3.0 // 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/snappy v0.0.4 // 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/hashicorp/hcl v1.0.0 // 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-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/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/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=
|
||||
|
@ -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.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
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 v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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/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/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.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
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:
|
||||
base:
|
||||
success:
|
||||
other: "Success."
|
||||
other: "Erfolgreich."
|
||||
unknown:
|
||||
other: "Unknown error."
|
||||
other: "Unbekannter Fehler."
|
||||
request_format_error:
|
||||
other: "Request format is not valid."
|
||||
unauthorized_error:
|
||||
other: "Unauthorized."
|
||||
other: "Nicht autorisiert."
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
other: "User"
|
||||
other: "Nutzer"
|
||||
admin:
|
||||
other: "Admin"
|
||||
moderator:
|
||||
other: "Moderator"
|
||||
description:
|
||||
user:
|
||||
other: "Default with no special access."
|
||||
other: "Standard ohne speziellen Zugriff."
|
||||
admin:
|
||||
other: "Have the full power to access the site."
|
||||
moderator:
|
||||
other: "Has access to all posts except admin settings."
|
||||
other: "Hat Zugriff auf alle Beiträge außer Admin-Einstellungen."
|
||||
email:
|
||||
other: "Email"
|
||||
other: "E-Mail"
|
||||
password:
|
||||
other: "Password"
|
||||
other: "Passwort"
|
||||
email_or_password_wrong_error:
|
||||
other: "Email and password do not match."
|
||||
other: "E-Mail und Password stimmen nicht überein."
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong:
|
||||
other: Email and password do not match.
|
||||
other: E-Mail und Password stimmen nicht überein.
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Keine Berechtigung zum Löschen."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Keine Berechtigung zum Aktualisieren."
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Comment are not allowed to edit."
|
||||
other: "Kommentar kann nicht bearbeitet werden."
|
||||
not_found:
|
||||
other: "Comment not found."
|
||||
other: "Kommentar wurde nicht gefunden."
|
||||
email:
|
||||
duplicate:
|
||||
other: "Email already exists."
|
||||
other: "E-Mail existiert bereits."
|
||||
need_to_be_verified:
|
||||
other: "Email should be verified."
|
||||
other: "E-Mail muss überprüft werden."
|
||||
verify_url_expired:
|
||||
other: "Email verified URL has expired, please resend the email."
|
||||
lang:
|
||||
not_found:
|
||||
other: "Language file not found."
|
||||
other: "Sprachdatei nicht gefunden."
|
||||
object:
|
||||
captcha_verification_failed:
|
||||
other: "Captcha wrong."
|
||||
other: "Captcha ist falsch."
|
||||
disallow_follow:
|
||||
other: "You are not allowed to follow."
|
||||
disallow_vote:
|
||||
|
@ -70,22 +70,22 @@ backend:
|
|||
not_found:
|
||||
other: "Object not found."
|
||||
verification_failed:
|
||||
other: "Verification failed."
|
||||
other: "Verifizierung fehlgeschlagen."
|
||||
email_or_password_incorrect:
|
||||
other: "Email and password do not match."
|
||||
other: "E-Mail und Password stimmen nicht überein."
|
||||
old_password_verification_failed:
|
||||
other: "The old password verification failed"
|
||||
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:
|
||||
not_found:
|
||||
other: "Question not found."
|
||||
other: "Frage nicht gefunden."
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Keine Berechtigung zum Löschen."
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Keine Berechtigung zum Aktualisieren."
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
|
@ -96,7 +96,7 @@ backend:
|
|||
other: "Report not found."
|
||||
tag:
|
||||
not_found:
|
||||
other: "Tag not found."
|
||||
other: "Schlagwort nicht gefunden."
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
recommend_tag_enter:
|
||||
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -181,7 +184,7 @@ backend:
|
|||
name:
|
||||
other: "spam"
|
||||
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:
|
||||
name:
|
||||
other: "a community-specific reason"
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -230,24 +240,24 @@ ui:
|
|||
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>
|
||||
pagination:
|
||||
prev: Prev
|
||||
next: Next
|
||||
prev: Zurück
|
||||
next: Weiter
|
||||
page_title:
|
||||
question: Question
|
||||
questions: Questions
|
||||
tag: Tag
|
||||
tags: Tags
|
||||
question: Frage
|
||||
questions: Fragen
|
||||
tag: Schlagwort
|
||||
tags: Schlagwörter
|
||||
tag_wiki: tag wiki
|
||||
edit_tag: Edit Tag
|
||||
ask_a_question: Add Question
|
||||
edit_question: Edit Question
|
||||
edit_answer: Edit Answer
|
||||
search: Search
|
||||
search: Suche
|
||||
posts_containing: Posts containing
|
||||
settings: Settings
|
||||
settings: Einstellungen
|
||||
notifications: Notifications
|
||||
login: Log In
|
||||
sign_up: Sign Up
|
||||
login: Anmelden
|
||||
sign_up: Registrieren
|
||||
account_recovery: Account Recovery
|
||||
account_activation: Account Activation
|
||||
confirm_email: Confirm Email
|
||||
|
@ -367,7 +377,7 @@ ui:
|
|||
unordered_list:
|
||||
text: Bulleted List
|
||||
table:
|
||||
text: Table
|
||||
text: Tabelle
|
||||
heading: Heading
|
||||
cell: Cell
|
||||
close_modal:
|
||||
|
@ -586,13 +596,13 @@ ui:
|
|||
empty: Name cannot be empty.
|
||||
range: Name up to 30 characters.
|
||||
email:
|
||||
label: Email
|
||||
label: E-Mail
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
empty: E-Mail-Feld darf nicht leer sein.
|
||||
password:
|
||||
label: Password
|
||||
label: Passwort
|
||||
msg:
|
||||
empty: Password cannot be empty.
|
||||
empty: Passwort-Feld darf nicht leer sein.
|
||||
different: The passwords entered on both sides are inconsistent
|
||||
account_forgot:
|
||||
page_title: Forgot Your Password
|
||||
|
@ -600,7 +610,7 @@ ui:
|
|||
send_success: >-
|
||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
||||
email:
|
||||
label: Email
|
||||
label: E-Mail
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
change_email:
|
||||
|
@ -674,24 +684,24 @@ ui:
|
|||
radio: "Answers to your questions, comments, and more"
|
||||
account:
|
||||
heading: Account
|
||||
change_email_btn: Change email
|
||||
change_pass_btn: Change password
|
||||
change_email_btn: E-Mail-Adresse ändern
|
||||
change_pass_btn: Passwort ändern
|
||||
change_email_info: >-
|
||||
We've sent an email to that address. Please follow the confirmation instructions.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email cannot be empty.
|
||||
password_title: Password
|
||||
label: E-Mail
|
||||
msg: E-Mail-Feld darf nicht leer sein.
|
||||
password_title: Passwort
|
||||
current_pass:
|
||||
label: Current Password
|
||||
label: Aktuelles Passwort
|
||||
msg:
|
||||
empty: Current Password cannot be empty.
|
||||
length: The length needs to be between 8 and 32.
|
||||
different: The two entered passwords do not match.
|
||||
new_pass:
|
||||
label: New Password
|
||||
label: Neues Passwort
|
||||
pass_confirm:
|
||||
label: Confirm New Password
|
||||
label: Neues Passwort bestätigen
|
||||
interface:
|
||||
heading: Interface
|
||||
lang:
|
||||
|
@ -747,10 +757,10 @@ ui:
|
|||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
cancel: Cancel
|
||||
save: Save
|
||||
delete: Delete
|
||||
confirm: Bestätigen
|
||||
cancel: Abbrechen
|
||||
save: Speichern
|
||||
delete: Löschen
|
||||
login: Log in
|
||||
signup: Sign up
|
||||
logout: Log out
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -871,7 +886,7 @@ ui:
|
|||
placeholder: root
|
||||
msg: Username cannot be empty.
|
||||
db_password:
|
||||
label: Password
|
||||
label: Passwort
|
||||
placeholder: root
|
||||
msg: Password cannot be empty.
|
||||
db_host:
|
||||
|
@ -991,13 +1006,14 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
latest: Latest
|
||||
check_failed: Check failed
|
||||
"yes": "Yes"
|
||||
"no": "No"
|
||||
"yes": "Ja"
|
||||
"no": "Nein"
|
||||
not_allowed: Not allowed
|
||||
allowed: Allowed
|
||||
enabled: Enabled
|
||||
|
@ -1045,7 +1061,7 @@ ui:
|
|||
users:
|
||||
title: Users
|
||||
name: Name
|
||||
email: Email
|
||||
email: E-Mail
|
||||
reputation: Reputation
|
||||
created_at: Created Time
|
||||
delete_at: Deleted Time
|
||||
|
@ -1184,7 +1200,7 @@ ui:
|
|||
ssl: SSL
|
||||
none: None
|
||||
smtp_port:
|
||||
label: SMTP Port
|
||||
label: SMTP-Port
|
||||
msg: SMTP port must be number 1 ~ 65535.
|
||||
text: The port to your mail server.
|
||||
smtp_username:
|
||||
|
|
|
@ -509,6 +509,8 @@ ui:
|
|||
label: Revision
|
||||
answer:
|
||||
label: Answer
|
||||
feedback:
|
||||
characters: content must be at least 6 characters in length.
|
||||
edit_summary:
|
||||
label: Edit Summary
|
||||
placeholder: >-
|
||||
|
@ -748,7 +750,7 @@ ui:
|
|||
text: User interface language. It will change when you refresh the page.
|
||||
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_content: Are you sure you want to remove this login from your account?
|
||||
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
|
||||
edit link to refine and improve your existing answer, instead.</p>
|
||||
empty: Answer cannot be empty.
|
||||
characters: content must be at least 6 characters in length.
|
||||
reopen:
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
|
@ -1012,7 +1015,11 @@ ui:
|
|||
db_failed: Database connection failed
|
||||
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.
|
||||
|
||||
counts:
|
||||
views: views
|
||||
votes: votes
|
||||
answers: answers
|
||||
accepted: Accepted
|
||||
page_404:
|
||||
desc: "Unfortunately, this page doesn't exist."
|
||||
back_home: Back to homepage
|
||||
|
@ -1050,7 +1057,7 @@ ui:
|
|||
title: Admin
|
||||
dashboard:
|
||||
title: Dashboard
|
||||
welcome: Welcome to {{site_name}} Admin!
|
||||
welcome: Welcome to Answer Admin!
|
||||
site_statistics: Site Statistics
|
||||
questions: "Questions:"
|
||||
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:
|
||||
name:
|
||||
user:
|
||||
other: "User"
|
||||
other: "Utente"
|
||||
admin:
|
||||
other: "Admin"
|
||||
other: "Amministratore"
|
||||
moderator:
|
||||
other: "Moderator"
|
||||
other: "Moderatore"
|
||||
description:
|
||||
user:
|
||||
other: "Default with no special access."
|
||||
other: "Predefinito senza alcun accesso speciale."
|
||||
admin:
|
||||
other: "Have the full power to access the site."
|
||||
other: "Avere il pieno potere di accedere al sito."
|
||||
moderator:
|
||||
other: "Has access to all posts except admin settings."
|
||||
other: "Ha accesso a tutti i post tranne le impostazioni di amministratore."
|
||||
email:
|
||||
other: "email"
|
||||
password:
|
||||
|
@ -40,9 +40,9 @@ backend:
|
|||
not_found:
|
||||
other: "Risposta non trovata"
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Permesso per cancellare mancante."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Nessun permesso per l'aggiornamento."
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "Non si hanno di privilegi sufficienti per modificare il commento"
|
||||
|
@ -81,11 +81,11 @@ backend:
|
|||
not_found:
|
||||
other: "domanda non trovata"
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "Permesso per cancellare mancante."
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
other: "Nessun permesso per chiudere."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Nessun permesso per l'aggiornamento."
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Condizioni non valide per il grado"
|
||||
|
@ -98,23 +98,26 @@ backend:
|
|||
not_found:
|
||||
other: "Etichetta non trovata"
|
||||
recommend_tag_not_found:
|
||||
other: "Recommend Tag is not exist."
|
||||
other: "Il Tag consigliato non esiste."
|
||||
recommend_tag_enter:
|
||||
other: "Please enter at least one required tag."
|
||||
other: "Inserisci almeno un tag."
|
||||
not_contain_synonym_tags:
|
||||
other: "Should not contain synonym tags."
|
||||
other: "Non deve contenere tag sinonimi."
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "Nessun permesso per l'aggiornamento."
|
||||
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:
|
||||
not_found:
|
||||
other: "tema non trovato"
|
||||
revision:
|
||||
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:
|
||||
other: "No permission to Revision."
|
||||
other: "Nessun permesso per la revisione."
|
||||
user:
|
||||
email_or_password_wrong:
|
||||
other:
|
||||
|
@ -128,75 +131,82 @@ backend:
|
|||
username_duplicate:
|
||||
other: "utente già in uso"
|
||||
set_avatar:
|
||||
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."
|
||||
other: "Inserimento dell'Avatar non riuscito."
|
||||
cannot_update_your_role:
|
||||
other: "You cannot modify your role."
|
||||
other: "Non puoi modificare il tuo ruolo."
|
||||
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:
|
||||
spam:
|
||||
name:
|
||||
other: "spam"
|
||||
other: "posta indesiderata"
|
||||
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:
|
||||
name:
|
||||
other: "scortese o violento"
|
||||
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:
|
||||
name:
|
||||
other: "duplicato"
|
||||
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:
|
||||
name:
|
||||
other: "non è una risposta"
|
||||
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:
|
||||
name:
|
||||
other: "non più necessario"
|
||||
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:
|
||||
name:
|
||||
other: "altro"
|
||||
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:
|
||||
close:
|
||||
duplicate:
|
||||
name:
|
||||
other: "spam"
|
||||
other: "posta indesiderata"
|
||||
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:
|
||||
name:
|
||||
other: "motivo legato alla community"
|
||||
desc:
|
||||
other: "This question doesn't meet a community guideline."
|
||||
other: "Questa domanda non soddisfa le linee guida della comunità."
|
||||
multiple:
|
||||
name:
|
||||
other: "richiede maggiori dettagli o chiarezza"
|
||||
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:
|
||||
name:
|
||||
other: "altro"
|
||||
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:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -206,7 +216,7 @@ backend:
|
|||
update_answer:
|
||||
other: "risposta aggiornata"
|
||||
accept_answer:
|
||||
other: "risposta accepted"
|
||||
other: "risposta accettata"
|
||||
comment_question:
|
||||
other: "domanda commentata"
|
||||
comment_answer:
|
||||
|
@ -226,21 +236,21 @@ backend:
|
|||
#The following fields are used for interface presentation(Front-end)
|
||||
ui:
|
||||
how_to_format:
|
||||
title: How to Format
|
||||
title: Come formattare
|
||||
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:
|
||||
prev: Prev
|
||||
next: Next
|
||||
prev: Prec
|
||||
next: Successivo
|
||||
page_title:
|
||||
question: Question
|
||||
questions: Questions
|
||||
question: Domanda
|
||||
questions: Domande
|
||||
tag: Tag
|
||||
tags: Tags
|
||||
tag_wiki: tag wiki
|
||||
edit_tag: Edit Tag
|
||||
ask_a_question: Add Question
|
||||
edit_question: Edit Question
|
||||
edit_tag: Modifica Tag
|
||||
ask_a_question: Aggiungi una domanda
|
||||
edit_question: Modifica Domanda
|
||||
edit_answer: Edit Answer
|
||||
search: Search
|
||||
posts_containing: Posts containing
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
backend:
|
||||
base:
|
||||
success:
|
||||
other: "Success."
|
||||
other: "Sucesso."
|
||||
unknown:
|
||||
other: "Unknown error."
|
||||
other: "Erro desconhecido."
|
||||
request_format_error:
|
||||
other: "Request format is not valid."
|
||||
other: "Formato de solicitação não é válido."
|
||||
unauthorized_error:
|
||||
other: "Unauthorized."
|
||||
other: "Não autorizado."
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
other: "Erro no servidor de dados."
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -107,6 +107,9 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
414
i18n/zh_CN.yaml
414
i18n/zh_CN.yaml
|
@ -2,15 +2,15 @@
|
|||
backend:
|
||||
base:
|
||||
success:
|
||||
other: "成功"
|
||||
other: "成功。"
|
||||
unknown:
|
||||
other: "未知错误"
|
||||
other: "未知错误。"
|
||||
request_format_error:
|
||||
other: "请求格式错误"
|
||||
other: "请求格式错误。"
|
||||
unauthorized_error:
|
||||
other: "未登录"
|
||||
other: "未授权。"
|
||||
database_error:
|
||||
other: "数据服务异常"
|
||||
other: "数据服务器错误。"
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -23,38 +23,38 @@ backend:
|
|||
user:
|
||||
other: "默认没有特殊访问权限。"
|
||||
admin:
|
||||
other: "拥有进入网站的全部权限。"
|
||||
other: "拥有管理网站的全部权限。"
|
||||
moderator:
|
||||
other: "有权访问所有的帖子,无法进入管理员设置页面。"
|
||||
other: "拥有访问除管理员设置以外的所有权限。"
|
||||
email:
|
||||
other: "邮箱"
|
||||
password:
|
||||
other: "密码"
|
||||
email_or_password_wrong_error:
|
||||
other: "邮箱或密码错误"
|
||||
other: "邮箱和密码不匹配。"
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong:
|
||||
other: 邮箱或密码错误
|
||||
other: 邮箱和密码不匹配。
|
||||
answer:
|
||||
not_found:
|
||||
other: "答案未找到"
|
||||
other: "没有找到答案。"
|
||||
cannot_deleted:
|
||||
other: "无删除权限"
|
||||
other: "没有删除权限。"
|
||||
cannot_update:
|
||||
other: "无修改权限"
|
||||
other: "没有更新权限。"
|
||||
comment:
|
||||
edit_without_permission:
|
||||
other: "不允许编辑评论"
|
||||
other: "不允许编辑评论。"
|
||||
not_found:
|
||||
other: "评论未找到"
|
||||
other: "评论未找到。"
|
||||
email:
|
||||
duplicate:
|
||||
other: "邮箱已经存在"
|
||||
other: "邮箱已经存在。"
|
||||
need_to_be_verified:
|
||||
other: "邮箱需要验证"
|
||||
other: "邮箱需要验证。"
|
||||
verify_url_expired:
|
||||
other: "邮箱验证的网址已过期,请重新发送邮件"
|
||||
other: "邮箱验证的网址已过期,请重新发送邮件。"
|
||||
lang:
|
||||
not_found:
|
||||
other: "语言未找到"
|
||||
|
@ -106,7 +106,10 @@ backend:
|
|||
cannot_update:
|
||||
other: "没有更新标签权限。"
|
||||
cannot_set_synonym_as_itself:
|
||||
other: "你无法将当前标签的同义词设置为当前标签自己"
|
||||
other: "您不能将当前标签的同义词设置为本身。"
|
||||
smtp:
|
||||
config_from_name_cannot_be_email:
|
||||
other: "发件人名称不能是电子邮件地址。"
|
||||
theme:
|
||||
not_found:
|
||||
other: "主题未找到"
|
||||
|
@ -129,75 +132,81 @@ backend:
|
|||
other: "用户名已被使用"
|
||||
set_avatar:
|
||||
other: "头像设置错误"
|
||||
config:
|
||||
read_config_failed:
|
||||
other: "读取配置失败"
|
||||
database:
|
||||
connection_failed:
|
||||
other: "数据连接异常!"
|
||||
create_table_failed:
|
||||
other: "创建表失败"
|
||||
install:
|
||||
create_config_failed:
|
||||
other: "无法创建配置文件"
|
||||
cannot_update_your_role:
|
||||
other: "你无法修改自己的角色"
|
||||
other: "您不能修改自己的角色。"
|
||||
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:
|
||||
spam:
|
||||
name:
|
||||
other: "垃圾信息"
|
||||
desc:
|
||||
other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
|
||||
other: "这个帖子是一个广告,或是破坏性行为。它对当前的主题没有用处,也不相关。"
|
||||
rude:
|
||||
name:
|
||||
other: "粗鲁或辱骂的"
|
||||
desc:
|
||||
other: "A reasonable person would find this content inappropriate for respectful discourse."
|
||||
other: "一个有理智的人都会认为这种内容不适合进行尊重性的讨论。"
|
||||
duplicate:
|
||||
name:
|
||||
other: "重复信息"
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||
not_answer:
|
||||
name:
|
||||
other: "不是答案"
|
||||
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:
|
||||
name:
|
||||
other: "不再需要"
|
||||
desc:
|
||||
other: "This comment is outdated, conversational or not relevant to this post."
|
||||
other: "此评论已过时,对话或与此帖子无关。"
|
||||
other:
|
||||
name:
|
||||
other: "其他原因"
|
||||
desc:
|
||||
other: "This post requires staff attention for another reason not listed above."
|
||||
other: "此帖子需要工作人员关注,因为是上述所列以外的其他理由。"
|
||||
question:
|
||||
close:
|
||||
duplicate:
|
||||
name:
|
||||
other: "垃圾信息"
|
||||
desc:
|
||||
other: "This question has been asked before and already has an answer."
|
||||
other: "此问题以前就有人问过,而且已经有了答案。"
|
||||
guideline:
|
||||
name:
|
||||
other: "社区特定原因"
|
||||
desc:
|
||||
other: "This question doesn't meet a community guideline."
|
||||
other: "此问题不符合社区准则。"
|
||||
multiple:
|
||||
name:
|
||||
other: "需要细节或澄清"
|
||||
desc:
|
||||
other: "This question currently includes multiple questions in one. It should focus on one problem only."
|
||||
other: "此问题目前涵盖多个问题。它应该只集中在一个问题上。"
|
||||
other:
|
||||
name:
|
||||
other: "其他原因"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
other: "这个帖子需要上面没有列出的另一个原因。"
|
||||
operation_type:
|
||||
asked:
|
||||
other: "提问于"
|
||||
answered:
|
||||
other: "回答于"
|
||||
modified:
|
||||
other: "修改于"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -207,7 +216,7 @@ backend:
|
|||
update_answer:
|
||||
other: "更新了答案"
|
||||
accept_answer:
|
||||
other: "接受了答案"
|
||||
other: "已接受的回答"
|
||||
comment_question:
|
||||
other: "评论了问题"
|
||||
comment_answer:
|
||||
|
@ -229,15 +238,7 @@ ui:
|
|||
how_to_format:
|
||||
title: 如何设定文本格式
|
||||
desc: >-
|
||||
<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>
|
||||
<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>
|
||||
pagination:
|
||||
prev: 上一页
|
||||
next: 下一页
|
||||
|
@ -266,7 +267,7 @@ ui:
|
|||
install: Answer 安装
|
||||
upgrade: Answer 升级
|
||||
maintenance: 网站维护
|
||||
users: Users
|
||||
users: 用户
|
||||
notifications:
|
||||
title: 通知
|
||||
inbox: 收件箱
|
||||
|
@ -290,7 +291,7 @@ ui:
|
|||
class_diagram: 类图
|
||||
state_diagram: 状态图
|
||||
entity_relationship_diagram: ER 图
|
||||
user_defined_diagram: User defined diagram
|
||||
user_defined_diagram: 用户自定义图表
|
||||
gantt_chart: 甘特图
|
||||
pie_chart: 饼图
|
||||
code:
|
||||
|
@ -339,7 +340,7 @@ ui:
|
|||
only_image: 只能上传图片文件。
|
||||
max_size: 图片文件大小不能超过 4 MB。
|
||||
desc:
|
||||
label: 图片描述(可选)
|
||||
label: 描述(可选)
|
||||
tab_url: 网络图片
|
||||
form_url:
|
||||
fields:
|
||||
|
@ -410,13 +411,13 @@ ui:
|
|||
range: 不能超过 35 个字符
|
||||
slug_name:
|
||||
label: URL 固定链接
|
||||
desc: '必须由 "a-z", "0-9", "+ # - ." 组成'
|
||||
desc: '必须使用字符集 "a-z"、"0-9"、"+ # - ."'
|
||||
msg:
|
||||
empty: 不能为空
|
||||
range: 不能超过 35 个字符
|
||||
character: 包含非法字符
|
||||
desc:
|
||||
label: 标签描述(可选)
|
||||
label: 描述(可选)
|
||||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
tag_info:
|
||||
|
@ -564,8 +565,7 @@ ui:
|
|||
placeholder: 搜索
|
||||
footer:
|
||||
build_on: >-
|
||||
Built on <1> Answer </1>- the open-source software that powers Q&A
|
||||
communities<br />Made with love © 2022 Answer
|
||||
基于<1>Answer</1>--为问答社区提供动力的开源软件。<br />Made with love © {{cc}}.
|
||||
upload_img:
|
||||
name: 更改图片
|
||||
loading: 加载中...
|
||||
|
@ -616,7 +616,7 @@ ui:
|
|||
btn_cancel: 取消
|
||||
btn_update: 更新电子邮件地址
|
||||
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:
|
||||
label: 新邮箱
|
||||
msg:
|
||||
|
@ -645,8 +645,8 @@ ui:
|
|||
account: 账号
|
||||
interface: 界面
|
||||
profile:
|
||||
heading: Profile
|
||||
btn_name: Save
|
||||
heading: 个人资料
|
||||
btn_name: 保存
|
||||
display_name:
|
||||
label: 昵称
|
||||
msg: 昵称不能为空
|
||||
|
@ -664,7 +664,7 @@ ui:
|
|||
custom: 自定义
|
||||
btn_refresh: 刷新
|
||||
custom_text: 您可以上传您的图片。
|
||||
default: System
|
||||
default: 系统
|
||||
msg: 请上传头像
|
||||
bio:
|
||||
label: 关于我 (可选)
|
||||
|
@ -676,12 +676,12 @@ ui:
|
|||
label: 位置 (可选)
|
||||
placeholder: "城市, 国家"
|
||||
notification:
|
||||
heading: Notifications
|
||||
heading: 通知
|
||||
email:
|
||||
label: 邮件通知
|
||||
radio: "你的提问有新的回答,评论,和其他"
|
||||
account:
|
||||
heading: Account
|
||||
heading: 账号
|
||||
change_email_btn: 更改邮箱
|
||||
change_pass_btn: 更改密码
|
||||
change_email_info: >-
|
||||
|
@ -701,7 +701,7 @@ ui:
|
|||
pass_confirm:
|
||||
label: 确认新密码
|
||||
interface:
|
||||
heading: Interface
|
||||
heading: 界面
|
||||
lang:
|
||||
label: 界面语言
|
||||
text: 设置用户界面语言,在刷新页面后生效。
|
||||
|
@ -709,7 +709,7 @@ ui:
|
|||
update: 更新成功
|
||||
update_password: 更改密码成功。
|
||||
flag_success: 感谢您的标记,我们会尽快处理。
|
||||
forbidden_operate_self: Forbidden to operate on yourself
|
||||
forbidden_operate_self: 禁止自己操作
|
||||
review: 您的修订将在审核通过后显示。
|
||||
related_question:
|
||||
title: 相关问题
|
||||
|
@ -735,16 +735,16 @@ ui:
|
|||
write_answer:
|
||||
title: 你的回答
|
||||
btn_name: 提交你的回答
|
||||
add_another_answer: Add another answer
|
||||
add_another_answer: 添加另一个答案
|
||||
confirm_title: 继续回答
|
||||
continue: 继续
|
||||
confirm_info: >-
|
||||
<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
|
||||
empty: 回答内容不能为空。
|
||||
reopen:
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
title: 重新打开这个帖子
|
||||
content: 确定要重新打开吗?
|
||||
success: 这个帖子已被重新打开
|
||||
delete:
|
||||
title: 删除
|
||||
question: >-
|
||||
|
@ -808,6 +808,11 @@ ui:
|
|||
confirm_new_email: 你的电子邮箱已更新
|
||||
confirm_new_email_invalid: >-
|
||||
抱歉,此验证链接已失效。也许是你的邮箱已经成功更改了?
|
||||
unsubscribe:
|
||||
page_title: 退订
|
||||
success_title: 取消订阅成功
|
||||
success_desc: 您已成功地从此订阅者列表中移除,并且将不会再收到我们的任何电子邮件。
|
||||
link: 更改设置
|
||||
question:
|
||||
following_tags: 已关注的标签
|
||||
edit: 编辑
|
||||
|
@ -870,9 +875,9 @@ ui:
|
|||
done: 完成
|
||||
config_yaml_error: 无法创建配置文件
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
label: 请选择一种语言
|
||||
db_type:
|
||||
label: Database Engine
|
||||
label: 数据库引擎
|
||||
db_username:
|
||||
label: 用户名
|
||||
placeholder: root
|
||||
|
@ -887,17 +892,17 @@ ui:
|
|||
msg: 数据库地址不能为空
|
||||
db_name:
|
||||
label: 数据库名
|
||||
placeholder: answer
|
||||
placeholder: 回答
|
||||
msg: 数据库名称不能为空。
|
||||
db_file:
|
||||
label: Database File
|
||||
label: 数据库文件
|
||||
placeholder: /data/answer.db
|
||||
msg: 数据库文件不能为空。
|
||||
config_yaml:
|
||||
title: 创建 config.yaml
|
||||
label: 已创建 config.yaml 文件。
|
||||
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: "完成后,点击“下一步”按钮。"
|
||||
site_information: 站点信息
|
||||
admin_account: 管理员账户
|
||||
|
@ -906,52 +911,52 @@ ui:
|
|||
msg: 站点名称不能为空。
|
||||
site_url:
|
||||
label: 站点地址(URL)
|
||||
text: The address of your site.
|
||||
text: 此网站的地址。
|
||||
msg:
|
||||
empty: 站点URL不能为空。
|
||||
incorrect: 站点URL格式不正确。
|
||||
contact_email:
|
||||
label: 联系邮箱
|
||||
text: Email address of key contact responsible for this site.
|
||||
text: 负责本网站的主要联系人的电子邮件地址。
|
||||
msg:
|
||||
empty: Contact Email cannot be empty.
|
||||
incorrect: Contact Email incorrect format.
|
||||
empty: 联系人邮箱地址不能为空。
|
||||
incorrect: 联系人邮箱地址不正确。
|
||||
admin_name:
|
||||
label: Name
|
||||
msg: Name cannot be empty.
|
||||
label: 昵称
|
||||
msg: 昵称不能为空。
|
||||
admin_password:
|
||||
label: Password
|
||||
label: 密码
|
||||
text: >-
|
||||
You will need this password to log in. Please store it in a secure location.
|
||||
msg: Password cannot be empty.
|
||||
您需要此密码才能登录。请将其存储在一个安全的位置。
|
||||
msg: 密码不能为空。
|
||||
admin_email:
|
||||
label: Email
|
||||
text: You will need this email to log in.
|
||||
label: 邮箱
|
||||
text: 您需要此电子邮件才能登录。
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
incorrect: Email incorrect format.
|
||||
ready_title: Your Answer is Ready!
|
||||
empty: 邮箱不能为空。
|
||||
incorrect: 邮箱格式不正确。
|
||||
ready_title: 你的答案已经准备好了!
|
||||
ready_desc: >-
|
||||
If you ever feel like changing more settings, visit <1>admin section</1>; find it in the site menu.
|
||||
good_luck: "Have fun, and good luck!"
|
||||
warn_title: Warning
|
||||
如果你想改变更多的设置,请访问<1>管理员部分</1>;在网站菜单中找到它。
|
||||
good_luck: "玩得愉快,祝您好运!"
|
||||
warn_title: 警告
|
||||
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.
|
||||
install_now: You may try <1>installing now</1>.
|
||||
文件<1>config.yaml</1>已存在。如果您需要重置此文件中的任何配置项,请先删除它。
|
||||
install_now: 您可以尝试<1>现在安装</1>。
|
||||
installed: 已安裝
|
||||
installed_desc: >-
|
||||
You appear to have already installed. To reinstall please clear your old database tables first.
|
||||
您似乎已经安装过了。要重新安装,请先清除旧的数据库表。
|
||||
db_failed: 数据连接异常!
|
||||
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:
|
||||
desc: 页面不存在
|
||||
desc: "很抱歉,此页面不存在。"
|
||||
back_home: 回到主页
|
||||
page_50X:
|
||||
desc: 服务器遇到了一个错误,无法完成你的请求。
|
||||
back_home: 回到主页
|
||||
page_maintenance:
|
||||
desc: "We are under maintenance, we’ll be back soon."
|
||||
desc: "我们正在进行维护,我们将很快回来。"
|
||||
nav_menus:
|
||||
dashboard: 后台管理
|
||||
contents: 内容管理
|
||||
|
@ -969,17 +974,17 @@ ui:
|
|||
tos: 服务条款
|
||||
privacy: 隐私政策
|
||||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
customize: 自定义
|
||||
themes: 主题
|
||||
css-html: CSS/HTML
|
||||
login: Login
|
||||
login: 登录
|
||||
website_welcome: 欢迎来到 {{site_name}}
|
||||
admin:
|
||||
admin_header:
|
||||
title: 后台管理
|
||||
dashboard:
|
||||
title: 后台管理
|
||||
welcome: 欢迎来到 {{site_name}} 后台管理!
|
||||
welcome: 欢迎来到 Answer 后台管理!
|
||||
site_statistics: 站点统计
|
||||
questions: "问题:"
|
||||
answers: "回答:"
|
||||
|
@ -999,6 +1004,7 @@ ui:
|
|||
answer_links: 回答链接
|
||||
documents: 文档
|
||||
feedback: 用户反馈
|
||||
support: 帮助
|
||||
review: 审查
|
||||
config: 配置
|
||||
update_to: 更新到
|
||||
|
@ -1023,11 +1029,11 @@ ui:
|
|||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
normal_name: 正常
|
||||
normal_desc: 正常状态的用户可以提问和回答。
|
||||
normal_desc: 普通用户可以提问和回答。
|
||||
suspended_name: 封禁
|
||||
suspended_desc: 被封禁的用户将无法登录。
|
||||
deleted_name: 删除
|
||||
deleted_desc: 删除用户的个人信息,认证等等。
|
||||
deleted_desc: "删除个人资料和身份验证关联。"
|
||||
inactive_name: 不活跃
|
||||
inactive_desc: 不活跃的用户必须重新验证邮箱。
|
||||
confirm_title: 删除此用户
|
||||
|
@ -1038,11 +1044,11 @@ ui:
|
|||
status_modal:
|
||||
title: "更改 {{ type }} 状态为..."
|
||||
normal_name: 正常
|
||||
normal_desc: 所有用户都可以访问
|
||||
normal_desc: 所有用户都可以访问的普通帖子。
|
||||
closed_name: 关闭
|
||||
closed_desc: 不能回答,但仍然可以编辑、投票和评论。
|
||||
closed_desc: "关闭的问题不能回答,但仍然可以编辑、投票和评论。"
|
||||
deleted_name: 删除
|
||||
deleted_desc: 所有获得/损失的声望将会恢复。
|
||||
deleted_desc: 获得和丧失的所有信誉积分将被恢复。
|
||||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
btn_next: 下一步
|
||||
|
@ -1077,32 +1083,32 @@ ui:
|
|||
change_status: 更改状态
|
||||
change_role: 更改角色
|
||||
show_logs: 显示日志
|
||||
add_user: Add user
|
||||
add_user: 添加用户
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
title: 设置新密码
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: 密码
|
||||
text: 用户将被注销,需要再次登录。
|
||||
msg: 密码的长度必须是8-32个字符。
|
||||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
user_modal:
|
||||
title: Add new user
|
||||
title: 添加新用户
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: display_name must be at 4 - 30 characters in length.
|
||||
label: 昵称
|
||||
msg: 昵称的长度必须是4-30个字符。
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
label: 邮箱
|
||||
msg: 电子邮箱无效。
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8 - 32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: 密码
|
||||
msg: 密码的长度必须是8-32个字符。
|
||||
btn_cancel: 取消
|
||||
btn_submit: 提交
|
||||
questions:
|
||||
page_title: 问题
|
||||
normal: 正常
|
||||
|
@ -1116,7 +1122,7 @@ ui:
|
|||
action: 操作
|
||||
change: 更改
|
||||
filter:
|
||||
placeholder: "Filter by title, question:id"
|
||||
placeholder: "按标题过滤,问题:id"
|
||||
answers:
|
||||
page_title: 回答
|
||||
normal: 正常
|
||||
|
@ -1128,21 +1134,31 @@ ui:
|
|||
action: 操作
|
||||
change: 更改
|
||||
filter:
|
||||
placeholder: "Filter by title, answer:id"
|
||||
placeholder: "按标题筛选,答案:id"
|
||||
general:
|
||||
page_title: 一般
|
||||
name:
|
||||
label: 站点名称
|
||||
msg: 不能为空
|
||||
text: 站点的名称,作为站点的标题(HTML 的 title 标签)。
|
||||
text: "站点的名称,作为站点的标题(HTML 的 title 标签)。"
|
||||
site_url:
|
||||
label: 网站网址
|
||||
msg: 网站网址不能为空。
|
||||
validate: 请输入一个有效的 URL。
|
||||
text: 此网站的地址。
|
||||
short_desc:
|
||||
label: 简短的站点标语 (可选)
|
||||
msg: 不能为空
|
||||
text: 简短的标语,作为网站主页的标题(HTML 的 title 标签)。
|
||||
label: 简短网站描述(可选)
|
||||
msg: 简短网站描述不能为空。
|
||||
text: "简短的标语,作为网站主页的标题(Html 的 title 标签)。"
|
||||
desc:
|
||||
label: 网站描述 (可选)
|
||||
msg: 不能为空
|
||||
text: 使用一句话描述本站,作为网站的描述(HTML 的 meta 标签)。
|
||||
msg: 网站描述不能为空。
|
||||
text: "使用一句话描述本站,作为网站的描述(Html 的 meta 标签)。"
|
||||
contact_email:
|
||||
label: 联系人邮箱
|
||||
msg: 联系人邮箱不能为空。
|
||||
validate: 联系人邮箱无效。
|
||||
text: 负责本网站的主要联系人的电子邮件地址。
|
||||
interface:
|
||||
page_title: 界面
|
||||
logo:
|
||||
|
@ -1158,9 +1174,9 @@ ui:
|
|||
msg: 不能为空
|
||||
text: 设置用户界面语言,在刷新页面后生效。
|
||||
time_zone:
|
||||
label: Timezone
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
label: 时区
|
||||
msg: 时区不能为空。
|
||||
text: 选择一个与您相同时区的城市。
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
|
@ -1196,97 +1212,97 @@ ui:
|
|||
text: 提供用于接收测试邮件的邮箱地址。
|
||||
msg: 地址无效
|
||||
smtp_authentication:
|
||||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
label: 启用身份验证
|
||||
title: SMTP身份验证
|
||||
msg: 不能为空
|
||||
"yes": "是"
|
||||
"no": "否"
|
||||
branding:
|
||||
page_title: Branding
|
||||
page_title: 品牌
|
||||
logo:
|
||||
label: Logo
|
||||
msg: Logo cannot be empty.
|
||||
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.
|
||||
label: 图标
|
||||
msg: 图标不能为空。
|
||||
text: 在你的网站左上方的Logo图标。使用一个高度为56,长宽比大于3:1的宽长方形图像。如果留空,将显示网站标题文本。
|
||||
mobile_logo:
|
||||
label: Mobile Logo (optional)
|
||||
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.
|
||||
label: 移动端图标(可选)
|
||||
text: 在你的网站的移动版上使用的标志。使用一个高度为56的宽矩形图像。如果留空,将使用 "Logo"设置中的图像。
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
label: 方形图标
|
||||
msg: 方形图标不能为空。
|
||||
text: 用作元数据图标的基础的图像。最好是大于512x512。
|
||||
favicon:
|
||||
label: Favicon (optional)
|
||||
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.
|
||||
label: 收藏夹图标(可选)
|
||||
text: 网站的图标。要在 CDN 正常工作,它必须是 png。 将调整大小到32x32。如果留空,将使用“方形图标”。
|
||||
legal:
|
||||
page_title: Legal
|
||||
page_title: 法律条款
|
||||
terms_of_service:
|
||||
label: Terms of Service
|
||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: 服务条款
|
||||
text: "您可以在此添加服务内容的条款。如果您已经在别处托管了文档,请在这里提供完整的URL。"
|
||||
privacy_policy:
|
||||
label: Privacy Policy
|
||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: 隐私条款
|
||||
text: "您可以在此添加隐私政策内容。如果您已经在别处托管了文档,请在这里提供完整的URL。"
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: 编辑
|
||||
recommend_tags:
|
||||
label: Recommend Tags
|
||||
text: "Please input tag slug above, one tag per line."
|
||||
label: 推荐标签
|
||||
text: "请输入以上标签,每行一个标签。"
|
||||
required_tag:
|
||||
title: Required Tag
|
||||
label: Set recommend tag as required
|
||||
text: "Every new question must have at least one recommend tag."
|
||||
title: 必需的标签
|
||||
label: 根据需要设置推荐标签
|
||||
text: "每个新问题必须至少有一个推荐标签。"
|
||||
reserved_tags:
|
||||
label: Reserved Tags
|
||||
text: "Reserved tags can only be added to a post by moderator."
|
||||
label: 保留标签
|
||||
text: "保留的标签只能由版主添加到一个帖子中。"
|
||||
seo:
|
||||
page_title: SEO
|
||||
page_title: 搜索引擎优化
|
||||
permalink:
|
||||
label: Permalink
|
||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
||||
label: 固定链接
|
||||
text: 自定义URL结构可以提高可用性,以及你的链接的向前兼容性。
|
||||
robots:
|
||||
label: robots.txt
|
||||
text: This will permanently override any related site settings.
|
||||
text: 这将永久覆盖任何相关的网站设置。
|
||||
themes:
|
||||
page_title: Themes
|
||||
page_title: 主题
|
||||
themes:
|
||||
label: Themes
|
||||
text: Select an existing theme.
|
||||
label: 主题
|
||||
text: 选择一个现有主题。
|
||||
navbar_style:
|
||||
label: Navbar Style
|
||||
text: Select an existing theme.
|
||||
label: 导航栏样式
|
||||
text: 选择一个现有主题。
|
||||
primary_color:
|
||||
label: Primary Color
|
||||
text: Modify the colors used by your themes
|
||||
label: 主色调
|
||||
text: 修改您主题使用的颜色
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
page_title: CSS 与 HTML
|
||||
custom_css:
|
||||
label: Custom CSS
|
||||
text: This will insert as <link>
|
||||
label: 自定义CSS
|
||||
text: 这将在 <link> 之前插入
|
||||
head:
|
||||
label: Head
|
||||
text: This will insert before </head>
|
||||
label: 头部
|
||||
text: 这将在 </head> 之前插入
|
||||
header:
|
||||
label: Header
|
||||
text: This will insert after <body>
|
||||
label: 标题
|
||||
text: 这将在 <body> 之前插入
|
||||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
label: 页脚
|
||||
text: 这将在 </html> 之前插入
|
||||
login:
|
||||
page_title: Login
|
||||
page_title: 登录
|
||||
membership:
|
||||
title: Membership
|
||||
label: Allow new registrations
|
||||
text: Turn off to prevent anyone from creating a new account.
|
||||
title: 会员
|
||||
label: 允许新注册
|
||||
text: 关闭以防止任何人创建新帐户。
|
||||
private:
|
||||
title: Private
|
||||
label: Login required
|
||||
text: Only logged in users can access this community.
|
||||
title: 非公开的
|
||||
label: 需要登录
|
||||
text: 只有登录用户才能访问这个社区。
|
||||
form:
|
||||
empty: cannot be empty
|
||||
invalid: is invalid
|
||||
btn_submit: Save
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
empty: 不能为空
|
||||
invalid: 是无效的
|
||||
btn_submit: 保存
|
||||
not_found_props: "所需属性 {{ key }} 未找到。"
|
||||
page_review:
|
||||
review: Review
|
||||
review: 评论
|
||||
proposed: 提案
|
||||
question_edit: 问题编辑
|
||||
answer_edit: 回答编辑
|
||||
|
@ -1324,11 +1340,11 @@ ui:
|
|||
comment: 评论
|
||||
no_data: "空空如也"
|
||||
users:
|
||||
title: Users
|
||||
users_with_the_most_reputation: Users with the highest reputation scores
|
||||
users_with_the_most_vote: Users who voted the most
|
||||
staffs: Our community staff
|
||||
reputation: reputation
|
||||
votes: votes
|
||||
title: 用户
|
||||
users_with_the_most_reputation: 信誉积分最高的用户
|
||||
users_with_the_most_vote: 投票最多的用户
|
||||
staffs: 我们的社区工作人员
|
||||
reputation: 声望值
|
||||
votes: 投票
|
||||
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@ backend:
|
|||
success:
|
||||
other: "成功!"
|
||||
unknown:
|
||||
other: "Unknown error."
|
||||
other: "未知的錯誤。"
|
||||
request_format_error:
|
||||
other: "Request format is not valid."
|
||||
other: "請求的格式無效。"
|
||||
unauthorized_error:
|
||||
other: "Unauthorized."
|
||||
other: "未授權。"
|
||||
database_error:
|
||||
other: "Data server error."
|
||||
role:
|
||||
|
@ -29,13 +29,13 @@ backend:
|
|||
email:
|
||||
other: "Email"
|
||||
password:
|
||||
other: "Password"
|
||||
other: "密碼"
|
||||
email_or_password_wrong_error:
|
||||
other: "Email and password do not match."
|
||||
other: "電子郵箱和密碼不匹配。"
|
||||
error:
|
||||
admin:
|
||||
email_or_password_wrong:
|
||||
other: Email and password do not match.
|
||||
other: 電子郵箱和密碼不匹配。
|
||||
answer:
|
||||
not_found:
|
||||
other: "Answer do not found."
|
||||
|
@ -70,22 +70,22 @@ backend:
|
|||
not_found:
|
||||
other: "Object not found."
|
||||
verification_failed:
|
||||
other: "Verification failed."
|
||||
other: "驗證失敗。"
|
||||
email_or_password_incorrect:
|
||||
other: "Email and password do not match."
|
||||
other: "電子郵箱和密碼不匹配。"
|
||||
old_password_verification_failed:
|
||||
other: "The old password verification failed"
|
||||
other: "舊密碼驗證失敗"
|
||||
new_password_same_as_previous_setting:
|
||||
other: "The new password is the same as the previous one."
|
||||
other: "新密碼與先前的一樣。"
|
||||
question:
|
||||
not_found:
|
||||
other: "Question not found."
|
||||
other: "找不到問題。"
|
||||
cannot_deleted:
|
||||
other: "No permission to delete."
|
||||
other: "沒有刪除的權限。"
|
||||
cannot_close:
|
||||
other: "No permission to close."
|
||||
other: "沒有關閉的權限。"
|
||||
cannot_update:
|
||||
other: "No permission to update."
|
||||
other: "沒有更新的權限。"
|
||||
rank:
|
||||
fail_to_meet_the_condition:
|
||||
other: "Rank fail to meet the condition."
|
||||
|
@ -107,9 +107,12 @@ backend:
|
|||
other: "No permission to update."
|
||||
cannot_set_synonym_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:
|
||||
not_found:
|
||||
other: "Theme not found."
|
||||
other: "未找到主題。"
|
||||
revision:
|
||||
review_underway:
|
||||
other: "Can't edit currently, there is a version in the review queue."
|
||||
|
@ -129,21 +132,21 @@ backend:
|
|||
other: "Username is already in use."
|
||||
set_avatar:
|
||||
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:
|
||||
other: "You cannot modify your role."
|
||||
not_allowed_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:
|
||||
spam:
|
||||
name:
|
||||
|
@ -197,6 +200,13 @@ backend:
|
|||
other: "something else"
|
||||
desc:
|
||||
other: "This post requires another reason not listed above."
|
||||
operation_type:
|
||||
asked:
|
||||
other: "asked"
|
||||
answered:
|
||||
other: "answered"
|
||||
modified:
|
||||
other: "modified"
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -801,6 +811,11 @@ ui:
|
|||
confirm_new_email: Your email has been updated.
|
||||
confirm_new_email_invalid: >-
|
||||
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:
|
||||
following_tags: Following Tags
|
||||
edit: Edit
|
||||
|
@ -991,6 +1006,7 @@ ui:
|
|||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
|
|
|
@ -21,7 +21,7 @@ type RespBody struct {
|
|||
// TrMsg translate the reason cause as a message
|
||||
func (r *RespBody) TrMsg(lang i18n.Language) *RespBody {
|
||||
if len(r.Message) == 0 {
|
||||
r.Message = translator.GlobalTrans.Tr(lang, r.Reason)
|
||||
r.Message = translator.Tr(lang, r.Reason)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -106,3 +106,12 @@ func CheckLanguageIsValid(lang string) bool {
|
|||
}
|
||||
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) {
|
||||
defer func() {
|
||||
if len(res) > 0 {
|
||||
res = translator.GlobalTrans.Tr(la, res)
|
||||
res = translator.Tr(la, res)
|
||||
}
|
||||
}()
|
||||
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
|
||||
}
|
||||
for _, errField := range errFields {
|
||||
errField.ErrorMsg = translator.GlobalTrans.Tr(m.Lang, errField.ErrorMsg)
|
||||
errField.ErrorMsg = translator.Tr(m.Lang, errField.ErrorMsg)
|
||||
}
|
||||
return errFields, err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ package controller
|
|||
import (
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/answerdev/answer/internal/service/uploader"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
@ -63,3 +65,21 @@ func (uc *UploadController) UploadFile(ctx *gin.Context) {
|
|||
}
|
||||
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 {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -124,7 +124,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
|
|||
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeLogin, ctx.ClientIP())
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -151,7 +151,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
|
|||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -236,7 +236,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
|||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -245,7 +245,8 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
|
|||
resp, errFields, err := uc.userService.UserRegisterByEmail(ctx, req)
|
||||
if len(errFields) > 0 {
|
||||
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)
|
||||
} else {
|
||||
|
@ -312,7 +313,7 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
|
|||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -350,7 +351,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
|||
if !oldPassVerification {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -358,7 +359,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
|
|||
if req.OldPass == req.Pass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
@ -386,7 +387,7 @@ func (uc *UserController) UserUpdateInfo(ctx *gin.Context) {
|
|||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
errFields, err := uc.userService.UpdateInfo(ctx, req)
|
||||
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)
|
||||
}
|
||||
|
@ -491,7 +492,7 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
|
|||
if !captchaPass {
|
||||
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
return
|
||||
|
|
|
@ -77,7 +77,7 @@ type InitBaseInfoReq struct {
|
|||
SiteName string `validate:"required,gt=0,lte=30" json:"site_name"`
|
||||
SiteURL string `validate:"required,gt=0,lte=512,url" json:"site_url"`
|
||||
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"`
|
||||
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) {
|
||||
sum := &entity.ActivityRankSum{}
|
||||
_, err := ar.data.DB.Table(entity.Activity{}.TableName()).
|
||||
Select("sum(rank) as rank").
|
||||
Select("sum(`rank`) as `rank`").
|
||||
Where("user_id =?", userID).
|
||||
And("object_id = ?", objectID).
|
||||
And("cancelled =0").
|
||||
|
@ -113,7 +113,7 @@ func (ar *ActivityRepo) AddActivity(ctx context.Context, activity *entity.Activi
|
|||
func (ar *ActivityRepo) GetUsersWhoHasGainedTheMostReputation(
|
||||
ctx context.Context, startTime, endTime time.Time, limit int) (rankStat []*entity.ActivityUserRankStat, err error) {
|
||||
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("created_at >= ?", startTime)
|
||||
session.Where("created_at <= ?", endTime)
|
||||
|
|
|
@ -60,7 +60,7 @@ func (tr *tagCommonRepo) GetTagListByName(ctx context.Context, name string, hasR
|
|||
cond := &entity.Tag{}
|
||||
session := tr.data.DB.Where("")
|
||||
if name != "" {
|
||||
session.Where("slug_name LIKE ?", name+"%")
|
||||
session.Where("slug_name LIKE ? or display_name LIKE ?", name+"%", name+"%")
|
||||
} else {
|
||||
session.UseBool("recommend")
|
||||
cond.Recommend = true
|
||||
|
|
|
@ -217,6 +217,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
|
||||
// upload file
|
||||
r.POST("/file", a.uploadController.UploadFile)
|
||||
r.POST("/post/render", a.uploadController.PostRender)
|
||||
|
||||
// activity
|
||||
r.GET("/activity/timeline", a.activityController.GetObjectTimeline)
|
||||
|
|
|
@ -246,9 +246,9 @@ type QuestionPageReq struct {
|
|||
}
|
||||
|
||||
const (
|
||||
QuestionPageRespOperationTypeAsked = "question.operation_type.asked"
|
||||
QuestionPageRespOperationTypeAnswered = "question.operation_type.answered"
|
||||
QuestionPageRespOperationTypeModified = "question.operation_type.modified"
|
||||
QuestionPageRespOperationTypeAsked = "asked"
|
||||
QuestionPageRespOperationTypeAnswered = "answered"
|
||||
QuestionPageRespOperationTypeModified = "modified"
|
||||
)
|
||||
|
||||
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
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ type SiteThemeResp struct {
|
|||
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
|
||||
la := handler.GetLangByCtx(ctx)
|
||||
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 != option.Value {
|
||||
option.Label = tr
|
||||
|
|
|
@ -228,7 +228,7 @@ type UserEmailLogin struct {
|
|||
// UserRegisterReq user register request
|
||||
type UserRegisterReq struct {
|
||||
// name
|
||||
Name string `validate:"required,gt=4,lte=30" json:"name"`
|
||||
Name string `validate:"required,gt=3,lte=30" json:"name"`
|
||||
// email
|
||||
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" `
|
||||
// password
|
||||
|
@ -277,7 +277,7 @@ type UpdateInfoRequest struct {
|
|||
// display_name
|
||||
DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"`
|
||||
// username
|
||||
Username string `validate:"omitempty,gt=0,lte=30" json:"username"`
|
||||
Username string `validate:"omitempty,gt=3,lte=30" json:"username"`
|
||||
// avatar
|
||||
Avatar AvatarInfo `json:"avatar"`
|
||||
// bio
|
||||
|
@ -300,12 +300,13 @@ type AvatarInfo struct {
|
|||
|
||||
func (u *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, err error) {
|
||||
if len(u.Username) > 0 {
|
||||
errFields := make([]*validator.FormErrorField, 0)
|
||||
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
|
||||
match := re.MatchString(u.Username)
|
||||
if !match {
|
||||
errField := &validator.FormErrorField{
|
||||
ErrorField: "username",
|
||||
ErrorMsg: err.Error(),
|
||||
ErrorMsg: reason.UsernameInvalid,
|
||||
}
|
||||
errFields = append(errFields, errField)
|
||||
return errFields, errors.BadRequest(reason.UsernameInvalid)
|
||||
|
|
|
@ -134,7 +134,7 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
|
|||
continue
|
||||
}
|
||||
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.UpdateTime = notificationInfo.UpdatedAt.Unix()
|
||||
if notificationInfo.IsRead == schema.NotificationRead {
|
||||
|
|
|
@ -6,9 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"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/translator"
|
||||
"github.com/answerdev/answer/internal/service/activity_common"
|
||||
"github.com/answerdev/answer/internal/service/activity_queue"
|
||||
"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(
|
||||
ctx context.Context, questionList []*entity.Question, loginUserID string, orderCond string) (
|
||||
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)
|
||||
|
||||
questionIDs := make([]string, 0)
|
||||
userIDs := make([]string, 0)
|
||||
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 orderCond == schema.QuestionOrderCondNewest || (!haveEdited && !haveAnswered) {
|
||||
t.OperationType = askedOp
|
||||
t.OperationType = schema.QuestionPageRespOperationTypeAsked
|
||||
t.OperatedAt = questionInfo.CreatedAt.Unix()
|
||||
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.UserID}
|
||||
} else {
|
||||
// if no one
|
||||
if haveEdited {
|
||||
t.OperationType = modifiedOp
|
||||
t.OperationType = schema.QuestionPageRespOperationTypeModified
|
||||
t.OperatedAt = questionInfo.UpdatedAt.Unix()
|
||||
t.Operator = &schema.QuestionPageRespOperator{ID: questionInfo.LastEditUserID}
|
||||
}
|
||||
|
||||
if haveAnswered {
|
||||
if t.LastAnsweredAt.Unix() > t.OperatedAt {
|
||||
t.OperationType = answeredOp
|
||||
t.OperationType = schema.QuestionPageRespOperationTypeAnswered
|
||||
t.OperatedAt = t.LastAnsweredAt.Unix()
|
||||
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()
|
||||
}
|
||||
for _, t := range resp {
|
||||
t.Name = translator.GlobalTrans.Tr(lang, t.Name)
|
||||
t.Description = translator.GlobalTrans.Tr(lang, t.Description)
|
||||
t.Name = translator.Tr(lang, t.Name)
|
||||
t.Description = translator.Tr(lang, t.Description)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que
|
|||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
|
@ -176,7 +176,7 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que
|
|||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
|
@ -213,7 +213,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
|||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.TagNotFound),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
|
@ -226,7 +226,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
|||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
|
@ -539,7 +539,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
|
|||
errorlist := make([]*validator.FormErrorField, 0)
|
||||
errorlist = append(errorlist, &validator.FormErrorField{
|
||||
ErrorField: "tags",
|
||||
ErrorMsg: translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.RecommendTagEnter),
|
||||
})
|
||||
err = errors.BadRequest(reason.RecommendTagEnter)
|
||||
return errorlist, err
|
||||
|
|
|
@ -74,8 +74,8 @@ func (rs *ReportService) GetReportTypeList(ctx context.Context, lang i18n.Langua
|
|||
err = errors.BadRequest(reason.UnknownError)
|
||||
}
|
||||
for _, t := range resp {
|
||||
t.Name = translator.GlobalTrans.Tr(lang, t.Name)
|
||||
t.Description = translator.GlobalTrans.Tr(lang, t.Description)
|
||||
t.Name = translator.Tr(lang, t.Name)
|
||||
t.Description = translator.Tr(lang, t.Description)
|
||||
}
|
||||
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) {
|
||||
switch role.Name {
|
||||
case roleUserName:
|
||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameUser)
|
||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionUser)
|
||||
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameUser)
|
||||
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionUser)
|
||||
case roleAdminName:
|
||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameAdmin)
|
||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionAdmin)
|
||||
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameAdmin)
|
||||
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionAdmin)
|
||||
case roleModeratorName:
|
||||
role.Name = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleNameModerator)
|
||||
role.Description = translator.GlobalTrans.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionModerator)
|
||||
role.Name = translator.Tr(handler.GetLangByCtx(ctx), trRoleNameModerator)
|
||||
role.Description = translator.Tr(handler.GetLangByCtx(ctx), trRoleDescriptionModerator)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -496,7 +496,7 @@ func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *
|
|||
thisObjTagNameList := make([]string, 0)
|
||||
thisObjTagIDList := make([]string, 0)
|
||||
for _, t := range objectTagData.Tags {
|
||||
t.SlugName = strings.ToLower(t.SlugName)
|
||||
// t.SlugName = strings.ToLower(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)
|
||||
for _, tag := range tagListInDb {
|
||||
tagInDbMapping[tag.SlugName] = tag
|
||||
tagInDbMapping[strings.ToLower(tag.SlugName)] = tag
|
||||
thisObjTagIDList = append(thisObjTagIDList, tag.ID)
|
||||
}
|
||||
|
||||
addTagList := make([]*entity.Tag, 0)
|
||||
for _, tag := range objectTagData.Tags {
|
||||
_, ok := tagInDbMapping[tag.SlugName]
|
||||
_, ok := tagInDbMapping[strings.ToLower(tag.SlugName)]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"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/role"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
|
@ -38,6 +39,7 @@ type UserAdminService struct {
|
|||
userRoleRelService *role.UserRoleRelService
|
||||
authService *auth.AuthService
|
||||
userCommonService *usercommon.UserCommon
|
||||
userActivity activity.UserActiveActivityRepo
|
||||
}
|
||||
|
||||
// NewUserAdminService new user admin service
|
||||
|
@ -46,12 +48,14 @@ func NewUserAdminService(
|
|||
userRoleRelService *role.UserRoleRelService,
|
||||
authService *auth.AuthService,
|
||||
userCommonService *usercommon.UserCommon,
|
||||
userActivity activity.UserActiveActivityRepo,
|
||||
) *UserAdminService {
|
||||
return &UserAdminService{
|
||||
userRepo: userRepo,
|
||||
userRoleRelService: userRoleRelService,
|
||||
authService: authService,
|
||||
userCommonService: userCommonService,
|
||||
userActivity: userActivity,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +87,17 @@ func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.Up
|
|||
userInfo.Status = entity.UserStatusAvailable
|
||||
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
|
||||
|
|
|
@ -510,7 +510,7 @@ func (us *UserService) UserChangeEmailSendCode(ctx context.Context, req *schema.
|
|||
if exist {
|
||||
resp = append([]*validator.FormErrorField{}, &validator.FormErrorField{
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -3,22 +3,26 @@ package converter
|
|||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"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
|
||||
func Markdown2HTML(source string) string {
|
||||
mdConverter := goldmark.New(
|
||||
goldmark.WithExtensions(extension.GFM),
|
||||
goldmark.WithExtensions(&DangerousHTMLFilterExtension{}, extension.GFM),
|
||||
goldmark.WithParserOptions(
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithHardWraps(),
|
||||
goldmarkHTML.WithHardWraps(),
|
||||
),
|
||||
)
|
||||
var buf bytes.Buffer
|
||||
|
@ -28,3 +32,56 @@ func Markdown2HTML(source string) 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",
|
||||
"dayjs": "^1.11.5",
|
||||
"diff": "^5.1.0",
|
||||
"dompurify": "^2.4.3",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"html-react-parser": "^3.0.8",
|
||||
"i18next": "^21.9.0",
|
||||
"katex": "^0.16.2",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -51,6 +53,7 @@
|
|||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@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 name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -58,7 +58,7 @@ export interface QuestionParams {
|
|||
title: string;
|
||||
url_title?: string;
|
||||
content: string;
|
||||
html: string;
|
||||
html?: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ export interface AnswerItem {
|
|||
|
||||
export interface PostAnswerReq {
|
||||
content: string;
|
||||
html: string;
|
||||
html?: string;
|
||||
question_id: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState, useEffect, memo } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
@ -7,7 +7,7 @@ import classNames from 'classnames';
|
|||
import { TextArea, Mentions } from '@/components';
|
||||
import { usePageUsers } from '@/hooks';
|
||||
|
||||
const Form = ({
|
||||
const Index = ({
|
||||
className = '',
|
||||
value: initialValue = '',
|
||||
onSendReply,
|
||||
|
@ -18,7 +18,7 @@ const Form = ({
|
|||
const [value, setValue] = useState('');
|
||||
const pageUsers = usePageUsers();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||
|
||||
const [validationErrorMsg, setValidationErrorMsg] = useState('');
|
||||
useEffect(() => {
|
||||
if (!initialValue) {
|
||||
return;
|
||||
|
@ -32,6 +32,13 @@ const Form = ({
|
|||
const handleSelected = (val) => {
|
||||
setValue(val);
|
||||
};
|
||||
const handleSendReply = () => {
|
||||
onSendReply(value).catch((ex) => {
|
||||
if (ex.isError) {
|
||||
setValidationErrorMsg(ex.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
|
@ -39,17 +46,27 @@ const Form = ({
|
|||
className,
|
||||
)}>
|
||||
<div>
|
||||
<Mentions pageUsers={pageUsers.getUsers()} onSelected={handleSelected}>
|
||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||
</Mentions>
|
||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||
<div
|
||||
className={classNames('custom-form-control', {
|
||||
'is-invalid': validationErrorMsg,
|
||||
})}>
|
||||
<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>
|
||||
{type === 'edit' ? (
|
||||
<div className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-nowrap "
|
||||
onClick={() => onSendReply(value)}>
|
||||
onClick={() => handleSendReply()}>
|
||||
{t('btn_save_edits')}
|
||||
</Button>
|
||||
<Button
|
||||
|
@ -64,7 +81,7 @@ const Form = ({
|
|||
<Button
|
||||
size="sm"
|
||||
className="text-nowrap ms-0 ms-md-2 mt-2 mt-md-0"
|
||||
onClick={() => onSendReply(value)}>
|
||||
onClick={() => handleSendReply()}>
|
||||
{t('btn_add_comment')}
|
||||
</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 { Button } from 'react-bootstrap';
|
||||
import { Button, Form } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { TextArea, Mentions } from '@/components';
|
||||
import { usePageUsers } from '@/hooks';
|
||||
|
||||
const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
||||
const Index = ({ userName, onSendReply, onCancel, mode }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const pageUsers = usePageUsers();
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
|
||||
|
||||
const [validationErrorMsg, setValidationErrorMsg] = useState('');
|
||||
const handleChange = (e) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
const handleSelected = (val) => {
|
||||
setValue(val);
|
||||
};
|
||||
const handleSendReply = () => {
|
||||
onSendReply(value).catch((ex) => {
|
||||
if (ex.isError) {
|
||||
setValidationErrorMsg(ex.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
|
@ -24,18 +33,26 @@ const Form = ({ userName, onSendReply, onCancel, mode }) => {
|
|||
</div>
|
||||
<div className="d-flex mb-1 align-items-start flex-column flex-md-row">
|
||||
<div>
|
||||
<Mentions
|
||||
pageUsers={pageUsers.getUsers()}
|
||||
onSelected={handleSelected}>
|
||||
<TextArea size="sm" value={value} onChange={handleChange} />
|
||||
</Mentions>
|
||||
<div className="form-text">{t(`tip_${mode}`)}</div>
|
||||
<div
|
||||
className={classNames('custom-form-control', {
|
||||
'is-invalid': validationErrorMsg,
|
||||
})}>
|
||||
<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 className="d-flex flex-row flex-md-column ms-0 ms-md-2 mt-2 mt-md-0">
|
||||
<Button
|
||||
size="sm"
|
||||
className="text-nowrap"
|
||||
onClick={() => onSendReply(value)}>
|
||||
onClick={() => handleSendReply()}>
|
||||
{t('btn_add_comment')}
|
||||
</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 { Modal } from '@/components';
|
||||
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 {
|
||||
useQueryComments,
|
||||
|
@ -43,7 +48,7 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
const scrollCallback = useCallback((el, co) => {
|
||||
if (pageIndex === 0 && co.comment_id === commentId) {
|
||||
setTimeout(() => {
|
||||
scrollTop(el);
|
||||
scrollToElementTop(el);
|
||||
bgFadeOut(el);
|
||||
}, 100);
|
||||
}
|
||||
|
@ -102,13 +107,14 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
const handleSendReply = (item) => {
|
||||
const users = matchedUsers(item.value);
|
||||
const userNames = unionBy(users.map((user) => user.userName));
|
||||
const html = marked.parse(parseUserInfo(item.value));
|
||||
if (!item.value || !html) {
|
||||
return;
|
||||
}
|
||||
const commentMarkDown = parseUserInfo(item.value);
|
||||
const html = marked.parse(commentMarkDown);
|
||||
// if (!commentMarkDown || !html) {
|
||||
// return;
|
||||
// }
|
||||
const params = {
|
||||
object_id: objectId,
|
||||
original_text: item.value,
|
||||
original_text: commentMarkDown,
|
||||
mention_username_list: userNames,
|
||||
parsed_text: html,
|
||||
...(item.type === 'reply'
|
||||
|
@ -119,7 +125,7 @@ const Comment = ({ objectId, mode, commentId }) => {
|
|||
};
|
||||
|
||||
if (item.type === 'edit') {
|
||||
updateComment({
|
||||
return updateComment({
|
||||
...params,
|
||||
comment_id: item.comment_id,
|
||||
}).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) => {
|
||||
|
|
|
@ -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';
|
||||
|
||||
import { markdownToHtml } from '@/services';
|
||||
import { htmlToReact } from '@/utils';
|
||||
|
||||
import { htmlRender } from './utils';
|
||||
|
||||
|
@ -38,6 +39,7 @@ const Index = ({ value }, ref) => {
|
|||
}
|
||||
|
||||
previewRef.current?.scrollTo(0, scrollTop);
|
||||
|
||||
htmlRender(previewRef.current);
|
||||
}, [html]);
|
||||
useImperativeHandle(ref, () => {
|
||||
|
@ -49,9 +51,9 @@ const Index = ({ value }, ref) => {
|
|||
return (
|
||||
<div
|
||||
ref={previewRef}
|
||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt">
|
||||
{htmlToReact(html)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ import { Pagination } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
import { scrollToDocTop } from '@/utils';
|
||||
|
||||
interface Props {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
|
@ -49,7 +51,7 @@ const PageItem = ({ page, currentPage, path }: PageItemProps) => {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigate(path);
|
||||
window.scrollTo(0, 0);
|
||||
scrollToDocTop();
|
||||
}}>
|
||||
{page}
|
||||
</Pagination.Item>
|
||||
|
@ -91,7 +93,7 @@ const Index: FC<Props> = ({
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigate(handleParams(currentPage - 1));
|
||||
window.scrollTo(0, 0);
|
||||
scrollToDocTop();
|
||||
}}>
|
||||
{t('prev')}
|
||||
</Pagination.Prev>
|
||||
|
@ -186,7 +188,7 @@ const Index: FC<Props> = ({
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
navigate(handleParams(currentPage + 1));
|
||||
window.scrollTo(0, 0);
|
||||
scrollToDocTop();
|
||||
}}>
|
||||
{t('next')}
|
||||
</Pagination.Next>
|
||||
|
|
|
@ -6,7 +6,6 @@ import { useTranslation } from 'react-i18next';
|
|||
import { pathFactory } from '@/router/pathFactory';
|
||||
import type * as Type from '@/common/interface';
|
||||
import {
|
||||
Icon,
|
||||
Tag,
|
||||
Pagination,
|
||||
FormatTime,
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
BaseUserCard,
|
||||
QueryGroup,
|
||||
QuestionListLoader,
|
||||
Counts,
|
||||
} from '@/components';
|
||||
import { useQuestionList } from '@/services';
|
||||
|
||||
|
@ -95,29 +95,15 @@ const QuestionList: FC<Props> = ({ source }) => {
|
|||
preFix={t(li.operation_type)}
|
||||
/>
|
||||
</div>
|
||||
<div className="ms-0 ms-md-3 mt-2 mt-md-0">
|
||||
<span>
|
||||
<Icon name="hand-thumbs-up-fill" />
|
||||
<em className="fst-normal ms-1">{li.vote_count}</em>
|
||||
</span>
|
||||
<span
|
||||
className={`ms-3 ${
|
||||
li.accepted_answer_id >= 1 ? 'text-success' : ''
|
||||
}`}>
|
||||
<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>
|
||||
<Counts
|
||||
data={{
|
||||
votes: li.vote_count,
|
||||
answers: li.answer_count,
|
||||
views: li.view_count,
|
||||
}}
|
||||
isAccepted={li.accepted_answer_id >= 1}
|
||||
className="ms-0 ms-md-3 mt-2 mt-md-0"
|
||||
/>
|
||||
</div>
|
||||
<div className="question-tags m-n1">
|
||||
{Array.isArray(li.tags)
|
||||
|
@ -139,7 +125,7 @@ const QuestionList: FC<Props> = ({ source }) => {
|
|||
currentPage={curPage}
|
||||
totalSize={count}
|
||||
pageSize={pageSize}
|
||||
pathname="/questions"
|
||||
pathname={source === 'questions' ? '/questions' : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -155,6 +155,7 @@ const TagSelector: FC<IProps> = ({
|
|||
};
|
||||
const handleKeyDown = (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!tags) {
|
||||
return;
|
||||
}
|
||||
|
@ -166,13 +167,20 @@ const TagSelector: FC<IProps> = ({
|
|||
if (keyCode === 40 && currentIndex < tags.length - 1) {
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
}
|
||||
if (
|
||||
keyCode === 13 &&
|
||||
currentIndex > -1 &&
|
||||
currentIndex <= tags.length - 1
|
||||
) {
|
||||
|
||||
if (keyCode === 13 && currentIndex > -1) {
|
||||
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 (
|
||||
|
|
|
@ -33,6 +33,7 @@ import PageTags from './PageTags';
|
|||
import QuestionListLoader from './QuestionListLoader';
|
||||
import TagsLoader from './TagsLoader';
|
||||
import WelcomeTitle from './WelcomeTitle';
|
||||
import Counts from './Counts';
|
||||
|
||||
export {
|
||||
Avatar,
|
||||
|
@ -72,5 +73,6 @@ export {
|
|||
QuestionListLoader,
|
||||
TagsLoader,
|
||||
WelcomeTitle,
|
||||
Counts,
|
||||
};
|
||||
export type { EditorRef, JSONSchema, UISchema };
|
||||
|
|
|
@ -143,21 +143,7 @@ const Ask = () => {
|
|||
const checkValidated = (): boolean => {
|
||||
const bol = true;
|
||||
const { title, content, tags, answer } = formData;
|
||||
if (!title.value) {
|
||||
// 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 {
|
||||
if (title.value && Array.from(title.value).length <= 150) {
|
||||
formData.title = {
|
||||
value: title.value,
|
||||
isInvalid: false,
|
||||
|
@ -165,14 +151,7 @@ const Ask = () => {
|
|||
};
|
||||
}
|
||||
|
||||
if (!content.value) {
|
||||
// bol = false;
|
||||
// formData.content = {
|
||||
// value: '',
|
||||
// isInvalid: true,
|
||||
// errorMsg: t('form.fields.body.msg.empty'),
|
||||
// };
|
||||
} else {
|
||||
if (content.value) {
|
||||
formData.content = {
|
||||
value: content.value,
|
||||
isInvalid: false,
|
||||
|
@ -180,29 +159,16 @@ const Ask = () => {
|
|||
};
|
||||
}
|
||||
|
||||
if (tags.value.length === 0) {
|
||||
// bol = false;
|
||||
// formData.tags = {
|
||||
// value: [],
|
||||
// isInvalid: true,
|
||||
// errorMsg: t('form.fields.tags.msg.empty'),
|
||||
// };
|
||||
} else {
|
||||
if (Array.isArray(tags.value) && tags.value.length > 0) {
|
||||
formData.tags = {
|
||||
value: tags.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
if (!answer.value) {
|
||||
// bol = false;
|
||||
// formData.answer = {
|
||||
// value: '',
|
||||
// isInvalid: true,
|
||||
// errorMsg: t('form.fields.answer.msg.empty'),
|
||||
// };
|
||||
} else {
|
||||
if (answer.value) {
|
||||
formData.answer = {
|
||||
value: answer.value,
|
||||
isInvalid: false,
|
||||
|
@ -227,7 +193,6 @@ const Ask = () => {
|
|||
const params: Type.QuestionParams = {
|
||||
title: formData.title.value,
|
||||
content: formData.content.value,
|
||||
html: editorRef.current.getHtml(),
|
||||
tags: formData.tags.value,
|
||||
};
|
||||
if (isEdit) {
|
||||
|
@ -261,7 +226,6 @@ const Ask = () => {
|
|||
postAnswer({
|
||||
question_id: id,
|
||||
content: formData.answer.value,
|
||||
html: editorRef2.current.getHtml(),
|
||||
})
|
||||
.then(() => {
|
||||
navigate(pathFactory.questionLanding(id, params.url_title));
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
FormatTime,
|
||||
htmlRender,
|
||||
} from '@/components';
|
||||
import { scrollTop, bgFadeOut } from '@/utils';
|
||||
import { scrollToElementTop, bgFadeOut } from '@/utils';
|
||||
import { AnswerItem } from '@/common/interface';
|
||||
import { acceptanceAnswer } from '@/services';
|
||||
|
||||
|
@ -60,7 +60,7 @@ const Index: FC<Props> = ({
|
|||
if (aid === data.id) {
|
||||
setTimeout(() => {
|
||||
const element = answerRef.current;
|
||||
scrollTop(element);
|
||||
scrollToElementTop(element);
|
||||
if (!searchParams.get('commentId')) {
|
||||
bgFadeOut(answerRef.current);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import classNames from 'classnames';
|
|||
import { Editor, Modal, TextArea } from '@/components';
|
||||
import { FormDataType } from '@/common/interface';
|
||||
import { postAnswer } from '@/services';
|
||||
import { guard } from '@/utils';
|
||||
import { guard, handleFormError } from '@/utils';
|
||||
|
||||
interface Props {
|
||||
visible?: boolean;
|
||||
|
@ -35,35 +35,60 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
|
|||
const [focusType, setFocusType] = useState('');
|
||||
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 = () => {
|
||||
if (!guard.tryNormalLogged(true)) {
|
||||
return;
|
||||
}
|
||||
if (!formData.content.value) {
|
||||
setFormData({
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: true,
|
||||
errorMsg: t('empty'),
|
||||
},
|
||||
});
|
||||
if (!checkValidated()) {
|
||||
return;
|
||||
}
|
||||
postAnswer({
|
||||
question_id: data?.qid,
|
||||
content: formData.content.value,
|
||||
html: marked.parse(formData.content.value),
|
||||
}).then((res) => {
|
||||
setShowEditor(false);
|
||||
setFormData({
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
setShowEditor(false);
|
||||
setFormData({
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
},
|
||||
});
|
||||
callback?.(res.info);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
callback?.(res.info);
|
||||
});
|
||||
};
|
||||
|
||||
const clickBtn = () => {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import Pattern from '@/common/pattern';
|
||||
import { Pagination } from '@/components';
|
||||
import { loggedUserInfoStore, toastStore } from '@/stores';
|
||||
import { scrollTop } from '@/utils';
|
||||
import { scrollToElementTop } from '@/utils';
|
||||
import { usePageTags, usePageUsers } from '@/hooks';
|
||||
import type {
|
||||
ListResult,
|
||||
|
@ -80,7 +80,7 @@ const Index = () => {
|
|||
if (page > 0 || order) {
|
||||
// scroll into view;
|
||||
const element = document.getElementById('answerHeader');
|
||||
scrollTop(element);
|
||||
scrollToElementTop(element);
|
||||
}
|
||||
|
||||
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 { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import dayjs from 'dayjs';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { handleFormError } from '@/utils';
|
||||
import { usePageTags } from '@/hooks';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
import { Editor, EditorRef, Icon } from '@/components';
|
||||
|
@ -19,11 +20,11 @@ import {
|
|||
import './index.scss';
|
||||
|
||||
interface FormDataItem {
|
||||
answer: Type.FormValue<string>;
|
||||
content: Type.FormValue<string>;
|
||||
description: Type.FormValue<string>;
|
||||
}
|
||||
const initFormData = {
|
||||
answer: {
|
||||
content: {
|
||||
value: '',
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
|
@ -35,7 +36,6 @@ const initFormData = {
|
|||
},
|
||||
};
|
||||
const Index = () => {
|
||||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||
const { aid = '', qid = '' } = useParams();
|
||||
const [focusType, setForceType] = useState('');
|
||||
|
||||
|
@ -43,6 +43,10 @@ const Index = () => {
|
|||
const navigate = useNavigate();
|
||||
|
||||
const { data } = useQueryAnswerInfo(aid);
|
||||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||
|
||||
initFormData.content.value = data?.info.content || '';
|
||||
|
||||
const { data: revisions = [] } = useQueryRevisions(aid);
|
||||
|
||||
const editorRef = useRef<EditorRef>({
|
||||
|
@ -51,18 +55,10 @@ const Index = () => {
|
|||
|
||||
const questionContentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
formData.answer.value = data.info.content;
|
||||
setFormData({ ...formData });
|
||||
}, [data]);
|
||||
|
||||
const handleAnswerChange = (value: string) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
answer: { ...formData.answer, value },
|
||||
content: { ...formData.content, value },
|
||||
});
|
||||
const handleSummaryChange = (evt) => {
|
||||
const v = evt.currentTarget.value;
|
||||
|
@ -74,18 +70,18 @@ const Index = () => {
|
|||
|
||||
const checkValidated = (): boolean => {
|
||||
let bol = true;
|
||||
const { answer } = formData;
|
||||
const { content } = formData;
|
||||
|
||||
if (!answer.value) {
|
||||
if (!content.value || Array.from(content.value.trim()).length < 6) {
|
||||
bol = false;
|
||||
formData.answer = {
|
||||
value: '',
|
||||
formData.content = {
|
||||
value: content.value,
|
||||
isInvalid: true,
|
||||
errorMsg: '标题不能为空',
|
||||
errorMsg: t('form.fields.answer.feedback.characters'),
|
||||
};
|
||||
} else {
|
||||
formData.answer = {
|
||||
value: answer.value,
|
||||
formData.content = {
|
||||
value: content.value,
|
||||
isInvalid: false,
|
||||
errorMsg: '',
|
||||
};
|
||||
|
@ -105,29 +101,36 @@ const Index = () => {
|
|||
}
|
||||
|
||||
const params: Type.AnswerParams = {
|
||||
content: formData.answer.value,
|
||||
content: formData.content.value,
|
||||
html: editorRef.current.getHtml(),
|
||||
question_id: qid,
|
||||
id: aid,
|
||||
edit_summary: formData.description.value,
|
||||
};
|
||||
modifyAnswer(params).then((res) => {
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
slugTitle: data?.question?.url_title,
|
||||
answerId: aid,
|
||||
}),
|
||||
{
|
||||
state: { isReview: res?.wait_for_review },
|
||||
},
|
||||
);
|
||||
});
|
||||
modifyAnswer(params)
|
||||
.then((res) => {
|
||||
navigate(
|
||||
pathFactory.answerLanding({
|
||||
questionId: qid,
|
||||
slugTitle: data?.question?.url_title,
|
||||
answerId: aid,
|
||||
}),
|
||||
{
|
||||
state: { isReview: res?.wait_for_review },
|
||||
},
|
||||
);
|
||||
})
|
||||
.catch((ex) => {
|
||||
if (ex.isError) {
|
||||
const stateData = handleFormError(ex, formData);
|
||||
setFormData({ ...stateData });
|
||||
}
|
||||
});
|
||||
};
|
||||
const handleSelectedRevision = (e) => {
|
||||
const index = e.target.value;
|
||||
const revision = revisions[index];
|
||||
formData.answer.value = revision.content.content;
|
||||
formData.content.value = revision.content.content;
|
||||
setFormData({ ...formData });
|
||||
};
|
||||
|
||||
|
@ -190,7 +193,7 @@ const Index = () => {
|
|||
<Form.Group controlId="answer" className="mt-3">
|
||||
<Form.Label>{t('form.fields.answer.label')}</Form.Label>
|
||||
<Editor
|
||||
value={formData.answer.value}
|
||||
value={formData.content.value}
|
||||
onChange={handleAnswerChange}
|
||||
className={classNames(
|
||||
'form-control p-0',
|
||||
|
@ -205,14 +208,14 @@ const Index = () => {
|
|||
ref={editorRef}
|
||||
/>
|
||||
<Form.Control
|
||||
value={formData.answer.value}
|
||||
value={formData.content.value}
|
||||
type="text"
|
||||
isInvalid={formData.answer.isInvalid}
|
||||
isInvalid={formData.content.isInvalid}
|
||||
readOnly
|
||||
hidden
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData.answer.errorMsg}
|
||||
{formData.content.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="edit_summary" className="my-3">
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ListGroupItem } from 'react-bootstrap';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
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 { escapeRemove } from '@/utils';
|
||||
|
||||
|
@ -51,23 +51,17 @@ const Index: FC<Props> = ({ data }) => {
|
|||
className="me-3"
|
||||
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">
|
||||
<Icon name="hand-thumbs-up-fill me-1" />
|
||||
<span> {data.object?.vote_count}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`d-flex align-items-center ${
|
||||
data.object?.accepted ? 'text-success' : ''
|
||||
}`}>
|
||||
{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>
|
||||
|
||||
<Counts
|
||||
className="my-2 my-sm-0"
|
||||
showViews={false}
|
||||
isAccepted={data.object?.accepted}
|
||||
data={{
|
||||
votes: data.object?.vote_count,
|
||||
answers: data.object?.answer_count,
|
||||
views: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{data.object?.excerpt && (
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Link, useSearchParams } from 'react-router-dom';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { usePageTags } from '@/hooks';
|
||||
import { loggedUserInfoStore } from '@/stores';
|
||||
import { loggedUserInfoStore, siteInfoStore } from '@/stores';
|
||||
import { changeEmailVerify, getLoggedUserInfo } from '@/services';
|
||||
|
||||
const Index: FC = () => {
|
||||
|
@ -13,6 +13,7 @@ const Index: FC = () => {
|
|||
const [step, setStep] = useState('loading');
|
||||
|
||||
const updateUser = loggedUserInfoStore((state) => state.update);
|
||||
const siteName = siteInfoStore((state) => state.siteInfo.name);
|
||||
|
||||
useEffect(() => {
|
||||
const code = searchParams.get('code');
|
||||
|
@ -38,7 +39,9 @@ const Index: FC = () => {
|
|||
<Container className="pt-4 mt-2 mb-5">
|
||||
<Row className="justify-content-center">
|
||||
<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' && (
|
||||
<>
|
||||
<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 { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon, FormatTime, Tag } from '@/components';
|
||||
import { FormatTime, Tag, Counts } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
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">
|
||||
<FormatTime
|
||||
time={item.create_time}
|
||||
className="me-4"
|
||||
className="me-3"
|
||||
preFix={t('answered')}
|
||||
/>
|
||||
|
||||
<div className="d-flex align-items-center me-3">
|
||||
<Icon name="hand-thumbs-up-fill me-1" />
|
||||
<span>{item?.vote_count}</span>
|
||||
</div>
|
||||
|
||||
{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>
|
||||
)}
|
||||
<Counts
|
||||
data={{ votes: item?.vote_count, views: 0, answers: 0 }}
|
||||
showAnswers={false}
|
||||
showViews={false}
|
||||
showAccepted={item.accepted === 2}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{item.question_info?.tags?.map((tag) => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { FC, memo } from 'react';
|
|||
import { ListGroup, ListGroupItem } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Icon, FormatTime, Tag, BaseUserCard } from '@/components';
|
||||
import { FormatTime, Tag, BaseUserCard, Counts } from '@/components';
|
||||
import { pathFactory } from '@/router/pathFactory';
|
||||
|
||||
interface Props {
|
||||
|
@ -44,35 +44,23 @@ const Index: FC<Props> = ({ visible, tabName, data }) => {
|
|||
<span className="split-dot" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormatTime
|
||||
time={item.create_time}
|
||||
time={
|
||||
tabName === 'bookmarks' ? item.create_time : item.created_at
|
||||
}
|
||||
className="me-3"
|
||||
preFix={t('asked')}
|
||||
/>
|
||||
|
||||
<div className="d-flex align-items-center me-3">
|
||||
<Icon name="hand-thumbs-up-fill me-1" />
|
||||
<span>{item.vote_count}</span>
|
||||
</div>
|
||||
|
||||
{tabName !== 'answers' && (
|
||||
<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>
|
||||
<Counts
|
||||
isAccepted={Number(item.accepted_answer_id) > 0}
|
||||
data={{
|
||||
votes: item.vote_count,
|
||||
answers: item.answer_count,
|
||||
views: item.view_count,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{item.tags?.map((tag) => {
|
||||
|
|
|
@ -32,9 +32,12 @@ const Index: FC<Props> = ({ data, type }) => {
|
|||
}>
|
||||
{type === 'answer' ? item.question_info.title : item.title}
|
||||
</a>
|
||||
|
||||
<div className="d-inline-block text-secondary ms-3 fs-14">
|
||||
<Icon name="hand-thumbs-up-fill" />
|
||||
<span> {item.vote_count}</span>
|
||||
<Icon name="hand-thumbs-up-fill me-1" />
|
||||
<span>
|
||||
{item.vote_count} {t('votes', { keyPrefix: 'counts' })}
|
||||
</span>
|
||||
</div>
|
||||
{type === 'question' && (
|
||||
<div
|
||||
|
@ -47,7 +50,10 @@ const Index: FC<Props> = ({ data, type }) => {
|
|||
<Icon name="chat-square-text-fill" />
|
||||
)}
|
||||
|
||||
<span> {item.answer_count}</span>
|
||||
<span>
|
||||
{' '}
|
||||
{item.answer_count} {t('answers', { keyPrefix: 'counts' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ const Index = () => {
|
|||
return (
|
||||
<div className="mt-5">
|
||||
<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">
|
||||
{data?.map((item) => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import i18next from 'i18next';
|
||||
import parse from 'html-react-parser';
|
||||
import * as DOMPurify from 'dompurify';
|
||||
|
||||
const Diff = require('diff');
|
||||
|
||||
|
@ -21,7 +23,7 @@ function formatCount($num: number): string {
|
|||
return res;
|
||||
}
|
||||
|
||||
function scrollTop(element) {
|
||||
function scrollToElementTop(element) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
@ -36,6 +38,15 @@ function scrollTop(element) {
|
|||
});
|
||||
}
|
||||
|
||||
const scrollToDocTop = () => {
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const bgFadeOut = (el) => {
|
||||
if (el && !el.classList.contains('bg-fade-out')) {
|
||||
el.classList.add('bg-fade-out');
|
||||
|
@ -160,14 +171,17 @@ function handleFormError(
|
|||
) {
|
||||
if (error.list?.length > 0) {
|
||||
error.list.forEach((item) => {
|
||||
data[item.error_field].isInvalid = true;
|
||||
data[item.error_field].errorMsg = item.error_msg;
|
||||
const errorFieldObject = data[item.error_field];
|
||||
if (errorFieldObject) {
|
||||
errorFieldObject.isInvalid = true;
|
||||
errorFieldObject.errorMsg = item.error_msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function diffText(newText: string, oldText: string): string {
|
||||
function diffText(newText: string, oldText?: string): string {
|
||||
if (!newText) {
|
||||
return '';
|
||||
}
|
||||
|
@ -181,8 +195,6 @@ function diffText(newText: string, oldText: string): string {
|
|||
?.replace(/<input/gi, '<input');
|
||||
}
|
||||
const diff = Diff.diffChars(oldText, newText);
|
||||
console.log(diff);
|
||||
|
||||
const result = diff.map((part) => {
|
||||
if (part.added) {
|
||||
if (part.value.replace(/\n/g, '').length <= 0) {
|
||||
|
@ -214,10 +226,18 @@ function diffText(newText: string, oldText: string): string {
|
|||
?.replace(/<input/gi, '<input');
|
||||
}
|
||||
|
||||
function htmlToReact(html: string) {
|
||||
const cleanedHtml = DOMPurify.sanitize(html, {
|
||||
USE_PROFILES: { html: true },
|
||||
});
|
||||
return parse(cleanedHtml);
|
||||
}
|
||||
|
||||
export {
|
||||
thousandthDivision,
|
||||
formatCount,
|
||||
scrollTop,
|
||||
scrollToElementTop,
|
||||
scrollToDocTop,
|
||||
bgFadeOut,
|
||||
matchedUsers,
|
||||
parseUserInfo,
|
||||
|
@ -228,4 +248,5 @@ export {
|
|||
labelStyle,
|
||||
handleFormError,
|
||||
diffText,
|
||||
htmlToReact,
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@ class Request {
|
|||
},
|
||||
(error) => {
|
||||
const { status, data: respData } = error.response || {};
|
||||
const { data = {}, msg = '' } = respData || {};
|
||||
const { data = {}, msg = '', reason = '' } = respData || {};
|
||||
if (status === 400) {
|
||||
// show error message
|
||||
if (data instanceof Object && data.err_type) {
|
||||
|
@ -79,7 +79,13 @@ class Request {
|
|||
|
||||
if (data instanceof Array && data.length > 0) {
|
||||
// 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) {
|
||||
|
|
Loading…
Reference in New Issue