diff --git a/Makefile b/Makefile
index c7c00ffa..c8dbf23d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
.PHONY: build clean ui
-VERSION=1.0.5
+VERSION=1.0.6
BIN=answer
DIR_SRC=./cmd/answer
DOCKER_CMD=docker
diff --git a/docs/docs.go b/docs/docs.go
index d6741817..b28986b7 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -5984,9 +5984,6 @@ const docTemplate = `{
"schema.GetOtherUserInfoResp": {
"type": "object",
"properties": {
- "has": {
- "type": "boolean"
- },
"info": {
"$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp"
}
@@ -7688,7 +7685,6 @@ const docTemplate = `{
],
"properties": {
"status": {
- "description": "user status",
"type": "string",
"enum": [
"normal",
@@ -7698,7 +7694,6 @@ const docTemplate = `{
]
},
"user_id": {
- "description": "user id",
"type": "string"
}
}
@@ -7994,6 +7989,10 @@ const docTemplate = `{
"label": {
"type": "string"
},
+ "progress": {
+ "description": "Translation completion percentage",
+ "type": "integer"
+ },
"value": {
"type": "string"
}
diff --git a/docs/swagger.json b/docs/swagger.json
index 8433f7c5..21be9a5c 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -5972,9 +5972,6 @@
"schema.GetOtherUserInfoResp": {
"type": "object",
"properties": {
- "has": {
- "type": "boolean"
- },
"info": {
"$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp"
}
@@ -7676,7 +7673,6 @@
],
"properties": {
"status": {
- "description": "user status",
"type": "string",
"enum": [
"normal",
@@ -7686,7 +7682,6 @@
]
},
"user_id": {
- "description": "user id",
"type": "string"
}
}
@@ -7982,6 +7977,10 @@
"label": {
"type": "string"
},
+ "progress": {
+ "description": "Translation completion percentage",
+ "type": "integer"
+ },
"value": {
"type": "string"
}
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index f3663b28..c57892fa 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -488,8 +488,6 @@ definitions:
type: object
schema.GetOtherUserInfoResp:
properties:
- has:
- type: boolean
info:
$ref: '#/definitions/schema.GetOtherUserInfoByUsernameResp'
type: object
@@ -1691,7 +1689,6 @@ definitions:
schema.UpdateUserStatusReq:
properties:
status:
- description: user status
enum:
- normal
- suspended
@@ -1699,7 +1696,6 @@ definitions:
- inactive
type: string
user_id:
- description: user id
type: string
required:
- status
@@ -1911,6 +1907,9 @@ definitions:
properties:
label:
type: string
+ progress:
+ description: Translation completion percentage
+ type: integer
value:
type: string
type: object
diff --git a/go.mod b/go.mod
index baaa08c5..a980e167 100644
--- a/go.mod
+++ b/go.mod
@@ -35,13 +35,13 @@ require (
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
- github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
+ github.com/swaggo/files v1.0.0
github.com/swaggo/gin-swagger v1.5.3
- github.com/swaggo/swag v1.8.7
+ github.com/swaggo/swag v1.8.10
github.com/tidwall/gjson v1.14.4
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.1.0
- golang.org/x/net v0.1.0
+ golang.org/x/net v0.2.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.14.2
@@ -121,7 +121,7 @@ require (
go.uber.org/zap v1.23.0 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
- golang.org/x/sys v0.1.0 // indirect
+ golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
diff --git a/go.sum b/go.sum
index 95de6076..58423a80 100644
--- a/go.sum
+++ b/go.sum
@@ -668,13 +668,14 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
-github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
+github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4=
+github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc=
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
-github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
-github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
+github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
+github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -850,8 +851,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
-golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -944,11 +945,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index c4f5da5c..18d311c3 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -35,6 +35,10 @@ backend:
other: Email and password do not match.
error:
admin:
+ cannot_update_their_password:
+ other: You cannot modify your password.
+ cannot_modify_self_status:
+ other: You cannot modify your status.
email_or_password_wrong:
other: Email and password do not match.
answer:
@@ -81,6 +85,8 @@ backend:
new_password_same_as_previous_setting:
other: The new password is the same as the previous one.
question:
+ already_deleted:
+ other: This post has been deleted.
not_found:
other: Question not found.
cannot_deleted:
@@ -819,6 +825,7 @@ ui:
approve: Approve
reject: Reject
skip: Skip
+ discard_draft: Discard draft
search:
title: Search Results
keywords: Keywords
@@ -1015,9 +1022,11 @@ ui:
answers: answers
accepted: Accepted
page_404:
+ http_error: HTTP Error 404
desc: "Unfortunately, this page doesn't exist."
back_home: Back to homepage
page_50X:
+ http_error: HTTP Error 500
desc: The server encountered an error and could not complete your request.
back_home: Back to homepage
page_maintenance:
@@ -1407,6 +1416,10 @@ ui:
reputation: reputation
votes: votes
prompt:
- leave_page: "Are you sure you want to leave the page?"
- changes_not_save: "Your changes may not be saved."
+ leave_page: Are you sure you want to leave the page?
+ changes_not_save: Your changes may not be saved.
+ draft:
+ discard_confirm: Are you sure you want to discard your draft?
+ messages:
+ post_deleted: This post has been deleted.
diff --git a/i18n/i18n.yaml b/i18n/i18n.yaml
index 87db7100..a0b7a80f 100644
--- a/i18n/i18n.yaml
+++ b/i18n/i18n.yaml
@@ -1,26 +1,41 @@
# all support language
language_options:
- - label: "English(US)"
+ - label: "English"
value: "en_US"
- - label: "Español(ES)"
+ progress: 100
+ - label: "Español"
value: "es_ES"
- - label: "Português(PT)"
+ progress: 0
+ - label: "Português(BR)"
+ value: "pt_BR"
+ progress: 0
+ - label: "Português"
value: "pt_PT"
- - label: "Deutsch(DE)"
+ progress: 0
+ - label: "Deutsch"
value: "de_DE"
- - label: "Français(FR)"
+ progress: 4
+ - label: "Français"
value: "fr_FR"
- - label: "日本語(JA)"
+ progress: 100
+ - label: "日本語"
value: "ja_JP"
- - label: "Italiano(IT)"
+ progress: 0
+ - label: "Italiano"
value: "it_IT"
- - label: "Русский(RU)"
+ progress: 16
+ - label: "Русский"
value: "ru_RU"
- - label: "简体中文(CN)"
+ progress: 13
+ - label: "简体中文"
value: "zh_CN"
- - label: "繁體中文(CN)"
+ progress: 100
+ - label: "繁體中文"
value: "zh_TW"
- - label: "한국어(KO)"
+ progress: 100
+ - label: "한국어"
value: "ko_KR"
- - label: "Tiếng Việt(VI)"
+ progress: 0
+ - label: "Tiếng Việt"
value: "vi_VN"
+ progress: 0
diff --git a/i18n/pt_BR.yaml b/i18n/pt_BR.yaml
index 14c553f5..49a4bd4f 100644
--- a/i18n/pt_BR.yaml
+++ b/i18n/pt_BR.yaml
@@ -2,914 +2,914 @@
backend:
base:
success:
- other: Success.
+ other: Sucesso.
unknown:
- other: Unknown error.
+ other: Erro desconhecido.
request_format_error:
- other: Request format is not valid.
+ other: O formato da Requisiçã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:
- other: User
+ other: Usuário
admin:
- other: Admin
+ other: Administrador
moderator:
- other: Moderator
+ other: Moderador
description:
user:
- other: Default with no special access.
+ other: Padrão sem acesso especial.
admin:
- other: Have the full power to access the site.
+ other: Possui controle total para acessar o site.
moderator:
- other: Has access to all posts except admin settings.
+ other: Não possui controle a todas as postagens exceto configurações de administrador.
email:
- other: Email
+ other: E-mail
password:
- other: Password
+ other: Senha
email_or_password_wrong_error:
- other: Email and password do not match.
+ other: E-mail e Senha inválidos.
error:
admin:
email_or_password_wrong:
- other: Email and password do not match.
+ other: E-mail e Senha inválidos.
answer:
not_found:
- other: Answer do not found.
+ other: Resposta não encontrada.
cannot_deleted:
- other: No permission to delete.
+ other: Permissão insuficiente para deletar.
cannot_update:
- other: No permission to update.
+ other: Permissão insuficiente para atualizar.
comment:
edit_without_permission:
- other: Comment are not allowed to edit.
+ other: Não é permitido atualizar comentários.
not_found:
- other: Comment not found.
+ other: Comentário não encontrado.
cannot_edit_after_deadline:
- other: The comment time has been too long to modify.
+ other: O tempo para comentários expirou.
email:
duplicate:
- other: Email already exists.
+ other: E-mail já existe na base de dados.
need_to_be_verified:
- other: Email should be verified.
+ other: E-mail precisa ser verificado.
verify_url_expired:
- other: Email verified URL has expired, please resend the email.
+ other: A URL de validação do e-mail expirou, por favor, re-envie o e-mail.
lang:
not_found:
- other: Language file not found.
+ other: Arquivo de idioma não encontrado.
object:
captcha_verification_failed:
- other: Captcha wrong.
+ other: Captcha inválido.
disallow_follow:
- other: You are not allowed to follow.
+ other: Você não possui permissão para seguir.
disallow_vote:
- other: You are not allowed to vote.
+ other: Você não possui permissão para votar.
disallow_vote_your_self:
- other: You can't vote for your own post.
+ other: Você não pode votar na sua própria postagem.
not_found:
- other: Object not found.
+ other: Objeto não encontrado.
verification_failed:
- other: Verification failed.
+ other: Falha na verificação.
email_or_password_incorrect:
- other: Email and password do not match.
+ other: E-mail e Senha não conferem.
old_password_verification_failed:
- other: The old password verification failed
+ other: Verficação de senhas antigas falhou.
new_password_same_as_previous_setting:
- other: The new password is the same as the previous one.
+ other: A nova senha é idêntica à senha anterior.
question:
not_found:
- other: Question not found.
+ other: Pergunta não encontrada.
cannot_deleted:
- other: No permission to delete.
+ other: Não possui permissão para deletar.
cannot_close:
- other: No permission to close.
+ other: Não possui permissão para fechar.
cannot_update:
- other: No permission to update.
+ other: Não possui permissão para atualizar..
rank:
fail_to_meet_the_condition:
- other: Rank fail to meet the condition.
+ other: A classificação não atende à condição.
report:
handle_failed:
- other: Report handle failed.
+ other: Falha no processamento do relatório.
not_found:
- other: Report not found.
+ other: Relatório não encontrado.
tag:
not_found:
- other: Tag not found.
+ other: Marcador não encontada.
recommend_tag_not_found:
- other: Recommend Tag is not exist.
+ other: Marcador recomendado não existe.
recommend_tag_enter:
- other: Please enter at least one required tag.
+ other: Por favor, insira ao menos um marcador obrigatório.
not_contain_synonym_tags:
- other: Should not contain synonym tags.
+ other: Não pode contar marcadores de sinônimos.
cannot_update:
- other: No permission to update.
+ other: Não possui permissão para atualizar.
cannot_set_synonym_as_itself:
- other: You cannot set the synonym of the current tag as itself.
+ other: Você não pode definir o sinônimo do marcador atual como ela mesma.
smtp:
config_from_name_cannot_be_email:
- other: The From Name cannot be a email address.
- theme:
+ other: O De Nome não pode ser um endereço de e-mail.
+ Tema:
not_found:
- other: Theme not found.
+ other: Tema não encontrado.
revision:
review_underway:
- other: Can't edit currently, there is a version in the review queue.
+ other: Não é possível editar no momento, há uma versão na fila de revisão.
no_permission:
- other: No permission to Revision.
+ other: Sem pemissão para revisar.
user:
email_or_password_wrong:
other:
- other: Email and password do not match.
+ other: E-mail e senha não correspondem.
not_found:
- other: User not found.
+ other: Usuário não encontrado.
suspended:
- other: User has been suspended.
+ other: Usuário foi suspenso.
username_invalid:
- other: Username is invalid.
+ other: Nome de usuário inválido.
username_duplicate:
- other: Username is already in use.
+ other: Nome de usuário já está em uso.
set_avatar:
- other: Avatar set failed.
+ other: Falha ao configurar Avatar.
cannot_update_your_role:
- other: You cannot modify your role.
+ other: Você não pode modificar a sua função.
not_allowed_registration:
- other: Currently the site is not open for registration
+ other: Atualmente o site não está aberto para cadastro
config:
read_config_failed:
- other: Read config failed
+ other: A leitura da configuração falhou
database:
connection_failed:
- other: Database connection failed
+ other: Falha na conexão com o banco de dados
create_table_failed:
- other: Create table failed
+ other: Falha na criação de tabela
install:
create_config_failed:
- other: Can't create the config.yaml file.
+ other: Não foi possível criar o arquivo config.yaml.
upload:
unsupported_file_format:
- other: Unsupported file format.
+ other: Formato de arquivo não suportado.
report:
spam:
name:
other: spam
desc:
- other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
+ other: Este post é um anúncio, ou vandalismo. Não é útil ou relevante para o tópico atual.
rude:
name:
- other: rude or abusive
+ other: rude ou abusivo
desc:
- other: A reasonable person would find this content inappropriate for respectful discourse.
+ other: Uma pessoa razoável acharia este conteúdo impróprio para um discurso respeitoso.
duplicate:
name:
- other: a duplicate
+ other: uma duplicidade
desc:
- other: This question has been asked before and already has an answer.
+ other: Esta pergunta já foi feita antes e já tem uma resposta.
not_answer:
name:
- other: not an answer
+ other: não é uma resposta
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: Isso foi postado como uma resposta, mas não tenta responder à pergunta. Possivelmente deve ser uma edição, um comentário, outra pergunta ou excluído completamente.
not_need:
name:
- other: no longer needed
+ other: não é mais necessário
desc:
- other: This comment is outdated, conversational or not relevant to this post.
+ other: Este comentário está desatualizado, coloquial ou não é relevante para esta postagem.
other:
name:
- other: something else
+ other: algo mais
desc:
- other: This post requires staff attention for another reason not listed above.
+ other: Esta postagem requer atenção da equipe por outro motivo não listado acima.
question:
close:
duplicate:
name:
other: spam
desc:
- other: This question has been asked before and already has an answer.
+ other: Esta pergunta já foi feita antes e já tem uma resposta.
guideline:
name:
- other: a community-specific reason
+ other: um motivo específico da comunidade
desc:
- other: This question doesn't meet a community guideline.
+ other: Esta pergunta não atende a uma diretriz da comunidade.
multiple:
name:
- other: needs details or clarity
+ other: precisa de detalhes ou clareza
desc:
- other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other: Esta pergunta atualmente inclui várias perguntas em uma. Deve se concentrar em apenas um problema.
other:
name:
- other: something else
+ other: algo mais
desc:
- other: This post requires another reason not listed above.
+ other: Este post requer outro motivo não listado acima.
operation_type:
asked:
- other: asked
+ other: perguntado
answered:
- other: answered
+ other: respondido
modified:
- other: modified
+ other: modificado
notification:
action:
update_question:
- other: updated question
+ other: pergunta atualizada
answer_the_question:
- other: answered question
+ other: pergunta respondida
update_answer:
- other: updated answer
+ other: resposta atualizada
accept_answer:
- other: accepted answer
+ other: resposta aceita
comment_question:
- other: commented question
+ other: pergunta comentada
comment_answer:
- other: commented answer
+ other: resposta comentada
reply_to_you:
- other: replied to you
+ other: respondeu a você
mention_you:
- other: mentioned you
+ other: mencionou você
your_question_is_closed:
- other: Your question has been closed
+ other: A sua pergunta foi fechada
your_question_was_deleted:
- other: Your question has been deleted
+ other: A sua pergunta foi deletada
your_answer_was_deleted:
- other: Your answer has been deleted
+ other: A sua resposta foi deletada
your_comment_was_deleted:
- other: Your comment has been deleted
+ other: O seu comentário foi deletado
#The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
- title: How to Format
+ title: Como formatar
desc: >-
-
to make links
<https://url.com>
[Title](https://url.com)
put returns between paragraphs
_italic_ or **bold**
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+
para mais links
<https://url.com>
[Título](https://url.com)
colocar retornos entre parágrafos
_italic_ ou **bold**
identar código por 4 espaços
cotação colocando > no início da linha
aspas invertidas `tipo_isso`
criar cercas de código com aspas invertidas `
``` código aqui ```
pagination:
- prev: Prev
- next: Next
+ prev: Anterior
+ next: Próximo
page_title:
- question: Question
- questions: Questions
- tag: Tag
- tags: Tags
- tag_wiki: tag wiki
- edit_tag: Edit Tag
- ask_a_question: Add Question
- edit_question: Edit Question
- edit_answer: Edit Answer
- search: Search
- posts_containing: Posts containing
- settings: Settings
- notifications: Notifications
- login: Log In
- sign_up: Sign Up
- account_recovery: Account Recovery
- account_activation: Account Activation
- confirm_email: Confirm Email
- account_suspended: Account Suspended
- admin: Admin
- change_email: Modify Email
- install: Answer Installation
- upgrade: Answer Upgrade
- maintenance: Website Maintenance
- users: Users
+ question: Pergunta
+ questions: Perguntas
+ tag: Marcador
+ tags: Marcadores
+ tag_wiki: marcador wiki
+ edit_tag: Editar Marcador
+ ask_a_question: Adicionar Pergunta
+ edit_question: Editar Pergunta
+ edit_answer: Editar Resposta
+ search: Busca
+ posts_containing: Postagens contendo
+ settings: Configurações
+ notifications: Notificações
+ login: Entrar
+ sign_up: Criar conta
+ account_recovery: Recuperação de conta
+ account_activation: Ativação de conta
+ confirm_email: Confirmação de e-mail
+ account_suspended: Conta suspensa
+ admin: Administrador
+ change_email: Modificar e-mail
+ install: Instalação do Resposta
+ upgrade: Atualização do Resposta
+ maintenance: Manutençã do Website
+ users: Usuários
notifications:
- title: Notifications
- inbox: Inbox
- achievement: Achievements
- all_read: Mark all as read
- show_more: Show more
+ title: Notificações
+ inbox: Caixa de entrada
+ achievement: Conquistas
+ all_read: Marcar todas como lidas
+ show_more: Mostrar mais
suspended:
- title: Your Account has been Suspended
- until_time: "Your account was suspended until {{ time }}."
- forever: This user was suspended forever.
- end: You don't meet a community guideline.
+ title: A sua conta foi suspensa
+ until_time: "A sua conta foi suspensa até {{ time }}."
+ forever: Este usuário foi suspenso por tempo indeterminado.
+ end: Você não atende a uma diretriz da comunidade.
editor:
blockquote:
- text: Blockquote
+ text: Bloco de citação
bold:
- text: Strong
+ text: Forte
chart:
- text: Chart
- flow_chart: Flow chart
- sequence_diagram: Sequence diagram
- class_diagram: Class diagram
- state_diagram: State diagram
- entity_relationship_diagram: Entity relationship diagram
- user_defined_diagram: User defined diagram
- gantt_chart: Gantt chart
- pie_chart: Pie chart
+ text: Gráfico
+ flow_chart: Fluxograma
+ sequence_diagram: Diagrama de sequência
+ class_diagram: Diagrama de classe
+ state_diagram: Diagrama de estado
+ entity_relationship_diagram: Diagrama de relacionamento de entidade
+ user_defined_diagram: Diagrama definido pelo usuário
+ gantt_chart: Diagrama de Gantt
+ pie_chart: Gráfico de pizza
code:
- text: Code Sample
- add_code: Add code sample
+ text: Código de exemplo
+ add_code: Adicione código de exemplo
form:
fields:
code:
- label: Code
+ label: Código
msg:
- empty: Code cannot be empty.
+ empty: Código não pode ser vazio.
language:
- label: Language (optional)
- placeholder: Automatic detection
- btn_cancel: Cancel
- btn_confirm: Add
+ label: Idioma (opcional)
+ placeholder: Tetecção automática
+ btn_cancel: Cancelar
+ btn_confirm: Adicionar
formula:
- text: Formula
+ text: Fórmula
options:
- inline: Inline formula
- block: Block formula
- heading:
- text: Heading
+ inline: Fórmula em linha
+ block: Fórmula em bloco
+ Cabeçalho:
+ text: Cabeçalho
options:
- h1: Heading 1
- h2: Heading 2
- h3: Heading 3
- h4: Heading 4
- h5: Heading 5
- h6: Heading 6
+ h1: Cabeçalho 1
+ h2: Cabeçalho 2
+ h3: Cabeçalho 3
+ h4: Cabeçalho 4
+ h5: Cabeçalho 5
+ h6: Cabeçalho 6
help:
- text: Help
+ text: Ajuda
hr:
- text: Horizontal Rule
+ text: Régua horizontal
image:
- text: Image
- add_image: Add image
- tab_image: Upload image
+ text: Imagem
+ add_image: Adicionar imagem
+ tab_image: Enviar image,
form_image:
fields:
file:
- label: Image File
- btn: Select image
+ label: Arquivo de imagem
+ btn: Selecione imagem
msg:
- empty: File cannot be empty.
- only_image: Only image files are allowed.
- max_size: File size cannot exceed 4MB.
+ empty: Arquivo não pode ser vazio.
+ only_image: Somente um arquivo de imagem é permitido.
+ max_size: O tamanho do arquivo não pode exceder 4MB.
desc:
- label: Description (optional)
- tab_url: Image URL
+ label: Descrição (opcional)
+ tab_url: URL da imagem
form_url:
fields:
url:
- label: Image URL
+ label: URL da imagem
msg:
- empty: Image URL cannot be empty.
+ empty: URL da imagem não pode ser vazia.
name:
- label: Description (optional)
- btn_cancel: Cancel
- btn_confirm: Add
- uploading: Uploading
+ label: Descrição (opcional)
+ btn_cancel: Cancelar
+ btn_confirm: Adicionar
+ uploading: Enviando
indent:
- text: Indent
+ text: Identação
outdent:
- text: Outdent
+ text: Não identado
italic:
- text: Emphasis
+ text: Emphase
link:
- text: Hyperlink
- add_link: Add hyperlink
+ text: Superlink (Hyperlink)
+ add_link: Adicionar superlink (hyperlink)
form:
fields:
url:
label: URL
msg:
- empty: URL cannot be empty.
+ empty: URL não pode ser vazia.
name:
- label: Description (optional)
- btn_cancel: Cancel
- btn_confirm: Add
+ label: Descrição (opcional)
+ btn_cancel: Cancelar
+ btn_confirm: Adicionar
ordered_list:
- text: Numbered List
+ text: Lista numerada
unordered_list:
- text: Bulleted List
+ text: Lista com marcadores
table:
- text: Table
- heading: Heading
- cell: Cell
+ text: Tabela
+ Cabeçalho: Cabeçalho
+ cell: Célula
close_modal:
- title: I am closing this post as...
- btn_cancel: Cancel
- btn_submit: Submit
+ title: Estou fechando este post como...
+ btn_cancel: Cancelar
+ btn_submit: Enviar
remark:
- empty: Cannot be empty.
+ empty: Não pode ser vazio.
msg:
- empty: Please select a reason.
+ empty: Por favor selecione um motivo.
report_modal:
flag_title: I am flagging to report this post as...
- close_title: I am closing this post as...
- review_question_title: Review question
- review_answer_title: Review answer
- review_comment_title: Review comment
- btn_cancel: Cancel
- btn_submit: Submit
+ close_title: Estou fechando este post como...
+ review_question_title: Revisar pergunta
+ review_answer_title: Revisar resposta
+ review_comment_title: Revisar comentário
+ btn_cancel: Cancelar
+ btn_submit: Enviar
remark:
- empty: Cannot be empty.
+ empty: Não pode ser vazio.
msg:
- empty: Please select a reason.
+ empty: Por favor selecione um motivo.
tag_modal:
- title: Create new tag
+ title: Criar novo marcador
form:
fields:
display_name:
- label: Display Name
+ label: Nome de exibição
msg:
- empty: Display name cannot be empty.
- range: Display name up to 35 characters.
+ empty: Nome de exibição não pode ser vazio.
+ range: Nome de exibição tem que ter até 35 caracteres.
slug_name:
- label: URL Slug
- desc: 'Must use the character set "a-z", "0-9", "+ # - ."'
+ label: Slug de URL
+ desc: 'Deve usar o conjunto de caracteres "a-z", "0-9", "+ # - ."'
msg:
- empty: URL slug cannot be empty.
- range: URL slug up to 35 characters.
- character: URL slug contains unallowed character set.
+ empty: URL slug não pode ser vazio.
+ range: URL slug até 35 caracteres.
+ character: URL slug contém conjunto de caracteres não permitido.
desc:
- label: Description (optional)
- btn_cancel: Cancel
- btn_submit: Submit
+ label: Descrição (opcional)
+ btn_cancel: Cancelar
+ btn_submit: Enviar
tag_info:
- created_at: Created
- edited_at: Edited
- history: History
+ created_at: Criado
+ edited_at: Editado
+ history: Histórico
synonyms:
- title: Synonyms
- text: The following tags will be remapped to
- empty: No synonyms found.
- btn_add: Add a synonym
- btn_edit: Edit
- btn_save: Save
- synonyms_text: The following tags will be remapped to
+ title: Sinônimos
+ text: Os marcadores a seguir serão re-mapeados para
+ empty: Sinônimos não encotrados.
+ btn_add: Adicionar um sinônimo
+ btn_edit: Editar
+ btn_save: Salvar
+ synonyms_text: Os marcadores a seguir serão re-mapeados para
delete:
- title: Delete this tag
+ title: Deletar este marcador
content: >-
-
We do not allow deleting tag with posts.
Please remove this tag from the posts first.
- content2: Are you sure you wish to delete?
- close: Close
+
Não permitimos a exclusão de marcadores com postagens.
Por favor, remova este marcador das postagens primeiro.
You could use the edit link to refine and improve your existing answer, instead.
- empty: Answer cannot be empty.
- characters: content must be at least 6 characters in length.
+
Tem certeza de que deseja adicionar outra resposta?
Você pode usar o link de edição para refinar e melhorar uma resposta existente.
+ empty: Resposta não pode ser vazio.
+ caracteres: conteúdo deve ser pelo menos 6 caracteres em comprimento.
reopen:
- title: Reopen this post
- content: Are you sure you want to reopen?
- success: This post has been reopened
+ title: Reabrir esta postagem
+ content: Tem certeza que deseja reabrir?
+ success: Esta postagem foi reaberta
delete:
- title: Delete this post
+ title: Excluir esta postagem
question: >-
- We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.
Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ Nós não recomendamos excluindo perguntas com respostas porque isso priva os futuros leitores desse conhecimento.
Repeated deletion of answered questions can result in a sua account being blocked from asking. Você tem certeza que deseja deletar?
answer_accepted: >-
-
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
- other: Are you sure you wish to delete?
- tip_question_deleted: This post has been deleted
- tip_answer_deleted: This answer has been deleted
+
Nós não recomendamos deleting accepted answer porque isso priva os futuros leitores desse conhecimento.
Repeated deletion of accepted answers can result in a sua account being blocked from answering. Você tem certeza que deseja deletar?
+ other: Você tem certeza que deseja deletar?
+ tip_question_deleted: Esta postagem foi deletada
+ tip_answer_deleted: Esta resposta foi deletada
btns:
- confirm: Confirm
- cancel: Cancel
- save: Save
- delete: Delete
- login: Log in
- signup: Sign up
- logout: Log out
- verify: Verify
- add_question: Add question
- approve: Approve
- reject: Reject
- skip: Skip
+ confirm: Confirmar
+ cancel: Cancelar
+ save: Salvar
+ delete: Deletar
+ login: Entrar
+ signup: Cadastrar-se
+ logout: Sair
+ verify: Verificar
+ add_question: Adicionar pergunta
+ approve: Aprovar
+ reject: Rejetar
+ skip: Pular
search:
- title: Search Results
- keywords: Keywords
- options: Options
- follow: Follow
- following: Following
- counts: "{{count}} Results"
- more: More
+ title: Procurar Resultados
+ keywords: Palavras-chave
+ options: Opções
+ follow: Seguir
+ following: Seguindo
+ counts: "{{count}} Resultados"
+ more: Mais
sort_btns:
- relevance: Relevance
- newest: Newest
- active: Active
- score: Score
- more: More
+ relevance: Relevância
+ newest: Mais recente
+ active: Ativar
+ score: Pontuação
+ more: Mais
tips:
- title: Advanced Search Tips
- tag: "<1>[tag]1> search withing a tag"
- user: "<1>user:username1> search by author"
- answer: "<1>answers:01> unanswered questions"
- score: "<1>score:31> posts with a 3+ score"
- question: "<1>is:question1> search questions"
- is_answer: "<1>is:answer1> search answers"
- empty: We couldn't find anything. Try different or less specific keywords.
+ title: Dicas de Pesquisa Avançada
+ tag: "<1>[tag]1> pesquisar dentro de um marcador"
+ user: "<1>user:username1> buscar por autor"
+ answer: "<1>answers:01> perguntas não respondidas"
+ score: "<1>score:31> postagens com mais de 3+ placares"
+ question: "<1>is:question1> buscar perguntas"
+ is_answer: "<1>is:answer1> buscar respostas"
+ empty: Não conseguimos encontrar nada. Tente palavras-chave diferentes ou menos específicas.
share:
- name: Share
- copy: Copy link
- via: Share post via...
- copied: Copied
- facebook: Share to Facebook
- twitter: Share to Twitter
- cannot_vote_for_self: You can't vote for your own post
+ name: Compartilhar
+ copy: Copiar link
+ via: Compartilhar postagem via...
+ copied: Copiado
+ facebook: Compartilhar no Facebook
+ twitter: Compartilhar no Twitter
+ cannot_vote_for_self: Você não pode votar na sua própria postagem
modal_confirm:
- title: Error...
+ title: Erro...
account_result:
- page_title: Welcome to {{site_name}}
- success: Your new account is confirmed; you will be redirected to the home page.
- link: Continue to homepage
+ page_title: Bem vindo ao {{site_name}}
+ success: A sua nova conta está confirmada; você será redirecionado para a página inicial.
+ link: Continuar para a página inicial.
invalid: >-
- Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ Desculpe, este link de confirmação não é mais válido. Talvez a sua já está ativa.
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?
+ Desculpe, este link de confirmação não é mais válido. Talvez o seu e-mail já tenha sido alterado.
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
+ page_title: Cancelar subscrição
+ success_title: Cancelamento de inscrição bem-sucedido
+ success_desc: Você foi removido com sucesso desta lista de assinantes e não receberá mais nenhum e-mail nosso.
+ link: Mudar configurações
question:
- following_tags: Following Tags
- edit: Edit
- save: Save
- follow_tag_tip: Follow tags to curate your list of questions.
- hot_questions: Hot Questions
- all_questions: All Questions
- x_questions: "{{ count }} Questions"
- x_answers: "{{ count }} answers"
- questions: Questions
- answers: Answers
- newest: Newest
- active: Active
- frequent: Frequent
- score: Score
- unanswered: Unanswered
- modified: modified
- answered: answered
- asked: asked
- closed: closed
- follow_a_tag: Follow a tag
- more: More
+ following_tags: Seguindo Marcadores
+ edit: Editar
+ save: Salvar
+ follow_tag_tip: Seguir tags to curate a sua lista de perguntas.
+ hot_questions: Perguntas quentes
+ all_questions: Todas Perguntas
+ x_questions: "{{ count }} perguntas"
+ x_answers: "{{ count }} respostas"
+ questions: Perguntas
+ answers: Respostas
+ newest: Mais recente
+ active: Ativo
+ frequent: Frequente
+ score: Pontuação
+ unanswered: Não Respondido
+ modified: modificado
+ answered: respondido
+ asked: perguntado
+ closed: fechado
+ follow_a_tag: Seguir o marcador
+ more: Mais
personal:
- overview: Overview
- answers: Answers
- answer: answer
- questions: Questions
- question: question
- bookmarks: Bookmarks
- reputation: Reputation
- comments: Comments
- votes: Votes
- newest: Newest
- score: Score
- edit_profile: Edit Profile
- visited_x_days: "Visited {{ count }} days"
+ overview: Visão geral
+ answers: Respostas
+ answer: resposta
+ questions: Perguntas
+ question: pergunta
+ bookmarks: Favoritas
+ reputation: Reputação
+ comments: Comentários
+ Votos: Votos
+ newest: Mais recente
+ score: Pontuação
+ edit_profile: Editar Perfil
+ visited_x_days: "Visitado {{ count }} dias"
viewed: Viewed
- joined: Joined
- last_login: Seen
- about_me: About Me
- about_me_empty: "// Hello, World !"
- top_answers: Top Answers
- top_questions: Top Questions
- stats: Stats
- list_empty: No posts found. Perhaps you'd like to select a different tab?
- accepted: Accepted
- answered: answered
- asked: asked
- upvote: upvote
- downvote: downvote
- mod_short: Mod
- mod_long: Moderators
- x_reputation: reputation
- x_votes: votes received
- x_answers: answers
- x_questions: questions
+ joined: Ingressou
+ last_login: Visto
+ about_me: Sobre mim
+ about_me_empty: "// Olá, Mundo !"
+ top_answers: Melhores Respostas
+ top_questions: Melhores Perguntas
+ stats: Estatísticas
+ list_empty: Postagens não encontradas. Talvez você queira selecionar uma guia diferente?
+ accepted: Aceito
+ answered: respondido
+ asked: perguntado
+ upvote: voto positivo
+ downvote: voto negativo
+ mod_short: Moderador
+ mod_long: Moderadores
+ x_reputation: reputação
+ x_Votos: votos recebidos
+ x_answers: respostas
+ x_questions: perguntas
install:
- title: Answer
- next: Next
- done: Done
- config_yaml_error: Can't create the config.yaml file.
+ title: Resposta
+ next: Proximo
+ done: Completo
+ config_yaml_error: Não é possível criar o arquivo config.yaml.
lang:
- label: Please Choose a Language
+ label: Por favor Escolha um Idioma
db_type:
label: Database Engine
db_username:
- label: Username
+ label: Nome de usuário
placeholder: root
- msg: Username cannot be empty.
+ msg: Nome de usuário não pode ser vazio.
db_password:
- label: Password
+ label: Senha
placeholder: root
- msg: Password cannot be empty.
+ msg: Senha não pode ser vazio.
db_host:
label: Database Host
placeholder: "db:3306"
- msg: Database Host cannot be empty.
+ msg: Database Host não pode ser vazio.
db_name:
- label: Database Name
+ label: Database Nome
placeholder: answer
- msg: Database Name cannot be empty.
+ msg: Database Nome não pode ser vazio.
db_file:
label: Database File
placeholder: /data/answer.db
- msg: Database File cannot be empty.
+ msg: Database File não pode ser vazio.
config_yaml:
title: Create config.yaml
label: The config.yaml file created.
@@ -917,37 +917,37 @@ ui:
You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
info: After you've done that, click "Next" button.
site_information: Site Information
- admin_account: Admin Account
+ admin_account: Administrador Conta
site_name:
- label: Site Name
- msg: Site Name cannot be empty.
+ label: Site Nome
+ msg: Site Nome não pode ser vazio.
site_url:
label: Site URL
- text: The address of your site.
+ text: The address of a sua site.
msg:
- empty: Site URL cannot be empty.
+ empty: Site URL não pode ser vazio.
incorrect: Site URL incorrect format.
contact_email:
- label: Contact Email
+ label: E-mail par contato
text: Email address of key contact responsible for this site.
msg:
- empty: Contact Email cannot be empty.
- incorrect: Contact Email incorrect format.
+ empty: E-mail par contato não pode ser vazio.
+ incorrect: E-mail par contato incorrect format.
admin_name:
- label: Name
- msg: Name cannot be empty.
+ label: Nome
+ msg: Nome não pode ser vazio.
admin_password:
- label: Password
+ label: Senha
text: >-
- You will need this password to log in. Please store it in a secure location.
- msg: Password cannot be empty.
+ You will need this password to log in. Por favor store it in a secure location.
+ msg: Senha não pode ser vazio.
admin_email:
label: Email
text: You will need this email to log in.
msg:
- empty: Email cannot be empty.
+ empty: Email não pode ser vazio.
incorrect: Email incorrect format.
- ready_title: Your Answer is Ready!
+ ready_title: Your Resposta is Ready!
ready_desc: >-
If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
good_luck: "Have fun, and good luck!"
@@ -957,272 +957,272 @@ ui:
install_now: You may try <1>installing now1>.
installed: Already installed
installed_desc: >-
- You appear to have already installed. To reinstall please clear your old database tables first.
+ You appear to have already installed. To reinstall please clear a sua old database tables first.
db_failed: Database connection failed
db_failed_desc: >-
- This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host’s database server is down.
+ This either means that the database information in a sua <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean a sua host’s database server is down.
counts:
- views: views
- votes: votes
- answers: answers
- accepted: Accepted
+ views: visualizações
+ Votos: votos
+ answers: respostas
+ accepted: Aceito
page_404:
- desc: "Unfortunately, this page doesn't exist."
- back_home: Back to homepage
+ desc: "Infelizmente, esta postagem não existe mais."
+ back_home: Voltar para a página inicial
page_50X:
- desc: The server encountered an error and could not complete your request.
- back_home: Back to homepage
+ desc: O servidor encontrou um erro e não pôde concluir uma solicitação sua.
+ back_home: Voltar para a página inicial
page_maintenance:
- desc: "We are under maintenance, we'll be back soon."
+ desc: "Estamos em manutenção, voltaremos em breve."
nav_menus:
- dashboard: Dashboard
- contents: Contents
- questions: Questions
- answers: Answers
- users: Users
- flags: Flags
- settings: Settings
- general: General
+ dashboard: Painel
+ contents: Conteúdos
+ questions: Perguntas
+ answers: Respostas
+ users: Usuários
+ flags: Marcadores
+ settings: Configurações
+ general: Geral
interface: Interface
smtp: SMTP
- branding: Branding
+ branding: Marca
legal: Legal
- write: Write
- tos: Terms of Service
- privacy: Privacy
+ write: Escrever
+ tos: Termos de Serviços
+ privacy: Privacidade
seo: SEO
- customize: Customize
- themes: Themes
+ customize: Personalização
+ Temas: Temas
css-html: CSS/HTML
login: Login
admin:
admin_header:
- title: Admin
+ title: Administrador
dashboard:
- title: Dashboard
- welcome: Welcome to Answer Admin!
- site_statistics: Site Statistics
- questions: "Questions:"
- answers: "Answers:"
- comments: "Comments:"
- votes: "Votes:"
- active_users: "Active users:"
- flags: "Flags:"
- site_health_status: Site Health Status
- version: "Version:"
+ title: Painel
+ welcome: Bem vindo ao Answer Administrador!
+ site_statistics: Estatísticas do Site
+ questions: "Perguntas:"
+ answers: "Respostas:"
+ comments: "Comentários:"
+ Votos: "Votos:"
+ active_users: "Usuários ativos:"
+ flags: "Marcadores:"
+ site_health_status: Estatísticas da saúde do site
+ version: "Versão:"
https: "HTTPS:"
- uploading_files: "Uploading files:"
+ uploading_files: "Enviando arquivos:"
smtp: "SMTP:"
- timezone: "Timezone:"
- system_info: System Info
- storage_used: "Storage used:"
- uptime: "Uptime:"
- 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"
- not_allowed: Not allowed
- allowed: Allowed
- enabled: Enabled
- disabled: Disabled
+ timezone: "Fuso horário:"
+ system_info: Informações do Sistema
+ storage_used: "Armazenamento usado:"
+ uptime: "Tempo de atividade:"
+ answer_links: Links das Respostas
+ documents: Documentos
+ feedback: Opinião
+ support: Supporte
+ review: Revisar
+ config: Configurações
+ update_to: Atualizar ao
+ latest: Ultimo
+ check_failed: Falha na verificação
+ "yes": "Sim"
+ "no": "Não"
+ not_allowed: Não permitido
+ allowed: Permitido
+ enabled: Ativo
+ disabled: Disponível
flags:
- title: Flags
- pending: Pending
- completed: Completed
- flagged: Flagged
- created: Created
- action: Action
- review: Review
+ title: Marcadores
+ pending: Pendente
+ completed: Completo
+ flagged: Marcado
+ created: Criado
+ action: Ação
+ review: Revisar
change_modal:
- title: Change user status to...
- btn_cancel: Cancel
- btn_submit: Submit
+ title: Mudar user status to...
+ btn_cancel: Cancelar
+ btn_submit: Enviar
normal_name: normal
- normal_desc: A normal user can ask and answer questions.
- suspended_name: suspended
- suspended_desc: A suspended user can't log in.
- deleted_name: deleted
- deleted_desc: "Delete profile, authentication associations."
- inactive_name: inactive
- inactive_desc: An inactive user must re-validate their email.
- confirm_title: Delete this user
- confirm_content: Are you sure you want to delete this user? This is permanent!
- confirm_btn: Delete
+ normal_desc: Um usuário normal pode fazer e responder perguntas.
+ suspended_name: suspenso
+ suspended_desc: Um usuário suspenso não pode fazer login.
+ deleted_name: removido
+ deleted_desc: "Deletar perfil, associações de autenticação."
+ inactive_name: inativo
+ inactive_desc: Um usuário inativo deve revalidar seu e-mail.
+ confirm_title: Remover este usuário
+ confirm_content: Tem certeza de que deseja excluir este usuário? Isso é permanente!
+ confirm_btn: Deletar
msg:
- empty: Please select a reason.
+ empty: Por favor selecione um motivo.
status_modal:
- title: "Change {{ type }} status to..."
+ title: "Mudar {{ type }} status para..."
normal_name: normal
- normal_desc: A normal post available to everyone.
- closed_name: closed
- closed_desc: "A closed question can't answer, but still can edit, vote and comment."
- deleted_name: deleted
- deleted_desc: All reputation gained and lost will be restored.
- btn_cancel: Cancel
- btn_submit: Submit
- btn_next: Next
+ normal_desc: Um post normal disponível para todos.
+ closed_name: fechado
+ closed_desc: "Uma pergunta fechada não pode responder, mas ainda pode editar, votar e comentar."
+ deleted_name: removido
+ deleted_desc: Toda reputação ganha e perdida será restaurada.
+ btn_cancel: Cancelar
+ btn_submit: Enviar
+ btn_next: Próximo
user_role_modal:
- title: Change user role to...
- btn_cancel: Cancel
- btn_submit: Submit
+ title: Altere a função do usuário para...
+ btn_cancel: Cancelar
+ btn_submit: Enviar
users:
- title: Users
- name: Name
+ title: Usuários
+ name: Nome
email: Email
- reputation: Reputation
- created_at: Created Time
- delete_at: Deleted Time
- suspend_at: Suspended Time
+ reputation: Reputação
+ created_at: Hora de criação
+ delete_at: Hora da remoção
+ suspend_at: Hora da suspensão
status: Status
- role: Role
- action: Action
- change: Change
- all: All
- staff: Staff
- inactive: Inactive
- suspended: Suspended
- deleted: Deleted
+ role: Função
+ action: Ação
+ change: Mudar
+ all: Todos
+ staff: Funcionários
+ inactive: Inativo
+ suspended: Suspenso
+ deleted: Removido
normal: Normal
- Moderator: Moderator
- Admin: Admin
- User: User
+ Moderador: Moderador
+ Administrador: Administrador
+ Usuário: Usuário
filter:
- placeholder: "Filter by name, user:id"
- set_new_password: Set new password
- change_status: Change status
- change_role: Change role
- show_logs: Show logs
- add_user: Add user
+ placeholder: "Filtrar por nome, user:id"
+ set_new_password: Configurar nova senha
+ change_status: Mudar status
+ change_role: Mudar função
+ show_logs: Mostrar registros
+ add_user: Adicionar usuário
new_password_modal:
- title: Set new password
+ title: Configurar nova senha
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: Senha
+ text: O usuário será desconectado e precisará fazer login novamente.
+ msg: Senha de ver entre 8-32 caracteres em comprimento.
+ btn_cancel: Cancelar
+ btn_submit: Enviar
user_modal:
- title: Add new user
+ title: Adicionar novo usuário
form:
fields:
display_name:
- label: Display Name
- msg: Display Name must be at 3-30 characters in length.
+ label: Nome de exibição
+ msg: Nome de exibição deve ser 3-30 caracteres em comprimento.
email:
label: Email
- msg: Email is not valid.
+ msg: Email não é válido.
password:
- label: Password
- msg: Password must be at 8-32 characters in length.
- btn_cancel: Cancel
- btn_submit: Submit
+ label: Senha
+ msg: Senha deve ser 8-32 caracteres em comprimento.
+ btn_cancel: Cancelar
+ btn_submit: Enviar
questions:
- page_title: Questions
+ page_title: Perguntas
normal: Normal
- closed: Closed
- deleted: Deleted
- post: Post
- votes: Votes
- answers: Answers
- created: Created
+ closed: Fechado
+ deleted: Removido
+ post: Publicação
+ Votos: Votos
+ answers: Respostas
+ created: Criado
status: Status
- action: Action
- change: Change
+ action: Ação
+ change: Mudar
filter:
- placeholder: "Filter by title, question:id"
+ placeholder: "Filtrar por título, question:id"
answers:
- page_title: Answers
+ page_title: Respostas
normal: Normal
- deleted: Deleted
- post: Post
- votes: Votes
- created: Created
+ deleted: Removido
+ post: Publicação
+ Votos: Votos
+ created: Criado
status: Status
- action: Action
- change: Change
+ action: Ação
+ change: Mudar
filter:
- placeholder: "Filter by title, answer:id"
+ placeholder: "Filtrar por título, answer:id"
general:
page_title: General
name:
- label: Site Name
- msg: Site name cannot be empty.
- text: "The name of this site, as used in the title tag."
+ label: Site Nome
+ msg: Site name não pode ser vazio.
+ text: "O nome deste site, conforme usado na tag de título."
site_url:
- label: Site URL
- msg: Site url cannot be empty.
- validate: Please enter a valid URL.
- text: The address of your site.
+ label: URL do Site
+ msg: Site url não pode ser vazio.
+ validate: Por favor digite uma URL válida.
+ text: O endereço do seu site.
short_desc:
- label: Short Site Description (optional)
- msg: Short site description cannot be empty.
- text: "Short description, as used in the title tag on homepage."
+ label: Breve Descrição do site (opcional)
+ msg: Breve Descrição do site não pode ser vazio.
+ text: "Breve descrição, conforme usado na tag de título na página inicial."
desc:
- label: Site Description (optional)
- msg: Site description cannot be empty.
- text: "Describe this site in one sentence, as used in the meta description tag."
+ label: Site Descrição (opcional)
+ msg: Descrição do site não pode ser vazio.
+ text: "Descreva este site em uma única sentença, conforme usado na meta tag de descrição."
contact_email:
- label: Contact Email
- msg: Contact email cannot be empty.
- validate: Contact email is not valid.
- text: Email address of key contact responsible for this site.
+ label: E-mail para contato
+ msg: E-mail par contato não pode ser vazio.
+ validate: E-mail par contato não é válido.
+ text: Endereço de e-mail do principal contato responsável por este site.
interface:
page_title: Interface
logo:
- label: Logo (optional)
- msg: Site logo cannot be empty.
- text: You can upload your image or <1>reset1> it to the site title text.
- theme:
- label: Theme
- msg: Theme cannot be empty.
- text: Select an existing theme.
+ label: Logo (opcional)
+ msg: Site logo não pode ser vazio.
+ text: Você pode enviar a sua image ou <1>reiniciar1> ao texto do título do site.
+ Tema:
+ label: Tema
+ msg: Tema não pode ser vazio.
+ text: Selecione um tema existente.
language:
- label: Interface Language
- msg: Interface language cannot be empty.
- text: User interface language. It will change when you refresh the page.
+ label: Idioma da interface
+ msg: Idioma da Interface não pode ser vazio.
+ text: Idioma da interface do Usuário. Ele mudará quando você atualizar a página.
time_zone:
- label: Timezone
- msg: Timezone cannot be empty.
- text: Choose a city in the same timezone as you.
+ label: Fuso horário
+ msg: Fuso horário não pode ser vazio.
+ text: Escolha a cidade no mesmo fuso horário que você.
smtp:
page_title: SMTP
from_email:
- label: From Email
- msg: From email cannot be empty.
- text: The email address which emails are sent from.
+ label: E-mail de origem
+ msg: E-mail de origem não pode ser vazio.
+ text: O endereço de e-mail de onde os e-mails são enviados.
from_name:
- label: From Name
- msg: From name cannot be empty.
- text: The name which emails are sent from.
+ label: Nome de origem
+ msg: Nome de origem não pode ser vazio.
+ text: O nome de onde os e-mails são enviados.
smtp_host:
label: SMTP Host
- msg: SMTP host cannot be empty.
- text: Your mail server.
+ msg: SMTP host não pode ser vazio.
+ text: O seu servidor de e-mails.
encryption:
- label: Encryption
- msg: Encryption cannot be empty.
- text: For most servers SSL is the recommended option.
+ label: Criptografia
+ msg: Criptografia não pode ser vazio.
+ text: Para a maioria dos servidores SSL é a opção recomendada.
ssl: SSL
none: None
smtp_port:
label: SMTP Port
msg: SMTP port must be number 1 ~ 65535.
- text: The port to your mail server.
+ text: The port to a sua mail server.
smtp_username:
- label: SMTP Username
- msg: SMTP username cannot be empty.
+ label: SMTP Nome de usuário
+ msg: SMTP username não pode ser vazio.
smtp_password:
- label: SMTP Password
- msg: SMTP password cannot be empty.
+ label: SMTP Senha
+ msg: SMTP password não pode ser vazio.
test_email_recipient:
label: Test Email Recipients
text: Provide email address that will receive test sends.
@@ -1230,25 +1230,25 @@ ui:
smtp_authentication:
label: Enable authentication
title: SMTP Authentication
- msg: SMTP authentication cannot be empty.
+ msg: SMTP authentication não pode ser vazio.
"yes": "Yes"
"no": "No"
branding:
page_title: Branding
logo:
- label: Logo (optional)
- 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: Logo (opcional)
+ msg: Logo não pode ser vazio.
+ text: The logo image at the top left of a sua 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.
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: Mobile Logo (opcional)
+ text: The logo used on mobile version of a sua site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
- label: Square Icon (optional)
- msg: Square icon cannot be empty.
- text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ label: Square Icon (opcional)
+ msg: Square icon não pode ser vazio.
+ text: Imagem used as the base for metadata icons. Should ideally be larger than 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: Favicon (opcional)
+ text: A favicon for a sua 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.
legal:
page_title: Legal
terms_of_service:
@@ -1260,34 +1260,34 @@ ui:
write:
page_title: Write
recommend_tags:
- label: Recommend Tags
- text: "Please input tag slug above, one tag per line."
+ label: Recommend Marcadores
+ text: "Por favor input tag slug above, one tag per line."
required_tag:
title: Required Tag
- label: Set recommend tag as required
- text: "Every new question must have at least one recommend tag."
+ label: Set recommend tag as requirido
+ text: "Every new question must have ao menos one recommend tag."
reserved_tags:
- label: Reserved Tags
+ label: Reserved Marcadores
text: "Reserved tags can only be added to a post by moderator."
seo:
page_title: SEO
permalink:
label: Permalink
- text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ text: Custom URL structures can improve the usability, and forward-compatibility of a sua links.
robots:
label: robots.txt
text: This will permanently override any related site settings.
- themes:
- page_title: Themes
- themes:
- label: Themes
- text: Select an existing theme.
+ Temas:
+ page_title: Temas
+ Temas:
+ label: Temas
+ text: Select an existing Tema.
navbar_style:
label: Navbar Style
- text: Select an existing theme.
+ text: Select an existing Tema.
primary_color:
label: Primary Color
- text: Modify the colors used by your themes
+ text: Modify the colors used by a sua Temas
css_and_html:
page_title: CSS and HTML
custom_css:
@@ -1310,23 +1310,23 @@ ui:
text: Turn off to prevent anyone from creating a new account.
private:
title: Private
- label: Login required
+ label: Login requirido
text: Only logged in users can access this community.
form:
- empty: cannot be empty
+ empty: não pode ser vazio
invalid: is invalid
- btn_submit: Save
+ btn_submit: Salvar
not_found_props: "Required property {{ key }} not found."
page_review:
- review: Review
+ review: Revisar
proposed: proposed
- question_edit: Question edit
- answer_edit: Answer edit
+ question_edit: Pergunta edit
+ answer_edit: Resposta edit
tag_edit: Tag edit
- edit_summary: Edit summary
- edit_question: Edit question
- edit_answer: Edit answer
- edit_tag: Edit tag
+ edit_summary: Editar summary
+ edit_question: Editar question
+ edit_answer: Editar answer
+ edit_tag: Editar tag
empty: No review tasks left.
timeline:
undeleted: undeleted
@@ -1343,9 +1343,9 @@ ui:
closed: closed
reopened: reopened
created: created
- title: "History for"
+ title: "Histórico for"
tag_title: "Timeline for"
- show_votes: "Show votes"
+ show_Votos: "Show Votos"
n_or_a: N/A
title_for_question: "Timeline for"
title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
@@ -1356,11 +1356,11 @@ ui:
comment: Comment
no_data: "We couldn't find anything."
users:
- title: Users
- users_with_the_most_reputation: Users with the highest reputation scores
- users_with_the_most_vote: Users who voted the most
+ title: Usuários
+ users_with_the_most_reputation: Usuários with the highest reputation scores
+ users_with_the_most_vote: Usuários who voted the most
staffs: Our community staff
reputation: reputation
- votes: votes
+ Votos: Votos
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 8143518b..604c7e23 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -1,4 +1,5 @@
#The following fields are used for back-end
+
backend:
base:
success:
@@ -80,6 +81,8 @@ backend:
new_password_same_as_previous_setting:
other: 新密码与之前的设置相同
question:
+ already_deleted:
+ other: 该内容已被删除
not_found:
other: 问题未找到
cannot_deleted:
diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go
index a33e33cc..17c00070 100644
--- a/internal/base/reason/reason.go
+++ b/internal/base/reason/reason.go
@@ -21,6 +21,7 @@ const (
QuestionCannotDeleted = "error.question.cannot_deleted"
QuestionCannotClose = "error.question.cannot_close"
QuestionCannotUpdate = "error.question.cannot_update"
+ QuestionAlreadyDeleted = "error.question.already_deleted"
AnswerNotFound = "error.answer.not_found"
AnswerCannotDeleted = "error.answer.cannot_deleted"
AnswerCannotUpdate = "error.answer.cannot_update"
@@ -64,4 +65,6 @@ const (
TagCannotSetSynonymAsItself = "error.tag.cannot_set_synonym_as_itself"
NotAllowedRegistration = "error.user.not_allowed_registration"
SMTPConfigFromNameCannotBeEmail = "error.smtp.config_from_name_cannot_be_email"
+ AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
+ AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
)
diff --git a/internal/base/translator/provider.go b/internal/base/translator/provider.go
index bff57486..e992aec3 100644
--- a/internal/base/translator/provider.go
+++ b/internal/base/translator/provider.go
@@ -20,6 +20,8 @@ var GlobalTrans i18n.Translator
type LangOption struct {
Label string `json:"label"`
Value string `json:"value"`
+ // Translation completion percentage
+ Progress int `json:"progress"`
}
// DefaultLangOption default language option. If user config the language is default, the language option is admin choose.
@@ -94,6 +96,11 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
return nil, fmt.Errorf("i18n file parsing failed: %s", err)
}
LanguageOptions = s.LangOption
+ for _, option := range LanguageOptions {
+ if option.Progress != 100 {
+ option.Label = fmt.Sprintf("%s (%d%%)", option.Label, option.Progress)
+ }
+ }
return GlobalTrans, err
}
diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go
index 79ed9681..6b74b754 100644
--- a/internal/controller/comment_controller.go
+++ b/internal/controller/comment_controller.go
@@ -112,6 +112,18 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
+ canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
+ permission.CommentAdd,
+ permission.CommentEdit,
+ permission.CommentDelete,
+ })
+ if err != nil {
+ handler.HandleResponse(ctx, err, nil)
+ return
+ }
+ req.CanAdd = canList[0]
+ req.CanEdit = canList[1]
+ req.CanDelete = canList[2]
can, err := cc.rankService.CheckOperationPermission(ctx, req.UserID, permission.CommentEdit, req.CommentID)
if err != nil {
handler.HandleResponse(ctx, err, nil)
@@ -122,8 +134,8 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
return
}
- err = cc.commentService.UpdateComment(ctx, req)
- handler.HandleResponse(ctx, err, nil)
+ resp, err := cc.commentService.UpdateComment(ctx, req)
+ handler.HandleResponse(ctx, err, resp)
}
// GetCommentWithPage get comment page
diff --git a/internal/controller/lang_controller.go b/internal/controller/lang_controller.go
index b48080ee..0cf2637a 100644
--- a/internal/controller/lang_controller.go
+++ b/internal/controller/lang_controller.go
@@ -41,7 +41,6 @@ func (u *LangController) GetLangMapping(ctx *gin.Context) {
// @Tags Lang
// @Produce json
// @Success 200 {object} handler.RespBody{}
-// @Router /answer/api/v1/language/options [get]
// @Router /answer/admin/api/language/options [get]
func (u *LangController) GetAdminLangOptions(ctx *gin.Context) {
handler.HandleResponse(ctx, nil, translator.LanguageOptions)
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index 894b4930..0ccd5ad3 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -404,22 +404,17 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
req := &schema.GetOtherUserInfoByUsernameReq{}
req.Username = username
userinfo, err := tc.templateRenderController.UserInfo(ctx, req)
-
if err != nil {
tc.Page404(ctx)
return
}
- if !userinfo.Has {
- tc.Page404(ctx)
- return
- }
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username)
siteInfo.Title = fmt.Sprintf("%s - %s", username, siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "homepage.html", siteInfo, gin.H{
"userinfo": userinfo,
- "bio": template.HTML(userinfo.Info.BioHTML),
+ "bio": template.HTML(userinfo.BioHTML),
})
}
@@ -451,6 +446,7 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
if !ok {
data["path"] = ""
}
+ ctx.Header("X-Frame-Options", "DENY")
ctx.HTML(code, tpl, data)
}
diff --git a/internal/controller/template_render/userinfo.go b/internal/controller/template_render/userinfo.go
index e10e2dfa..2186fac5 100644
--- a/internal/controller/template_render/userinfo.go
+++ b/internal/controller/template_render/userinfo.go
@@ -5,6 +5,6 @@ import (
"golang.org/x/net/context"
)
-func (q *TemplateRenderController) UserInfo(ctx context.Context, req *schema.GetOtherUserInfoByUsernameReq) (resp *schema.GetOtherUserInfoResp, err error) {
+func (q *TemplateRenderController) UserInfo(ctx context.Context, req *schema.GetOtherUserInfoByUsernameReq) (resp *schema.GetOtherUserInfoByUsernameResp, err error) {
return q.userService.GetOtherUserInfoByUsername(ctx, req.Username)
}
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index c2cf25bf..a1489e41 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -157,7 +157,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
return
}
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
- _, err := uc.userService.RetrievePassWord(ctx, req)
+ err := uc.userService.RetrievePassWord(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
@@ -203,6 +203,7 @@ func (uc *UserController) UserLogout(ctx *gin.Context) {
return
}
_ = uc.authService.RemoveUserCacheInfo(ctx, accessToken)
+ _ = uc.authService.RemoveAdminUserCacheInfo(ctx, accessToken)
handler.HandleResponse(ctx, nil, nil)
}
diff --git a/internal/controller_admin/user_backyard_controller.go b/internal/controller_admin/user_backyard_controller.go
index 848319b5..eaa06f73 100644
--- a/internal/controller_admin/user_backyard_controller.go
+++ b/internal/controller_admin/user_backyard_controller.go
@@ -34,6 +34,8 @@ func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) {
return
}
+ req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
+
err := uc.userService.UpdateUserStatus(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
diff --git a/internal/entity/answer_entity.go b/internal/entity/answer_entity.go
index 8c3bcdb1..43567efd 100644
--- a/internal/entity/answer_entity.go
+++ b/internal/entity/answer_entity.go
@@ -35,9 +35,11 @@ type Answer struct {
type AnswerSearch struct {
Answer
- Order string `json:"order_by" ` // default or updated
- Page int `json:"page" form:"page"` // Query number of pages
- PageSize int `json:"page_size" form:"page_size"` // Search page size
+ IncludeDeleted bool `json:"include_deleted"`
+ LoginUserID string `json:"login_user_id"`
+ Order string `json:"order_by"` // default or updated
+ Page int `json:"page" form:"page"` // Query number of pages
+ PageSize int `json:"page_size" form:"page_size"` // Search page size
}
type AdminAnswerSearch struct {
diff --git a/internal/install/install_req.go b/internal/install/install_req.go
index a465d6bd..96e16638 100644
--- a/internal/install/install_req.go
+++ b/internal/install/install_req.go
@@ -78,7 +78,7 @@ type InitEnvironmentResp struct {
// InitBaseInfoReq init base info request
type InitBaseInfoReq struct {
Language string `validate:"required,gt=0,lte=30" json:"lang"`
- SiteName string `validate:"required,gt=0,lte=30" json:"site_name"`
+ SiteName string `validate:"required,sanitizer,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=3,lte=30" json:"name"`
diff --git a/internal/repo/answer/answer_repo.go b/internal/repo/answer/answer_repo.go
index 97734343..48a54d6a 100644
--- a/internal/repo/answer/answer_repo.go
+++ b/internal/repo/answer/answer_repo.go
@@ -206,7 +206,9 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
default:
session = session.OrderBy("adopted desc,vote_count desc,created_at asc")
}
- session = session.And("status = ?", entity.AnswerStatusAvailable)
+ if !search.IncludeDeleted {
+ session = session.And("status = ? OR user_id = ?", entity.AnswerStatusAvailable, search.LoginUserID)
+ }
session = session.Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows)
diff --git a/internal/repo/captcha/captcha.go b/internal/repo/captcha/captcha.go
index bda41d1e..382e45e7 100644
--- a/internal/repo/captcha/captcha.go
+++ b/internal/repo/captcha/captcha.go
@@ -68,3 +68,11 @@ func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha stri
}
return captcha, nil
}
+
+func (cr *captchaRepo) DelCaptcha(ctx context.Context, key string) (err error) {
+ err = cr.data.Cache.Del(ctx, key)
+ if err != nil {
+ log.Debug(err)
+ }
+ return nil
+}
diff --git a/internal/router/ui.go b/internal/router/ui.go
index 2654a37e..86740dbc 100644
--- a/internal/router/ui.go
+++ b/internal/router/ui.go
@@ -106,6 +106,7 @@ func (a *UIRouter) Register(r *gin.Engine) {
default:
filePath = UIIndexFilePath
c.Header("content-type", "text/html;charset=utf-8")
+ c.Header("X-Frame-Options", "DENY")
}
file, err := ui.Build.ReadFile(filePath)
if err != nil {
diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go
index 7fc6a12d..3a67f2bf 100644
--- a/internal/schema/answer_schema.go
+++ b/internal/schema/answer_schema.go
@@ -83,6 +83,7 @@ type AnswerInfo struct {
VoteStatus string `json:"vote_status"`
VoteCount int `json:"vote_count"`
QuestionInfo *QuestionInfo `json:"question_info,omitempty"`
+ Status int `json:"status"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`
diff --git a/internal/schema/backyard_user_schema.go b/internal/schema/backyard_user_schema.go
index 57f3f183..995f0187 100644
--- a/internal/schema/backyard_user_schema.go
+++ b/internal/schema/backyard_user_schema.go
@@ -2,10 +2,9 @@ package schema
// UpdateUserStatusReq update user request
type UpdateUserStatusReq struct {
- // user id
- UserID string `validate:"required" json:"user_id"`
- // user status
- Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
+ UserID string `validate:"required" json:"user_id"`
+ Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
+ LoginUserID string `json:"-"`
}
const (
diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go
index dcc44bd5..5b045a07 100644
--- a/internal/schema/comment_schema.go
+++ b/internal/schema/comment_schema.go
@@ -53,6 +53,12 @@ type UpdateCommentReq struct {
// user id
UserID string `json:"-"`
IsAdmin bool `json:"-"`
+
+ CanAdd bool `json:"-"`
+ // whether user can edit it
+ CanEdit bool `json:"-"`
+ // whether user can delete it
+ CanDelete bool `json:"-"`
}
func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go
index b656b834..b0101141 100644
--- a/internal/schema/question_schema.go
+++ b/internal/schema/question_schema.go
@@ -176,11 +176,20 @@ type AdminQuestionInfo struct {
UserInfo *UserBasicInfo `json:"user_info"`
}
+type OperationLevel string
+
+const (
+ OperationLevelInfo OperationLevel = "info"
+ OperationLevelDanger OperationLevel = "danger"
+ OperationLevelWarning OperationLevel = "warning"
+)
+
type Operation struct {
- OperationType string `json:"operation_type"`
- OperationDescription string `json:"operation_description"`
- OperationMsg string `json:"operation_msg"`
- OperationTime int64 `json:"operation_time"`
+ Type string `json:"type"`
+ Description string `json:"description"`
+ Msg string `json:"msg"`
+ Time int64 `json:"time"`
+ Level OperationLevel `json:"level"`
}
type GetCloseTypeResp struct {
diff --git a/internal/schema/tag_schema.go b/internal/schema/tag_schema.go
index cc046b37..3eb8d389 100644
--- a/internal/schema/tag_schema.go
+++ b/internal/schema/tag_schema.go
@@ -90,7 +90,7 @@ type GetTagResp struct {
}
func (tr *GetTagResp) GetExcerpt() {
- excerpt := strings.TrimSpace(tr.OriginalText)
+ excerpt := strings.TrimSpace(tr.ParsedText)
idx := strings.Index(excerpt, "\n")
if idx >= 0 {
excerpt = excerpt[0:idx]
diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go
index 308568c8..e12a9051 100644
--- a/internal/schema/user_schema.go
+++ b/internal/schema/user_schema.go
@@ -309,7 +309,7 @@ func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, er
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
}
- req.BioHTML = converter.Markdown2HTML(req.Bio)
+ req.BioHTML = converter.Markdown2BasicHTML(req.Bio)
return nil, nil
}
@@ -386,7 +386,6 @@ type GetOtherUserInfoByUsernameReq struct {
type GetOtherUserInfoResp struct {
Info *GetOtherUserInfoByUsernameResp `json:"info"`
- Has bool `json:"has"`
}
type UserChangeEmailSendCodeReq struct {
diff --git a/internal/service/action/captcha_service.go b/internal/service/action/captcha_service.go
index a6ee8e04..84a189ed 100644
--- a/internal/service/action/captcha_service.go
+++ b/internal/service/action/captcha_service.go
@@ -16,6 +16,7 @@ import (
type CaptchaRepo interface {
SetCaptcha(ctx context.Context, key, captcha string) (err error)
GetCaptcha(ctx context.Context, key string) (captcha string, err error)
+ DelCaptcha(ctx context.Context, key string) (err error)
SetActionType(ctx context.Context, ip, actionType string, amount int) (err error)
GetActionType(ctx context.Context, ip, actionType string) (amount int, err error)
DelActionType(ctx context.Context, ip, actionType string) (err error)
@@ -143,6 +144,12 @@ func (cs *CaptchaService) GenerateCaptcha(ctx context.Context) (key, captchaBase
func (cs *CaptchaService) VerifyCaptcha(ctx context.Context, key, captcha string) (isCorrect bool, err error) {
realCaptcha, err := cs.captchaRepo.GetCaptcha(ctx, key)
if err != nil {
+ log.Error("VerifyCaptcha GetCaptcha Error", err.Error())
+ return false, nil
+ }
+ err = cs.captchaRepo.DelCaptcha(ctx, key)
+ if err != nil {
+ log.Error("VerifyCaptcha DelCaptcha Error", err.Error())
return false, nil
}
return strings.TrimSpace(captcha) == realCaptcha, nil
diff --git a/internal/service/answer_common/answer.go b/internal/service/answer_common/answer.go
index 758908e1..357484f2 100644
--- a/internal/service/answer_common/answer.go
+++ b/internal/service/answer_common/answer.go
@@ -73,6 +73,7 @@ func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *sc
}
info.UserID = data.UserID
info.UpdateUserID = data.LastEditUserID
+ info.Status = data.Status
return &info
}
diff --git a/internal/service/answer_service.go b/internal/service/answer_service.go
index 974482c9..f92784e1 100644
--- a/internal/service/answer_service.go
+++ b/internal/service/answer_service.go
@@ -164,7 +164,7 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
if err != nil {
log.Error("UpdateLastAnswer error", err.Error())
}
- err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
+ err = as.questionCommon.UpdatePostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@@ -232,6 +232,11 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", nil
}
+ if answerInfo.Status == entity.AnswerStatusDeleted {
+ err = errors.BadRequest(reason.AnswerCannotUpdate)
+ return "", err
+ }
+
//If the content is the same, ignore it
if answerInfo.OriginalText == req.Content {
return "", nil
@@ -268,7 +273,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
if err = as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "updated_at", "last_edit_user_id"}); err != nil {
return "", err
}
- err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
+ err = as.questionCommon.UpdatePostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@@ -473,6 +478,8 @@ func (as *AnswerService) SearchList(ctx context.Context, req *schema.AnswerListR
dbSearch.Page = req.Page
dbSearch.PageSize = req.PageSize
dbSearch.Order = req.Order
+ dbSearch.IncludeDeleted = req.CanDelete
+ dbSearch.LoginUserID = req.UserID
answerOriginalList, count, err := as.answerRepo.SearchList(ctx, &dbSearch)
if err != nil {
return list, count, err
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go
index 18b755ab..6a33ee90 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -209,24 +209,40 @@ func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveC
}
// UpdateComment update comment
-func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (err error) {
+func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (
+ resp *schema.GetCommentResp, err error) {
+ resp = &schema.GetCommentResp{}
+
old, exist, err := cs.commentCommonRepo.GetComment(ctx, req.CommentID)
if err != nil {
return
}
if !exist {
- return errors.BadRequest(reason.CommentNotFound)
+ return resp, errors.BadRequest(reason.CommentNotFound)
}
// user can edit the comment that was posted by himself before deadline.
if !req.IsAdmin && (time.Now().After(old.CreatedAt.Add(constant.CommentEditDeadline))) {
- return errors.BadRequest(reason.CommentCannotEditAfterDeadline)
+ return resp, errors.BadRequest(reason.CommentCannotEditAfterDeadline)
}
comment := &entity.Comment{}
_ = copier.Copy(comment, req)
comment.ID = req.CommentID
- return cs.commentRepo.UpdateComment(ctx, comment)
+ resp.SetFromComment(comment)
+ resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID,
+ time.Now(), req.CanEdit, req.CanDelete)
+ userInfo, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
+ if err != nil {
+ return nil, err
+ }
+ if exist {
+ resp.Username = userInfo.Username
+ resp.UserDisplayName = userInfo.DisplayName
+ resp.UserAvatar = userInfo.Avatar
+ resp.UserStatus = userInfo.Status
+ }
+ return resp, cs.commentRepo.UpdateComment(ctx, comment)
}
// GetComment get comment one
diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go
index 11868acd..e47645fe 100644
--- a/internal/service/dashboard/dashboard_service.go
+++ b/internal/service/dashboard/dashboard_service.go
@@ -89,6 +89,7 @@ func (ds *DashboardService) StatisticalByCache(ctx context.Context) (*schema.Das
}
startTime := time.Now().Unix() - schema.AppStartTime.Unix()
dashboardInfo.AppStartTime = fmt.Sprintf("%d", startTime)
+ dashboardInfo.VersionInfo.Version = constant.Version
return dashboardInfo, nil
}
diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go
index 23424d2c..926ca650 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -86,7 +86,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
}
}
-func (qs *QuestionCommon) UpdataPv(ctx context.Context, questionID string) error {
+func (qs *QuestionCommon) UpdatePv(ctx context.Context, questionID string) error {
return qs.questionRepo.UpdatePvCount(ctx, questionID)
}
@@ -112,14 +112,14 @@ func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionID, Answ
return qs.questionRepo.UpdateLastAnswer(ctx, question)
}
-func (qs *QuestionCommon) UpdataPostTime(ctx context.Context, questionID string) error {
+func (qs *QuestionCommon) UpdatePostTime(ctx context.Context, questionID string) error {
questioninfo := &entity.Question{}
now := time.Now()
questioninfo.ID = questionID
questioninfo.PostUpdateTime = now
return qs.questionRepo.UpdateQuestion(ctx, questioninfo, []string{"post_update_time"})
}
-func (qs *QuestionCommon) UpdataPostSetTime(ctx context.Context, questionID string, setTime time.Time) error {
+func (qs *QuestionCommon) UpdatePostSetTime(ctx context.Context, questionID string, setTime time.Time) error {
questioninfo := &entity.Question{}
questioninfo.ID = questionID
questioninfo.PostUpdateTime = setTime
@@ -148,7 +148,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
return showinfo, err
}
if !has {
- return showinfo, errors.BadRequest(reason.QuestionNotFound)
+ return showinfo, errors.NotFound(reason.QuestionNotFound)
}
showinfo = qs.ShowFormat(ctx, dbinfo)
@@ -170,10 +170,11 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
log.Error("json.Unmarshal QuestionCloseJson error", err.Error())
} else {
operation := &schema.Operation{}
- operation.OperationType = closeinfo.Name
- operation.OperationDescription = closeinfo.Description
- operation.OperationMsg = closemsg.CloseMsg
- operation.OperationTime = metainfo.CreatedAt.Unix()
+ operation.Type = closeinfo.Name
+ operation.Description = closeinfo.Description
+ operation.Msg = closemsg.CloseMsg
+ operation.Time = metainfo.CreatedAt.Unix()
+ operation.Level = schema.OperationLevelInfo
showinfo.Operation = operation
}
diff --git a/internal/service/question_service.go b/internal/service/question_service.go
index a21523b5..36bab816 100644
--- a/internal/service/question_service.go
+++ b/internal/service/question_service.go
@@ -470,6 +470,10 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
if !has {
return
}
+ if dbinfo.Status == entity.QuestionStatusDeleted {
+ err = errors.BadRequest(reason.QuestionCannotUpdate)
+ return nil, err
+ }
now := time.Now()
question := &entity.Question{}
@@ -614,12 +618,23 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
if err != nil {
return
}
+ // If the question is deleted, only the administrator and the author can view it
+ if question.Status == entity.QuestionStatusDeleted && !per.CanReopen && question.UserID != userID {
+ return nil, errors.NotFound(reason.QuestionNotFound)
+ }
if question.Status != entity.QuestionStatusClosed {
per.CanReopen = false
}
if question.Status == entity.QuestionStatusClosed {
per.CanClose = false
}
+ if question.Status == entity.QuestionStatusDeleted {
+ operation := &schema.Operation{}
+ operation.Msg = translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionAlreadyDeleted)
+ operation.Level = schema.OperationLevelDanger
+ question.Operation = operation
+ }
+
question.Description = htmltext.FetchExcerpt(question.HTML, "...", 240)
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen)
@@ -630,7 +645,7 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
func (qs *QuestionService) GetQuestionAndAddPV(ctx context.Context, questionID, loginUserID string,
per schema.QuestionPermission) (
resp *schema.QuestionInfo, err error) {
- err = qs.questioncommon.UpdataPv(ctx, questionID)
+ err = qs.questioncommon.UpdatePv(ctx, questionID)
if err != nil {
log.Error(err)
}
diff --git a/internal/service/revision_service.go b/internal/service/revision_service.go
index bb65db32..3e8c3605 100644
--- a/internal/service/revision_service.go
+++ b/internal/service/revision_service.go
@@ -191,7 +191,7 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem
if saveerr != nil {
return saveerr
}
- saveerr = rs.questionCommon.UpdataPostSetTime(ctx, answerinfo.QuestionID, PostUpdateTime)
+ saveerr = rs.questionCommon.UpdatePostSetTime(ctx, answerinfo.QuestionID, PostUpdateTime)
if saveerr != nil {
return saveerr
}
diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go
index 1fb118ec..db28ef09 100644
--- a/internal/service/tag/tag_service.go
+++ b/internal/service/tag/tag_service.go
@@ -102,7 +102,7 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
return nil, err
}
if !exist {
- return nil, errors.BadRequest(reason.TagNotFound)
+ return nil, errors.NotFound(reason.TagNotFound)
}
resp = &schema.GetTagResp{}
@@ -113,7 +113,7 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
return nil, err
}
if !exist {
- return nil, errors.BadRequest(reason.TagNotFound)
+ return nil, errors.NotFound(reason.TagNotFound)
}
resp.MainTagSlugName = tagInfo.SlugName
}
diff --git a/internal/service/user_admin/user_backyard.go b/internal/service/user_admin/user_backyard.go
index ae09ce85..76d35859 100644
--- a/internal/service/user_admin/user_backyard.go
+++ b/internal/service/user_admin/user_backyard.go
@@ -61,6 +61,10 @@ func NewUserAdminService(
// UpdateUserStatus update user
func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) {
+ // Admin cannot modify their status
+ if req.UserID == req.LoginUserID {
+ return errors.BadRequest(reason.AdminCannotModifySelfStatus)
+ }
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return
@@ -153,6 +157,10 @@ func (us *UserAdminService) AddUser(ctx context.Context, req *schema.AddUserReq)
// UpdateUserPassword update user password
func (us *UserAdminService) UpdateUserPassword(ctx context.Context, req *schema.UpdateUserPasswordReq) (err error) {
+ // Users cannot modify their password
+ if req.UserID == req.LoginUserID {
+ return errors.BadRequest(reason.AdminCannotUpdateTheirPassword)
+ }
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return err
diff --git a/internal/service/user_service.go b/internal/service/user_service.go
index e7bd4374..7532c80f 100644
--- a/internal/service/user_service.go
+++ b/internal/service/user_service.go
@@ -86,19 +86,17 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
}
func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) (
- resp *schema.GetOtherUserInfoResp, err error,
+ resp *schema.GetOtherUserInfoByUsernameResp, err error,
) {
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)
if err != nil {
return nil, err
}
- resp = &schema.GetOtherUserInfoResp{}
if !exist {
- return resp, nil
+ return nil, errors.NotFound(reason.UserNotFound)
}
- resp.Has = true
- resp.Info = &schema.GetOtherUserInfoByUsernameResp{}
- resp.Info.GetFromUserEntity(userInfo)
+ resp = &schema.GetOtherUserInfoByUsernameResp{}
+ resp.GetFromUserEntity(userInfo)
return resp, nil
}
@@ -149,13 +147,13 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
}
// RetrievePassWord .
-func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRetrievePassWordRequest) (string, error) {
+func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRetrievePassWordRequest) error {
userInfo, has, err := us.userRepo.GetByEmail(ctx, req.Email)
if err != nil {
- return "", err
+ return err
}
if !has {
- return "", errors.BadRequest(reason.UserNotFound)
+ return nil
}
// send email
@@ -167,10 +165,10 @@ func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRet
verifyEmailURL := fmt.Sprintf("%s/users/password-reset?code=%s", us.getSiteUrl(ctx), code)
title, body, err := us.emailService.PassResetTemplate(ctx, verifyEmailURL)
if err != nil {
- return "", err
+ return err
}
go us.emailService.SendAndSaveCode(ctx, req.Email, title, body, code, data.ToJSONString())
- return code, nil
+ return nil
}
// UseRePassword
diff --git a/pkg/converter/markdown.go b/pkg/converter/markdown.go
index d61d79f9..b1e2a418 100644
--- a/pkg/converter/markdown.go
+++ b/pkg/converter/markdown.go
@@ -35,6 +35,17 @@ func Markdown2HTML(source string) string {
return buf.String()
}
+// Markdown2BasicHTML convert markdown to html ,Only basic syntax can be used
+func Markdown2BasicHTML(source string) string {
+ content := Markdown2HTML(source)
+ filter := bluemonday.NewPolicy()
+ filter.AllowElements("p", "b", "br")
+ filter.AllowAttrs("src").OnElements("img")
+ filter.AddSpaceWhenStrippingTag(true)
+ content = filter.Sanitize(content)
+ return content
+}
+
type DangerousHTMLFilterExtension struct {
}
diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts
index 23844d4f..4b448c7e 100644
--- a/ui/src/common/constants.ts
+++ b/ui/src/common/constants.ts
@@ -6,6 +6,21 @@ export const LOGGED_USER_STORAGE_KEY = '_a_lui_';
export const LOGGED_TOKEN_STORAGE_KEY = '_a_ltk_';
export const REDIRECT_PATH_STORAGE_KEY = '_a_rp_';
export const CAPTCHA_CODE_STORAGE_KEY = '_a_captcha_';
+export const DRAFT_QUESTION_STORAGE_KEY = '_a_dq_';
+export const DRAFT_ANSWER_STORAGE_KEY = '_a_da_';
+export const DRAFT_TIMESIGH_STORAGE_KEY = '|_a_t_s_|';
+
+export const IGNORE_PATH_LIST = [
+ '/users/login',
+ '/users/register',
+ '/users/account-recovery',
+ '/users/change-email',
+ '/users/password-reset',
+ '/users/account-activation',
+ '/users/account-activation/success',
+ '/users/account-activation/failed',
+ '/users/confirm-new-email',
+];
export const ADMIN_LIST_STATUS = {
// normal;
diff --git a/ui/src/components/Comment/index.tsx b/ui/src/components/Comment/index.tsx
index 6f42a559..2101f4da 100644
--- a/ui/src/components/Comment/index.tsx
+++ b/ui/src/components/Comment/index.tsx
@@ -5,7 +5,6 @@ import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { unionBy } from 'lodash';
-import { marked } from 'marked';
import * as Types from '@/common/interface';
import { Modal } from '@/components';
@@ -108,15 +107,11 @@ const Comment = ({ objectId, mode, commentId }) => {
const users = matchedUsers(item.value);
const userNames = unionBy(users.map((user) => user.userName));
const commentMarkDown = parseUserInfo(item.value);
- const html = marked.parse(commentMarkDown);
- // if (!commentMarkDown || !html) {
- // return;
- // }
+
const params = {
object_id: objectId,
original_text: commentMarkDown,
mention_username_list: userNames,
- parsed_text: html,
...(item.type === 'reply'
? {
reply_comment_id: item.comment_id,
@@ -128,13 +123,13 @@ const Comment = ({ objectId, mode, commentId }) => {
return updateComment({
...params,
comment_id: item.comment_id,
- }).then(() => {
+ }).then((res) => {
setComments(
comments.map((comment) => {
if (comment.comment_id === item.comment_id) {
comment.showEdit = false;
- comment.parsed_text = html;
- comment.original_text = item.value;
+ comment.parsed_text = res.parsed_text;
+ comment.original_text = res.original_text;
}
return comment;
}),
diff --git a/ui/src/components/DiffContent/index.tsx b/ui/src/components/DiffContent/index.tsx
index 2a3b3bd9..e784c731 100644
--- a/ui/src/components/DiffContent/index.tsx
+++ b/ui/src/components/DiffContent/index.tsx
@@ -69,7 +69,10 @@ const Index: FC = ({
{objectType !== 'answer' && opts?.showTitle && (
diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts
index 10ec9664..56fe47a7 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -114,19 +114,8 @@ export function htmlRender(el: HTMLElement | null) {
},
);
- el.querySelectorAll('table').forEach((table) => {
- if (
- (table.parentNode as HTMLDivElement)?.classList.contains(
- 'table-responsive',
- )
- ) {
- return;
- }
-
- table.classList.add('table', 'table-bordered');
- const div = document.createElement('div');
- div.className = 'table-responsive';
- table.parentNode?.replaceChild(div, table);
- div.appendChild(table);
- });
+ // remove change table style to htmlToReact function
+ /**
+ * @description: You modify the DOM with other scripts after React has rendered the DOM. This way, on the next render cycle (re-render), React cannot find the DOM node it rendered before, because it has been modified or removed by other scripts.
+ */
}
diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx
index eda36a03..ced8ffc0 100644
--- a/ui/src/components/Header/index.tsx
+++ b/ui/src/components/Header/index.tsx
@@ -71,6 +71,10 @@ const Header: FC = () => {
};
const onLoginClick = (evt) => {
evt.preventDefault();
+ if (location.pathname === '/users/login') {
+ window.location.reload();
+ return;
+ }
floppyNavigation.navigateToLogin((loginPath) => {
navigate(loginPath, { replace: true });
});
diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx
index a004deb6..7ea53322 100644
--- a/ui/src/components/SchemaForm/index.tsx
+++ b/ui/src/components/SchemaForm/index.tsx
@@ -206,7 +206,6 @@ const SchemaForm: ForwardRefRenderFunction = (
const errors = requiredValidator();
if (errors.length > 0) {
formData = errors.reduce((acc, cur) => {
- console.log('schema.properties[cur]', cur);
acc[cur] = {
...formData[cur],
isInvalid: true,
diff --git a/ui/src/hooks/index.ts b/ui/src/hooks/index.ts
index 8144a4f0..ca4fa1ea 100644
--- a/ui/src/hooks/index.ts
+++ b/ui/src/hooks/index.ts
@@ -9,6 +9,7 @@ import useUserModal from './useUserModal';
import useChangePasswordModal from './useChangePasswordModal';
import usePageTags from './usePageTags';
import usePromptWithUnload from './usePrompt';
+import useImgViewer from './useImgViewer';
export {
useTagModal,
@@ -22,4 +23,5 @@ export {
useChangePasswordModal,
usePageTags,
usePromptWithUnload,
+ useImgViewer,
};
diff --git a/ui/src/hooks/useImgViewer/index.tsx b/ui/src/hooks/useImgViewer/index.tsx
new file mode 100644
index 00000000..6b3a30c2
--- /dev/null
+++ b/ui/src/hooks/useImgViewer/index.tsx
@@ -0,0 +1,78 @@
+import { useLayoutEffect, useState, MouseEvent, useEffect } from 'react';
+import { Modal } from 'react-bootstrap';
+import { useLocation } from 'react-router-dom';
+
+import ReactDOM from 'react-dom/client';
+
+const div = document.createElement('div');
+const root = ReactDOM.createRoot(div);
+
+const useImgViewer = () => {
+ const location = useLocation();
+ const [visible, setVisible] = useState(false);
+ const [imgSrc, setImgSrc] = useState('');
+ const onClose = () => {
+ setVisible(false);
+ setImgSrc('');
+ };
+
+ const checkIfInLink = (target) => {
+ let ret = false;
+ let el = target.parentElement;
+ while (el) {
+ if (el.nodeName.toLowerCase() === 'a') {
+ ret = true;
+ break;
+ }
+ el = el.parentElement;
+ }
+ return ret;
+ };
+
+ const checkClickForImgView = (evt: MouseEvent) => {
+ const { target } = evt;
+ // @ts-ignore
+ if (target.nodeName.toLowerCase() !== 'img') {
+ return;
+ }
+ const img = target as HTMLImageElement;
+ if (!img.naturalWidth || !img.naturalHeight) {
+ img.classList.add('broken');
+ return;
+ }
+ const src = img.currentSrc || img.src;
+ if (src && checkIfInLink(img) === false) {
+ setImgSrc(src);
+ setVisible(true);
+ }
+ };
+
+ useLayoutEffect(() => {
+ root.render(
+
+
+
+
+ ,
+ );
+ });
+ useEffect(() => {
+ onClose();
+ }, [location]);
+ return {
+ onClose,
+ checkClickForImgView,
+ };
+};
+
+export default useImgViewer;
diff --git a/ui/src/hooks/usePrompt/index.tsx b/ui/src/hooks/usePrompt/index.tsx
index 9b34db07..4eddc9c5 100644
--- a/ui/src/hooks/usePrompt/index.tsx
+++ b/ui/src/hooks/usePrompt/index.tsx
@@ -6,7 +6,7 @@ import {
import { useTranslation } from 'react-i18next';
// https://gist.github.com/chaance/2f3c14ec2351a175024f62fd6ba64aa6
-// The link above is an example of implementing usePromt with useBlocer.
+// The link above is an example of implementing usePrompt with useBlocker.
interface PromptProps {
when: boolean;
beforeUnload?: boolean;
diff --git a/ui/src/hooks/useReportModal/index.tsx b/ui/src/hooks/useReportModal/index.tsx
index 28b85eb5..748564d9 100644
--- a/ui/src/hooks/useReportModal/index.tsx
+++ b/ui/src/hooks/useReportModal/index.tsx
@@ -47,7 +47,11 @@ const useReportModal = (callback?: () => void) => {
setShow(true);
});
};
-
+ const asyncCallback = () => {
+ setTimeout(() => {
+ callback?.();
+ });
+ };
const handleRadio = (val) => {
setInvalidState(false);
setContent({
@@ -93,8 +97,8 @@ const useReportModal = (callback?: () => void) => {
close_type: reportType.type,
close_msg: content.value,
}).then(() => {
- callback?.();
onClose();
+ asyncCallback();
});
return;
}
@@ -109,8 +113,8 @@ const useReportModal = (callback?: () => void) => {
msg: t('flag_success', { keyPrefix: 'toast' }),
variant: 'warning',
});
- callback?.();
onClose();
+ asyncCallback();
});
}
@@ -121,8 +125,8 @@ const useReportModal = (callback?: () => void) => {
flagged_type: reportType.type,
id: params.id,
}).then(() => {
- callback?.();
onClose();
+ asyncCallback();
});
}
};
diff --git a/ui/src/index.scss b/ui/src/index.scss
index 1344917e..40d2e5e6 100644
--- a/ui/src/index.scss
+++ b/ui/src/index.scss
@@ -120,6 +120,14 @@ a {
cursor: pointer;
}
+.cursor-zoom-out {
+ cursor: zoom-out !important;
+}
+
+img:not(a img, img.broken) {
+ cursor: zoom-in;
+}
+
.resize-none {
resize: none;
}
diff --git a/ui/src/pages/404/index.jsx b/ui/src/pages/404/index.jsx
index d2aa8a85..90722e77 100644
--- a/ui/src/pages/404/index.jsx
+++ b/ui/src/pages/404/index.jsx
@@ -1,17 +1,30 @@
+import { useEffect } from 'react';
import { Container, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const Index = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_404' });
+ useEffect(() => {
+ // auto height of container
+ const pageWrap = document.querySelector('.page-wrap');
+ pageWrap.style.display = 'contents';
+
+ return () => {
+ pageWrap.style.display = 'block';
+ };
+ }, []);
return (
-
+