You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by ku...@apache.org on 2024/05/23 07:57:24 UTC
(incubator-answer) 03/03: feat(plugin/embed): merge from dev and add embed plugin
This is an automated email from the ASF dual-hosted git repository.
kumfo pushed a commit to branch feat/1.3.5/embed
in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit f972a23dd88dd2bd6adf2e8ef1c015c46a1186bd
Author: kumfo <ku...@sifou.com>
AuthorDate: Thu May 23 15:57:10 2024 +0800
feat(plugin/embed): merge from dev and add embed plugin
---
Dockerfile | 2 +-
Makefile | 4 +-
README.md | 2 +-
charts/values.yaml | 2 +-
cmd/answer/main.go | 2 +-
cmd/wire_gen.go | 4 +-
docs/docs.go | 134 +-
docs/swagger.json | 134 +-
docs/swagger.yaml | 90 +-
i18n/af_ZA.yaml | 6 +-
i18n/ar_SA.yaml | 6 +-
i18n/az_AZ.yaml | 6 +-
i18n/bal_BA.yaml | 6 +-
i18n/ban_ID.yaml | 6 +-
i18n/bn_BD.yaml | 6 +-
i18n/bs_BA.yaml | 6 +-
i18n/ca_ES.yaml | 6 +-
i18n/cs_CZ.yaml | 8 +-
i18n/cy_GB.yaml | 8 +-
i18n/da_DK.yaml | 8 +-
i18n/de_DE.yaml | 48 +-
i18n/el_GR.yaml | 6 +-
i18n/en_US.yaml | 8 +-
i18n/es_ES.yaml | 6 +
i18n/fa_IR.yaml | 12 +-
i18n/fi_FI.yaml | 6 +-
i18n/fr_FR.yaml | 12 +-
i18n/he_IL.yaml | 6 +-
i18n/hi_IN.yaml | 8 +-
i18n/hu_HU.yaml | 6 +-
i18n/hy_AM.yaml | 6 +-
i18n/id_ID.yaml | 8 +-
i18n/it_IT.yaml | 116 +-
i18n/ja_JP.yaml | 14 +-
i18n/ko_KR.yaml | 8 +-
i18n/nl_NL.yaml | 6 +-
i18n/no_NO.yaml | 6 +-
i18n/pl_PL.yaml | 86 +-
i18n/pt_PT.yaml | 12 +-
i18n/ro_RO.yaml | 30 +-
i18n/ru_RU.yaml | 66 +-
i18n/sk_SK.yaml | 6 +
i18n/sq_AL.yaml | 6 +-
i18n/sr_SP.yaml | 6 +-
i18n/sv_SE.yaml | 8 +-
i18n/te_IN.yaml | 8 +-
i18n/tr_TR.yaml | 8 +-
i18n/uk_UA.yaml | 10 +-
i18n/vi_VN.yaml | 8 +-
i18n/zh_CN.yaml | 8 +-
i18n/zh_TW.yaml | 22 +-
internal/controller/answer_controller.go | 3 +
internal/controller/controller.go | 2 +
internal/controller/embed_controller.go | 47 +
...{controller.go => plugin_captcha_controller.go} | 58 +-
internal/controller/question_controller.go | 5 +
internal/controller/template_controller.go | 5 +
internal/controller/template_render/answer.go | 4 +
internal/controller/user_controller.go | 33 +-
internal/controller_admin/siteinfo_controller.go | 2 +
internal/migrations/init.go | 138 ++
internal/repo/collection/collection_group_repo.go | 1 -
internal/repo/question/question_repo.go | 1 -
internal/repo/repo_test/tag_rel_repo_test.go | 14 +-
internal/repo/tag/tag_repo.go | 2 +-
internal/repo/user/user_repo.go | 8 +-
internal/router/answer_api_router.go | 2 +-
internal/router/plugin_api_router.go | 10 +
internal/schema/answer_schema.go | 2 +
internal/schema/comment_schema.go | 9 +-
internal/schema/plugin_option_schema.go | 6 +
internal/schema/question_schema.go | 4 +
internal/schema/search_schema.go | 14 +-
internal/schema/user_schema.go | 24 +-
internal/schema/vote_schema.go | 8 +-
internal/service/action/captcha_service.go | 106 +-
internal/service/action/captcha_strategy.go | 8 +-
internal/service/activity/activity.go | 2 +-
internal/service/content/answer_service.go | 26 +-
internal/service/content/question_service.go | 9 +-
internal/service/content/user_service.go | 20 +-
internal/service/rank/rank_service.go | 2 +-
internal/service/review/review_service.go | 46 +-
.../service/siteinfo_common/siteinfo_service.go | 2 +
internal/service/tag_common/tag_common.go | 15 +-
internal/service/user_common/user.go | 5 +-
pkg/converter/markdown.go | 2 +
plugin/captcha.go | 82 ++
plugin/plugin.go | 15 +
plugin/reviewer.go | 14 +
script/plugin_list | 3 +-
ui/config-overrides.js | 67 +-
ui/package.json | 10 +-
ui/pnpm-lock.yaml | 1533 +++++++-------------
ui/public/index.html | 4 +
ui/src/components/Actions/index.tsx | 80 +-
.../components/Comment/components/Form/index.tsx | 1 +
ui/src/components/Comment/index.tsx | 283 ++--
ui/src/components/Editor/ToolBars/blockquote.tsx | 2 +-
ui/src/components/Editor/ToolBars/bold.tsx | 4 +-
ui/src/components/Editor/ToolBars/code.tsx | 4 +-
ui/src/components/Editor/ToolBars/formula.tsx | 106 --
ui/src/components/Editor/ToolBars/heading.tsx | 4 +-
ui/src/components/Editor/ToolBars/hr.tsx | 4 +-
ui/src/components/Editor/ToolBars/image.tsx | 26 +-
ui/src/components/Editor/ToolBars/index.ts | 2 -
ui/src/components/Editor/ToolBars/italic.tsx | 4 +-
ui/src/components/Editor/ToolBars/link.tsx | 4 +-
ui/src/components/Editor/ToolBars/ol.tsx | 4 +-
ui/src/components/Editor/ToolBars/ul.tsx | 4 +-
ui/src/components/Editor/types.ts | 48 +-
ui/src/components/Editor/utils/extension.ts | 255 ++++
ui/src/components/Editor/utils/index.ts | 186 +--
.../Admin => components/Mentions}/index.scss | 6 +-
ui/src/components/Mentions/index.tsx | 17 +-
ui/src/components/Operate/index.tsx | 107 +-
ui/src/components/PageTags/index.tsx | 25 +
ui/src/components/SchemaForm/index.tsx | 3 +
ui/src/components/SideNav/index.scss | 4 +-
ui/src/components/Unactivate/index.tsx | 16 +-
ui/src/hooks/usePrompt/index.tsx | 1 +
ui/src/hooks/useReportModal/index.tsx | 63 +-
ui/src/i18n/init.ts | 11 +-
ui/src/pages/Admin/Answers/index.tsx | 9 +-
ui/src/pages/Admin/Branding/index.tsx | 4 +-
ui/src/pages/Admin/CssAndHtml/index.tsx | 4 +-
ui/src/pages/Admin/General/index.tsx | 4 +-
ui/src/pages/Admin/Interface/index.tsx | 4 +-
ui/src/pages/Admin/Legal/index.tsx | 4 +-
ui/src/pages/Admin/Login/index.tsx | 4 +-
ui/src/pages/Admin/Plugins/Installed/index.tsx | 4 +-
ui/src/pages/Admin/Privileges/index.tsx | 8 +-
ui/src/pages/Admin/Questions/index.tsx | 7 +-
ui/src/pages/Admin/Seo/index.tsx | 4 +-
ui/src/pages/Admin/SettingsUsers/index.tsx | 4 +-
ui/src/pages/Admin/Smtp/index.tsx | 4 +-
ui/src/pages/Admin/Themes/index.tsx | 4 +-
ui/src/pages/Admin/Users/index.tsx | 9 +-
ui/src/pages/Admin/Write/index.tsx | 4 +-
ui/src/pages/Admin/index.scss | 18 +
ui/src/pages/Install/index.tsx | 9 +-
ui/src/pages/Layout/index.tsx | 2 +-
ui/src/pages/Questions/Ask/index.tsx | 186 ++-
.../Detail/components/InviteToAnswer/index.tsx | 47 +-
.../Detail/components/WriteAnswer/index.tsx | 100 +-
ui/src/pages/Questions/EditAnswer/index.tsx | 76 +-
ui/src/pages/Questions/index.tsx | 2 +-
.../Review/components/ApproveDropdown/index.tsx | 89 +-
.../Review/components/EditPostModal/index.tsx | 80 +-
.../pages/Review/components/FlagContent/index.tsx | 6 +-
ui/src/pages/Search/index.tsx | 15 +-
ui/src/pages/SideNavLayout/index.tsx | 4 +-
ui/src/pages/Tags/Create/index.tsx | 15 +-
ui/src/pages/Timeline/components/Item/index.tsx | 3 +-
ui/src/pages/{Admin => Timeline}/index.scss | 19 +-
ui/src/pages/Timeline/index.tsx | 6 +-
.../Users/AccountForgot/components/sendEmail.tsx | 26 +-
.../Users/ChangeEmail/components/sendEmail.tsx | 21 +-
ui/src/pages/Users/Login/index.tsx | 49 +-
ui/src/pages/Users/OauthBindEmail/index.tsx | 9 +-
ui/src/pages/Users/PasswordReset/index.tsx | 15 +-
.../Users/Register/components/SignUpForm/index.tsx | 25 +-
.../Account/components/ModifyEmail/index.tsx | 37 +-
.../Account/components/ModifyPass/index.tsx | 35 +-
ui/src/pages/Users/Settings/Profile/index.tsx | 17 +-
ui/src/plugins/builtin/HostingConnector/index.tsx | 7 +-
ui/src/plugins/builtin/SearchInfo/index.tsx | 7 +-
.../plugins/builtin/ThirdPartyConnector/index.tsx | 7 +-
ui/src/router/index.tsx | 4 +-
ui/src/router/pathFactory.ts | 2 +-
ui/src/services/client/user.ts | 7 +
ui/src/utils/localize.ts | 2 +-
ui/src/utils/pluginKit/index.ts | 85 +-
.../index.scss => utils/pluginKit/interface.ts} | 10 +-
ui/src/utils/pluginKit/utils.ts | 22 +-
175 files changed, 3357 insertions(+), 2447 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 6278e1a5..bd5f8768 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -34,7 +34,7 @@ ARG CGO_EXTRA_CFLAGS
COPY . ${BUILD_DIR}
WORKDIR ${BUILD_DIR}
-RUN apk --no-cache add build-base git bash nodejs npm && npm install -g pnpm@latest \
+RUN apk --no-cache add build-base git bash nodejs npm && npm install -g pnpm@8.9.2 \
&& make clean build
RUN chmod 755 answer
diff --git a/Makefile b/Makefile
index 9da39bec..9205c033 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
.PHONY: build clean ui
-VERSION=1.3.0
+VERSION=1.3.1
BIN=answer
DIR_SRC=./cmd/answer
DOCKER_CMD=docker
@@ -38,7 +38,7 @@ clean:
install-ui-packages:
@corepack enable
- @corepack prepare pnpm@latest --activate
+ @corepack prepare pnpm@8.9.2 --activate
ui:
@cd ui && pnpm pre-install && pnpm build && cd -
diff --git a/README.md b/README.md
index c3b30b71..b2d89834 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ To learn more about the project, visit [answer.apache.org](https://answer.apache
### Running with docker
```bash
-docker run -d -p 9080:80 -v answer-data:/data --name answer apache/answer:1.3.0
+docker run -d -p 9080:80 -v answer-data:/data --name answer apache/answer:1.3.1
```
For more information, see [Installation](https://answer.apache.org/docs/installation).
diff --git a/charts/values.yaml b/charts/values.yaml
index c7a9c20d..8bb36c46 100644
--- a/charts/values.yaml
+++ b/charts/values.yaml
@@ -35,7 +35,7 @@ env:
# [DEBUG INFO WARN ERROR]
value: "INFO"
# uncomment the below values to use AUTO_INSTALL and not have to go through the setup process.
- # Once used to do the initial setup, these variables wont be used moving forward.
+ # Once used to do the initial setup, these variables won't be used moving forward.
# You must at a minimum comment AUTO_INSTALL after initial setup to prevent an error about the database already being initiated.
# - name: AUTO_INSTALL
# value: "true"
diff --git a/cmd/answer/main.go b/cmd/answer/main.go
index f8e88bae..1ac3b237 100644
--- a/cmd/answer/main.go
+++ b/cmd/answer/main.go
@@ -20,7 +20,7 @@
package main
import (
- _ "github.com/apache/incubator-answer-plugins/reviewer-basic"
+ _ "github.com/apache/incubator-answer-plugins/embed-basic"
answercmd "github.com/apache/incubator-answer/cmd"
)
diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index 7ed904d0..1bad939a 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -262,7 +262,9 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
userCenterController := controller.NewUserCenterController(userCenterLoginService, siteInfoCommonService)
- pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController)
+ captchaController := controller.NewCaptchaController()
+ embedController := controller.NewEmbedController()
+ pluginAPIRouter := router.NewPluginAPIRouter(connectorController, userCenterController, captchaController, embedController)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware, avatarMiddleware, shortIDMiddleware, templateRouter, pluginAPIRouter)
scheduledTaskManager := cron.NewScheduledTaskManager(siteInfoCommonService, questionService)
application := newApplication(serverConf, ginEngine, scheduledTaskManager)
diff --git a/docs/docs.go b/docs/docs.go
index 1c463c12..7d96f4b0 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -2653,6 +2653,44 @@ const docTemplate = `{
}
}
},
+ "/answer/api/v1/embed/config": {
+ "get": {
+ "description": "GetEmbedConfig",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "PluginEmbed"
+ ],
+ "summary": "GetEmbedConfig",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/handler.RespBody"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/schema.GetEmbedOptionResp"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
"/answer/api/v1/file": {
"post": {
"security": [
@@ -6188,9 +6226,9 @@ const docTemplate = `{
}
}
},
- "/answer/api/v1/user/register/captcha": {
- "get": {
- "description": "UserRegisterCaptcha",
+ "/answer/api/v1/user/register/email": {
+ "post": {
+ "description": "UserRegisterByEmail",
"consumes": [
"application/json"
],
@@ -6200,7 +6238,18 @@ const docTemplate = `{
"tags": [
"User"
],
- "summary": "UserRegisterCaptcha",
+ "summary": "UserRegisterByEmail",
+ "parameters": [
+ {
+ "description": "UserRegisterReq",
+ "name": "data",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/schema.UserRegisterReq"
+ }
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -6223,9 +6272,14 @@ const docTemplate = `{
}
}
},
- "/answer/api/v1/user/register/email": {
- "post": {
- "description": "UserRegisterByEmail",
+ "/answer/api/v1/user/staff": {
+ "get": {
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ],
+ "description": "get user staff",
"consumes": [
"application/json"
],
@@ -6235,15 +6289,15 @@ const docTemplate = `{
"tags": [
"User"
],
- "summary": "UserRegisterByEmail",
+ "summary": "get user staff",
"parameters": [
{
- "description": "UserRegisterReq",
+ "description": "GetUserStaffReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
- "$ref": "#/definitions/schema.UserRegisterReq"
+ "$ref": "#/definitions/schema.GetUserStaffReq"
}
}
],
@@ -6259,7 +6313,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"data": {
- "$ref": "#/definitions/schema.UserLoginResp"
+ "$ref": "#/definitions/schema.GetUserStaffResp"
}
}
}
@@ -6907,7 +6961,6 @@ const docTemplate = `{
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"mention_username_list": {
@@ -7590,6 +7643,17 @@ const docTemplate = `{
}
}
},
+ "schema.GetEmbedOptionResp": {
+ "type": "object",
+ "properties": {
+ "enable": {
+ "type": "boolean"
+ },
+ "platform": {
+ "type": "string"
+ }
+ }
+ },
"schema.GetFollowingTagsResp": {
"type": "object",
"properties": {
@@ -8313,6 +8377,36 @@ const docTemplate = `{
}
}
},
+ "schema.GetUserStaffReq": {
+ "type": "object",
+ "properties": {
+ "page_size": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "username": {
+ "type": "string",
+ "maxLength": 500
+ }
+ }
+ },
+ "schema.GetUserStaffResp": {
+ "type": "object",
+ "properties": {
+ "avatar": {
+ "description": "avatar",
+ "type": "string"
+ },
+ "display_name": {
+ "description": "display name",
+ "type": "string"
+ },
+ "username": {
+ "description": "username",
+ "type": "string"
+ }
+ }
+ },
"schema.GetVoteWithPageResp": {
"type": "object",
"properties": {
@@ -8849,7 +8943,6 @@ const docTemplate = `{
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"comment_id": {
@@ -10097,12 +10190,10 @@ const docTemplate = `{
],
"properties": {
"captcha_code": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"captcha_id": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"e_mail": {
"type": "string",
@@ -10267,12 +10358,10 @@ const docTemplate = `{
],
"properties": {
"captcha_code": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"captcha_id": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"old_pass": {
"type": "string",
@@ -10420,15 +10509,12 @@ const docTemplate = `{
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"is_cancel": {
- "description": "is cancel",
"type": "boolean"
},
"object_id": {
- "description": "id",
"type": "string"
}
}
diff --git a/docs/swagger.json b/docs/swagger.json
index 00b06d9a..a8fc8159 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -2642,6 +2642,44 @@
}
}
},
+ "/answer/api/v1/embed/config": {
+ "get": {
+ "description": "GetEmbedConfig",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "PluginEmbed"
+ ],
+ "summary": "GetEmbedConfig",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "allOf": [
+ {
+ "$ref": "#/definitions/handler.RespBody"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/schema.GetEmbedOptionResp"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
"/answer/api/v1/file": {
"post": {
"security": [
@@ -6177,9 +6215,9 @@
}
}
},
- "/answer/api/v1/user/register/captcha": {
- "get": {
- "description": "UserRegisterCaptcha",
+ "/answer/api/v1/user/register/email": {
+ "post": {
+ "description": "UserRegisterByEmail",
"consumes": [
"application/json"
],
@@ -6189,7 +6227,18 @@
"tags": [
"User"
],
- "summary": "UserRegisterCaptcha",
+ "summary": "UserRegisterByEmail",
+ "parameters": [
+ {
+ "description": "UserRegisterReq",
+ "name": "data",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/schema.UserRegisterReq"
+ }
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -6212,9 +6261,14 @@
}
}
},
- "/answer/api/v1/user/register/email": {
- "post": {
- "description": "UserRegisterByEmail",
+ "/answer/api/v1/user/staff": {
+ "get": {
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ],
+ "description": "get user staff",
"consumes": [
"application/json"
],
@@ -6224,15 +6278,15 @@
"tags": [
"User"
],
- "summary": "UserRegisterByEmail",
+ "summary": "get user staff",
"parameters": [
{
- "description": "UserRegisterReq",
+ "description": "GetUserStaffReq",
"name": "data",
"in": "body",
"required": true,
"schema": {
- "$ref": "#/definitions/schema.UserRegisterReq"
+ "$ref": "#/definitions/schema.GetUserStaffReq"
}
}
],
@@ -6248,7 +6302,7 @@
"type": "object",
"properties": {
"data": {
- "$ref": "#/definitions/schema.UserLoginResp"
+ "$ref": "#/definitions/schema.GetUserStaffResp"
}
}
}
@@ -6896,7 +6950,6 @@
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"mention_username_list": {
@@ -7579,6 +7632,17 @@
}
}
},
+ "schema.GetEmbedOptionResp": {
+ "type": "object",
+ "properties": {
+ "enable": {
+ "type": "boolean"
+ },
+ "platform": {
+ "type": "string"
+ }
+ }
+ },
"schema.GetFollowingTagsResp": {
"type": "object",
"properties": {
@@ -8302,6 +8366,36 @@
}
}
},
+ "schema.GetUserStaffReq": {
+ "type": "object",
+ "properties": {
+ "page_size": {
+ "type": "integer",
+ "minimum": 1
+ },
+ "username": {
+ "type": "string",
+ "maxLength": 500
+ }
+ }
+ },
+ "schema.GetUserStaffResp": {
+ "type": "object",
+ "properties": {
+ "avatar": {
+ "description": "avatar",
+ "type": "string"
+ },
+ "display_name": {
+ "description": "display name",
+ "type": "string"
+ },
+ "username": {
+ "description": "username",
+ "type": "string"
+ }
+ }
+ },
"schema.GetVoteWithPageResp": {
"type": "object",
"properties": {
@@ -8838,7 +8932,6 @@
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"comment_id": {
@@ -10086,12 +10179,10 @@
],
"properties": {
"captcha_code": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"captcha_id": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"e_mail": {
"type": "string",
@@ -10256,12 +10347,10 @@
],
"properties": {
"captcha_code": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"captcha_id": {
- "type": "string",
- "maxLength": 500
+ "type": "string"
},
"old_pass": {
"type": "string",
@@ -10409,15 +10498,12 @@
"type": "string"
},
"captcha_id": {
- "description": "captcha_id",
"type": "string"
},
"is_cancel": {
- "description": "is cancel",
"type": "boolean"
},
"object_id": {
- "description": "id",
"type": "string"
}
}
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index e9bb895e..a3c8f887 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -164,7 +164,6 @@ definitions:
captcha_code:
type: string
captcha_id:
- description: captcha_id
type: string
mention_username_list:
description: '@ user id list'
@@ -652,6 +651,13 @@ definitions:
description: website
type: string
type: object
+ schema.GetEmbedOptionResp:
+ properties:
+ enable:
+ type: boolean
+ platform:
+ type: string
+ type: object
schema.GetFollowingTagsResp:
properties:
display_name:
@@ -1152,6 +1158,27 @@ definitions:
slug_name:
type: string
type: object
+ schema.GetUserStaffReq:
+ properties:
+ page_size:
+ minimum: 1
+ type: integer
+ username:
+ maxLength: 500
+ type: string
+ type: object
+ schema.GetUserStaffResp:
+ properties:
+ avatar:
+ description: avatar
+ type: string
+ display_name:
+ description: display name
+ type: string
+ username:
+ description: username
+ type: string
+ type: object
schema.GetVoteWithPageResp:
properties:
answer_id:
@@ -1524,7 +1551,6 @@ definitions:
captcha_code:
type: string
captcha_id:
- description: captcha_id
type: string
comment_id:
description: comment id
@@ -2380,10 +2406,8 @@ definitions:
schema.UserChangeEmailSendCodeReq:
properties:
captcha_code:
- maxLength: 500
type: string
captcha_id:
- maxLength: 500
type: string
e_mail:
maxLength: 500
@@ -2504,10 +2528,8 @@ definitions:
schema.UserModifyPasswordReq:
properties:
captcha_code:
- maxLength: 500
type: string
captcha_id:
- maxLength: 500
type: string
old_pass:
maxLength: 32
@@ -2611,13 +2633,10 @@ definitions:
captcha_code:
type: string
captcha_id:
- description: captcha_id
type: string
is_cancel:
- description: is cancel
type: boolean
object_id:
- description: id
type: string
required:
- object_id
@@ -4222,6 +4241,28 @@ paths:
summary: unbind external user login
tags:
- PluginConnector
+ /answer/api/v1/embed/config:
+ get:
+ consumes:
+ - application/json
+ description: GetEmbedConfig
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ allOf:
+ - $ref: '#/definitions/handler.RespBody'
+ - properties:
+ data:
+ items:
+ $ref: '#/definitions/schema.GetEmbedOptionResp'
+ type: array
+ type: object
+ summary: GetEmbedConfig
+ tags:
+ - PluginEmbed
/answer/api/v1/file:
post:
consumes:
@@ -6373,11 +6414,18 @@ paths:
summary: get user ranking
tags:
- User
- /answer/api/v1/user/register/captcha:
- get:
+ /answer/api/v1/user/register/email:
+ post:
consumes:
- application/json
- description: UserRegisterCaptcha
+ description: UserRegisterByEmail
+ parameters:
+ - description: UserRegisterReq
+ in: body
+ name: data
+ required: true
+ schema:
+ $ref: '#/definitions/schema.UserRegisterReq'
produces:
- application/json
responses:
@@ -6390,21 +6438,21 @@ paths:
data:
$ref: '#/definitions/schema.UserLoginResp'
type: object
- summary: UserRegisterCaptcha
+ summary: UserRegisterByEmail
tags:
- User
- /answer/api/v1/user/register/email:
- post:
+ /answer/api/v1/user/staff:
+ get:
consumes:
- application/json
- description: UserRegisterByEmail
+ description: get user staff
parameters:
- - description: UserRegisterReq
+ - description: GetUserStaffReq
in: body
name: data
required: true
schema:
- $ref: '#/definitions/schema.UserRegisterReq'
+ $ref: '#/definitions/schema.GetUserStaffReq'
produces:
- application/json
responses:
@@ -6415,9 +6463,11 @@ paths:
- $ref: '#/definitions/handler.RespBody'
- properties:
data:
- $ref: '#/definitions/schema.UserLoginResp'
+ $ref: '#/definitions/schema.GetUserStaffResp'
type: object
- summary: UserRegisterByEmail
+ security:
+ - ApiKeyAuth: []
+ summary: get user staff
tags:
- User
/answer/api/v1/vote/down:
diff --git a/i18n/af_ZA.yaml b/i18n/af_ZA.yaml
index 89c34f73..8c6d0d7d 100644
--- a/i18n/af_ZA.yaml
+++ b/i18n/af_ZA.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/ar_SA.yaml b/i18n/ar_SA.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/ar_SA.yaml
+++ b/i18n/ar_SA.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/az_AZ.yaml b/i18n/az_AZ.yaml
index 483744f7..07e50541 100644
--- a/i18n/az_AZ.yaml
+++ b/i18n/az_AZ.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/bal_BA.yaml b/i18n/bal_BA.yaml
index 483744f7..07e50541 100644
--- a/i18n/bal_BA.yaml
+++ b/i18n/bal_BA.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/ban_ID.yaml b/i18n/ban_ID.yaml
index 483744f7..07e50541 100644
--- a/i18n/ban_ID.yaml
+++ b/i18n/ban_ID.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/bn_BD.yaml b/i18n/bn_BD.yaml
index 483744f7..07e50541 100644
--- a/i18n/bn_BD.yaml
+++ b/i18n/bn_BD.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/bs_BA.yaml b/i18n/bs_BA.yaml
index 483744f7..07e50541 100644
--- a/i18n/bs_BA.yaml
+++ b/i18n/bs_BA.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/ca_ES.yaml b/i18n/ca_ES.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/ca_ES.yaml
+++ b/i18n/ca_ES.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/cs_CZ.yaml b/i18n/cs_CZ.yaml
index e71eeda4..eac0631a 100644
--- a/i18n/cs_CZ.yaml
+++ b/i18n/cs_CZ.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/cy_GB.yaml b/i18n/cy_GB.yaml
index a1c14f5f..2af2fde0 100644
--- a/i18n/cy_GB.yaml
+++ b/i18n/cy_GB.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/da_DK.yaml b/i18n/da_DK.yaml
index 0d48719d..2a7cf3ee 100644
--- a/i18n/da_DK.yaml
+++ b/i18n/da_DK.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/de_DE.yaml b/i18n/de_DE.yaml
index 7ac93bfd..690f68e0 100644
--- a/i18n/de_DE.yaml
+++ b/i18n/de_DE.yaml
@@ -78,10 +78,10 @@ backend:
other: Level 1 (weniger Reputation für privates Team, Gruppen)
level_2:
description:
- other: Level 2 (niedrige Reputation erforderlich für Start-Community)
+ other: Level 2 (niedrige Reputation für Startup-Community)
level_3:
description:
- other: Level 3 (hohe Reputation, für ausgereifte Community)
+ other: Level 3 (hohe Reputation für eine reife Community)
level_custom:
description:
other: Benutzerdefinierter Level
@@ -142,7 +142,7 @@ backend:
error:
common:
invalid_url:
- other: Invalid URL.
+ other: Ungültige URL.
password:
space_invalid:
other: Passwort darf keine Leerzeichen enthalten.
@@ -204,7 +204,7 @@ backend:
already_deleted:
other: Dieser Beitrag wurde gelöscht.
under_review:
- other: Your post is awaiting review. It will be visible after it has been approved.
+ other: Ihr Beitrag wartet auf Überprüfung. Er wird sichtbar sein, nachdem er genehmigt wurde.
not_found:
other: Frage nicht gefunden.
cannot_deleted:
@@ -491,14 +491,14 @@ backend:
accepted:
other: akzeptiert
edit:
- other: edit
+ other: bearbeiten
review:
queued_post:
other: Queued post
flagged_post:
- other: Flagged post
+ other: Beiträge gemeldet
suggested_post_edit:
- other: Suggested edits
+ other: Änderungsvorschläge
# The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
@@ -680,8 +680,8 @@ ui:
empty: Kann nicht leer sein.
msg:
empty: Bitte wähle einen Grund aus.
- not_a_url: URL format is incorrect.
- url_not_match: URL origin does not match the current website.
+ not_a_url: URL hat ein falsches Format.
+ url_not_match: URL-Ursprung stimmt nicht mit der aktuellen Website überein.
tag_modal:
title: Neuen Tag erstellen
form:
@@ -793,10 +793,10 @@ ui:
no_desc: Der Tag hat keine Beschreibung.
more: Mehr
ask:
- title: Frage hinzufügen
+ title: Frage stellen
edit_title: Frage bearbeiten
default_reason: Frage bearbeiten
- default_first_reason: Frage hinzufügen
+ default_first_reason: Frage stellen
similar_questions: Ähnliche Fragen
form:
fields:
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
Wir haben eine E-Mail an diese Adresse geschickt. Bitte befolge die Anweisungen zur Bestätigung.
email:
+ label: E-Mail
+ new_email:
label: Neue E-Mail
msg: Neue E-Mail darf nicht leer sein.
pass:
@@ -1038,8 +1040,8 @@ ui:
title: Verwandte Fragen
answers: antworten
invite_to_answer:
- title: Menschen gefragt
- desc: Wähle Leute aus, von denen du glaubst, dass sie die Antwort wissen könnten.
+ title: Frage jemanden
+ desc: Lade Leute ein, von denen du glaubst, dass sie die Antwort wissen könnten.
invite: Zur Antwort einladen
add: Personen hinzufügen
search: Personen suchen
@@ -1049,7 +1051,7 @@ ui:
asked: gefragt
update: Geändert
edit: bearbeitet
- commented: commented
+ commented: kommentiert
Views: Gesehen
Follow: Folgen
Following: Folgend
@@ -1067,7 +1069,7 @@ ui:
title: Antworten
score: Punkte
newest: Neueste
- oldest: Oldest
+ oldest: Älteste
btn_accept: Akzeptieren
btn_accepted: Akzeptiert
write_answer:
@@ -1092,8 +1094,8 @@ ui:
title: Diesen Beitrag erneut öffnen
content: Bist du sicher, dass du wieder öffnen willst?
list:
- confirm_btn: List
- title: List this post
+ confirm_btn: Liste
+ title: Diesen Beitrag auflisten
content: Are you sure you want to list?
unlist:
confirm_btn: Unlist
@@ -1127,7 +1129,7 @@ ui:
signup: Registrieren
logout: Ausloggen
verify: Überprüfen
- add_question: Frage hinzufügen
+ add_question: Frage stellen
approve: Genehmigen
reject: Ablehnen
skip: Überspringen
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Suchergebnisse
keywords: Schlüsselwörter
@@ -1257,7 +1260,7 @@ ui:
downvoted: negativ bewertet
mod_short: MOD
mod_long: Moderatoren
- x_reputation: Ansehen
+ x_reputation: ansehen
x_votes: Stimmen erhalten
x_answers: Antworten
x_questions: Fragen
@@ -1489,7 +1492,7 @@ ui:
btn_submit: Senden
users:
title: Benutzer
- name: Namen
+ name: Name
email: E-Mail
reputation: Ansehen
created_at: Erstellt
@@ -1774,6 +1777,9 @@ ui:
level:
label: Benötigtes Reputations-Level
text: Wähle die für die Privilegien erforderliche Reputation aus
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: kann nicht leer sein
@@ -1841,7 +1847,7 @@ ui:
no_data: "Wir konnten nichts finden."
users:
title: Benutzer
- users_with_the_most_reputation: Benutzer mit den höchsten Ansehenspunkten dieser Woche
+ users_with_the_most_reputation: Benutzer mit den höchsten Reputationspunkten dieser Woche
users_with_the_most_vote: Benutzer, die diese Woche am meisten gestimmt haben
staffs: Unsere Community Teammitglieder
reputation: Ansehen
diff --git a/i18n/el_GR.yaml b/i18n/el_GR.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/el_GR.yaml
+++ b/i18n/el_GR.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 943608dd..58bc6c77 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1029,6 +1029,8 @@ ui:
We've sent an email to that address. Please follow the confirmation
instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1197,6 +1199,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1213,7 +1216,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1817,6 +1820,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1 : number should be equal or larger than 1
form:
optional: (optional)
diff --git a/i18n/es_ES.yaml b/i18n/es_ES.yaml
index 2ffbe74c..a2811cfd 100644
--- a/i18n/es_ES.yaml
+++ b/i18n/es_ES.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
Te hemos enviado un email a esa dirección. Por favor sigue las instrucciones de confirmación.
email:
+ label: Correo
+ new_email:
label: Nuevo correo
msg: El nuevo correo no puede estar vacío.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Resultados de la búsqueda
keywords: Palabras claves
@@ -1774,6 +1777,9 @@ ui:
level:
label: Nivel de reputación requerido
text: Elegir reputación requerida para los privilegios
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (opcional)
empty: no puede estar en blanco
diff --git a/i18n/fa_IR.yaml b/i18n/fa_IR.yaml
index 0f52761b..589779ea 100644
--- a/i18n/fa_IR.yaml
+++ b/i18n/fa_IR.yaml
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
ما یک ایمیل به آن آدرس ارسال کردیم. لطفا مراحل تایید را طی کنید.
email:
- label: ایمیل جدید
- msg: ایمیل جدید نباید خالی باشد.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: رمز عبور فعلی
msg: رمز عبور نمی تواند خالی باشد.
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: نتایج جستجو
keywords: کلیدواژه ها
@@ -1176,7 +1179,7 @@ ui:
more: بیشتر
tips:
title: گزینه های پیشرفته جستجو
- tag: "<1>[tag]</1> جستجو با یک تگ"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> جستجو براساس نویسنده"
answer: "<1>answers:0</1> سوال بی جواب"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/fi_FI.yaml b/i18n/fi_FI.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/fi_FI.yaml
+++ b/i18n/fi_FI.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/fr_FR.yaml b/i18n/fr_FR.yaml
index a389a08e..ba633f73 100644
--- a/i18n/fr_FR.yaml
+++ b/i18n/fr_FR.yaml
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Nous vous avons envoyé un mail à cette adresse. Merci de suivre les instructions.
email:
- label: Nouvel e-mail
- msg: L'email ne peut pas être vide.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Mot de passe actuel
msg: Le mot de passe ne peut pas être vide.
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Résultats de la recherche
keywords: Mots-clés
@@ -1176,7 +1179,7 @@ ui:
more: Plus
tips:
title: Astuces de recherche avancée
- tag: "<1>[tag]</1> recherche à l'aide d'un tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>utilisateur:username</1> recherche par auteur"
answer: "<1>réponses:0</1> questions sans réponses"
score: "<1>score:3</1> messages avec plus de 3 points"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Niveau de réputation requis
text: Choisissez la réputation requise pour les privilèges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optionnel)
empty: ne peut pas être vide
diff --git a/i18n/he_IL.yaml b/i18n/he_IL.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/he_IL.yaml
+++ b/i18n/he_IL.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/hi_IN.yaml b/i18n/hi_IN.yaml
index 0d48719d..2a7cf3ee 100644
--- a/i18n/hi_IN.yaml
+++ b/i18n/hi_IN.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/hu_HU.yaml b/i18n/hu_HU.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/hu_HU.yaml
+++ b/i18n/hu_HU.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/hy_AM.yaml b/i18n/hy_AM.yaml
index 483744f7..07e50541 100644
--- a/i18n/hy_AM.yaml
+++ b/i18n/hy_AM.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/id_ID.yaml b/i18n/id_ID.yaml
index de2f95c4..4b3ae49e 100644
--- a/i18n/id_ID.yaml
+++ b/i18n/id_ID.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/it_IT.yaml b/i18n/it_IT.yaml
index 52b3d016..3b04a3b6 100644
--- a/i18n/it_IT.yaml
+++ b/i18n/it_IT.yaml
@@ -142,7 +142,7 @@ backend:
error:
common:
invalid_url:
- other: Invalid URL.
+ other: URL non valido.
password:
space_invalid:
other: La password non può contenere spazi.
@@ -204,7 +204,7 @@ backend:
already_deleted:
other: Questo post è stato eliminato.
under_review:
- other: Your post is awaiting review. It will be visible after it has been approved.
+ other: Il tuo post è in attesa di revisione. Sarà visibile dopo essere stato approvato.
not_found:
other: domanda non trovata
cannot_deleted:
@@ -491,14 +491,14 @@ backend:
accepted:
other: Accettato
edit:
- other: edit
+ other: modifica
review:
queued_post:
- other: Queued post
+ other: Post in coda
flagged_post:
- other: Flagged post
+ other: Post contrassegnato
suggested_post_edit:
- other: Suggested edits
+ other: Modifiche suggerite
# The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
@@ -680,8 +680,8 @@ ui:
empty: Non può essere vuoto.
msg:
empty: Per favore seleziona un motivo
- not_a_url: URL format is incorrect.
- url_not_match: URL origin does not match the current website.
+ not_a_url: Formato URL errato.
+ url_not_match: L'origine dell'URL non corrisponde al sito web corrente.
tag_modal:
title: Crea un nuovo tag
form:
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Abbiamo inviato una mail a quell'indirizzo. Si prega di seguire le istruzioni di conferma.
email:
- label: Nuova email
- msg: La nuova email non può essere vuota.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Password attuale
msg: La password non può essere vuota
@@ -1049,7 +1051,7 @@ ui:
asked: chiesto
update: Modificato
edit: modificato
- commented: commented
+ commented: commentato
Views: Visualizzati
Follow: Segui
Following: Segui già
@@ -1067,7 +1069,7 @@ ui:
title: Risposte
score: Punteggio
newest: Più recenti
- oldest: Oldest
+ oldest: Meno recente
btn_accept: Accetta
btn_accepted: Accettato
write_answer:
@@ -1092,13 +1094,13 @@ ui:
title: Riapri questo post
content: Sei sicuro di voler riaprire?
list:
- confirm_btn: List
- title: List this post
- content: Are you sure you want to list?
+ confirm_btn: Listare
+ title: Lista questo post
+ content: Sei sicuro di volerlo listare?
unlist:
- confirm_btn: Unlist
- title: Unlist this post
- content: Are you sure you want to unlist?
+ confirm_btn: Rimuovi dall'elenco
+ title: Rimuovi questo post
+ content: Sei sicuro di voler rimuovere dall'elenco?
pin:
title: Fissa questo post in cima al profilo
content: Sei sicuro di voler fissare in blocco? Questo post apparirà in cima a tutte le liste.
@@ -1120,9 +1122,9 @@ ui:
save: Salva
delete: Elimina
undelete: Ripristina
- list: List
- unlist: Unlist
- unlisted: Unlisted
+ list: Aggiungi all'elenco
+ unlist: Rimuovi dall'elenco
+ unlisted: Rimosso dall'elenco
login: Accedi
signup: Registrati
logout: Disconnetti
@@ -1153,13 +1155,14 @@ ui:
reset: Resetta
tag: Tag
post_lowercase: post
- filter: Filter
- ignore: Ignore
- submit: Submit
- normal: Normal
- closed: Closed
- deleted: Deleted
- pending: Pending
+ filter: 'Filtra'
+ ignore: Ignora
+ submit: Invia
+ normal: Normale
+ closed: Chiuso
+ deleted: Eliminato
+ pending: In attesa
+ more: More
search:
title: Risultati della ricerca
keywords: Parole chiave
@@ -1176,7 +1179,7 @@ ui:
more: Altro
tips:
title: Suggerimenti per ricerca avanzata
- tag: "<1>[tag]</1> cerca dentro un tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> ricerca per autore"
answer: "<1>risposte:0</1> domande senza risposta"
score: "<1>punteggio:3</1> messaggi con un punteggio di 3+"
@@ -1406,8 +1409,8 @@ ui:
votes: "Voti:"
users: "Utenti:"
flags: "Flags"
- reviews: "Reviews:"
- site_health: Site health
+ reviews: "Revisioni"
+ site_health: Stato del sito
version: "Versione:"
https: "HTTPS:"
upload_folder: "Carica Cartella"
@@ -1422,7 +1425,7 @@ ui:
database_size: "Dimensioni del database"
storage_used: "Spazio di archiviazione utilizzato:"
uptime: "Tempo di attività:"
- links: Links
+ links: Collegamenti
plugins: Plugin
github: GitHub
blog: Blog
@@ -1530,7 +1533,7 @@ ui:
content: Un utente sospeso non può accedere.
questions:
page_title: Domande
- unlisted: Unlisted
+ unlisted: Rimosso dall'elenco
post: Posta
votes: Voti
answers: Risposte
@@ -1538,7 +1541,7 @@ ui:
status: Stato
action: Azione
change: Modifica
- pending: Pending
+ pending: In attesa
filter:
placeholder: "Filtra per titolo, domanda:id"
answers:
@@ -1576,8 +1579,8 @@ ui:
validate: Email di contatto non valida.
text: Indirizzo e-mail del contatto chiave responsabile di questo sito.
check_update:
- label: Software updates
- text: Automatically check for updates
+ label: Aggiornamenti Software
+ text: Controlla automaticamente gli aggiornamenti
interface:
page_title: Interfaccia
language:
@@ -1774,6 +1777,9 @@ ui:
level:
label: Livello di reputazione richiesto
text: Scegli la reputazione richiesta per i privilegi
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (opzionale)
empty: non può essere vuoto
@@ -1792,22 +1798,22 @@ ui:
edit_answer: Edita la risposta
edit_tag: Edita tag
empty: Nessuna attività di revisione rimasta.
- approve_revision_tip: Do you approve this revision?
- approve_flag_tip: Do you approve this flag?
- approve_post_tip: Do you approve this post?
- approve_user_tip: Do you approve this user?
- suggest_edits: Suggested edits
- flag_post: Flag post
- flag_user: Flag user
- queued_post: Queued post
- queued_user: Queued user
- filter_label: Type
- reputation: reputation
- flag_post_type: Flagged this post as {{ type }}.
- flag_user_type: Flagged this user as {{ type }}.
- edit_post: Edit post
- list_post: List post
- unlist_post: Unlist post
+ approve_revision_tip: Approvi questa revisione?
+ approve_flag_tip: Approvi questo contrassegno?
+ approve_post_tip: Approvi questo post?
+ approve_user_tip: Approvate questo utente?
+ suggest_edits: Modifiche suggerite
+ flag_post: Post contrassegnato
+ flag_user: Utente contrassegno
+ queued_post: "Posta in coda\n"
+ queued_user: Utente in coda
+ filter_label: Digita
+ reputation: reputazione
+ flag_post_type: Post contrassegnato come {{ type }}.
+ flag_user_type: Utente contrassegnato come {{ type }}.
+ edit_post: Modifica il post
+ list_post: Lista il post
+ unlist_post: Rimuovi post
timeline:
undeleted: Non cancellato
deleted: eliminato
@@ -1858,6 +1864,6 @@ ui:
post_hide_list: Questo post è stato nascosto dall'elenco.
post_show_list: Questo post è stato mostrato in elenco.
post_reopen: Questo post è stato riaperto.
- post_list: This post has been listed.
- post_unlist: This post has been unlisted.
- post_pending: Your post is awaiting review. This is a preview, it will be visible after it has been approved.
+ post_list: Questo post è stato inserito.
+ post_unlist: Questo post è stato rimosso.
+ post_pending: Il tuo post è in attesa di revisione. Sarà visibile dopo essere stato approvato.
diff --git a/i18n/ja_JP.yaml b/i18n/ja_JP.yaml
index d1452052..10859efd 100644
--- a/i18n/ja_JP.yaml
+++ b/i18n/ja_JP.yaml
@@ -518,7 +518,7 @@ ui:
edit_tag: タグを編集
ask_a_question: 質問を追加
edit_question: 質問を編集
- edit_answer: 回答を編集
+ edit_answer: 回答を編集
search: 検索
posts_containing: Posts containing
settings: 設定
@@ -763,8 +763,8 @@ ui:
Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
tip_vote: It adds something useful to the post
edit_answer:
- title: 回答を編集
- default_reason: 回答を編集
+ title: 回答を編集
+ default_reason: 回答を編集
default_first_reason: 回答を追加
form:
fields:
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/ko_KR.yaml b/i18n/ko_KR.yaml
index 1e584537..ab5db7de 100644
--- a/i18n/ko_KR.yaml
+++ b/i18n/ko_KR.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/nl_NL.yaml b/i18n/nl_NL.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/nl_NL.yaml
+++ b/i18n/nl_NL.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/no_NO.yaml b/i18n/no_NO.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/no_NO.yaml
+++ b/i18n/no_NO.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/pl_PL.yaml b/i18n/pl_PL.yaml
index b33392da..aeb867dc 100644
--- a/i18n/pl_PL.yaml
+++ b/i18n/pl_PL.yaml
@@ -142,7 +142,7 @@ backend:
error:
common:
invalid_url:
- other: Invalid URL.
+ other: Nieprawidłowy URL.
password:
space_invalid:
other: Hasło nie może zawierać spacji.
@@ -204,7 +204,7 @@ backend:
already_deleted:
other: Ten post został usunięty.
under_review:
- other: Your post is awaiting review. It will be visible after it has been approved.
+ other: Twój post oczekuje na recenzje. Będzie widoczny po jej akceptacji.
not_found:
other: Pytanie nie zostało odnalezione.
cannot_deleted:
@@ -491,14 +491,14 @@ backend:
accepted:
other: zaakceptowane
edit:
- other: edit
+ other: edytuj
review:
queued_post:
- other: Queued post
+ other: Post w kolejce
flagged_post:
- other: Flagged post
+ other: Post oznaczony
suggested_post_edit:
- other: Suggested edits
+ other: Sugerowane zmiany
# The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
@@ -680,8 +680,8 @@ ui:
empty: Nie może być puste.
msg:
empty: Proszę wybrać powód.
- not_a_url: URL format is incorrect.
- url_not_match: URL origin does not match the current website.
+ not_a_url: Format adresu URL jest nieprawidłowy.
+ url_not_match: Wskazany URL nie pasuje do bieżącej witryny.
tag_modal:
title: Utwórz nowy tag
form:
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Wysłaliśmy e-mail na ten adres. Prosimy postępować zgodnie z instrukcjami potwierdzającymi.
email:
- label: Nowy email
- msg: Nowy adres email nie może być pusty.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Aktualne hasło
msg: Hasło nie może być puste.
@@ -1049,7 +1051,7 @@ ui:
asked: zadał(a)
update: Zmodyfikowane
edit: edytowany
- commented: commented
+ commented: skomentowano
Views: Wyświetlone
Follow: Obserwuj
Following: Obserwuje
@@ -1067,7 +1069,7 @@ ui:
title: Odpowiedzi
score: Ocena
newest: Najnowsze
- oldest: Oldest
+ oldest: Najstarsze
btn_accept: Akceptuj
btn_accepted: Zaakceptowane
write_answer:
@@ -1092,13 +1094,13 @@ ui:
title: Otwórz ponownie ten post
content: Czy na pewno chcesz go ponownie otworzyć?
list:
- confirm_btn: List
- title: List this post
+ confirm_btn: Lista
+ title: Pokaż ten post
content: Are you sure you want to list?
unlist:
- confirm_btn: Unlist
- title: Unlist this post
- content: Are you sure you want to unlist?
+ confirm_btn: Usuń z listy
+ title: Usuń ten post z listy
+ content: Czy na pewno chcesz usunąć z listy?
pin:
title: Przypnij ten post
content: Czy na pewno chcesz przypiąć go globalnie? Ten post będzie wyświetlany na górze wszystkich list postów.
@@ -1120,7 +1122,7 @@ ui:
save: Zapisz
delete: Usuń
undelete: Przywróć
- list: List
+ list: Lista
unlist: Unlist
unlisted: Unlisted
login: Zaloguj się
@@ -1155,11 +1157,12 @@ ui:
post_lowercase: post
filter: Filter
ignore: Ignore
- submit: Submit
- normal: Normal
- closed: Closed
- deleted: Deleted
- pending: Pending
+ submit: Prześlij
+ normal: Normalny
+ closed: Zamknięty
+ deleted: Usunięty
+ pending: Oczekujący
+ more: More
search:
title: Wyniki wyszukiwania
keywords: Słowa kluczowe
@@ -1176,7 +1179,7 @@ ui:
more: Więcej
tips:
title: Porady dotyczące wyszukiwania zaawansowanego
- tag: "<1>[tag]</1> wyszukiwanie w obrębie tagu"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> wyszukiwanie według autora"
answer: "<1> answers:0 </1> pytania bez odpowiedzi"
score: "<1>score:3</1> posty z oceną 3+"
@@ -1576,8 +1579,8 @@ ui:
validate: Email kontaktowy nie jest poprawny.
text: Adres email głównego kontaktu odpowiedzialnego za tę stronę.
check_update:
- label: Software updates
- text: Automatically check for updates
+ label: Aktualizacjia oprogramowania
+ text: Automatycznie sprawdzaj dostępność aktualizacji
interface:
page_title: Interfejs
language:
@@ -1774,6 +1777,9 @@ ui:
level:
label: Wymagany poziom reputacji
text: Wybierz reputację wymaganą dla uprawnień
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (opcjonalne)
empty: nie może być puste
@@ -1792,20 +1798,20 @@ ui:
edit_answer: Edytuj odpowiedź
edit_tag: Edytuj tag
empty: Nie ma więcej zadań do przeglądu.
- approve_revision_tip: Do you approve this revision?
- approve_flag_tip: Do you approve this flag?
- approve_post_tip: Do you approve this post?
- approve_user_tip: Do you approve this user?
+ approve_revision_tip: Czy akceptujesz tę wersję?
+ approve_flag_tip: Czy akceptujesz tę flagę?
+ approve_post_tip: Czy zatwierdzasz ten post?
+ approve_user_tip: Czy zatwierdzasz tego użytkownika?
suggest_edits: Suggested edits
- flag_post: Flag post
- flag_user: Flag user
+ flag_post: Oznacz wpis
+ flag_user: Oznacz użytkownika
queued_post: Queued post
queued_user: Queued user
- filter_label: Type
- reputation: reputation
- flag_post_type: Flagged this post as {{ type }}.
- flag_user_type: Flagged this user as {{ type }}.
- edit_post: Edit post
+ filter_label: Typ
+ reputation: reputacja
+ flag_post_type: Oznaczono ten wpis jako {{ type }}.
+ flag_user_type: Oznaczono tego użytkownika jako {{ type }}.
+ edit_post: Edytuj wpis
list_post: List post
unlist_post: Unlist post
timeline:
@@ -1858,6 +1864,6 @@ ui:
post_hide_list: Ten post został ukryty na liście.
post_show_list: Ten post został wyświetlony na liście.
post_reopen: Ten post został ponownie otwarty.
- post_list: This post has been listed.
- post_unlist: This post has been unlisted.
- post_pending: Your post is awaiting review. This is a preview, it will be visible after it has been approved.
+ post_list: Ten wpis został umieszczony na liście.
+ post_unlist: Ten wpis został usunięty z listy.
+ post_pending: Twój wpis oczekuje na recenzje. Będzie widoczny po jej akceptacji.
diff --git a/i18n/pt_PT.yaml b/i18n/pt_PT.yaml
index 8142d538..b88b9a10 100644
--- a/i18n/pt_PT.yaml
+++ b/i18n/pt_PT.yaml
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Enviamos um e-mail para esse endereço. Siga as instruções de confirmação.
email:
- label: E-mail
- msg: E-mail não pode ser vazio.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Senha atual
msg: Senha não pode ser vazio.
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Procurar Resultados
keywords: Palavras-chave
@@ -1176,7 +1179,7 @@ ui:
more: Mais
tips:
title: Dicas de Pesquisa Avançada
- tag: "<1>[tag]</1> pesquisar dentro de um marcador"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> buscar por autor"
answer: "<1>answers:0</1> perguntas não respondidas"
score: "<1>score:3</1> postagens com mais de 3+ placares"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Nível de reputação necessário
text: Escolha a reputação necessária para os privilégios
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (opcional)
empty: não pode ser vazio
diff --git a/i18n/ro_RO.yaml b/i18n/ro_RO.yaml
index 9f3ec8b6..2745023b 100644
--- a/i18n/ro_RO.yaml
+++ b/i18n/ro_RO.yaml
@@ -491,12 +491,12 @@ backend:
accepted:
other: acceptat
edit:
- other: edit
+ other: editează
review:
queued_post:
- other: Queued post
+ other: Posturi în așteptare
flagged_post:
- other: Flagged post
+ other: Postare marcată
suggested_post_edit:
other: Suggested edits
# The following fields are used for interface presentation(Front-end)
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Am trimis un e-mail la acea adresă. Vă rugăm să urmați instrucțiunile de confirmare.
email:
- label: E-mail nou
- msg: Câmpul e-mail nu poate fi gol.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Parolă actuală
msg: Parola nu poate fi goală.
@@ -1067,7 +1069,7 @@ ui:
title: Răspunsuri
score: Scor
newest: Cele mai noi
- oldest: Oldest
+ oldest: Cel mai vechi
btn_accept: Acceptă
btn_accepted: Acceptă
write_answer:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Rezultatele căutării
keywords: Cuvinte cheie
@@ -1176,7 +1179,7 @@ ui:
more: Mai mult
tips:
title: Sfaturi de căutare avansate
- tag: "<1>[tag]</1> caută fără o etichetă"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>utilizator:username</1> căutare de către autor"
answer: "<1>răspunsuri:0</1> întrebări fără răspuns"
score: "<1>scor:3</1> postări cu un scor de 3+"
@@ -1538,7 +1541,7 @@ ui:
status: Stare
action: Actiune
change: Schimbare
- pending: Pending
+ pending: În așteptare
filter:
placeholder: "Filtrează după titlu, întrebare: id"
answers:
@@ -1774,6 +1777,9 @@ ui:
level:
label: Nivel necesar de reputație
text: Alegeți reputația necesară pentru privilegii
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (opțional)
empty: nu poate fi lăsat necompletat
@@ -1801,12 +1807,12 @@ ui:
flag_user: Flag user
queued_post: Queued post
queued_user: Queued user
- filter_label: Type
+ filter_label: Tip
reputation: reputation
- flag_post_type: Flagged this post as {{ type }}.
+ flag_post_type: Acest post a fost marcat de {{ type }}.
flag_user_type: Flagged this user as {{ type }}.
- edit_post: Edit post
- list_post: List post
+ edit_post: Editează postarea
+ list_post: Listează postarea
unlist_post: Unlist post
timeline:
undeleted: restabilește
diff --git a/i18n/ru_RU.yaml b/i18n/ru_RU.yaml
index e56120b6..3b0dc47a 100644
--- a/i18n/ru_RU.yaml
+++ b/i18n/ru_RU.yaml
@@ -142,7 +142,7 @@ backend:
error:
common:
invalid_url:
- other: Invalid URL.
+ other: Неверная URL.
password:
space_invalid:
other: Пароль не должен содержать пробелы.
@@ -204,7 +204,7 @@ backend:
already_deleted:
other: Этот пост был удалён.
under_review:
- other: Your post is awaiting review. It will be visible after it has been approved.
+ other: Ваш пост ожидает проверки. Он станет видимым после одобрения.
not_found:
other: Вопрос не найден.
cannot_deleted:
@@ -491,18 +491,18 @@ backend:
accepted:
other: принято
edit:
- other: edit
+ other: редактировать
review:
queued_post:
other: Queued post
flagged_post:
other: Flagged post
suggested_post_edit:
- other: Suggested edits
+ other: Предложенные исправления
# The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
- title: Как отформатировать
+ title: 'Форматирование:'
desc: >-
<ul class="mb-0"><li><p class="mb-2">создание ссылок: </p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">пробел между абзацами</p></li><li><p class="mb-2"><em>_italic_</em> или **<strong>bold</strong>**</p></li><li><p class="mb-2">отступ для кода - 4 пробела</p></li><li><p class="mb-2">тег <code>></code> для цитаты в начале строки</p></li><li><p class="mb-2">обратные кавычки для строки кода <code>`like _thi [...]
pagination:
@@ -680,8 +680,8 @@ ui:
empty: Не может быть пустым.
msg:
empty: Пожалуйста, выбери причину.
- not_a_url: URL format is incorrect.
- url_not_match: URL origin does not match the current website.
+ not_a_url: Недопустимый формат URL.
+ url_not_match: URL адрес не соответствует текущему веб-сайту.
tag_modal:
title: Новый тег
form:
@@ -785,7 +785,7 @@ ui:
sort_buttons:
popular: Популярное
name: Имя
- newest: Самые новые
+ newest: Последние
button_follow: Подписаться
button_following: Подписки
tag_label: вопросы
@@ -809,9 +809,9 @@ ui:
empty: Заголовок не может быть пустым.
range: Заголовок должен быть меньше 150 символов
body:
- label: Тело вопроса
+ label: 'Вопрос:'
msg:
- empty: Тело вопроса не может быть пустым.
+ empty: Вопрос не может быть пустым.
tags:
label: Теги
msg:
@@ -824,7 +824,7 @@ ui:
label: Изменить краткое описание
placeholder: >-
Кратко опишите вносимые изменения (исправлена орфография, исправлена грамматика, улучшено форматирование)
- btn_post_question: Задайте свой вопрос
+ btn_post_question: Задать вопрос
btn_save_edits: Сохранить изменения
answer_question: Ответить на свой собственный вопрос
post_question&answer: Опубликуйте свой вопрос и ответ
@@ -832,7 +832,7 @@ ui:
add_btn: Тег
create_btn: новый тег
search_tag: Поиск тега
- hint: "Опишите, о чем ваш вопрос, требуется хотя бы один тег."
+ hint: "Добавьте хотя бы один тег."
no_result: Нет соответствующих тэгов
tag_required_text: Обязательный тег (хотя бы один)
header:
@@ -999,8 +999,10 @@ ui:
change_email_info: >-
Мы отправили электронное письмо на этот адрес. Пожалуйста, следуйте инструкциям из письма.
email:
- label: Новый email-адрес
- msg: Новый email не может быть пустым.
+ label: Email
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Текущий пароль
msg: Пароль не может быть пустым.
@@ -1066,7 +1068,7 @@ ui:
answers:
title: Ответы
score: Оценка
- newest: Самые новые
+ newest: Последние
oldest: Oldest
btn_accept: Принять
btn_accepted: Принято
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Результаты поиска
keywords: Ключевые слова
@@ -1170,13 +1173,13 @@ ui:
more: Ещё
sort_btns:
relevance: По релевантности
- newest: Самые новые
+ newest: Последние
active: Активные
score: Оценки
more: Больше
tips:
title: Советы по расширенному поиску
- tag: "<1>[тег]</1> поиск по тегу"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> поиск по автору"
answer: "<1>ответов:0</1> вопросы без ответов"
score: "<1>score:3</1> записи с рейтингом 3+"
@@ -1217,8 +1220,8 @@ ui:
x_answers: "{{ count }} ответов"
questions: Вопросы
answers: Ответы
- newest: Самые новые
- active: Активный
+ newest: Последние
+ active: Активные
frequent: Популярные
score: Оценка
unanswered: Без ответа
@@ -1238,7 +1241,7 @@ ui:
reputation: Репутация
comments: Комментарии
votes: Голоса
- newest: Самые новые
+ newest: Последние
score: Оценки
edit_profile: Редактировать профиль
visited_x_days: "Посещено {{ count }} дней"
@@ -1405,9 +1408,9 @@ ui:
comments: "Комментарии:"
votes: "Голоса:"
users: "Пользователи:"
- flags: "Флаги:"
+ flags: "Жалобы:"
reviews: "Reviews:"
- site_health: Site health
+ site_health: Здоровье сайта
version: "Версия:"
https: "HTTPS:"
upload_folder: "Каталог загрузки:"
@@ -1422,12 +1425,12 @@ ui:
database_size: "Размер базы данных:"
storage_used: "Использовано хранилища: "
uptime: "Время работы:"
- links: Links
+ links: Ссылки
plugins: Плагины
github: GitHub
blog: Блог
contact: Контакты
- forum: Forum
+ forum: Форум
documents: Документы
feedback: Обратная связь
support: Поддержка
@@ -1502,7 +1505,7 @@ ui:
all: Все
staff: Сотрудники
more: Ещё
- inactive: Неактивный
+ inactive: Неактивные
suspended: Заблокированные
deleted: Удаленные
normal: Обычный
@@ -1538,7 +1541,7 @@ ui:
status: Статус
action: Действие
change: Изменить
- pending: Pending
+ pending: Ожидают
filter:
placeholder: "Фильтровать по заголовку, question:id"
answers:
@@ -1576,8 +1579,8 @@ ui:
validate: Контактный адрес электронной почты не может быть пустым.
text: Адрес электронной почты контактного лица, ответственного за данный сайт.
check_update:
- label: Software updates
- text: Automatically check for updates
+ label: Обновления программного обеспечения
+ text: Автоматически проверять наличие обновлений
interface:
page_title: Интерфейс
language:
@@ -1774,6 +1777,9 @@ ui:
level:
label: Необходимый уровень репутации
text: Выберите количество репутации, необходимое для получения привилегий
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (опционально)
empty: не может быть пустым
@@ -1796,13 +1802,13 @@ ui:
approve_flag_tip: Do you approve this flag?
approve_post_tip: Do you approve this post?
approve_user_tip: Do you approve this user?
- suggest_edits: Suggested edits
+ suggest_edits: Предложенные исправления
flag_post: Flag post
flag_user: Flag user
queued_post: Queued post
queued_user: Queued user
filter_label: Type
- reputation: reputation
+ reputation: репутация
flag_post_type: Flagged this post as {{ type }}.
flag_user_type: Flagged this user as {{ type }}.
edit_post: Edit post
diff --git a/i18n/sk_SK.yaml b/i18n/sk_SK.yaml
index 7daf62b0..7bbe5cf1 100644
--- a/i18n/sk_SK.yaml
+++ b/i18n/sk_SK.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
Na túto adresu sme poslali e-mail. Postupujte podľa pokynov na potvrdenie.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Výsledky vyhľadávania
keywords: Kľúčové slová
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (voliteľné)
empty: nemôže byť prázdne
diff --git a/i18n/sq_AL.yaml b/i18n/sq_AL.yaml
index 483744f7..07e50541 100644
--- a/i18n/sq_AL.yaml
+++ b/i18n/sq_AL.yaml
@@ -708,7 +708,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -803,7 +805,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/sr_SP.yaml b/i18n/sr_SP.yaml
index 28941e9e..39b63e8c 100644
--- a/i18n/sr_SP.yaml
+++ b/i18n/sr_SP.yaml
@@ -715,7 +715,9 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Email
- msg: Email cannot be empty.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
password_title: Password
current_pass:
label: Current Password
@@ -811,7 +813,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
diff --git a/i18n/sv_SE.yaml b/i18n/sv_SE.yaml
index 7cef6932..66722d03 100644
--- a/i18n/sv_SE.yaml
+++ b/i18n/sv_SE.yaml
@@ -1000,6 +1000,8 @@ ui:
We've sent an email to that address. Please follow the confirmation instructions.
email:
label: Ny e-postadress
+ new_email:
+ label: New email
msg: New email cannot be empty.
pass:
label: Nuvarande lösenord
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Sökresultat
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/te_IN.yaml b/i18n/te_IN.yaml
index dc22340c..ef79b398 100644
--- a/i18n/te_IN.yaml
+++ b/i18n/te_IN.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/tr_TR.yaml b/i18n/tr_TR.yaml
index 0460fda5..9d2c8aca 100644
--- a/i18n/tr_TR.yaml
+++ b/i18n/tr_TR.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/uk_UA.yaml b/i18n/uk_UA.yaml
index 21421f66..49a8abfd 100644
--- a/i18n/uk_UA.yaml
+++ b/i18n/uk_UA.yaml
@@ -1000,7 +1000,9 @@ ui:
Ми надіслали електронний лист на цю адресу. Будь ласка, дотримуйтесь інструкцій для підтвердження.
email:
label: Нова електронна пошта
- msg: Комірка нової електронної пошти не може бути порожньою.
+ new_email:
+ label: New email
+ msg: New email cannot be empty.
pass:
label: Поточний пароль
msg: Комірка паролю не може бути порожньою.
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Результати пошуку
keywords: Ключові слова
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/vi_VN.yaml b/i18n/vi_VN.yaml
index d28041ce..a66565d1 100644
--- a/i18n/vi_VN.yaml
+++ b/i18n/vi_VN.yaml
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
We've sent an email to that address. Please follow the confirmation instructions.
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: Search Results
keywords: Keywords
@@ -1176,7 +1179,7 @@ ui:
more: More
tips:
title: Advanced Search Tips
- tag: "<1>[tag]</1> search withing a tag"
+ tag: "<1>[tag]</1> search with a tag"
user: "<1>user:username</1> search by author"
answer: "<1>answers:0</1> unanswered questions"
score: "<1>score:3</1> posts with a 3+ score"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (optional)
empty: cannot be empty
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index f4d142ca..a2459da5 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -142,7 +142,7 @@ backend:
error:
common:
invalid_url:
- other: 不合法的 URL
+ other: 无效的 URL。
password:
space_invalid:
other: 密码不得含有空格。
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
邮件已发送。请根据指引完成验证。
email:
+ label: 电子邮件地址
+ new_email:
label: 新的电子邮件地址
msg: 新邮箱不能为空。
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: 已关闭
deleted: 已删除
pending: 等待处理
+ more: 更多
search:
title: 搜索结果
keywords: 关键词
@@ -1774,6 +1777,9 @@ ui:
level:
label: 级别所需声望
text: 选择特权所需的声望值
+ msg:
+ should_be_number: 输入必须是数字
+ number_larger_1: 数字应该大于等于 1
form:
optional: (选填)
empty: 不能为空
diff --git a/i18n/zh_TW.yaml b/i18n/zh_TW.yaml
index c0377af7..3e3f26b0 100644
--- a/i18n/zh_TW.yaml
+++ b/i18n/zh_TW.yaml
@@ -29,9 +29,9 @@ backend:
database_error:
other: 資料庫錯誤。
forbidden_error:
- other: Forbidden.
+ other: 訪問被拒絕。
duplicate_request_error:
- other: Duplicate submission.
+ other: 提交重複。
action:
report:
other: 舉報
@@ -42,11 +42,11 @@ backend:
close:
other: 關閉
reopen:
- other: Reopen
+ other: 再次開啟。
forbidden_error:
- other: Forbidden.
+ other: 訪問被拒絕。
pin:
- other: Pin
+ other: 置頂
hide:
other: Unlist
unpin:
@@ -738,7 +738,7 @@ ui:
dates:
long_date: MM月DD日
long_date_with_year: "YYYY年MM月DD日"
- long_date_with_time: "YYYY年MM月DD日 HH:mm"
+ long_date_with_time: "YYYY 年 MM 月 DD 日 HH:mm"
now: 剛剛
x_seconds_ago: "{{count}} 秒前"
x_minutes_ago: "{{count}} 分鐘前"
@@ -756,7 +756,7 @@ ui:
btn_flag: 舉報
btn_save_edits: 保存
btn_cancel: 取消
- show_more: "{{count}}條剩餘評論"
+ show_more: "{{count}} 條剩餘評論"
tip_question: >-
通过評論询问更多问题或提出改進建議。避免在評論中回答問題。
tip_answer: >-
@@ -999,6 +999,8 @@ ui:
change_email_info: >-
我們已經寄出一封郵件至此電子郵件地址,請遵照說明進行確認。
email:
+ label: Email
+ new_email:
label: New email
msg: New email cannot be empty.
pass:
@@ -1160,6 +1162,7 @@ ui:
closed: Closed
deleted: Deleted
pending: Pending
+ more: More
search:
title: 搜尋結果
keywords: 關鍵詞
@@ -1657,7 +1660,7 @@ ui:
restrict_answer:
title: Restrict answer
label: Each user can only write one answer for each question
- text: "They can use the edit link to refine and improve their existing answer, instead."
+ text: "關閉此功能以允許使用者對同一問題撰寫多個答案,可能導致答案不集中。"
recommend_tags:
label: Recommend tags
text: "請在上方輸入標籤固定連結,每行一個標籤。"
@@ -1774,6 +1777,9 @@ ui:
level:
label: Reputation required level
text: Choose the reputation required for the privileges
+ msg:
+ should_be_number: the input should be number
+ number_larger_1: number should be equal or larger than 1
form:
optional: (選填)
empty: 不能為空
diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go
index 390aa422..0142c58c 100644
--- a/internal/controller/answer_controller.go
+++ b/internal/controller/answer_controller.go
@@ -260,6 +260,9 @@ func (ac *AnswerController) Add(ctx *gin.Context) {
}
}
+ req.UserAgent = ctx.GetHeader("User-Agent")
+ req.IP = ctx.ClientIP()
+
answerID, err := ac.answerService.Insert(ctx, req)
if err != nil {
handler.HandleResponse(ctx, err, nil)
diff --git a/internal/controller/controller.go b/internal/controller/controller.go
index 42c2c1d3..cfaf8f33 100644
--- a/internal/controller/controller.go
+++ b/internal/controller/controller.go
@@ -48,4 +48,6 @@ var ProviderSetController = wire.NewSet(
NewPermissionController,
NewUserPluginController,
NewReviewController,
+ NewCaptchaController,
+ NewEmbedController,
)
diff --git a/internal/controller/embed_controller.go b/internal/controller/embed_controller.go
new file mode 100644
index 00000000..109d8fb7
--- /dev/null
+++ b/internal/controller/embed_controller.go
@@ -0,0 +1,47 @@
+package controller
+
+import (
+ "github.com/apache/incubator-answer/internal/base/handler"
+ "github.com/apache/incubator-answer/internal/schema"
+ "github.com/apache/incubator-answer/plugin"
+ "github.com/gin-gonic/gin"
+)
+
+type EmbedController struct {
+}
+
+func NewEmbedController() *EmbedController {
+ return &EmbedController{}
+}
+
+// GetEmbedConfig godoc
+// @Summary GetEmbedConfig
+// @Description GetEmbedConfig
+// @Tags PluginEmbed
+// @Accept json
+// @Produce json
+// @Router /answer/api/v1/embed/config [get]
+// @Success 200 {object} handler.RespBody{data=[]schema.GetEmbedOptionResp}
+func (c *EmbedController) GetEmbedConfig(ctx *gin.Context) {
+ resp := make([]*schema.GetEmbedOptionResp, 0)
+ var slugName string
+
+ _ = plugin.CallEmbed(func(base plugin.Embed) error {
+ slugName = base.Info().SlugName
+ return nil
+ })
+
+ _ = plugin.CallConfig(func(fn plugin.Config) error {
+ if fn.Info().SlugName == slugName {
+ for _, field := range fn.ConfigFields() {
+ resp = append(resp, &schema.GetEmbedOptionResp{
+ Platform: field.Name,
+ Enable: field.Value.(bool),
+ })
+ }
+ return nil
+ }
+ return nil
+ })
+ handler.HandleResponse(ctx, nil, resp)
+}
diff --git a/internal/controller/controller.go b/internal/controller/plugin_captcha_controller.go
similarity index 50%
copy from internal/controller/controller.go
copy to internal/controller/plugin_captcha_controller.go
index 42c2c1d3..7b47e071 100644
--- a/internal/controller/controller.go
+++ b/internal/controller/plugin_captcha_controller.go
@@ -19,33 +19,35 @@
package controller
-import "github.com/google/wire"
+import (
+ "encoding/json"
-// ProviderSetController is controller providers.
-var ProviderSetController = wire.NewSet(
- NewLangController,
- NewCommentController,
- NewReportController,
- NewVoteController,
- NewTagController,
- NewFollowController,
- NewCollectionController,
- NewUserController,
- NewQuestionController,
- NewAnswerController,
- NewSearchController,
- NewRevisionController,
- NewRankController,
- NewReasonController,
- NewNotificationController,
- NewSiteInfoController,
- NewDashboardController,
- NewUploadController,
- NewActivityController,
- NewTemplateController,
- NewConnectorController,
- NewUserCenterController,
- NewPermissionController,
- NewUserPluginController,
- NewReviewController,
+ "github.com/apache/incubator-answer/internal/base/handler"
+ "github.com/apache/incubator-answer/plugin"
+ "github.com/gin-gonic/gin"
)
+
+// CaptchaController comment controller
+type CaptchaController struct {
+}
+
+// NewCaptchaController new controller
+func NewCaptchaController() *CaptchaController {
+ return &CaptchaController{}
+}
+
+type GetCaptchaConfigResp struct {
+ SlugName string `json:"slug_name"`
+ Config map[string]any `json:"config"`
+}
+
+// GetCaptchaConfig get captcha config
+func (uc *CaptchaController) GetCaptchaConfig(ctx *gin.Context) {
+ resp := &GetCaptchaConfigResp{}
+ _ = plugin.CallCaptcha(func(fn plugin.Captcha) error {
+ resp.SlugName = fn.Info().SlugName
+ _ = json.Unmarshal([]byte(fn.GetConfig()), &resp.Config)
+ return nil
+ })
+ handler.HandleResponse(ctx, nil, resp)
+}
diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go
index 072ffd74..1b0dee92 100644
--- a/internal/controller/question_controller.go
+++ b/internal/controller/question_controller.go
@@ -434,6 +434,9 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
return
}
+ req.UserAgent = ctx.GetHeader("User-Agent")
+ req.IP = ctx.ClientIP()
+
resp, err := qc.questionService.AddQuestion(ctx, req)
if err != nil {
errlist, ok := resp.([]*validator.FormErrorField)
@@ -526,6 +529,8 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) {
return
}
+ req.UserAgent = ctx.GetHeader("User-Agent")
+ req.IP = ctx.ClientIP()
resp, err := qc.questionService.AddQuestion(ctx, questionReq)
if err != nil {
errlist, ok := resp.([]*validator.FormErrorField)
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index b128b91a..fa95e460 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -218,6 +218,11 @@ func (tc *TemplateController) QuestionInfoeRdirect(ctx *gin.Context, siteInfo *s
}
}
+ if _, err := tc.templateRenderController.AnswerDetail(ctx, answerID); err != nil {
+ answerID = ""
+ titleIsAnswerID = false
+ }
+
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, questionID)
if siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionID || siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDByShortID {
if len(ctx.Request.URL.Query()) > 0 {
diff --git a/internal/controller/template_render/answer.go b/internal/controller/template_render/answer.go
index cc94f0dd..72a0925a 100644
--- a/internal/controller/template_render/answer.go
+++ b/internal/controller/template_render/answer.go
@@ -28,3 +28,7 @@ import (
func (t *TemplateRenderController) AnswerList(ctx context.Context, req *schema.AnswerListReq) ([]*schema.AnswerInfo, int64, error) {
return t.answerService.SearchList(ctx, req)
}
+
+func (t *TemplateRenderController) AnswerDetail(ctx context.Context, id string) (*schema.AnswerInfo, error) {
+ return t.answerService.GetDetail(ctx, id)
+}
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index f0e70cfe..20fc68ec 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -509,19 +509,6 @@ func (uc *UserController) ActionRecord(ctx *gin.Context) {
}
-// UserRegisterCaptcha godoc
-// @Summary UserRegisterCaptcha
-// @Description UserRegisterCaptcha
-// @Tags User
-// @Accept json
-// @Produce json
-// @Success 200 {object} handler.RespBody{data=schema.UserLoginResp}
-// @Router /answer/api/v1/user/register/captcha [get]
-func (uc *UserController) UserRegisterCaptcha(ctx *gin.Context) {
- resp, err := uc.actionService.UserRegisterCaptcha(ctx)
- handler.HandleResponse(ctx, err, resp)
-}
-
// GetUserNotificationConfig get user's notification config
// @Summary get user's notification config
// @Description get user's notification config
@@ -657,6 +644,26 @@ func (uc *UserController) UserRanking(ctx *gin.Context) {
handler.HandleResponse(ctx, err, resp)
}
+// UserStaff get user staff
+// @Summary get user staff
+// @Description get user staff
+// @Tags User
+// @Accept json
+// @Produce json
+// @Security ApiKeyAuth
+// @Param data body schema.GetUserStaffReq true "GetUserStaffReq"
+// @Success 200 {object} handler.RespBody{data=schema.GetUserStaffResp}
+// @Router /answer/api/v1/user/staff [get]
+func (uc *UserController) UserStaff(ctx *gin.Context) {
+ req := &schema.GetUserStaffReq{}
+ if handler.BindAndCheck(ctx, req) {
+ return
+ }
+
+ resp, err := uc.userService.GetUserStaff(ctx, req)
+ handler.HandleResponse(ctx, err, resp)
+}
+
// UserUnsubscribeNotification unsubscribe notification
// @Summary unsubscribe notification
// @Description unsubscribe notification
diff --git a/internal/controller_admin/siteinfo_controller.go b/internal/controller_admin/siteinfo_controller.go
index 6c1966b3..1526fd20 100644
--- a/internal/controller_admin/siteinfo_controller.go
+++ b/internal/controller_admin/siteinfo_controller.go
@@ -21,6 +21,7 @@ package controller_admin
import (
"net/http"
+ "html"
"github.com/apache/incubator-answer/internal/base/handler"
"github.com/apache/incubator-answer/internal/base/middleware"
@@ -237,6 +238,7 @@ func (sc *SiteInfoController) UpdateGeneral(ctx *gin.Context) {
return
}
err := sc.siteInfoService.SaveSiteGeneral(ctx, req)
+ req.Name = html.UnescapeString(req.Name)
handler.HandleResponse(ctx, err, req)
}
diff --git a/internal/migrations/init.go b/internal/migrations/init.go
index 8448bfc4..0c50857a 100644
--- a/internal/migrations/init.go
+++ b/internal/migrations/init.go
@@ -23,6 +23,10 @@ import (
"context"
"encoding/json"
"fmt"
+ "time"
+
+ "github.com/apache/incubator-answer/internal/base/data"
+ "github.com/apache/incubator-answer/internal/repo/unique"
"github.com/apache/incubator-answer/internal/schema"
"github.com/segmentfault/pacman/log"
@@ -73,6 +77,7 @@ func (m *Mentor) InitDB() error {
m.do("init site info user config", m.initSiteInfoUsersConfig)
m.do("init site info privilege rank", m.initSiteInfoPrivilegeRank)
m.do("init site info write", m.initSiteInfoWrite)
+ m.do("init default content", m.initDefaultContent)
return m.err
}
@@ -255,3 +260,136 @@ func (m *Mentor) initSiteInfoWrite() {
Status: 1,
})
}
+
+func (m *Mentor) initDefaultContent() {
+ uniqueIDRepo := unique.NewUniqueIDRepo(&data.Data{DB: m.engine})
+ now := time.Now()
+
+ tagId, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Tag{}.TableName())
+ if err != nil {
+ m.err = err
+ return
+ }
+
+ q1Id, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Question{}.TableName())
+ if err != nil {
+ m.err = err
+ return
+ }
+
+ a1Id, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Answer{}.TableName())
+ if err != nil {
+ m.err = err
+ return
+ }
+
+ q2Id, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Question{}.TableName())
+ if err != nil {
+ m.err = err
+ return
+ }
+
+ a2Id, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Answer{}.TableName())
+ if err != nil {
+ m.err = err
+ return
+ }
+
+ tag := entity.Tag{
+ ID: tagId,
+ SlugName: "support",
+ DisplayName: "support",
+ OriginalText: "For general support questions.",
+ ParsedText: "<p>For general support questions.</p>",
+ UserID: "1",
+ QuestionCount: 1,
+ Status: entity.TagStatusAvailable,
+ }
+
+ q1 := &entity.Question{
+ ID: q1Id,
+ CreatedAt: now,
+ UserID: "1",
+ Title: "What is a tag?",
+ OriginalText: "When asking a question, we need to choose tags. What are tags and why should I use them?",
+ ParsedText: "<p>When asking a question, we need to choose tags. What are tags and why should I use them?</p>",
+ Show: entity.QuestionShow,
+ Status: entity.QuestionStatusAvailable,
+ AnswerCount: 1,
+ }
+
+ a1 := &entity.Answer{
+ ID: a1Id,
+ CreatedAt: now,
+ QuestionID: q1Id,
+ UserID: "1",
+ OriginalText: "Tags help to organize content and make searching easier. It helps your question get more attention from people interested in that tag. Tags also send notifications. If you are interested in some topic, follow that tag to get updates.",
+ ParsedText: "<p>Tags help to organize content and make searching easier. It helps your question get more attention from people interested in that tag. Tags also send notifications. If you are interested in some topic, follow that tag to get updates.</p>",
+ Status: entity.AnswerStatusAvailable,
+ }
+
+ q2 := &entity.Question{
+ ID: q2Id,
+ CreatedAt: now,
+ UserID: "1",
+ Title: "What is reputation and how do I earn them?",
+ OriginalText: "I see that each user has reputation points, What is it and how do I earn them?",
+ ParsedText: "<p>I see that each user has reputation points, What is it and how do I earn them?</p>",
+ Show: entity.QuestionShow,
+ Status: entity.QuestionStatusAvailable,
+ AnswerCount: 1,
+ }
+
+ a2 := &entity.Answer{
+ ID: a2Id,
+ CreatedAt: now,
+ QuestionID: q2Id,
+ UserID: "1",
+ OriginalText: "Your reputation points show how much the community values your knowledge. You earn points when someone find your question or answer helpful. You also get points when the person who asked the question thinks you did a good job and accepts your answer.",
+ ParsedText: "<p>Your reputation points show how much the community values your knowledge. You earn points when someone find your question or answer helpful. You also get points when the person who asked the question thinks you did a good job and accepts your answer.</p>",
+ Status: entity.AnswerStatusAvailable,
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(tag)
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(q1)
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(a1)
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(entity.TagRel{
+ ObjectID: q1.ID,
+ TagID: tag.ID,
+ Status: entity.TagRelStatusAvailable,
+ })
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(q2)
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(a2)
+ if m.err != nil {
+ return
+ }
+
+ _, m.err = m.engine.Context(m.ctx).Insert(entity.TagRel{
+ ObjectID: q2.ID,
+ TagID: tag.ID,
+ Status: entity.TagRelStatusAvailable,
+ })
+ if m.err != nil {
+ return
+ }
+}
diff --git a/internal/repo/collection/collection_group_repo.go b/internal/repo/collection/collection_group_repo.go
index 90c21963..05711f5a 100644
--- a/internal/repo/collection/collection_group_repo.go
+++ b/internal/repo/collection/collection_group_repo.go
@@ -21,7 +21,6 @@ package collection
import (
"context"
- "xorm.io/xorm"
"github.com/apache/incubator-answer/internal/service/collection"
"xorm.io/xorm"
diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go
index 8dca0c65..29ab9c5d 100644
--- a/internal/repo/question/question_repo.go
+++ b/internal/repo/question/question_repo.go
@@ -26,7 +26,6 @@ import (
"strings"
"time"
"unicode"
- "xorm.io/xorm"
"github.com/apache/incubator-answer/internal/base/constant"
"github.com/apache/incubator-answer/internal/base/data"
diff --git a/internal/repo/repo_test/tag_rel_repo_test.go b/internal/repo/repo_test/tag_rel_repo_test.go
index edcd45e9..f3696590 100644
--- a/internal/repo/repo_test/tag_rel_repo_test.go
+++ b/internal/repo/repo_test/tag_rel_repo_test.go
@@ -36,13 +36,13 @@ var (
tagRelOnce sync.Once
testTagRelList = []*entity.TagRel{
{
- ObjectID: "10010000000000001",
- TagID: "10030000000000001",
+ ObjectID: "10010000000000101",
+ TagID: "10030000000000101",
Status: entity.TagRelStatusAvailable,
},
{
- ObjectID: "10010000000000002",
- TagID: "10030000000000002",
+ ObjectID: "10010000000000202",
+ TagID: "10030000000000202",
Status: entity.TagRelStatusAvailable,
},
}
@@ -68,7 +68,7 @@ func Test_tagListRepo_BatchGetObjectTagRelList(t *testing.T) {
func Test_tagListRepo_CountTagRelByTagID(t *testing.T) {
tagRelOnce.Do(addTagRelList)
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
- count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
+ count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
assert.NoError(t, err)
assert.Equal(t, int64(1), count)
}
@@ -96,7 +96,7 @@ func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
err = tagRelRepo.RemoveTagRelListByIDs(context.TODO(), ids)
assert.NoError(t, err)
- count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
+ count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
@@ -107,7 +107,7 @@ func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
err = tagRelRepo.EnableTagRelByIDs(context.TODO(), ids)
assert.NoError(t, err)
- count, err = tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000001")
+ count, err = tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
assert.NoError(t, err)
assert.Equal(t, int64(1), count)
}
diff --git a/internal/repo/tag/tag_repo.go b/internal/repo/tag/tag_repo.go
index eea54995..c9c2c1fa 100644
--- a/internal/repo/tag/tag_repo.go
+++ b/internal/repo/tag/tag_repo.go
@@ -77,7 +77,7 @@ func (tr *tagRepo) RecoverTag(ctx context.Context, tagID string) (err error) {
return
}
-// MustGetTagByID get tag by id
+// MustGetTagByNameOrID get tag by name or id
func (tr *tagRepo) MustGetTagByNameOrID(ctx context.Context, tagID, slugName string) (
tag *entity.Tag, exist bool, err error) {
if len(tagID) == 0 && len(slugName) == 0 {
diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go
index 628394cf..568ebf90 100644
--- a/internal/repo/user/user_repo.go
+++ b/internal/repo/user/user_repo.go
@@ -246,12 +246,16 @@ func (ur *userRepo) GetUserCount(ctx context.Context) (count int64, err error) {
return count, nil
}
-func (ur *userRepo) SearchUserListByName(ctx context.Context, name string, limit int) (userList []*entity.User, err error) {
+func (ur *userRepo) SearchUserListByName(ctx context.Context, name string, limit int,
+ onlyStaff bool) (userList []*entity.User, err error) {
userList = make([]*entity.User, 0)
session := ur.data.DB.Context(ctx)
+ if onlyStaff {
+ session.Join("INNER", "user_role_rel", "`user`.id = `user_role_rel`.user_id AND `user_role_rel`.role_id > 1")
+ }
session.Where("status = ?", entity.UserStatusAvailable)
session.Where("username LIKE ? OR display_name LIKE ?", strings.ToLower(name)+"%", name+"%")
- session.OrderBy("username ASC, id DESC")
+ session.OrderBy("username ASC, `user`.id DESC")
session.Limit(limit)
err = session.Find(&userList)
if err != nil {
diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go
index 1ea2be89..40a5bba3 100644
--- a/internal/router/answer_api_router.go
+++ b/internal/router/answer_api_router.go
@@ -131,7 +131,6 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(authUserMiddleware *
routerGroup := r.Group("", middleware.BanAPIForUserCenter)
routerGroup.POST("/user/login/email", a.userController.UserEmailLogin)
routerGroup.POST("/user/register/email", a.userController.UserRegisterByEmail)
- routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
routerGroup.POST("/user/email/verification", a.userController.UserVerifyEmail)
routerGroup.PUT("/user/email", a.userController.UserChangeEmailVerify)
routerGroup.POST("/user/password/reset", a.userController.RetrievePassWord)
@@ -146,6 +145,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
// user
r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername)
r.GET("/user/ranking", a.userController.UserRanking)
+ r.GET("/user/staff", a.userController.UserStaff)
// answer
r.GET("/answer/info", a.answerController.Get)
diff --git a/internal/router/plugin_api_router.go b/internal/router/plugin_api_router.go
index 8554b4c7..e4a85a15 100644
--- a/internal/router/plugin_api_router.go
+++ b/internal/router/plugin_api_router.go
@@ -27,15 +27,21 @@ import (
type PluginAPIRouter struct {
connectorController *controller.ConnectorController
userCenterController *controller.UserCenterController
+ captchaController *controller.CaptchaController
+ embedController *controller.EmbedController
}
func NewPluginAPIRouter(
connectorController *controller.ConnectorController,
userCenterController *controller.UserCenterController,
+ captchaController *controller.CaptchaController,
+ embedController *controller.EmbedController,
) *PluginAPIRouter {
return &PluginAPIRouter{
connectorController: connectorController,
userCenterController: userCenterController,
+ captchaController: captchaController,
+ embedController: embedController,
}
}
@@ -54,6 +60,10 @@ func (pr *PluginAPIRouter) RegisterUnAuthConnectorRouter(r *gin.RouterGroup) {
r.GET(controller.UserCenterSignUpRedirectRouter, pr.userCenterController.UserCenterSignUpRedirect)
r.GET("/user-center/login/callback", pr.userCenterController.UserCenterLoginCallback)
r.GET("/user-center/sign-up/callback", pr.userCenterController.UserCenterSignUpCallback)
+
+ // captcha plugin
+ r.GET("/captcha/config", pr.captchaController.GetCaptchaConfig)
+ r.GET("/embed/config", pr.embedController.GetEmbedConfig)
}
func (pr *PluginAPIRouter) RegisterAuthUserConnectorRouter(r *gin.RouterGroup) {
diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go
index d0c208e6..01bc64a2 100644
--- a/internal/schema/answer_schema.go
+++ b/internal/schema/answer_schema.go
@@ -54,6 +54,8 @@ type AnswerAddReq struct {
CanRecover bool `json:"-"`
CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
+ IP string `json:"-"`
+ UserAgent string `json:"-"`
}
func (req *AnswerAddReq) Check() (errFields []*validator.FormErrorField, err error) {
diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go
index 4f318d87..275e1b9a 100644
--- a/internal/schema/comment_schema.go
+++ b/internal/schema/comment_schema.go
@@ -38,6 +38,9 @@ type AddCommentReq struct {
ParsedText string `json:"-"`
// @ user id list
MentionUsernameList []string `validate:"omitempty" json:"mention_username_list"`
+ CaptchaID string `json:"captcha_id"`
+ CaptchaCode string `json:"captcha_code"`
+
// user id
UserID string `json:"-"`
// whether user can add it
@@ -45,9 +48,7 @@ type AddCommentReq struct {
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
- CanDelete bool `json:"-"`
- CaptchaID string `json:"captcha_id"` // captcha_id
- CaptchaCode string `json:"captcha_code"`
+ CanDelete bool `json:"-"`
}
func (req *AddCommentReq) Check() (errFields []*validator.FormErrorField, err error) {
@@ -61,7 +62,7 @@ type RemoveCommentReq struct {
CommentID string `validate:"required" json:"comment_id"`
// user id
UserID string `json:"-"`
- CaptchaID string `json:"captcha_id"` // captcha_id
+ CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
}
diff --git a/internal/schema/plugin_option_schema.go b/internal/schema/plugin_option_schema.go
new file mode 100644
index 00000000..e0224609
--- /dev/null
+++ b/internal/schema/plugin_option_schema.go
@@ -0,0 +1,6 @@
+package schema
+
+type GetEmbedOptionResp struct {
+ Platform string `json:"platform"`
+ Enable bool `json:"enable"`
+}
diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go
index 48fd97c5..f915d5ea 100644
--- a/internal/schema/question_schema.go
+++ b/internal/schema/question_schema.go
@@ -86,6 +86,8 @@ type QuestionAdd struct {
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
+ IP string `json:"-"`
+ UserAgent string `json:"-"`
}
func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err error) {
@@ -115,6 +117,8 @@ type QuestionAddByAnswer struct {
QuestionPermission
CaptchaID string `json:"captcha_id"` // captcha_id
CaptchaCode string `json:"captcha_code"`
+ IP string `json:"-"`
+ UserAgent string `json:"-"`
}
func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, err error) {
diff --git a/internal/schema/search_schema.go b/internal/schema/search_schema.go
index 59f32c5e..50aeaa6f 100644
--- a/internal/schema/search_schema.go
+++ b/internal/schema/search_schema.go
@@ -29,13 +29,13 @@ import (
)
type SearchDTO struct {
- UserID string // UserID current login user ID
- Query string `validate:"required,gte=1,lte=60" json:"q" form:"q"` // Query the query string
- Page int `validate:"omitempty,min=1" form:"page,default=1" json:"page"` //Query number of pages
- Size int `validate:"omitempty,min=1,max=50" form:"size,default=30" json:"size"` //Search page size
- Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" json:"order" enums:"newest,active,score,relevance"`
- CaptchaID string `json:"captcha_id"` // captcha_id
- CaptchaCode string `json:"captcha_code"`
+ Query string `validate:"required,gte=1,lte=60" form:"q"`
+ Page int `validate:"omitempty,min=1" form:"page,default=1"`
+ Size int `validate:"omitempty,min=1,max=50" form:"size,default=30"`
+ Order string `validate:"required,oneof=newest active score relevance" form:"order,default=relevance" enums:"newest,active,score,relevance"`
+ CaptchaID string `form:"captcha_id"`
+ CaptchaCode string `form:"captcha_code"`
+ UserID string `json:"-"`
}
func (s *SearchDTO) Check() (errField []*validator.FormErrorField, err error) {
diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go
index a95acfc7..13971708 100644
--- a/internal/schema/user_schema.go
+++ b/internal/schema/user_schema.go
@@ -220,8 +220,8 @@ func (u *UserRegisterReq) Check() (errFields []*validator.FormErrorField, err er
type UserModifyPasswordReq struct {
OldPass string `validate:"omitempty,gte=8,lte=32" json:"old_pass"`
Pass string `validate:"required,gte=8,lte=32" json:"pass"`
- CaptchaID string `validate:"omitempty,gt=0,lte=500" json:"captcha_id"`
- CaptchaCode string `validate:"omitempty,gt=0,lte=500" json:"captcha_code"`
+ CaptchaID string `json:"captcha_id"`
+ CaptchaCode string `json:"captcha_code"`
UserID string `json:"-"`
AccessToken string `json:"-"`
}
@@ -382,8 +382,8 @@ type UserChangeEmailVerifyReq struct {
}
type UserVerifyEmailSendReq struct {
- CaptchaID string `validate:"omitempty,gt=0,lte=500" json:"captcha_id"`
- CaptchaCode string `validate:"omitempty,gt=0,lte=500" json:"captcha_code"`
+ CaptchaID string `json:"captcha_id"`
+ CaptchaCode string `json:"captcha_code"`
}
// UserRankingResp user ranking response
@@ -412,3 +412,19 @@ type UserUnsubscribeNotificationReq struct {
Code string `validate:"required,gt=0,lte=500" json:"code"`
Content string `json:"-"`
}
+
+// GetUserStaffReq get user staff request
+type GetUserStaffReq struct {
+ Username string `validate:"omitempty,gt=0,lte=500" form:"username"`
+ PageSize int `validate:"omitempty,min=1" form:"page_size"`
+}
+
+// GetUserStaffResp get user staff response
+type GetUserStaffResp struct {
+ // username
+ Username string `json:"username"`
+ // display name
+ DisplayName string `json:"display_name"`
+ // avatar
+ Avatar string `json:"avatar"`
+}
diff --git a/internal/schema/vote_schema.go b/internal/schema/vote_schema.go
index f941e1e4..e82adcc2 100644
--- a/internal/schema/vote_schema.go
+++ b/internal/schema/vote_schema.go
@@ -20,11 +20,11 @@
package schema
type VoteReq struct {
- ObjectID string `validate:"required" form:"object_id" json:"object_id"` // id
- IsCancel bool `validate:"omitempty" form:"is_cancel" json:"is_cancel"` // is cancel
- UserID string `json:"-"`
- CaptchaID string `json:"captcha_id"` // captcha_id
+ ObjectID string `validate:"required" json:"object_id"`
+ IsCancel bool `validate:"omitempty" json:"is_cancel"`
+ CaptchaID string `json:"captcha_id"`
CaptchaCode string `json:"captcha_code"`
+ UserID string `json:"-"`
}
type VoteResp struct {
diff --git a/internal/service/action/captcha_service.go b/internal/service/action/captcha_service.go
index 7619b983..326ea637 100644
--- a/internal/service/action/captcha_service.go
+++ b/internal/service/action/captcha_service.go
@@ -21,14 +21,11 @@ package action
import (
"context"
- "image/color"
- "strings"
- "github.com/apache/incubator-answer/internal/base/reason"
"github.com/apache/incubator-answer/internal/entity"
"github.com/apache/incubator-answer/internal/schema"
- "github.com/mojocn/base64Captcha"
- "github.com/segmentfault/pacman/errors"
+ "github.com/apache/incubator-answer/pkg/token"
+ "github.com/apache/incubator-answer/plugin"
"github.com/segmentfault/pacman/log"
)
@@ -84,49 +81,29 @@ func (cs *CaptchaService) ActionRecord(ctx context.Context, req *schema.ActionRe
}
verificationResult := cs.ValidationStrategy(ctx, unit, req.Action)
if !verificationResult {
- resp.CaptchaID, resp.CaptchaImg, err = cs.GenerateCaptcha(ctx)
resp.Verify = true
+ resp.CaptchaID, resp.CaptchaImg, err = cs.GenerateCaptcha(ctx)
+ if err != nil {
+ log.Errorf("GenerateCaptcha error: %v", err)
+ }
}
return
}
-func (cs *CaptchaService) UserRegisterCaptcha(ctx context.Context) (resp *schema.ActionRecordResp, err error) {
- resp = &schema.ActionRecordResp{}
- resp.CaptchaID, resp.CaptchaImg, err = cs.GenerateCaptcha(ctx)
- resp.Verify = true
- return
-}
-
-func (cs *CaptchaService) UserRegisterVerifyCaptcha(
- ctx context.Context, id string, VerifyValue string,
-) bool {
- if id == "" || VerifyValue == "" {
- return false
- }
- pass, err := cs.VerifyCaptcha(ctx, id, VerifyValue)
- if err != nil {
- return false
- }
- return pass
-}
-
// ActionRecordVerifyCaptcha
// Verify that you need to enter a CAPTCHA, and that the CAPTCHA is correct
func (cs *CaptchaService) ActionRecordVerifyCaptcha(
- ctx context.Context, actionType string, unit string, id string, VerifyValue string,
+ ctx context.Context, actionType string, unit string, captchaID string, captchaCode string,
) bool {
verificationResult := cs.ValidationStrategy(ctx, unit, actionType)
- if !verificationResult {
- if id == "" || VerifyValue == "" {
- return false
- }
- pass, err := cs.VerifyCaptcha(ctx, id, VerifyValue)
- if err != nil {
- return false
- }
- return pass
+ if verificationResult {
+ return true
+ }
+ pass, err := cs.VerifyCaptcha(ctx, captchaID, captchaCode)
+ if err != nil {
+ return false
}
- return true
+ return pass
}
func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, unit string) (int, error) {
@@ -155,43 +132,32 @@ func (cs *CaptchaService) ActionRecordDel(ctx context.Context, actionType string
// GenerateCaptcha generate captcha
func (cs *CaptchaService) GenerateCaptcha(ctx context.Context) (key, captchaBase64 string, err error) {
- driverString := base64Captcha.DriverString{
- Height: 60,
- Width: 200,
- NoiseCount: 0,
- ShowLineOptions: 2 | 4,
- Length: 4,
- Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
- BgColor: &color.RGBA{R: 211, G: 211, B: 211, A: 0},
- Fonts: []string{"wqy-microhei.ttc"},
- }
- driver := driverString.ConvertFonts()
-
- id, content, answer := driver.GenerateIdQuestionAnswer()
- item, err := driver.DrawCaptcha(content)
- if err != nil {
- return "", "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
- }
- err = cs.captchaRepo.SetCaptcha(ctx, id, answer)
- if err != nil {
- return "", "", err
+ realCaptcha := ""
+ key = token.GenerateToken()
+ _ = plugin.CallCaptcha(func(fn plugin.Captcha) error {
+ if captcha, code := fn.Create(); len(code) > 0 {
+ captchaBase64 = captcha
+ realCaptcha = code
+ }
+ return nil
+ })
+ if len(realCaptcha) == 0 {
+ return key, captchaBase64, nil
}
- captchaBase64 = item.EncodeB64string()
- return id, captchaBase64, nil
+ err = cs.captchaRepo.SetCaptcha(ctx, key, realCaptcha)
+ return key, captchaBase64, err
}
// VerifyCaptcha generate captcha
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
+ realCaptcha, _ := cs.captchaRepo.GetCaptcha(ctx, key)
+
+ _ = plugin.CallCaptcha(func(fn plugin.Captcha) error {
+ isCorrect = fn.Verify(realCaptcha, captcha)
+ return nil
+ })
+
+ _ = cs.captchaRepo.DelCaptcha(ctx, key)
+ return isCorrect, nil
}
diff --git a/internal/service/action/captcha_strategy.go b/internal/service/action/captcha_strategy.go
index 72dfefed..1d2dcbc3 100644
--- a/internal/service/action/captcha_strategy.go
+++ b/internal/service/action/captcha_strategy.go
@@ -21,9 +21,11 @@ package action
import (
"context"
- "github.com/segmentfault/pacman/log"
"time"
+ "github.com/apache/incubator-answer/plugin"
+ "github.com/segmentfault/pacman/log"
+
"github.com/apache/incubator-answer/internal/entity"
)
@@ -31,6 +33,10 @@ import (
// true pass
// false need captcha
func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionType string) bool {
+ // If the captcha is not enabled, the verification is passed directly
+ if !plugin.CaptchaEnabled() {
+ return true
+ }
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if err != nil {
log.Error(err)
diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go
index cb634d2a..0f3f8393 100644
--- a/internal/service/activity/activity.go
+++ b/internal/service/activity/activity.go
@@ -259,7 +259,7 @@ func (as *ActivityService) GetObjectTimelineDetail(ctx context.Context, req *sch
return resp, nil
}
-// GetObjectTimelineDetail get object detail
+// getOneObjectDetail get object detail
func (as *ActivityService) getOneObjectDetail(ctx context.Context, revisionID string) (
resp *schema.ObjectTimelineDetail, err error) {
resp = &schema.ObjectTimelineDetail{Tags: make([]*schema.ObjectTimelineTag, 0)}
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go
index d96d75c5..f8feda8a 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -241,12 +241,9 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
if err = as.answerRepo.AddAnswer(ctx, insertData); err != nil {
return "", err
}
- if as.reviewService.AddAnswerReview(ctx, insertData) {
- if err := as.answerRepo.UpdateAnswerStatus(ctx, insertData.ID, entity.AnswerStatusAvailable); err != nil {
- return "", err
- } else {
- insertData.Status = entity.AnswerStatusAvailable
- }
+ insertData.Status = as.reviewService.AddAnswerReview(ctx, insertData, req.IP, req.UserAgent)
+ if err := as.answerRepo.UpdateAnswerStatus(ctx, insertData.ID, insertData.Status); err != nil {
+ return "", err
}
err = as.questionCommon.UpdateAnswerCount(ctx, req.QuestionID)
if err != nil {
@@ -510,6 +507,18 @@ func (as *AnswerService) Get(ctx context.Context, answerID, loginUserID string)
return info, questionInfo, has, nil
}
+func (as *AnswerService) GetDetail(ctx context.Context, answerID string) (*schema.AnswerInfo, error) {
+ answerInfo, has, err := as.answerRepo.GetByID(ctx, answerID)
+ if err != nil {
+ return nil, err
+ }
+ if !has {
+ return nil, errors.BadRequest(reason.AnswerNotFound)
+ }
+ info := as.ShowFormat(ctx, answerInfo)
+ return info, nil
+}
+
func (as *AnswerService) GetCountByUserIDQuestionID(ctx context.Context, userId string, questionId string) (ids []string, err error) {
return as.answerRepo.GetIDsByUserIDAndQuestionID(ctx, userId, questionId)
}
@@ -625,6 +634,11 @@ func (as *AnswerService) ShowFormat(ctx context.Context, data *entity.Answer) *s
}
func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionUserID, answerID, answerUserID string) {
+ // If the answer is updated by me, there is no notification for myself.
+ // equivalent behaviour as AnswerService.notificationAnswerTheQuestion
+ if questionUserID == answerUserID {
+ return
+ }
msg := &schema.NotificationMsg{
TriggerUserID: answerUserID,
ReceiverUserID: questionUserID,
diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go
index e4f2b499..171f67bd 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -327,12 +327,9 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
if err != nil {
return
}
- if qs.reviewService.AddQuestionReview(ctx, question, req.Tags) {
- if err := qs.questionRepo.UpdateQuestionStatus(ctx, question.ID, entity.QuestionStatusAvailable); err != nil {
- return nil, err
- } else {
- question.Status = entity.AnswerStatusAvailable
- }
+ question.Status = qs.reviewService.AddQuestionReview(ctx, question, req.Tags, req.IP, req.UserAgent)
+ if err := qs.questionRepo.UpdateQuestionStatus(ctx, question.ID, question.Status); err != nil {
+ return nil, err
}
objectTagData := schema.TagChange{}
objectTagData.ObjectID = question.ID
diff --git a/internal/service/content/user_service.go b/internal/service/content/user_service.go
index 4c9843a3..a2b33005 100644
--- a/internal/service/content/user_service.go
+++ b/internal/service/content/user_service.go
@@ -741,6 +741,24 @@ func (us *UserService) UserRanking(ctx context.Context) (resp *schema.UserRankin
return us.warpStatRankingResp(userInfoMapping, rankStat, voteStat, userRoleRels), nil
}
+// GetUserStaff get user staff
+func (us *UserService) GetUserStaff(ctx context.Context, req *schema.GetUserStaffReq) (
+ resp []*schema.GetUserStaffResp, err error) {
+ userList, err := us.userRepo.SearchUserListByName(ctx, req.Username, req.PageSize, true)
+ if err != nil {
+ return nil, err
+ }
+ avatarMapping := us.siteInfoService.FormatListAvatar(ctx, userList)
+ for _, u := range userList {
+ resp = append(resp, &schema.GetUserStaffResp{
+ Username: u.Username,
+ DisplayName: u.DisplayName,
+ Avatar: avatarMapping[u.ID].GetURL(),
+ })
+ }
+ return resp, nil
+}
+
// UserUnsubscribeNotification user unsubscribe email notification
func (us *UserService) UserUnsubscribeNotification(
ctx context.Context, req *schema.UserUnsubscribeNotificationReq) (err error) {
@@ -858,7 +876,7 @@ func (us *UserService) SearchUserListByName(ctx context.Context, req *schema.Get
if len(req.Username) == 0 {
return resp, nil
}
- userList, err := us.userRepo.SearchUserListByName(ctx, req.Username, 5)
+ userList, err := us.userRepo.SearchUserListByName(ctx, req.Username, 5, false)
if err != nil {
return resp, err
}
diff --git a/internal/service/rank/rank_service.go b/internal/service/rank/rank_service.go
index ab164d4d..2b4462a6 100644
--- a/internal/service/rank/rank_service.go
+++ b/internal/service/rank/rank_service.go
@@ -242,7 +242,7 @@ func (rs *RankService) getUserPowerMapping(ctx context.Context, userID string) (
return powerMapping
}
-// CheckRankPermission verify that the user meets the prestige criteria
+// checkUserRank verify that the user meets the prestige criteria
func (rs *RankService) checkUserRank(ctx context.Context, userID string, userRank int, action string) (
can bool, rank int) {
// get the amount of rank required for the current operation
diff --git a/internal/service/review/review_service.go b/internal/service/review/review_service.go
index 80c318b1..ab4079ee 100644
--- a/internal/service/review/review_service.go
+++ b/internal/service/review/review_service.go
@@ -99,28 +99,54 @@ func NewReviewService(
// AddQuestionReview add review for question if needed
func (cs *ReviewService) AddQuestionReview(ctx context.Context,
- question *entity.Question, tags []*schema.TagItem) (needReview bool) {
+ question *entity.Question, tags []*schema.TagItem, ip, ua string) (questionStatus int) {
reviewContent := &plugin.ReviewContent{
ObjectType: constant.QuestionObjectType,
Title: question.Title,
Content: question.ParsedText,
+ IP: ip,
+ UserAgent: ua,
}
for _, tag := range tags {
reviewContent.Tags = append(reviewContent.Tags, tag.SlugName)
}
reviewContent.Author = cs.getReviewContentAuthorInfo(ctx, question.UserID)
- return cs.callPluginToReview(ctx, question.UserID, question.ID, reviewContent)
+ reviewStatus := cs.callPluginToReview(ctx, question.UserID, question.ID, reviewContent)
+ switch reviewStatus {
+ case plugin.ReviewStatusApproved:
+ questionStatus = entity.QuestionStatusAvailable
+ case plugin.ReviewStatusNeedReview:
+ questionStatus = entity.QuestionStatusPending
+ case plugin.ReviewStatusDeleteDirectly:
+ questionStatus = entity.QuestionStatusDeleted
+ default:
+ questionStatus = entity.QuestionStatusAvailable
+ }
+ return questionStatus
}
// AddAnswerReview add review for answer if needed
func (cs *ReviewService) AddAnswerReview(ctx context.Context,
- answer *entity.Answer) (needReview bool) {
+ answer *entity.Answer, ip, ua string) (answerStatus int) {
reviewContent := &plugin.ReviewContent{
ObjectType: constant.AnswerObjectType,
Content: answer.ParsedText,
+ IP: ip,
+ UserAgent: ua,
}
reviewContent.Author = cs.getReviewContentAuthorInfo(ctx, answer.UserID)
- return cs.callPluginToReview(ctx, answer.UserID, answer.ID, reviewContent)
+ reviewStatus := cs.callPluginToReview(ctx, answer.UserID, answer.ID, reviewContent)
+ switch reviewStatus {
+ case plugin.ReviewStatusApproved:
+ answerStatus = entity.AnswerStatusAvailable
+ case plugin.ReviewStatusNeedReview:
+ answerStatus = entity.AnswerStatusPending
+ case plugin.ReviewStatusDeleteDirectly:
+ answerStatus = entity.AnswerStatusDeleted
+ default:
+ answerStatus = entity.AnswerStatusAvailable
+ }
+ return answerStatus
}
// get review content author info
@@ -143,9 +169,9 @@ func (cs *ReviewService) getReviewContentAuthorInfo(ctx context.Context, userID
// call plugin to review
func (cs *ReviewService) callPluginToReview(ctx context.Context, userID, objectID string,
- reviewContent *plugin.ReviewContent) (approved bool) {
+ reviewContent *plugin.ReviewContent) (reviewStatus plugin.ReviewStatus) {
// As default, no need review
- approved = true
+ reviewStatus = plugin.ReviewStatusApproved
objectID = uid.DeShortID(objectID)
r := &entity.Review{
@@ -161,23 +187,23 @@ func (cs *ReviewService) callPluginToReview(ctx context.Context, userID, objectI
_ = plugin.CallReviewer(func(reviewer plugin.Reviewer) error {
// If one of the reviewer plugin return false, then the review is not approved
- if !approved {
+ if reviewStatus != plugin.ReviewStatusApproved {
return nil
}
if result := reviewer.Review(reviewContent); !result.Approved {
- approved = false
+ reviewStatus = result.ReviewStatus
r.Reason = result.Reason
r.Submitter = reviewer.Info().SlugName
}
return nil
})
- if !approved {
+ if reviewStatus == plugin.ReviewStatusNeedReview {
if err := cs.reviewRepo.AddReview(ctx, r); err != nil {
log.Errorf("add review failed, err: %v", err)
}
}
- return approved
+ return reviewStatus
}
// UpdateReview update review
diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go
index 2036ec6e..01585876 100644
--- a/internal/service/siteinfo_common/siteinfo_service.go
+++ b/internal/service/siteinfo_common/siteinfo_service.go
@@ -22,6 +22,7 @@ package siteinfo_common
import (
"context"
"encoding/json"
+ "html"
"github.com/apache/incubator-answer/internal/base/constant"
"github.com/apache/incubator-answer/internal/entity"
@@ -70,6 +71,7 @@ func (s *siteInfoCommonService) GetSiteGeneral(ctx context.Context) (resp *schem
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeGeneral, resp); err != nil {
return nil, err
}
+ resp.Name = html.UnescapeString(resp.Name)
return resp, nil
}
diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go
index 953b96f0..1dfe4f7f 100644
--- a/internal/service/tag_common/tag_common.go
+++ b/internal/service/tag_common/tag_common.go
@@ -321,10 +321,10 @@ func (ts *TagCommonService) AddTag(ctx context.Context, req *schema.AddTagReq) (
if exist {
return nil, errors.BadRequest(reason.TagAlreadyExist)
}
- SlugName := strings.ReplaceAll(req.SlugName, " ", "-")
- SlugName = strings.ToLower(SlugName)
+ slugName := strings.ReplaceAll(req.SlugName, " ", "-")
+ slugName = strings.ToLower(slugName)
tagInfo := &entity.Tag{
- SlugName: SlugName,
+ SlugName: slugName,
DisplayName: req.DisplayName,
OriginalText: req.OriginalText,
ParsedText: req.ParsedText,
@@ -837,14 +837,19 @@ func (ts *TagCommonService) UpdateTag(ctx context.Context, req *schema.UpdateTag
if !exist {
return errors.BadRequest(reason.TagNotFound)
}
+
+ //Adding equivalent slug formatting for tag update
+ slugName := strings.ReplaceAll(req.SlugName, " ", "-")
+ slugName = strings.ToLower(slugName)
+
//If the content is the same, ignore it
if tagInfo.OriginalText == req.OriginalText &&
tagInfo.DisplayName == req.DisplayName &&
- tagInfo.SlugName == req.SlugName {
+ tagInfo.SlugName == slugName {
return nil
}
- tagInfo.SlugName = req.SlugName
+ tagInfo.SlugName = slugName
tagInfo.DisplayName = req.DisplayName
tagInfo.OriginalText = req.OriginalText
tagInfo.ParsedText = req.ParsedText
diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go
index 334bcc4e..5e1a4830 100644
--- a/internal/service/user_common/user.go
+++ b/internal/service/user_common/user.go
@@ -21,9 +21,10 @@ package usercommon
import (
"context"
+ "strings"
+
"github.com/apache/incubator-answer/internal/base/constant"
"github.com/apache/incubator-answer/pkg/converter"
- "strings"
"github.com/Chain-Zhang/pinyin"
"github.com/apache/incubator-answer/internal/base/reason"
@@ -57,7 +58,7 @@ type UserRepo interface {
GetByUsernames(ctx context.Context, usernames []string) ([]*entity.User, error)
GetByEmail(ctx context.Context, email string) (userInfo *entity.User, exist bool, err error)
GetUserCount(ctx context.Context) (count int64, err error)
- SearchUserListByName(ctx context.Context, name string, limit int) (userList []*entity.User, err error)
+ SearchUserListByName(ctx context.Context, name string, limit int, onlyStaff bool) (userList []*entity.User, err error)
}
// UserCommon user service
diff --git a/pkg/converter/markdown.go b/pkg/converter/markdown.go
index ad762126..e537f554 100644
--- a/pkg/converter/markdown.go
+++ b/pkg/converter/markdown.go
@@ -21,6 +21,7 @@ package converter
import (
"bytes"
+ "regexp"
"github.com/asaskevich/govalidator"
"github.com/microcosm-cc/bluemonday"
@@ -58,6 +59,7 @@ func Markdown2HTML(source string) string {
filter.RequireParseableURLs(false)
filter.RequireNoFollowOnFullyQualifiedLinks(false)
filter.AllowElements("kbd")
+ filter.AllowAttrs("title").Matching(regexp.MustCompile(`^[\p{L}\p{N}\s\-_',\[\]!\./\\\(\)]*$|^@embed?$`)).Globally()
html = filter.Sanitize(html)
return html
}
diff --git a/plugin/captcha.go b/plugin/captcha.go
new file mode 100644
index 00000000..96047e67
--- /dev/null
+++ b/plugin/captcha.go
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package plugin
+
+type Captcha interface {
+ Base
+ // GetConfig required. Get the captcha plugin configuration.
+ // The configuration is used to generate the captcha for frontend. Such as the token for third-party service.
+ GetConfig() (configJsonStr string)
+ // Create optional. If this plugin need to create captcha via backend, implement this method.
+ // On other hand, if this plugin create captcha via third-party service, ignore this method.
+ // Return captcha: The captcha image base64 string, code: The real captcha code.
+ Create() (captcha, code string)
+ // Verify required. Verify the user input captcha is correct or not
+ // captcha: The captchaCode generated by Create method, if not implemented, it's empty.
+ Verify(captchaCode, userInput string) (pass bool)
+}
+
+var (
+ // CallCaptcha is a function that calls all registered parsers
+ callCaptcha,
+ registerCaptcha = MakePlugin[Captcha](false)
+)
+
+func CallCaptcha(fn func(fn Captcha) error) error {
+ slugName := ""
+ _ = callCaptcha(func(captcha Captcha) error {
+ slugName = captcha.Info().SlugName
+ return nil
+ })
+ if slugName == "" {
+ return nil
+ }
+ return callCaptcha(func(captcha Captcha) error {
+ if captcha.Info().SlugName == slugName {
+ return fn(captcha)
+ }
+ return nil
+ })
+}
+
+func CaptchaEnabled() (enabled bool) {
+ _ = callCaptcha(func(fn Captcha) error {
+ enabled = true
+ return nil
+ })
+ return
+}
+
+func coordinatedCaptchaPlugins(slugName string) (enabledSlugNames []string) {
+ isCaptcha := false
+ _ = callCaptcha(func(captcha Captcha) error {
+ name := captcha.Info().SlugName
+ if slugName == name {
+ isCaptcha = true
+ } else {
+ enabledSlugNames = append(enabledSlugNames, name)
+ }
+ return nil
+ })
+ if isCaptcha {
+ return enabledSlugNames
+ }
+ return nil
+}
diff --git a/plugin/plugin.go b/plugin/plugin.go
index 6aade8ee..df049417 100644
--- a/plugin/plugin.go
+++ b/plugin/plugin.go
@@ -21,6 +21,7 @@ package plugin
import (
"encoding/json"
+ "sync"
"github.com/segmentfault/pacman/i18n"
@@ -94,6 +95,10 @@ func Register(p Base) {
registerReviewer(p.(Reviewer))
}
+ if _, ok := p.(Captcha); ok {
+ registerCaptcha(p.(Captcha))
+ }
+
if _, ok := p.(Embed); ok {
registerEmbed(p.(Embed))
}
@@ -142,11 +147,21 @@ func MakePlugin[T Base](super bool) (CallFn[T], RegisterFn[T]) {
}
type statusManager struct {
+ lock sync.Mutex
status map[string]bool
}
func (m *statusManager) Enable(name string, enabled bool) {
+ m.lock.Lock()
+ defer m.lock.Unlock()
+ if !enabled {
+ m.status[name] = enabled
+ return
+ }
m.status[name] = enabled
+ for _, slugName := range coordinatedCaptchaPlugins(name) {
+ m.status[slugName] = false
+ }
}
func (m *statusManager) IsEnabled(name string) bool {
diff --git a/plugin/reviewer.go b/plugin/reviewer.go
index 4c9165c6..488c6f9b 100644
--- a/plugin/reviewer.go
+++ b/plugin/reviewer.go
@@ -39,6 +39,10 @@ type ReviewContent struct {
// Review Language, the site language. e.g. en_US
// The plugin may reply the review result according to the language
Language string
+ // The user agent of the request web browser
+ UserAgent string
+ // The IP address of the request
+ IP string
}
type ReviewContentAuthor struct {
@@ -52,10 +56,20 @@ type ReviewContentAuthor struct {
Role int
}
+type ReviewStatus string
+
+const (
+ ReviewStatusApproved ReviewStatus = "approved"
+ ReviewStatusDeleteDirectly ReviewStatus = "delete_directly"
+ ReviewStatusNeedReview ReviewStatus = "need_review"
+)
+
// ReviewResult is a struct that contains the result of a review
type ReviewResult struct {
// If the review is approved
Approved bool
+ // The status of the review
+ ReviewStatus ReviewStatus
// The reason for the result
Reason string
}
diff --git a/script/plugin_list b/script/plugin_list
index 49324ca0..67ba306f 100644
--- a/script/plugin_list
+++ b/script/plugin_list
@@ -1,2 +1,3 @@
github.com/apache/incubator-answer-plugins/connector-basic@latest
-github.com/apache/incubator-answer-plugins/reviewer-basic@latest
\ No newline at end of file
+github.com/apache/incubator-answer-plugins/reviewer-basic@latest
+github.com/apache/incubator-answer-plugins/captcha-basic@latest
\ No newline at end of file
diff --git a/ui/config-overrides.js b/ui/config-overrides.js
index b3ff6e8a..18237a1f 100644
--- a/ui/config-overrides.js
+++ b/ui/config-overrides.js
@@ -44,71 +44,62 @@ module.exports = {
minChunks: 2,
cacheGroups: {
automaticNamePrefix: 'chunk',
- components: {
- test: /[\\/]components[\\/]/,
- name: 'components',
+ mix1: {
+ test: (module, chunks) => {
+ return (
+ module.resource &&
+ (module.resource.includes('components') ||
+ /\/node_modules\/react-bootstrap\//.test(module.resource))
+ );
+ },
+ name: 'chunk-mix1',
+ filename: 'static/js/[name].[contenthash:8].chunk.js',
priority: 14,
reuseExistingChunk: true,
minChunks: process.env.NODE_ENV === 'production' ? 1 : 2,
chunks: 'initial',
},
- i18next: {
- name: 'i18next',
- test: /[\/]node_modules[\/](i18next)[\/]/,
+ mix2: {
+ name: 'chunk-mix2',
+ test: /[\/]node_modules[\/](i18next|lodash|marked|next-share)[\/]/,
filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 12,
+ priority: 13,
reuseExistingChunk: true,
minChunks: 1,
chunks: 'initial',
},
- reactBootstrap: {
- name: 'react-bootstrap',
- test: /[\/]node_modules[\/](react-bootstrap)[\/]/,
- filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 11,
- minChunks: 1,
- chunks: 'initial',
- reuseExistingChunk: true,
- },
- lodash: {
- name: 'lodash',
- test: /[\/]node_modules[\/](lodash)[\/]/,
+ mix3: {
+ name: 'chunk-mix3',
+ test: /[\/]node_modules[\/](@remix-run|@restart|axios|diff)[\/]/,
filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 10,
+ priority: 12,
reuseExistingChunk: true,
minChunks: 1,
chunks: 'initial',
},
codemirror: {
name: 'codemirror',
- test: /[\/]node_modules[\/](codemirror)[\/]/,
- priority: 9,
- reuseExistingChunk: true,
- enforce: true,
- },
- nextShare: {
- name: 'next-share',
- test: /[\/]node_modules[\/](next-share)[\/]/,
- filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 8,
+ test: /[\/]node_modules[\/](\@codemirror)[\/]/,
+ priority: 10,
reuseExistingChunk: true,
- minChunks: 1,
+ minChunks: process.env.NODE_ENV === 'production' ? 1 : 2,
chunks: 'initial',
+ enforce: true,
},
- marked: {
- name: 'marked',
- test: /[\/]node_modules[\/](marked)[\/]/,
- filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 7,
+ lezer: {
+ name: 'lezer',
+ test: /[\/]node_modules[\/](\@lezer)[\/]/,
+ priority: 9,
reuseExistingChunk: true,
- minChunks: 1,
+ minChunks: process.env.NODE_ENV === 'production' ? 1 : 2,
chunks: 'initial',
+ enforce: true,
},
reactDom: {
name: 'react-dom',
test: /[\/]node_modules[\/](react-dom)[\/]/,
filename: 'static/js/[name].[contenthash:8].chunk.js',
- priority: 7,
+ priority: 8,
reuseExistingChunk: true,
chunks: 'all',
enforce: true,
diff --git a/ui/package.json b/ui/package.json
index 63a1f85d..50ea3688 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -16,11 +16,15 @@
"analyze": "source-map-explorer 'build/static/js/*.js'"
},
"dependencies": {
- "axios": "^0.27.2",
+ "@codemirror/lang-markdown": "^6.2.4",
+ "@codemirror/language-data": "^6.5.0",
+ "@codemirror/state": "^6.4.1",
+ "@codemirror/view": "^6.26.1",
+ "axios": "^0.28.1",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.10.5",
"classnames": "^2.3.1",
- "codemirror": "5.65.0",
+ "codemirror": "^6.0.1",
"color": "^4.2.3",
"copy-to-clipboard": "^3.3.2",
"dayjs": "^1.11.5",
@@ -37,7 +41,7 @@
"react-dom": "^18.2.0",
"react-helmet-async": "^1.3.0",
"react-i18next": "^11.18.3",
- "react-router-dom": "^6.8.1",
+ "react-router-dom": "^6.22.3",
"semver": "^7.3.8",
"swr": "^1.3.0",
"zustand": "^4.1.1"
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 0369458a..66974ab7 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -1,20 +1,3 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
lockfileVersion: '6.0'
settings:
@@ -25,9 +8,21 @@ importers:
.:
dependencies:
+ '@codemirror/lang-markdown':
+ specifier: ^6.2.4
+ version: 6.2.5
+ '@codemirror/language-data':
+ specifier: ^6.5.0
+ version: 6.5.1(@codemirror/view@6.26.3)
+ '@codemirror/state':
+ specifier: ^6.4.1
+ version: 6.4.1
+ '@codemirror/view':
+ specifier: ^6.26.1
+ version: 6.26.3
axios:
- specifier: ^0.27.2
- version: 0.27.2
+ specifier: ^0.28.1
+ version: 0.28.1
bootstrap:
specifier: ^5.3.2
version: 5.3.2(@popperjs/core@2.11.8)
@@ -38,8 +33,8 @@ importers:
specifier: ^2.3.1
version: 2.3.2
codemirror:
- specifier: 5.65.0
- version: 5.65.0
+ specifier: ^6.0.1
+ version: 6.0.1(@lezer/common@1.2.1)
color:
specifier: ^4.2.3
version: 4.2.3
@@ -89,8 +84,8 @@ importers:
specifier: ^11.18.3
version: 11.18.6(i18next@21.9.2)(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
- specifier: ^6.8.1
- version: 6.8.1(react-dom@18.2.0)(react@18.2.0)
+ specifier: ^6.22.3
+ version: 6.23.0(react-dom@18.2.0)(react@18.2.0)
semver:
specifier: ^7.3.8
version: 7.3.8
@@ -231,37 +226,6 @@ importers:
specifier: ^0.8.0
version: 0.8.0
- src/plugins/editor-chart:
- dependencies:
- mermaid:
- specifier: ^9.1.7
- version: 9.4.3
- devDependencies:
- '@typescript-eslint/eslint-plugin':
- specifier: ^6.0.0
- version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2)
- '@typescript-eslint/parser':
- specifier: ^6.0.0
- version: 6.11.0(eslint@8.53.0)(typescript@5.3.2)
- '@vitejs/plugin-react-swc':
- specifier: ^3.3.2
- version: 3.5.0(vite@4.5.0)
- eslint:
- specifier: ^8.45.0
- version: 8.53.0
- eslint-plugin-react-hooks:
- specifier: ^4.6.0
- version: 4.6.0(eslint@8.53.0)
- eslint-plugin-react-refresh:
- specifier: ^0.4.3
- version: 0.4.5(eslint@8.53.0)
- typescript:
- specifier: ^5.0.2
- version: 5.3.2
- vite:
- specifier: ^4.4.5
- version: 4.5.0(@types/node@16.11.59)(sass@1.54.9)
-
packages:
/@aashutoshrathi/word-wrap@1.2.6:
@@ -1599,8 +1563,318 @@ packages:
/@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
- /@braintree/sanitize-url@6.0.4:
- resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==}
+ /@codemirror/autocomplete@6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1):
+ resolution: {integrity: sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==}
+ peerDependencies:
+ '@codemirror/language': ^6.0.0
+ '@codemirror/state': ^6.0.0
+ '@codemirror/view': ^6.0.0
+ '@lezer/common': ^1.0.0
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ dev: false
+
+ /@codemirror/commands@6.5.0:
+ resolution: {integrity: sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ dev: false
+
+ /@codemirror/lang-angular@0.1.3:
+ resolution: {integrity: sha512-xgeWGJQQl1LyStvndWtruUvb4SnBZDAu/gvFH/ZU+c0W25tQR8e5hq7WTwiIY2dNxnf+49mRiGI/9yxIwB6f5w==}
+ dependencies:
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/lang-javascript': 6.2.2
+ '@codemirror/language': 6.10.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@codemirror/lang-cpp@6.0.2:
+ resolution: {integrity: sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@lezer/cpp': 1.1.2
+ dev: false
+
+ /@codemirror/lang-css@6.2.1(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/css': 1.1.8
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-go@6.0.0(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-mMT4YeYdKGjnffDBOhr1ur1glee4oV/rfMe28vzazNHZkSt7vSiuHiBcgr3L/79Cl2RIjFdpQ1XMD0/T8Rx64g==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/go': 1.0.0
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-html@6.4.9:
+ resolution: {integrity: sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/lang-css': 6.2.1(@codemirror/view@6.26.3)
+ '@codemirror/lang-javascript': 6.2.2
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/css': 1.1.8
+ '@lezer/html': 1.3.9
+ dev: false
+
+ /@codemirror/lang-java@6.0.1:
+ resolution: {integrity: sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@lezer/java': 1.1.2
+ dev: false
+
+ /@codemirror/lang-javascript@6.2.2:
+ resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/lint': 6.5.0
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/javascript': 1.4.15
+ dev: false
+
+ /@codemirror/lang-json@6.0.1:
+ resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@lezer/json': 1.0.2
+ dev: false
+
+ /@codemirror/lang-less@6.0.2(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==}
+ dependencies:
+ '@codemirror/lang-css': 6.2.1(@codemirror/view@6.26.3)
+ '@codemirror/language': 6.10.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-liquid@6.2.1:
+ resolution: {integrity: sha512-J1Mratcm6JLNEiX+U2OlCDTysGuwbHD76XwuL5o5bo9soJtSbz2g6RU3vGHFyS5DC8rgVmFSzi7i6oBftm7tnA==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@codemirror/lang-markdown@6.2.5:
+ resolution: {integrity: sha512-Hgke565YcO4fd9pe2uLYxnMufHO5rQwRr+AAhFq8ABuhkrjyX8R5p5s+hZUTdV60O0dMRjxKhBLxz8pu/MkUVA==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/markdown': 1.3.0
+ dev: false
+
+ /@codemirror/lang-php@6.0.1:
+ resolution: {integrity: sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==}
+ dependencies:
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/php': 1.0.2
+ dev: false
+
+ /@codemirror/lang-python@6.1.5(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-hCm+8X6wrnXJCGf+QhmFu1AXkdTVG7dHy0Ly6SI1N3SRPptaMvwX6oNQonOXOMPvmcjiB0xq342KAxX3BYpijw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/python': 1.1.13
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-rust@6.0.1:
+ resolution: {integrity: sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@lezer/rust': 1.0.2
+ dev: false
+
+ /@codemirror/lang-sass@6.0.2(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==}
+ dependencies:
+ '@codemirror/lang-css': 6.2.1(@codemirror/view@6.26.3)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/sass': 1.0.6
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-sql@6.6.3(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-fo5i3OD/7TmmqMtKycC4OaqfPsRxk0sKOb35g8cOtyUyyI2hfP2qXkDc7Asb6h7BiJK+MU/DYVPnQm6iNB5ZTw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/lang-vue@0.1.3:
+ resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==}
+ dependencies:
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/lang-javascript': 6.2.2
+ '@codemirror/language': 6.10.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@codemirror/lang-wast@6.0.2:
+ resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@codemirror/lang-xml@6.1.0:
+ resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/xml': 1.0.5
+ dev: false
+
+ /@codemirror/lang-yaml@6.1.1(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-HV2NzbK9bbVnjWxwObuZh5FuPCowx51mEfoFT9y3y+M37fA3+pbxx4I7uePuygFzDsAmCTwQSc/kXh/flab4uw==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/language': 6.10.1
+ '@codemirror/state': 6.4.1
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/yaml': 1.0.2
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/language-data@6.5.1(@codemirror/view@6.26.3):
+ resolution: {integrity: sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==}
+ dependencies:
+ '@codemirror/lang-angular': 0.1.3
+ '@codemirror/lang-cpp': 6.0.2
+ '@codemirror/lang-css': 6.2.1(@codemirror/view@6.26.3)
+ '@codemirror/lang-go': 6.0.0(@codemirror/view@6.26.3)
+ '@codemirror/lang-html': 6.4.9
+ '@codemirror/lang-java': 6.0.1
+ '@codemirror/lang-javascript': 6.2.2
+ '@codemirror/lang-json': 6.0.1
+ '@codemirror/lang-less': 6.0.2(@codemirror/view@6.26.3)
+ '@codemirror/lang-liquid': 6.2.1
+ '@codemirror/lang-markdown': 6.2.5
+ '@codemirror/lang-php': 6.0.1
+ '@codemirror/lang-python': 6.1.5(@codemirror/view@6.26.3)
+ '@codemirror/lang-rust': 6.0.1
+ '@codemirror/lang-sass': 6.0.2(@codemirror/view@6.26.3)
+ '@codemirror/lang-sql': 6.6.3(@codemirror/view@6.26.3)
+ '@codemirror/lang-vue': 0.1.3
+ '@codemirror/lang-wast': 6.0.2
+ '@codemirror/lang-xml': 6.1.0
+ '@codemirror/lang-yaml': 6.1.1(@codemirror/view@6.26.3)
+ '@codemirror/language': 6.10.1
+ '@codemirror/legacy-modes': 6.4.0
+ transitivePeerDependencies:
+ - '@codemirror/view'
+ dev: false
+
+ /@codemirror/language@6.10.1:
+ resolution: {integrity: sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==}
+ dependencies:
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ style-mod: 4.1.2
+ dev: false
+
+ /@codemirror/legacy-modes@6.4.0:
+ resolution: {integrity: sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==}
+ dependencies:
+ '@codemirror/language': 6.10.1
+ dev: false
+
+ /@codemirror/lint@6.5.0:
+ resolution: {integrity: sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==}
+ dependencies:
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ crelt: 1.0.6
+ dev: false
+
+ /@codemirror/search@6.5.6:
+ resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==}
+ dependencies:
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ crelt: 1.0.6
+ dev: false
+
+ /@codemirror/state@6.4.1:
+ resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
+ dev: false
+
+ /@codemirror/view@6.26.3:
+ resolution: {integrity: sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==}
+ dependencies:
+ '@codemirror/state': 6.4.1
+ style-mod: 4.1.2
+ w3c-keyname: 2.2.8
dev: false
/@commitlint/cli@17.1.2:
@@ -1914,204 +2188,6 @@ packages:
postcss: 8.4.16
postcss-selector-parser: 6.0.10
- /@esbuild/android-arm64@0.18.20:
- resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/android-arm@0.18.20:
- resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/android-x64@0.18.20:
- resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/darwin-arm64@0.18.20:
- resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/darwin-x64@0.18.20:
- resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/freebsd-arm64@0.18.20:
- resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/freebsd-x64@0.18.20:
- resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-arm64@0.18.20:
- resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-arm@0.18.20:
- resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-ia32@0.18.20:
- resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-loong64@0.18.20:
- resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-mips64el@0.18.20:
- resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-ppc64@0.18.20:
- resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-riscv64@0.18.20:
- resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-s390x@0.18.20:
- resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/linux-x64@0.18.20:
- resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/netbsd-x64@0.18.20:
- resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/openbsd-x64@0.18.20:
- resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/sunos-x64@0.18.20:
- resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-arm64@0.18.20:
- resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-ia32@0.18.20:
- resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@esbuild/win32-x64@0.18.20:
- resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
/@eslint-community/eslint-utils@4.4.0(eslint@8.53.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2496,6 +2572,133 @@ packages:
/@leichtgewicht/ip-codec@2.0.4:
resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
+ /@lezer/common@1.2.1:
+ resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
+ dev: false
+
+ /@lezer/cpp@1.1.2:
+ resolution: {integrity: sha512-macwKtyeUO0EW86r3xWQCzOV9/CF8imJLpJlPv3sDY57cPGeUZ8gXWOWNlJr52TVByMV3PayFQCA5SHEERDmVQ==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/css@1.1.8:
+ resolution: {integrity: sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/go@1.0.0:
+ resolution: {integrity: sha512-co9JfT3QqX1YkrMmourYw2Z8meGC50Ko4d54QEcQbEYpvdUvN4yb0NBZdn/9ertgvjsySxHsKzH3lbm3vqJ4Jw==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/highlight@1.2.0:
+ resolution: {integrity: sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ dev: false
+
+ /@lezer/html@1.3.9:
+ resolution: {integrity: sha512-MXxeCMPyrcemSLGaTQEZx0dBUH0i+RPl8RN5GwMAzo53nTsd/Unc/t5ZxACeQoyPUM5/GkPLRUs2WliOImzkRA==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/java@1.1.2:
+ resolution: {integrity: sha512-3j8X70JvYf0BZt8iSRLXLkt0Ry1hVUgH6wT32yBxH/Xi55nW2VMhc1Az4SKwu4YGSmxCm1fsqDDcHTuFjC8pmg==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/javascript@1.4.15:
+ resolution: {integrity: sha512-B082ZdjI0vo2AgLqD834GlRTE9gwRX8NzHzKq5uDwEnQ9Dq+A/CEhd3nf68tiNA2f9O+8jS1NeSTUYT9IAqcTw==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/json@1.0.2:
+ resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/lr@1.4.0:
+ resolution: {integrity: sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ dev: false
+
+ /@lezer/markdown@1.3.0:
+ resolution: {integrity: sha512-ErbEQ15eowmJUyT095e9NJc3BI9yZ894fjSDtHftD0InkfUBGgnKSU6dvan9jqsZuNHg2+ag/1oyDRxNsENupQ==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ dev: false
+
+ /@lezer/php@1.0.2:
+ resolution: {integrity: sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/python@1.1.13:
+ resolution: {integrity: sha512-AdbRAtdQq94PfTNd4kqMEJhH2fqa2JdoyyqqVewY6w34w2Gi6dg2JuOtOgR21Bi0zP9r0KjSSHOUq/tP7FVT8A==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/rust@1.0.2:
+ resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/sass@1.0.6:
+ resolution: {integrity: sha512-w/RCO2dIzZH1To8p+xjs8cE+yfgGus8NZ/dXeWl/QzHyr+TeBs71qiE70KPImEwvTsmEjoWh0A5SxMzKd5BWBQ==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/xml@1.0.5:
+ resolution: {integrity: sha512-VFouqOzmUWfIg+tfmpcdV33ewtK+NSwd4ngSe1aG7HFb4BN0ExyY1b8msp+ndFrnlG4V4iC8yXacjFtrwERnaw==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
+ /@lezer/yaml@1.0.2:
+ resolution: {integrity: sha512-XCkwuxe+eumJ28nA9e1S6XKsXz9W7V/AG+WBiWOtiIuUpKcZ/bHuvN8bLxSDREIcybSRpEd/jvphh4vgm6Ed2g==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.0
+ dev: false
+
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
dependencies:
@@ -2584,9 +2787,9 @@ packages:
react: 18.2.0
dev: false
- /@remix-run/router@1.3.2:
- resolution: {integrity: sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==}
- engines: {node: '>=14'}
+ /@remix-run/router@1.16.0:
+ resolution: {integrity: sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==}
+ engines: {node: '>=14.0.0'}
dev: false
/@restart/hooks@0.4.15(react@18.2.0):
@@ -2786,129 +2989,12 @@ packages:
transitivePeerDependencies:
- supports-color
- /@swc/core-darwin-arm64@1.3.100:
- resolution: {integrity: sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-darwin-x64@1.3.100:
- resolution: {integrity: sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [darwin]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-linux-arm64-gnu@1.3.100:
- resolution: {integrity: sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-linux-arm64-musl@1.3.100:
- resolution: {integrity: sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-linux-x64-gnu@1.3.100:
- resolution: {integrity: sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-linux-x64-musl@1.3.100:
- resolution: {integrity: sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-win32-arm64-msvc@1.3.100:
- resolution: {integrity: sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==}
- engines: {node: '>=10'}
- cpu: [arm64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-win32-ia32-msvc@1.3.100:
- resolution: {integrity: sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==}
- engines: {node: '>=10'}
- cpu: [ia32]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core-win32-x64-msvc@1.3.100:
- resolution: {integrity: sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==}
- engines: {node: '>=10'}
- cpu: [x64]
- os: [win32]
- requiresBuild: true
- dev: true
- optional: true
-
- /@swc/core@1.3.100:
- resolution: {integrity: sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==}
- engines: {node: '>=10'}
- requiresBuild: true
- peerDependencies:
- '@swc/helpers': ^0.5.0
- peerDependenciesMeta:
- '@swc/helpers':
- optional: true
- dependencies:
- '@swc/counter': 0.1.2
- '@swc/types': 0.1.5
- optionalDependencies:
- '@swc/core-darwin-arm64': 1.3.100
- '@swc/core-darwin-x64': 1.3.100
- '@swc/core-linux-arm64-gnu': 1.3.100
- '@swc/core-linux-arm64-musl': 1.3.100
- '@swc/core-linux-x64-gnu': 1.3.100
- '@swc/core-linux-x64-musl': 1.3.100
- '@swc/core-win32-arm64-msvc': 1.3.100
- '@swc/core-win32-ia32-msvc': 1.3.100
- '@swc/core-win32-x64-msvc': 1.3.100
- dev: true
-
- /@swc/counter@0.1.2:
- resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==}
- dev: true
-
/@swc/helpers@0.5.3:
resolution: {integrity: sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==}
dependencies:
tslib: 2.6.2
dev: false
- /@swc/types@0.1.5:
- resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==}
- dev: true
-
/@testing-library/dom@8.18.1:
resolution: {integrity: sha512-oEvsm2B/WtcHKE+IcEeeCqNU/ltFGaVyGbpcm4g/2ytuT49jrlH9x5qRKL/H3A6yfM4YAbSbC0ceT5+9CEXnLg==}
engines: {node: '>=12'}
@@ -3072,7 +3158,7 @@ packages:
resolution: {integrity: sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==}
dependencies:
'@types/estree': 1.0.0
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
/@types/eslint@8.56.2:
resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==}
@@ -3149,12 +3235,8 @@ packages:
pretty-format: 27.5.1
dev: true
- /@types/json-schema@7.0.13:
- resolution: {integrity: sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==}
-
/@types/json-schema@7.0.15:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- dev: true
/@types/json5@0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
@@ -3341,35 +3423,6 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.2):
- resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==}
- engines: {node: ^16.0.0 || >=18.0.0}
- peerDependencies:
- '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
- eslint: ^7.0.0 || ^8.0.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
- dependencies:
- '@eslint-community/regexpp': 4.10.0
- '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
- '@typescript-eslint/scope-manager': 6.11.0
- '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
- '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
- '@typescript-eslint/visitor-keys': 6.11.0
- debug: 4.3.4
- eslint: 8.53.0
- graphemer: 1.4.0
- ignore: 5.2.4
- natural-compare: 1.4.0
- semver: 7.5.4
- ts-api-utils: 1.0.3(typescript@5.3.2)
- typescript: 5.3.2
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@typescript-eslint/experimental-utils@5.38.0(eslint@8.53.0)(typescript@4.9.5):
resolution: {integrity: sha512-kzXBRfvGlicgGk4CYuRUqKvwc2s3wHXNssUWWJU18bhMRxriFm3BZWyQ6vEHBRpEIMKB6b7MIQHO+9lYlts19w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3422,27 +3475,6 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.3.2):
- resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==}
- engines: {node: ^16.0.0 || >=18.0.0}
- peerDependencies:
- eslint: ^7.0.0 || ^8.0.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
- dependencies:
- '@typescript-eslint/scope-manager': 6.11.0
- '@typescript-eslint/types': 6.11.0
- '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
- '@typescript-eslint/visitor-keys': 6.11.0
- debug: 4.3.4
- eslint: 8.53.0
- typescript: 5.3.2
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@typescript-eslint/scope-manager@5.38.0:
resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3497,26 +3529,6 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.2):
- resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
- engines: {node: ^16.0.0 || >=18.0.0}
- peerDependencies:
- eslint: ^7.0.0 || ^8.0.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
- dependencies:
- '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
- '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.2)
- debug: 4.3.4
- eslint: 8.53.0
- ts-api-utils: 1.0.3(typescript@5.3.2)
- typescript: 5.3.2
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@typescript-eslint/types@5.38.0:
resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3567,34 +3579,13 @@ packages:
- supports-color
dev: true
- /@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.2):
- resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
- engines: {node: ^16.0.0 || >=18.0.0}
- peerDependencies:
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
- dependencies:
- '@typescript-eslint/types': 6.11.0
- '@typescript-eslint/visitor-keys': 6.11.0
- debug: 4.3.4
- globby: 11.1.0
- is-glob: 4.0.3
- semver: 7.5.4
- ts-api-utils: 1.0.3(typescript@5.3.2)
- typescript: 5.3.2
- transitivePeerDependencies:
- - supports-color
- dev: true
-
/@typescript-eslint/utils@5.38.0(eslint@8.53.0)(typescript@4.9.5):
resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
'@typescript-eslint/scope-manager': 5.38.0
'@typescript-eslint/types': 5.38.0
'@typescript-eslint/typescript-estree': 5.38.0(typescript@4.9.5)
@@ -3612,7 +3603,7 @@ packages:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
'@types/semver': 7.5.5
'@typescript-eslint/scope-manager': 6.11.0
'@typescript-eslint/types': 6.11.0
@@ -3624,25 +3615,6 @@ packages:
- typescript
dev: true
- /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.3.2):
- resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
- engines: {node: ^16.0.0 || >=18.0.0}
- peerDependencies:
- eslint: ^7.0.0 || ^8.0.0
- dependencies:
- '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
- '@types/json-schema': 7.0.13
- '@types/semver': 7.5.5
- '@typescript-eslint/scope-manager': 6.11.0
- '@typescript-eslint/types': 6.11.0
- '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.2)
- eslint: 8.53.0
- semver: 7.5.4
- transitivePeerDependencies:
- - supports-color
- - typescript
- dev: true
-
/@typescript-eslint/visitor-keys@5.38.0:
resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3661,17 +3633,6 @@ packages:
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
- /@vitejs/plugin-react-swc@3.5.0(vite@4.5.0):
- resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==}
- peerDependencies:
- vite: ^4 || ^5
- dependencies:
- '@swc/core': 1.3.100
- vite: 4.5.0(@types/node@16.11.59)(sass@1.54.9)
- transitivePeerDependencies:
- - '@swc/helpers'
- dev: true
-
/@webassemblyjs/ast@1.11.1:
resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
dependencies:
@@ -3950,7 +3911,6 @@ packages:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
engines: {node: '>=0.4.0'}
hasBin: true
- dev: true
/acorn@8.8.0:
resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==}
@@ -4253,11 +4213,12 @@ packages:
resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==}
engines: {node: '>=4'}
- /axios@0.27.2:
- resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+ /axios@0.28.1:
+ resolution: {integrity: sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==}
dependencies:
follow-redirects: 1.15.2
form-data: 4.0.0
+ proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
@@ -4761,8 +4722,18 @@ packages:
chalk: 2.4.2
q: 1.5.1
- /codemirror@5.65.0:
- resolution: {integrity: sha512-gWEnHKEcz1Hyz7fsQWpK7P0sPI2/kSkRX2tc7DFA6TmZuDN75x/1ejnH/Pn8adYKrLEA1V2ww6L00GudHZbSKw==}
+ /codemirror@6.0.1(@lezer/common@1.2.1):
+ resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1)
+ '@codemirror/commands': 6.5.0
+ '@codemirror/language': 6.10.1
+ '@codemirror/lint': 6.5.0
+ '@codemirror/search': 6.5.6
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.26.3
+ transitivePeerDependencies:
+ - '@lezer/common'
dev: false
/collect-v8-coverage@1.0.1:
@@ -4953,18 +4924,6 @@ packages:
/core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
- /cose-base@1.0.3:
- resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}
- dependencies:
- layout-base: 1.0.2
- dev: false
-
- /cose-base@2.2.0:
- resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}
- dependencies:
- layout-base: 2.0.1
- dev: false
-
/cosmiconfig-typescript-loader@4.1.0(@types/node@14.18.29)(cosmiconfig@7.0.1)(ts-node@10.9.1)(typescript@4.9.5):
resolution: {integrity: sha512-HbWIuR5O+XO5Oj9SZ5bzgrD4nN+rfhrm2PMb0FVx+t+XIvC45n8F0oTNnztXtspWGw0i2IzHaUWFD5LzV1JB4A==}
engines: {node: '>=12', npm: '>=6'}
@@ -5003,6 +4962,10 @@ packages:
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+ /crelt@1.0.6:
+ resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
+ dev: false
+
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@@ -5249,287 +5212,6 @@ packages:
lodash.flow: 3.5.0
dev: true
- /cytoscape-cose-bilkent@4.1.0(cytoscape@3.27.0):
- resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
- peerDependencies:
- cytoscape: ^3.2.0
- dependencies:
- cose-base: 1.0.3
- cytoscape: 3.27.0
- dev: false
-
- /cytoscape-fcose@2.2.0(cytoscape@3.27.0):
- resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
- peerDependencies:
- cytoscape: ^3.2.0
- dependencies:
- cose-base: 2.2.0
- cytoscape: 3.27.0
- dev: false
-
- /cytoscape@3.27.0:
- resolution: {integrity: sha512-pPZJilfX9BxESwujODz5pydeGi+FBrXq1rcaB1mfhFXXFJ9GjE6CNndAk+8jPzoXGD+16LtSS4xlYEIUiW4Abg==}
- engines: {node: '>=0.10'}
- dependencies:
- heap: 0.2.7
- lodash: 4.17.21
- dev: false
-
- /d3-array@3.2.4:
- resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
- engines: {node: '>=12'}
- dependencies:
- internmap: 2.0.3
- dev: false
-
- /d3-axis@3.0.0:
- resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-brush@3.0.0:
- resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
- engines: {node: '>=12'}
- dependencies:
- d3-dispatch: 3.0.1
- d3-drag: 3.0.0
- d3-interpolate: 3.0.1
- d3-selection: 3.0.0
- d3-transition: 3.0.1(d3-selection@3.0.0)
- dev: false
-
- /d3-chord@3.0.1:
- resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
- engines: {node: '>=12'}
- dependencies:
- d3-path: 3.1.0
- dev: false
-
- /d3-color@3.1.0:
- resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-contour@4.0.2:
- resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
- engines: {node: '>=12'}
- dependencies:
- d3-array: 3.2.4
- dev: false
-
- /d3-delaunay@6.0.4:
- resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
- engines: {node: '>=12'}
- dependencies:
- delaunator: 5.0.0
- dev: false
-
- /d3-dispatch@3.0.1:
- resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-drag@3.0.0:
- resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
- engines: {node: '>=12'}
- dependencies:
- d3-dispatch: 3.0.1
- d3-selection: 3.0.0
- dev: false
-
- /d3-dsv@3.0.1:
- resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
- engines: {node: '>=12'}
- hasBin: true
- dependencies:
- commander: 7.2.0
- iconv-lite: 0.6.3
- rw: 1.3.3
- dev: false
-
- /d3-ease@3.0.1:
- resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-fetch@3.0.1:
- resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
- engines: {node: '>=12'}
- dependencies:
- d3-dsv: 3.0.1
- dev: false
-
- /d3-force@3.0.0:
- resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
- engines: {node: '>=12'}
- dependencies:
- d3-dispatch: 3.0.1
- d3-quadtree: 3.0.1
- d3-timer: 3.0.1
- dev: false
-
- /d3-format@3.1.0:
- resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-geo@3.1.0:
- resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==}
- engines: {node: '>=12'}
- dependencies:
- d3-array: 3.2.4
- dev: false
-
- /d3-hierarchy@3.1.2:
- resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-interpolate@3.0.1:
- resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
- engines: {node: '>=12'}
- dependencies:
- d3-color: 3.1.0
- dev: false
-
- /d3-path@3.1.0:
- resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-polygon@3.0.1:
- resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-quadtree@3.0.1:
- resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-random@3.0.1:
- resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-scale-chromatic@3.0.0:
- resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==}
- engines: {node: '>=12'}
- dependencies:
- d3-color: 3.1.0
- d3-interpolate: 3.0.1
- dev: false
-
- /d3-scale@4.0.2:
- resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
- engines: {node: '>=12'}
- dependencies:
- d3-array: 3.2.4
- d3-format: 3.1.0
- d3-interpolate: 3.0.1
- d3-time: 3.1.0
- d3-time-format: 4.1.0
- dev: false
-
- /d3-selection@3.0.0:
- resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-shape@3.2.0:
- resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
- engines: {node: '>=12'}
- dependencies:
- d3-path: 3.1.0
- dev: false
-
- /d3-time-format@4.1.0:
- resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
- engines: {node: '>=12'}
- dependencies:
- d3-time: 3.1.0
- dev: false
-
- /d3-time@3.1.0:
- resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
- engines: {node: '>=12'}
- dependencies:
- d3-array: 3.2.4
- dev: false
-
- /d3-timer@3.0.1:
- resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
- engines: {node: '>=12'}
- dev: false
-
- /d3-transition@3.0.1(d3-selection@3.0.0):
- resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
- engines: {node: '>=12'}
- peerDependencies:
- d3-selection: 2 - 3
- dependencies:
- d3-color: 3.1.0
- d3-dispatch: 3.0.1
- d3-ease: 3.0.1
- d3-interpolate: 3.0.1
- d3-selection: 3.0.0
- d3-timer: 3.0.1
- dev: false
-
- /d3-zoom@3.0.0:
- resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
- engines: {node: '>=12'}
- dependencies:
- d3-dispatch: 3.0.1
- d3-drag: 3.0.0
- d3-interpolate: 3.0.1
- d3-selection: 3.0.0
- d3-transition: 3.0.1(d3-selection@3.0.0)
- dev: false
-
- /d3@7.8.5:
- resolution: {integrity: sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==}
- engines: {node: '>=12'}
- dependencies:
- d3-array: 3.2.4
- d3-axis: 3.0.0
- d3-brush: 3.0.0
- d3-chord: 3.0.1
- d3-color: 3.1.0
- d3-contour: 4.0.2
- d3-delaunay: 6.0.4
- d3-dispatch: 3.0.1
- d3-drag: 3.0.0
- d3-dsv: 3.0.1
- d3-ease: 3.0.1
- d3-fetch: 3.0.1
- d3-force: 3.0.0
- d3-format: 3.1.0
- d3-geo: 3.1.0
- d3-hierarchy: 3.1.2
- d3-interpolate: 3.0.1
- d3-path: 3.1.0
- d3-polygon: 3.0.1
- d3-quadtree: 3.0.1
- d3-random: 3.0.1
- d3-scale: 4.0.2
- d3-scale-chromatic: 3.0.0
- d3-selection: 3.0.0
- d3-shape: 3.2.0
- d3-time: 3.1.0
- d3-time-format: 4.1.0
- d3-timer: 3.0.1
- d3-transition: 3.0.1(d3-selection@3.0.0)
- d3-zoom: 3.0.0
- dev: false
-
- /dagre-d3-es@7.0.9:
- resolution: {integrity: sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==}
- dependencies:
- d3: 7.8.5
- lodash-es: 4.17.21
- dev: false
-
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -5546,10 +5228,6 @@ packages:
whatwg-mimetype: 2.3.0
whatwg-url: 8.7.0
- /dayjs@1.11.10:
- resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
- dev: false
-
/dayjs@1.11.5:
resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
dev: false
@@ -5674,12 +5352,6 @@ packages:
/defined@1.0.0:
resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==}
- /delaunator@5.0.0:
- resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==}
- dependencies:
- robust-predicates: 3.0.2
- dev: false
-
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -5828,10 +5500,6 @@ packages:
dependencies:
domelementtype: 2.3.0
- /dompurify@2.4.3:
- resolution: {integrity: sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==}
- dev: false
-
/domutils@1.7.0:
resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==, tarball: https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz}
dependencies:
@@ -5889,10 +5557,6 @@ packages:
resolution: {integrity: sha512-z/6oZ/Muqk4BaE7P69bXhUhpJbUM9ZJeka43ZwxsDshKtePns4mhBlh8bU5+yrnOnz3fhG82XLzGUXazOmsWnA==}
dev: true
- /elkjs@0.8.2:
- resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==}
- dev: false
-
/emittery@0.10.2:
resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==}
engines: {node: '>=12'}
@@ -6068,36 +5732,6 @@ packages:
is-date-object: 1.0.5
is-symbol: 1.0.4
- /esbuild@0.18.20:
- resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
- engines: {node: '>=12'}
- hasBin: true
- requiresBuild: true
- optionalDependencies:
- '@esbuild/android-arm': 0.18.20
- '@esbuild/android-arm64': 0.18.20
- '@esbuild/android-x64': 0.18.20
- '@esbuild/darwin-arm64': 0.18.20
- '@esbuild/darwin-x64': 0.18.20
- '@esbuild/freebsd-arm64': 0.18.20
- '@esbuild/freebsd-x64': 0.18.20
- '@esbuild/linux-arm': 0.18.20
- '@esbuild/linux-arm64': 0.18.20
- '@esbuild/linux-ia32': 0.18.20
- '@esbuild/linux-loong64': 0.18.20
- '@esbuild/linux-mips64el': 0.18.20
- '@esbuild/linux-ppc64': 0.18.20
- '@esbuild/linux-riscv64': 0.18.20
- '@esbuild/linux-s390x': 0.18.20
- '@esbuild/linux-x64': 0.18.20
- '@esbuild/netbsd-x64': 0.18.20
- '@esbuild/openbsd-x64': 0.18.20
- '@esbuild/sunos-x64': 0.18.20
- '@esbuild/win32-arm64': 0.18.20
- '@esbuild/win32-ia32': 0.18.20
- '@esbuild/win32-x64': 0.18.20
- dev: true
-
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
@@ -6510,14 +6144,6 @@ packages:
dependencies:
eslint: 8.53.0
- /eslint-plugin-react-refresh@0.4.5(eslint@8.53.0):
- resolution: {integrity: sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==}
- peerDependencies:
- eslint: '>=7'
- dependencies:
- eslint: 8.53.0
- dev: true
-
/eslint-plugin-react@7.33.2(eslint@8.53.0):
resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
engines: {node: '>=4'}
@@ -6954,7 +6580,7 @@ packages:
optional: true
dependencies:
'@babel/code-frame': 7.18.6
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
chalk: 4.1.2
chokidar: 3.5.3
cosmiconfig: 6.0.0
@@ -7258,10 +6884,6 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
- /heap@0.2.7:
- resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
- dev: false
-
/hoopy@0.1.4:
resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==}
engines: {node: '>= 6.0.0'}
@@ -7528,11 +7150,6 @@ packages:
hasown: 2.0.0
side-channel: 1.0.4
- /internmap@2.0.3:
- resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
- engines: {node: '>=12'}
- dev: false
-
/invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
dependencies:
@@ -8421,7 +8038,7 @@ packages:
optional: true
dependencies:
abab: 2.0.6
- acorn: 8.10.0
+ acorn: 8.11.3
acorn-globals: 6.0.0
cssom: 0.4.4
cssstyle: 2.3.0
@@ -8527,10 +8144,6 @@ packages:
object.assign: 4.1.4
object.values: 1.1.7
- /khroma@2.1.0:
- resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
- dev: false
-
/kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
@@ -8552,14 +8165,6 @@ packages:
dependencies:
language-subtag-registry: 0.3.22
- /layout-base@1.0.2:
- resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
- dev: false
-
- /layout-base@2.0.1:
- resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
- dev: false
-
/leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'}
@@ -8662,10 +8267,6 @@ packages:
dependencies:
p-locate: 5.0.0
- /lodash-es@4.17.21:
- resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
- dev: false
-
/lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
@@ -8807,27 +8408,6 @@ packages:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
- /mermaid@9.4.3:
- resolution: {integrity: sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==}
- dependencies:
- '@braintree/sanitize-url': 6.0.4
- cytoscape: 3.27.0
- cytoscape-cose-bilkent: 4.1.0(cytoscape@3.27.0)
- cytoscape-fcose: 2.2.0(cytoscape@3.27.0)
- d3: 7.8.5
- dagre-d3-es: 7.0.9
- dayjs: 1.11.10
- dompurify: 2.4.3
- elkjs: 0.8.2
- khroma: 2.1.0
- lodash-es: 4.17.21
- non-layered-tidy-tree-layout: 2.0.2
- stylis: 4.3.0
- ts-dedent: 2.2.0
- uuid: 9.0.1
- web-worker: 1.2.0
- dev: false
-
/methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
@@ -8935,12 +8515,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- /nanoid@3.3.7:
- resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
- dev: true
-
/natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -8987,10 +8561,6 @@ packages:
/node-releases@2.0.6:
resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==}
- /non-layered-tidy-tree-layout@2.0.2:
- resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==}
- dev: false
-
/normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -10062,15 +9632,6 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
- /postcss@8.4.32:
- resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
- engines: {node: ^10 || ^12 || >=14}
- dependencies:
- nanoid: 3.3.7
- picocolors: 1.0.0
- source-map-js: 1.0.2
- dev: true
-
/prelude-ls@1.1.2:
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
engines: {node: '>= 0.8.0'}
@@ -10168,6 +9729,10 @@ packages:
forwarded: 0.2.0
ipaddr.js: 1.9.1
+ /proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ dev: false
+
/psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
@@ -10418,26 +9983,26 @@ packages:
resolution: {integrity: sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==}
engines: {node: '>=0.10.0'}
- /react-router-dom@6.8.1(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==}
- engines: {node: '>=14'}
+ /react-router-dom@6.23.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
- '@remix-run/router': 1.3.2
+ '@remix-run/router': 1.16.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- react-router: 6.8.1(react@18.2.0)
+ react-router: 6.23.0(react@18.2.0)
dev: false
- /react-router@6.8.1(react@18.2.0):
- resolution: {integrity: sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==}
- engines: {node: '>=14'}
+ /react-router@6.23.0(react@18.2.0):
+ resolution: {integrity: sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==}
+ engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
dependencies:
- '@remix-run/router': 1.3.2
+ '@remix-run/router': 1.16.0
react: 18.2.0
dev: false
@@ -10822,10 +10387,6 @@ packages:
dependencies:
glob: 7.2.3
- /robust-predicates@3.0.2:
- resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
- dev: false
-
/rollup-plugin-terser@7.0.2(rollup@2.79.0):
resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==}
peerDependencies:
@@ -10844,14 +10405,6 @@ packages:
optionalDependencies:
fsevents: 2.3.3
- /rollup@3.29.4:
- resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
- engines: {node: '>=14.18.0', npm: '>=8.0.0'}
- hasBin: true
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
/run-applescript@5.0.0:
resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
engines: {node: '>=12'}
@@ -10864,10 +10417,6 @@ packages:
dependencies:
queue-microtask: 1.2.3
- /rw@1.3.3:
- resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
- dev: false
-
/safe-array-concat@1.0.1:
resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==}
engines: {node: '>=0.4'}
@@ -10947,7 +10496,7 @@ packages:
resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==}
engines: {node: '>= 8.9.0'}
dependencies:
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
@@ -10955,7 +10504,7 @@ packages:
resolution: {integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==}
engines: {node: '>= 8.9.0'}
dependencies:
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
@@ -10963,7 +10512,7 @@ packages:
resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
@@ -10980,7 +10529,7 @@ packages:
resolution: {integrity: sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==}
engines: {node: '>= 12.13.0'}
dependencies:
- '@types/json-schema': 7.0.13
+ '@types/json-schema': 7.0.15
ajv: 8.11.0
ajv-formats: 2.1.1(ajv@8.11.0)
ajv-keywords: 5.1.0(ajv@8.11.0)
@@ -11494,6 +11043,10 @@ packages:
dependencies:
webpack: 5.74.0
+ /style-mod@4.1.2:
+ resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==}
+ dev: false
+
/stylehacks@5.1.0(postcss@8.4.16):
resolution: {integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==}
engines: {node: ^10 || ^12 || >=14.0}
@@ -11504,10 +11057,6 @@ packages:
postcss: 8.4.16
postcss-selector-parser: 6.0.10
- /stylis@4.3.0:
- resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==}
- dev: false
-
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -11713,7 +11262,7 @@ packages:
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.2
- acorn: 8.10.0
+ acorn: 8.11.3
commander: 2.20.3
source-map-support: 0.5.21
@@ -11823,20 +11372,6 @@ packages:
typescript: 4.9.5
dev: true
- /ts-api-utils@1.0.3(typescript@5.3.2):
- resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
- engines: {node: '>=16.13.0'}
- peerDependencies:
- typescript: '>=4.2.0'
- dependencies:
- typescript: 5.3.2
- dev: true
-
- /ts-dedent@2.2.0:
- resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
- engines: {node: '>=6.10'}
- dev: false
-
/ts-node@10.9.1(@types/node@16.11.59)(typescript@4.9.5):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
@@ -11989,12 +11524,6 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
- /typescript@5.3.2:
- resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==}
- engines: {node: '>=14.17'}
- hasBin: true
- dev: true
-
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@@ -12139,11 +11668,6 @@ packages:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
- /uuid@9.0.1:
- resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
- hasBin: true
- dev: false
-
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
@@ -12166,43 +11690,6 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
- /vite@4.5.0(@types/node@16.11.59)(sass@1.54.9):
- resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
- engines: {node: ^14.18.0 || >=16.0.0}
- hasBin: true
- peerDependencies:
- '@types/node': '>= 14'
- less: '*'
- lightningcss: ^1.21.0
- sass: '*'
- stylus: '*'
- sugarss: '*'
- terser: ^5.4.0
- peerDependenciesMeta:
- '@types/node':
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- dependencies:
- '@types/node': 16.11.59
- esbuild: 0.18.20
- postcss: 8.4.32
- rollup: 3.29.4
- sass: 1.54.9
- optionalDependencies:
- fsevents: 2.3.3
- dev: true
-
/void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
@@ -12213,6 +11700,10 @@ packages:
dependencies:
browser-process-hrtime: 1.0.0
+ /w3c-keyname@2.2.8:
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+ dev: false
+
/w3c-xmlserializer@2.0.0:
resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==}
engines: {node: '>=10'}
@@ -12242,10 +11733,6 @@ packages:
dependencies:
minimalistic-assert: 1.0.1
- /web-worker@1.2.0:
- resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==}
- dev: false
-
/webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
diff --git a/ui/public/index.html b/ui/public/index.html
index f5a9c141..f15dd970 100644
--- a/ui/public/index.html
+++ b/ui/public/index.html
@@ -89,6 +89,10 @@
</div>
</body>
<script>
+ // disable react devtools
+ if (typeof window.__REACT_DEVTOOLS_GLOBAL_HOOK__ === 'object') {
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject = function () {};
+ }
/**
* @description: Prompt that the browser version is too low
*/
diff --git a/ui/src/components/Actions/index.tsx b/ui/src/components/Actions/index.tsx
index b8357105..b58d0c8c 100644
--- a/ui/src/components/Actions/index.tsx
+++ b/ui/src/components/Actions/index.tsx
@@ -25,7 +25,8 @@ import classNames from 'classnames';
import { Icon } from '@/components';
import { loggedUserInfoStore } from '@/stores';
-import { useToast, useCaptchaModal } from '@/hooks';
+import { useToast } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import { tryNormalLogged } from '@/utils/guard';
import { bookmark, postVote } from '@/services';
import * as Types from '@/common/interface';
@@ -56,7 +57,7 @@ const Index: FC<Props> = ({ className, data, source }) => {
const { username = '' } = loggedUserInfoStore((state) => state.user);
const toast = useToast();
const { t } = useTranslation();
- const vCaptcha = useCaptchaModal('vote');
+ const vCaptcha = useCaptchaPlugin('vote');
useEffect(() => {
if (data) {
@@ -70,6 +71,42 @@ const Index: FC<Props> = ({ className, data, source }) => {
}
}, []);
+ const submitVote = (type) => {
+ const isCancel = (type === 'up' && like) || (type === 'down' && hate);
+ const imgCode: Types.ImgCodeReq = {
+ captcha_id: undefined,
+ captcha_code: undefined,
+ };
+ vCaptcha?.resolveCaptchaReq?.(imgCode);
+
+ postVote(
+ {
+ object_id: data?.id,
+ is_cancel: isCancel,
+ ...imgCode,
+ },
+ type,
+ )
+ .then(async (res) => {
+ await vCaptcha?.close();
+ setVotes(res.votes);
+ setLike(res.vote_status === 'vote_up');
+ setHated(res.vote_status === 'vote_down');
+ })
+ .catch((err) => {
+ if (err?.isError) {
+ vCaptcha?.handleCaptchaError(err.list);
+ }
+ const errMsg = err?.value;
+ if (errMsg) {
+ toast.onShow({
+ msg: errMsg,
+ variant: 'danger',
+ });
+ }
+ });
+ };
+
const handleVote = (type: 'up' | 'down') => {
if (!tryNormalLogged(true)) {
return;
@@ -82,39 +119,14 @@ const Index: FC<Props> = ({ className, data, source }) => {
});
return;
}
- const isCancel = (type === 'up' && like) || (type === 'down' && hate);
+
+ if (!vCaptcha) {
+ submitVote(type);
+ return;
+ }
+
vCaptcha.check(() => {
- const imgCode: Types.ImgCodeReq = {
- captcha_id: undefined,
- captcha_code: undefined,
- };
- vCaptcha.resolveCaptchaReq(imgCode);
- postVote(
- {
- object_id: data?.id,
- is_cancel: isCancel,
- ...imgCode,
- },
- type,
- )
- .then(async (res) => {
- await vCaptcha.close();
- setVotes(res.votes);
- setLike(res.vote_status === 'vote_up');
- setHated(res.vote_status === 'vote_down');
- })
- .catch((err) => {
- if (err?.isError) {
- vCaptcha.handleCaptchaError(err.list);
- }
- const errMsg = err?.value;
- if (errMsg) {
- toast.onShow({
- msg: errMsg,
- variant: 'danger',
- });
- }
- });
+ submitVote(type);
});
};
diff --git a/ui/src/components/Comment/components/Form/index.tsx b/ui/src/components/Comment/components/Form/index.tsx
index 05ae5b2d..12368193 100644
--- a/ui/src/components/Comment/components/Form/index.tsx
+++ b/ui/src/components/Comment/components/Form/index.tsx
@@ -65,6 +65,7 @@ const Index = ({
}
});
};
+
return (
<div
className={classNames(
diff --git a/ui/src/components/Comment/index.tsx b/ui/src/components/Comment/index.tsx
index 81345e7e..69c705b8 100644
--- a/ui/src/components/Comment/index.tsx
+++ b/ui/src/components/Comment/index.tsx
@@ -35,6 +35,7 @@ import {
bgFadeOut,
} from '@/utils';
import { tryNormalLogged } from '@/utils/guard';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import {
useQueryComments,
addComment,
@@ -66,14 +67,14 @@ const Comment = ({ objectId, mode, commentId }) => {
const reportModal = useReportModal();
const addCaptcha = useCaptchaModal('comment');
- const editCaptcha = useCaptchaModal('edit');
- const dCaptcha = useCaptchaModal('delete');
- const vCaptcha = useCaptchaModal('vote');
+ const editCaptcha = useCaptchaPlugin('edit');
+ const dCaptcha = useCaptchaPlugin('delete');
+ const vCaptcha = useCaptchaPlugin('vote');
const { t } = useTranslation('translation', { keyPrefix: 'comment' });
useEffect(() => {
- if (pageIndex === 0 && commentId) {
+ if (pageIndex === 0 && commentId && comments.length !== 0) {
setTimeout(() => {
const el = document.getElementById(commentId);
scrollToElementTop(el);
@@ -84,7 +85,7 @@ const Comment = ({ objectId, mode, commentId }) => {
return () => {
updateCurrentReplyId('');
};
- }, []);
+ }, [comments]);
useEffect(() => {
if (!data) {
@@ -136,6 +137,85 @@ const Comment = ({ objectId, mode, commentId }) => {
);
};
+ const submitUpdateComment = (params, item) => {
+ const up = {
+ ...params,
+ comment_id: item.comment_id,
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ editCaptcha?.resolveCaptchaReq(up);
+
+ return updateComment(up)
+ .then(async (res) => {
+ await editCaptcha?.close();
+ setComments(
+ comments.map((comment) => {
+ if (comment.comment_id === item.comment_id) {
+ comment.showEdit = false;
+ comment.parsed_text = res.parsed_text;
+ comment.original_text = res.original_text;
+ }
+ return comment;
+ }),
+ );
+ })
+ .catch((err) => {
+ if (err.isError) {
+ const captchaErr = editCaptcha?.handleCaptchaError(err.list);
+ // If it is not a CAPTCHA error, leave it to the subsequent error handling logic to continue processing.
+ if (!(captchaErr && err.list.length === 1)) {
+ return Promise.reject(err);
+ }
+ }
+ return Promise.resolve();
+ });
+ };
+
+ const submitAddComment = (params, item) => {
+ const req = {
+ ...params,
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ addCaptcha?.resolveCaptchaReq(req);
+
+ return addComment(req)
+ .then(async (res) => {
+ await addCaptcha?.close();
+ if (item.type === 'reply') {
+ const index = comments.findIndex(
+ (comment) => comment.comment_id === item.comment_id,
+ );
+ updateCurrentReplyId('');
+ comments.splice(index + 1, 0, res);
+ setComments([...comments]);
+ } else {
+ setComments([
+ ...comments.map((comment) => {
+ if (comment.comment_id === item.comment_id) {
+ updateCurrentReplyId('');
+ }
+ return comment;
+ }),
+ res,
+ ]);
+ }
+
+ setVisibleComment(false);
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ const captchaErr = addCaptcha?.handleCaptchaError(ex.list);
+ // If it is not a CAPTCHA error, leave it to the subsequent error handling logic to continue processing.
+ if (!(captchaErr && ex.list.length === 1)) {
+ return Promise.reject(ex);
+ }
+ }
+ return Promise.resolve();
+ });
+ };
+
const handleSendReply = (item) => {
const users = matchedUsers(item.value);
const userNames = unionBy(users.map((user) => user.userName));
@@ -153,80 +233,36 @@ const Comment = ({ objectId, mode, commentId }) => {
};
if (item.type === 'edit') {
- return editCaptcha.check(() => {
- const up = {
- ...params,
- comment_id: item.comment_id,
- captcha_code: undefined,
- captcha_id: undefined,
- };
- editCaptcha.resolveCaptchaReq(up);
-
- return updateComment(up)
- .then(async (res) => {
- await editCaptcha.close();
- setComments(
- comments.map((comment) => {
- if (comment.comment_id === item.comment_id) {
- comment.showEdit = false;
- comment.parsed_text = res.parsed_text;
- comment.original_text = res.original_text;
- }
- return comment;
- }),
- );
- })
- .catch((err) => {
- if (err.isError) {
- editCaptcha.handleCaptchaError(err.list);
- }
- });
- });
+ if (!editCaptcha) {
+ return submitUpdateComment(params, item);
+ }
+ return editCaptcha.check(() => submitUpdateComment(params, item));
}
- return addCaptcha.check(() => {
- const req = {
- ...params,
- captcha_code: undefined,
- captcha_id: undefined,
- };
- addCaptcha.resolveCaptchaReq(req);
-
- return addComment(req)
- .then(async (res) => {
- await addCaptcha.close();
- if (item.type === 'reply') {
- const index = comments.findIndex(
- (comment) => comment.comment_id === item.comment_id,
- );
- updateCurrentReplyId('');
- comments.splice(index + 1, 0, res);
- setComments([...comments]);
- } else {
- setComments([
- ...comments.map((comment) => {
- if (comment.comment_id === item.comment_id) {
- updateCurrentReplyId('');
- }
- return comment;
- }),
- res,
- ]);
- }
+ if (!addCaptcha) {
+ return submitAddComment(params, item);
+ }
- setVisibleComment(false);
- })
- .catch((ex) => {
- if (ex.isError) {
- const captchaErr = addCaptcha.handleCaptchaError(ex.list);
- // If it is not a CAPTCHA error, leave it to the subsequent error handling logic to continue processing.
- if (!(captchaErr && ex.list.length === 1)) {
- return Promise.reject(ex);
- }
- }
- return Promise.resolve();
- });
- });
+ return addCaptcha.check(() => submitAddComment(params, item));
+ };
+
+ const submitDeleteComment = (id) => {
+ const imgCode = { captcha_id: undefined, captcha_code: undefined };
+ dCaptcha?.resolveCaptchaReq(imgCode);
+
+ deleteComment(id, imgCode)
+ .then(async () => {
+ await dCaptcha?.close();
+ if (pageIndex === 0) {
+ mutate();
+ }
+ setComments(comments.filter((item) => item.comment_id !== id));
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ dCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
};
const handleDelete = (id) => {
@@ -236,67 +272,64 @@ const Comment = ({ objectId, mode, commentId }) => {
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => {
+ if (!dCaptcha) {
+ submitDeleteComment(id);
+ return;
+ }
dCaptcha.check(() => {
- const imgCode = { captcha_id: undefined, captcha_code: undefined };
- dCaptcha.resolveCaptchaReq(imgCode);
-
- deleteComment(id, imgCode)
- .then(async () => {
- await dCaptcha.close();
- if (pageIndex === 0) {
- mutate();
- }
- setComments(comments.filter((item) => item.comment_id !== id));
- })
- .catch((ex) => {
- if (ex.isError) {
- dCaptcha.handleCaptchaError(ex.list);
- }
- });
+ submitDeleteComment(id);
});
},
});
};
+ const submitVoteComment = (id, is_cancel) => {
+ const imgCode: Types.ImgCodeReq = {
+ captcha_id: undefined,
+ captcha_code: undefined,
+ };
+ vCaptcha?.resolveCaptchaReq(imgCode);
+
+ postVote(
+ {
+ object_id: id,
+ is_cancel,
+ ...imgCode,
+ },
+ 'up',
+ )
+ .then(async () => {
+ await vCaptcha?.close();
+ setComments(
+ comments.map((item) => {
+ if (item.comment_id === id) {
+ item.vote_count = is_cancel
+ ? item.vote_count - 1
+ : item.vote_count + 1;
+ item.is_vote = !is_cancel;
+ }
+ return item;
+ }),
+ );
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ vCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
+ };
const handleVote = (id, is_cancel) => {
if (!tryNormalLogged(true)) {
return;
}
+ if (!vCaptcha) {
+ submitVoteComment(id, is_cancel);
+ return;
+ }
+
vCaptcha.check(() => {
- const imgCode: Types.ImgCodeReq = {
- captcha_id: undefined,
- captcha_code: undefined,
- };
- vCaptcha.resolveCaptchaReq(imgCode);
-
- postVote(
- {
- object_id: id,
- is_cancel,
- ...imgCode,
- },
- 'up',
- )
- .then(async () => {
- await vCaptcha.close();
- setComments(
- comments.map((item) => {
- if (item.comment_id === id) {
- item.vote_count = is_cancel
- ? item.vote_count - 1
- : item.vote_count + 1;
- item.is_vote = !is_cancel;
- }
- return item;
- }),
- );
- })
- .catch((ex) => {
- if (ex.isError) {
- vCaptcha.handleCaptchaError(ex.list);
- }
- });
+ submitVoteComment(id, is_cancel);
});
};
diff --git a/ui/src/components/Editor/ToolBars/blockquote.tsx b/ui/src/components/Editor/ToolBars/blockquote.tsx
index 30a99141..68d73cfc 100644
--- a/ui/src/components/Editor/ToolBars/blockquote.tsx
+++ b/ui/src/components/Editor/ToolBars/blockquote.tsx
@@ -29,7 +29,7 @@ const BlockQuote = () => {
const item = {
label: 'blockquote',
- keyMap: ['Ctrl-Q'],
+ keyMap: ['Ctrl-q'],
tip: `${t('blockquote.text')} (Ctrl+Q)`,
};
diff --git a/ui/src/components/Editor/ToolBars/bold.tsx b/ui/src/components/Editor/ToolBars/bold.tsx
index 6731ee07..480872ae 100644
--- a/ui/src/components/Editor/ToolBars/bold.tsx
+++ b/ui/src/components/Editor/ToolBars/bold.tsx
@@ -28,8 +28,8 @@ const Bold = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'bold',
- keyMap: ['Ctrl-B'],
- tip: `${t('bold.text')} (Ctrl+B)`,
+ keyMap: ['Ctrl-b'],
+ tip: `${t('bold.text')} (Ctrl+b)`,
};
const DEFAULTTEXT = t('bold.text');
diff --git a/ui/src/components/Editor/ToolBars/code.tsx b/ui/src/components/Editor/ToolBars/code.tsx
index a3cdbffc..df52d163 100644
--- a/ui/src/components/Editor/ToolBars/code.tsx
+++ b/ui/src/components/Editor/ToolBars/code.tsx
@@ -156,8 +156,8 @@ const Code = () => {
const item = {
label: 'code',
- keyMap: ['Ctrl-K'],
- tip: `${t('code.text')} (Ctrl+K)`,
+ keyMap: ['Ctrl-k'],
+ tip: `${t('code.text')} (Ctrl+k)`,
};
const [code, setCode] = useState({
diff --git a/ui/src/components/Editor/ToolBars/formula.tsx b/ui/src/components/Editor/ToolBars/formula.tsx
deleted file mode 100644
index 9b273e87..00000000
--- a/ui/src/components/Editor/ToolBars/formula.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { FC, useState, memo } from 'react';
-import { Dropdown } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
-
-const Formula: FC<IEditorContext> = ({ editor, wrapText }) => {
- const { t } = useTranslation('translation', { keyPrefix: 'editor' });
- const formulaList = [
- {
- type: 'line',
- label: t('formula.options.inline'),
- },
- {
- type: 'block',
- label: t('formula.options.block'),
- },
- ];
- const item = {
- label: 'formula',
- tip: t('formula.text'),
- };
- const [isShow, setShowState] = useState(false);
- const [isLocked, setLockState] = useState(false);
-
- const handleClick = (type, label) => {
- if (!editor) {
- return;
- }
- if (type === 'line') {
- wrapText('\\\\( ', ' \\\\)', label);
- } else {
- const cursor = editor.getCursor();
-
- wrapText('\n$$\n', '\n$$\n', label);
-
- editor.setSelection(
- { line: cursor.line + 2, ch: 0 },
- { line: cursor.line + 2, ch: label.length },
- );
- }
- editor?.focus();
- setShowState(false);
- };
- const onAddFormula = () => {
- if (isLocked) {
- return;
- }
- setShowState(!isShow);
- };
-
- const handleMouseEnter = () => {
- setLockState(true);
- };
-
- const handleMouseLeave = () => {
- setLockState(false);
- };
- return (
- <ToolItem
- as="dropdown"
- {...item}
- isShow={isShow}
- onClick={onAddFormula}
- onBlur={onAddFormula}>
- <Dropdown.Menu
- onMouseEnter={handleMouseEnter}
- onMouseLeave={handleMouseLeave}>
- {formulaList.map((formula) => {
- return (
- <Dropdown.Item
- key={formula.label}
- onClick={(e) => {
- e.preventDefault();
- handleClick(formula.type, formula.label);
- }}>
- {formula.label}
- </Dropdown.Item>
- );
- })}
- </Dropdown.Menu>
- </ToolItem>
- );
-};
-
-export default memo(Formula);
diff --git a/ui/src/components/Editor/ToolBars/heading.tsx b/ui/src/components/Editor/ToolBars/heading.tsx
index 9ae243ef..5c22b606 100644
--- a/ui/src/components/Editor/ToolBars/heading.tsx
+++ b/ui/src/components/Editor/ToolBars/heading.tsx
@@ -61,8 +61,8 @@ const Heading = () => {
];
const item = {
label: 'heading',
- keyMap: ['Ctrl-H'],
- tip: `${t('heading.text')} (Ctrl+H)`,
+ keyMap: ['Ctrl-h'],
+ tip: `${t('heading.text')} (Ctrl+h)`,
};
const [isShow, setShowState] = useState(false);
const [isLocked, setLockState] = useState(false);
diff --git a/ui/src/components/Editor/ToolBars/hr.tsx b/ui/src/components/Editor/ToolBars/hr.tsx
index 76d08ea0..ac988a87 100644
--- a/ui/src/components/Editor/ToolBars/hr.tsx
+++ b/ui/src/components/Editor/ToolBars/hr.tsx
@@ -28,8 +28,8 @@ const Hr = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'hr',
- keyMap: ['Ctrl-R'],
- tip: `${t('hr.text')} (Ctrl+R)`,
+ keyMap: ['Ctrl-r'],
+ tip: `${t('hr.text')} (Ctrl+r)`,
};
const handleClick = (ctx) => {
context = ctx;
diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx
index 1bc429a0..60099193 100644
--- a/ui/src/components/Editor/ToolBars/image.tsx
+++ b/ui/src/components/Editor/ToolBars/image.tsx
@@ -21,11 +21,11 @@ import { useEffect, useState, memo } from 'react';
import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
-import type { Editor } from 'codemirror';
+import { EditorState, StateEffect } from '@codemirror/state';
import { Modal as AnswerModal } from '@/components';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { IEditorContext, Editor } from '../types';
import { uploadImage } from '@/services';
let context: IEditorContext;
@@ -37,7 +37,7 @@ const Image = ({ editorInstance }) => {
const item = {
label: 'image',
- keyMap: ['Ctrl-G'],
+ keyMap: ['Ctrl-g'],
tip: `${t('image.text')} (Ctrl+G)`,
};
const [currentTab, setCurrentTab] = useState('localImage');
@@ -94,16 +94,16 @@ const Image = ({ editorInstance }) => {
return Promise.all(promises);
};
- function dragenter(_, e) {
+ function dragenter(e) {
e.stopPropagation();
e.preventDefault();
}
- function dragover(_, e) {
+ function dragover(e) {
e.stopPropagation();
e.preventDefault();
}
- const drop = async (_, e) => {
+ const drop = async (e) => {
const fileList = e.dataTransfer.files;
const bool = verifyImageSize(fileList);
@@ -112,7 +112,9 @@ const Image = ({ editorInstance }) => {
return;
}
+ // const startPos = editor.getCursor(''); codemirror 6
const startPos = editor.getCursor();
+
const endPos = { ...startPos, ch: startPos.ch + loadingText.length };
editor.replaceSelection(loadingText);
@@ -136,26 +138,30 @@ const Image = ({ editorInstance }) => {
}
};
- const paste = async (_, event) => {
+ const paste = async (event) => {
const clipboard = event.clipboardData;
const bool = verifyImageSize(clipboard.files);
if (bool) {
event.preventDefault();
- editor.setOption('readOnly', true);
- const startPos = editor.getCursor('');
+ const startPos = editor.getCursor();
const endPos = { ...startPos, ch: startPos.ch + loadingText.length };
editor.replaceSelection(loadingText);
+ editor.dispatch({
+ effects: StateEffect.appendConfig.of([EditorState.readOnly.of(true)]),
+ });
const urls = await upload(clipboard.files);
const text = urls.map(({ name, url }) => {
return ``;
});
editor.replaceRange(text.join('\n'), startPos, endPos);
+ editor.dispatch({
+ effects: StateEffect.appendConfig.of([EditorState.readOnly.of(false)]),
+ });
- editor.setOption('readOnly', false);
return;
}
diff --git a/ui/src/components/Editor/ToolBars/index.ts b/ui/src/components/Editor/ToolBars/index.ts
index f3bc8209..ce04587d 100644
--- a/ui/src/components/Editor/ToolBars/index.ts
+++ b/ui/src/components/Editor/ToolBars/index.ts
@@ -18,7 +18,6 @@
*/
import Table from './table';
-import Formula from './formula';
import OL from './ol';
import UL from './ul';
import Indent from './indent';
@@ -36,7 +35,6 @@ import Chart from './chart';
export {
Table,
- Formula,
OL,
UL,
Indent,
diff --git a/ui/src/components/Editor/ToolBars/italic.tsx b/ui/src/components/Editor/ToolBars/italic.tsx
index 8237e974..f9b0e955 100644
--- a/ui/src/components/Editor/ToolBars/italic.tsx
+++ b/ui/src/components/Editor/ToolBars/italic.tsx
@@ -28,8 +28,8 @@ const Italic = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'italic',
- keyMap: ['Ctrl-I'],
- tip: `${t('italic.text')} (Ctrl+I)`,
+ keyMap: ['Ctrl-i'],
+ tip: `${t('italic.text')} (Ctrl+i)`,
};
const DEFAULTTEXT = t('italic.text');
diff --git a/ui/src/components/Editor/ToolBars/link.tsx b/ui/src/components/Editor/ToolBars/link.tsx
index 1fadcd63..f5b7c216 100644
--- a/ui/src/components/Editor/ToolBars/link.tsx
+++ b/ui/src/components/Editor/ToolBars/link.tsx
@@ -29,8 +29,8 @@ const Link = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'link',
- keyMap: ['Ctrl-L'],
- tip: `${t('link.text')} (Ctrl+L)`,
+ keyMap: ['Ctrl-l'],
+ tip: `${t('link.text')} (Ctrl+l)`,
};
const [visible, setVisible] = useState(false);
const [link, setLink] = useState({
diff --git a/ui/src/components/Editor/ToolBars/ol.tsx b/ui/src/components/Editor/ToolBars/ol.tsx
index 468d2baf..76179cf6 100644
--- a/ui/src/components/Editor/ToolBars/ol.tsx
+++ b/ui/src/components/Editor/ToolBars/ol.tsx
@@ -28,8 +28,8 @@ const OL = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'orderedList',
- keyMap: ['Ctrl-O'],
- tip: `${t('ordered_list.text')} (Ctrl+O)`,
+ keyMap: ['Ctrl-o'],
+ tip: `${t('ordered_list.text')} (Ctrl+o)`,
};
const handleClick = (ctx) => {
diff --git a/ui/src/components/Editor/ToolBars/ul.tsx b/ui/src/components/Editor/ToolBars/ul.tsx
index 6c5d0b81..c0c5df52 100644
--- a/ui/src/components/Editor/ToolBars/ul.tsx
+++ b/ui/src/components/Editor/ToolBars/ul.tsx
@@ -28,8 +28,8 @@ const UL = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'unorderedList',
- keyMap: ['Ctrl-U'],
- tip: `${t('unordered_list.text')} (Ctrl+U)`,
+ keyMap: ['Ctrl-u'],
+ tip: `${t('unordered_list.text')} (Ctrl+u)`,
};
const handleClick = (ctx) => {
diff --git a/ui/src/components/Editor/types.ts b/ui/src/components/Editor/types.ts
index ebc7dbca..ee16d6b3 100644
--- a/ui/src/components/Editor/types.ts
+++ b/ui/src/components/Editor/types.ts
@@ -17,8 +17,54 @@
* under the License.
*/
-import type { Editor } from 'codemirror';
+import { EditorView, Command } from '@codemirror/view';
+export interface Position {
+ ch: number;
+ line: number;
+ sticky?: string | undefined;
+}
+export interface ExtendEditor {
+ addKeyMap: (keyMap: Record<string, Command>) => void;
+ on: (
+ event:
+ | 'change'
+ | 'focus'
+ | 'blur'
+ | 'dragenter'
+ | 'dragover'
+ | 'drop'
+ | 'paste',
+ callback: (e?) => void,
+ ) => void;
+ getValue: () => string;
+ setValue: (value: string) => void;
+ off: (
+ event:
+ | 'change'
+ | 'focus'
+ | 'blur'
+ | 'dragenter'
+ | 'dragover'
+ | 'drop'
+ | 'paste',
+ callback: (e?) => void,
+ ) => void;
+ getSelection: () => string;
+ replaceSelection: (value: string) => void;
+ focus: () => void;
+ wrapText: (before: string, after?: string, defaultText?: string) => void;
+ replaceLines: (
+ replace: Parameters<Array<string>['map']>[0],
+ symbolLen?: number,
+ ) => void;
+ appendBlock: (content: string) => void;
+ getCursor: () => Position;
+ replaceRange: (value: string, from: Position, to: Position) => void;
+ setSelection: (anchor: Position, head?: Position) => void;
+}
+
+export type Editor = EditorView & ExtendEditor;
export interface CodeMirrorEditor extends Editor {
display: any;
diff --git a/ui/src/components/Editor/utils/extension.ts b/ui/src/components/Editor/utils/extension.ts
new file mode 100644
index 00000000..4118fed4
--- /dev/null
+++ b/ui/src/components/Editor/utils/extension.ts
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EditorSelection, StateEffect } from '@codemirror/state';
+import { EditorView, keymap, KeyBinding } from '@codemirror/view';
+
+import { Editor, Position } from '../types';
+
+const createEditorUtils = (editor: Editor) => {
+ editor.focus = () => {
+ editor.contentDOM.focus();
+ };
+
+ editor.getCursor = () => {
+ const range = editor.state.selection.ranges[0];
+ const line = editor.state.doc.lineAt(range.from).number;
+ const { from, to } = editor.state.doc.line(line);
+ return { from, to, ch: range.from - from, line };
+ };
+
+ editor.addKeyMap = (keyMap) => {
+ const array = Object.entries(keyMap).map(([key, value]) => {
+ const keyBinding: KeyBinding = {
+ key,
+ preventDefault: true,
+ run: value,
+ };
+ return keyBinding;
+ });
+
+ editor.dispatch({
+ effects: StateEffect.appendConfig.of(keymap.of(array)),
+ });
+ };
+
+ editor.getSelection = () => {
+ return editor.state.sliceDoc(
+ editor.state.selection.main.from,
+ editor.state.selection.main.to,
+ );
+ };
+
+ editor.replaceSelection = (value: string) => {
+ editor.dispatch({
+ changes: [
+ {
+ from: editor.state.selection.main.from,
+ to: editor.state.selection.main.to,
+ insert: value,
+ },
+ ],
+ selection: EditorSelection.cursor(
+ editor.state.selection.main.from + value.length,
+ ),
+ });
+ };
+
+ editor.setSelection = (anchor: Position, head?: Position) => {
+ editor.dispatch({
+ selection: EditorSelection.create([
+ EditorSelection.range(
+ editor.state.doc.line(anchor.line).from + anchor.ch,
+ head
+ ? editor.state.doc.line(head.line).from + head.ch
+ : editor.state.doc.line(anchor.line).from + anchor.ch,
+ ),
+ ]),
+ });
+ };
+
+ editor.on = (event, callback) => {
+ if (event === 'change') {
+ const change = EditorView.updateListener.of((update) => {
+ if (update.docChanged) {
+ callback();
+ }
+ });
+
+ editor.dispatch({
+ effects: StateEffect.appendConfig.of(change),
+ });
+ }
+ if (event === 'focus') {
+ editor.contentDOM.addEventListener('focus', callback);
+ }
+ if (event === 'blur') {
+ editor.contentDOM.addEventListener('blur', callback);
+ }
+
+ if (event === 'dragenter') {
+ editor.contentDOM.addEventListener('dragenter', callback);
+ }
+
+ if (event === 'dragover') {
+ editor.contentDOM.addEventListener('dragover', callback);
+ }
+
+ if (event === 'drop') {
+ editor.contentDOM.addEventListener('drop', callback);
+ }
+
+ if (event === 'paste') {
+ editor.contentDOM.addEventListener('paste', callback);
+ }
+ };
+
+ editor.off = (event, callback) => {
+ if (event === 'focus') {
+ editor.contentDOM.removeEventListener('focus', callback);
+ }
+
+ if (event === 'blur') {
+ editor.contentDOM.removeEventListener('blur', callback);
+ }
+
+ if (event === 'dragenter') {
+ editor.contentDOM.removeEventListener('dragenter', callback);
+ }
+
+ if (event === 'dragover') {
+ editor.contentDOM.removeEventListener('dragover', callback);
+ }
+
+ if (event === 'drop') {
+ editor.contentDOM.removeEventListener('drop', callback);
+ }
+
+ if (event === 'paste') {
+ editor.contentDOM.removeEventListener('paste', callback);
+ }
+ };
+
+ editor.getValue = () => {
+ return editor.state.doc.toString();
+ };
+
+ editor.setValue = (value: string) => {
+ editor.dispatch({
+ changes: { from: 0, to: editor.state.doc.length, insert: value },
+ });
+ };
+
+ editor.wrapText = (before: string, after = before, defaultText) => {
+ const range = editor.state.selection.ranges[0];
+ const selection = editor.state.sliceDoc(range.from, range.to);
+ const text = `${before}${selection || defaultText}${after}`;
+
+ editor.dispatch({
+ changes: [
+ {
+ from: range.from,
+ to: range.to,
+ insert: text,
+ },
+ ],
+ selection: EditorSelection.range(
+ range.from + before.length,
+ range.to + before.length,
+ ),
+ });
+ };
+
+ editor.replaceLines = (
+ replace: Parameters<Array<string>['map']>[0],
+ symbolLen = 0,
+ ) => {
+ const range = editor.state.selection.ranges[0];
+ const line = editor.state.doc.lineAt(range.from).number;
+ const { from, to } = editor.state.doc.line(line);
+ const lines = editor.state.sliceDoc(from, to).split('\n');
+
+ const insert = lines.map(replace).join('\n');
+ const selectionStart = from;
+ const selectionEnd = from + insert.length;
+
+ editor.dispatch({
+ changes: [
+ {
+ from,
+ to,
+ insert,
+ },
+ ],
+ selection: EditorSelection.create([
+ EditorSelection.range(selectionStart + symbolLen, selectionEnd),
+ ]),
+ });
+ };
+
+ editor.appendBlock = (content: string) => {
+ const range = editor.state.selection.ranges[0];
+ const line = editor.state.doc.lineAt(range.from).number;
+ const { from, to } = editor.state.doc.line(line);
+
+ let insert = `\n\n${content}`;
+
+ let selection = EditorSelection.single(to, to + content.length);
+ if (from === to) {
+ insert = `${content}\n`;
+ selection = EditorSelection.create([
+ EditorSelection.cursor(to + content.length),
+ ]);
+ }
+
+ editor.dispatch({
+ changes: [
+ {
+ from: to,
+ insert,
+ },
+ ],
+ selection,
+ });
+ };
+
+ editor.replaceRange = (
+ value: string,
+ selectionStart: Position,
+ selectionEnd: Position,
+ ) => {
+ const from =
+ editor.state.doc.line(selectionStart.line).from + selectionStart.ch;
+ const to = editor.state.doc.line(selectionEnd.line).from + selectionEnd.ch;
+ editor.dispatch({
+ changes: [
+ {
+ from,
+ to,
+ insert: value,
+ },
+ ],
+ selection: EditorSelection.cursor(from + value.length),
+ });
+ };
+
+ return editor;
+};
+
+export default createEditorUtils;
diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts
index 4930c768..d380620b 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -17,86 +17,17 @@
* under the License.
*/
-import { useEffect, useState, useRef } from 'react';
-
-import type { Editor, Position } from 'codemirror';
-import type CodeMirror from 'codemirror';
-import 'codemirror/lib/codemirror.css';
-
-export function createEditorUtils(
- codemirror: typeof CodeMirror,
- editor: Editor,
-) {
- editor.wrapText = (before: string, after = before, defaultText) => {
- const range = editor.somethingSelected()
- ? editor.listSelections()[0]
- : editor.findWordAt(editor.getCursor());
-
- const from = range.from();
- const to = range.to();
- const text = editor.getRange(from, to) || defaultText;
- const fromBefore = codemirror.Pos(from.line, from.ch - before.length);
- const toAfter = codemirror.Pos(to.line, to.ch + after.length);
+import { useEffect, useState } from 'react';
- if (
- editor.getRange(fromBefore, from) === before &&
- editor.getRange(to, toAfter) === after
- ) {
- editor.replaceRange(text, fromBefore, toAfter);
- editor.setSelection(
- fromBefore,
- codemirror.Pos(fromBefore.line, fromBefore.ch + text.length),
- );
- } else {
- editor.replaceRange(before + text + after, from, to);
- const cursor = editor.getCursor();
-
- editor.setSelection(
- codemirror.Pos(cursor.line, cursor.ch - after.length - text.length),
- codemirror.Pos(cursor.line, cursor.ch - after.length),
- );
- }
- };
- editor.replaceLines = (
- replace: Parameters<Array<string>['map']>[0],
- symbolLen = 0,
- ) => {
- const [selection] = editor.listSelections();
-
- const range = [
- codemirror.Pos(selection.from().line, 0),
- codemirror.Pos(selection.to().line),
- ] as const;
- const lines = editor.getRange(...range).split('\n');
-
- editor.replaceRange(lines.map(replace).join('\n'), ...range);
- const newRange = range;
-
- if (symbolLen > 0) {
- newRange[0].ch = symbolLen;
- }
- editor.setSelection(...newRange);
- };
- editor.appendBlock = (content: string): Position => {
- const cursor = editor.getCursor();
+import { minimalSetup } from 'codemirror';
+import { EditorState } from '@codemirror/state';
+import { EditorView, placeholder } from '@codemirror/view';
+import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
+import { languages } from '@codemirror/language-data';
- let emptyLine = -1;
+import { Editor } from '../types';
- for (let i = cursor.line; i < editor.lineCount(); i += 1) {
- if (!editor.getLine(i).trim()) {
- emptyLine = i;
- break;
- }
- }
- if (emptyLine === -1) {
- editor.replaceRange('\n', codemirror.Pos(editor.lineCount()));
- emptyLine = editor.lineCount();
- }
-
- editor.replaceRange(`\n${content}`, codemirror.Pos(emptyLine));
- return codemirror.Pos(emptyLine + 1, 0);
- };
-}
+import createEditorUtils from './extension';
export function htmlRender(el: HTMLElement | null) {
if (!el) return;
@@ -127,7 +58,7 @@ export function htmlRender(el: HTMLElement | null) {
div.appendChild(table);
});
- // add rel nofollow for link not inlcludes domain
+ // add rel nofollow for link not includes domain
el.querySelectorAll('a').forEach((a) => {
const base = window.location.origin;
const targetUrl = new URL(a.href, base);
@@ -140,85 +71,77 @@ export function htmlRender(el: HTMLElement | null) {
export const useEditor = ({
editorRef,
- placeholder,
+ placeholder: placeholderText,
autoFocus,
onChange,
onFocus,
onBlur,
}) => {
- const [editor, setEditor] = useState<CodeMirror.Editor | null>(null);
+ const [editor, setEditor] = useState<Editor | null>(null);
const [value, setValue] = useState<string>('');
- const isMountedRef = useRef(false);
-
- const onEnter = (cm) => {
- const cursor = cm.getCursor();
- const text = cm.getLine(cursor.line);
- const doc = cm.getDoc();
-
- const olRegexData = text.match(/^(\s{0,})(\d+)\.\s/);
- const ulRegexData = text.match(/^(\s{0,})(-|\*)\s/);
- const blockquoteData = text.match(/^>\s+?/g);
-
- if (olRegexData && text !== olRegexData[0]) {
- const num = olRegexData[2];
-
- doc.replaceSelection(`\n${olRegexData[1]}${Number(num) + 1}. `);
- } else if (ulRegexData && text !== ulRegexData[0]) {
- doc.replaceSelection(`\n${ulRegexData[1]}${ulRegexData[2]} `);
- } else if (blockquoteData && text !== blockquoteData[0]) {
- doc.replaceSelection(`\n> `);
- } else if (
- text.trim() === '>' ||
- text.trim().match(/^\d{1,}\.$/) ||
- text.trim().match(/^(\*|-)$/)
- ) {
- doc.replaceRange(`\n`, { ...cursor, ch: 0 }, cursor);
- } else {
- doc.replaceSelection(`\n`);
- }
- };
-
const init = async () => {
- if (isMountedRef.current) {
- return false;
- }
- isMountedRef.current = true;
-
- const { default: codeMirror } = await import('codemirror');
- await import('codemirror/mode/markdown/markdown');
- await import('codemirror/addon/display/placeholder');
+ const theme = EditorView.theme(
+ {
+ '&': {
+ height: '100%',
+ },
+ '&.cm-focused': {
+ outline: 'none',
+ },
+ '.cm-content': {
+ width: '100%',
+ padding: '1rem',
+ },
+ '.cm-line': {
+ whiteSpace: 'pre-wrap',
+ wordWrap: 'break-word',
+ wordBreak: 'break-all',
+ },
+ },
+ { dark: false },
+ );
+
+ const startState = EditorState.create({
+ extensions: [
+ minimalSetup,
+ markdown({
+ codeLanguages: languages,
+ base: markdownLanguage,
+ }),
+ theme,
+ placeholder(placeholderText),
+ EditorView.lineWrapping,
+ ],
+ });
- const cm = codeMirror(editorRef?.current, {
- mode: 'markdown',
- lineWrapping: true,
- placeholder,
+ const view = new EditorView({
+ parent: editorRef.current,
+ state: startState,
});
+ const cm = createEditorUtils(view as Editor);
+
if (autoFocus) {
setTimeout(() => {
cm.focus();
}, 10);
}
- cm.on('change', (e) => {
- const newValue = e.getValue();
+ cm.on('change', () => {
+ const newValue = cm.getValue();
setValue(newValue);
});
cm.on('focus', () => {
onFocus?.();
});
+
cm.on('blur', () => {
onBlur?.();
});
setEditor(cm);
- createEditorUtils(codeMirror, cm);
- cm.setSize('100%', '100%');
- cm.addKeyMap({
- Enter: onEnter,
- });
return cm;
};
@@ -227,11 +150,10 @@ export const useEditor = ({
}, [value]);
useEffect(() => {
- if (!(editorRef.current instanceof HTMLElement)) {
+ if (!(editorRef.current instanceof HTMLElement) || editor) {
return;
}
init();
- }, [editorRef]);
-
+ }, [editor]);
return editor;
};
diff --git a/ui/src/pages/Admin/index.scss b/ui/src/components/Mentions/index.scss
similarity index 88%
copy from ui/src/pages/Admin/index.scss
copy to ui/src/components/Mentions/index.scss
index c723b8ac..69c73f88 100644
--- a/ui/src/pages/Admin/index.scss
+++ b/ui/src/components/Mentions/index.scss
@@ -17,7 +17,7 @@
* under the License.
*/
-.admin-container {
- padding-top: 1.5rem;
- padding-bottom: 1.5rem;
+.bg-gray-200 {
+ color: var(--bs-body-color);
+ background-color: var(--an-invite-answer-item-active);
}
diff --git a/ui/src/components/Mentions/index.tsx b/ui/src/components/Mentions/index.tsx
index 9b61f0bf..ae5ea2ff 100644
--- a/ui/src/components/Mentions/index.tsx
+++ b/ui/src/components/Mentions/index.tsx
@@ -20,8 +20,11 @@
import React, { useEffect, useRef, useState, FC } from 'react';
import { Dropdown } from 'react-bootstrap';
+import { useSearchUserStaff } from '@/services';
import * as Types from '@/common/interface';
+import './index.scss';
+
interface IProps {
children: React.ReactNode;
pageUsers;
@@ -37,6 +40,17 @@ const Mentions: FC<IProps> = ({ children, pageUsers, onSelected }) => {
const [users, setUsers] = useState<Types.PageUser[]>([]);
const [cursor, setCursor] = useState(0);
const [isRequested, setRequestedState] = useState(false);
+ const { data: staffUserList = [] } = useSearchUserStaff(val);
+ const mapStaffUsers =
+ staffUserList
+ ?.map((item) => ({
+ displayName: item.display_name,
+ userName: item.username,
+ }))
+ ?.filter(
+ (item) =>
+ users.findIndex((user) => user.userName === item.userName) < 0,
+ ) || [];
const searchUser = () => {
const element = dropdownRef.current?.children[0];
@@ -57,6 +71,7 @@ const Mentions: FC<IProps> = ({ children, pageUsers, onSelected }) => {
if (str.substring(str.lastIndexOf(' '), selectionStart).indexOf('@') < 0) {
return;
}
+
setValue(str.substring(1));
if (!str.substring(1)) {
@@ -103,7 +118,7 @@ const Mentions: FC<IProps> = ({ children, pageUsers, onSelected }) => {
setValue('');
};
const filterData = val
- ? users.filter(
+ ? [...users, ...mapStaffUsers].filter(
(item) =>
item.displayName?.indexOf(val) === 0 ||
item.userName?.indexOf(val) === 0,
diff --git a/ui/src/components/Operate/index.tsx b/ui/src/components/Operate/index.tsx
index e04416f2..47b4463f 100644
--- a/ui/src/components/Operate/index.tsx
+++ b/ui/src/components/Operate/index.tsx
@@ -23,7 +23,8 @@ import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Modal } from '@/components';
-import { useReportModal, useToast, useCaptchaModal } from '@/hooks';
+import { useReportModal, useToast } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import { QuestionOperationReq } from '@/common/interface';
import Share from '../Share';
import {
@@ -63,7 +64,7 @@ const Index: FC<IProps> = ({
const toast = useToast();
const navigate = useNavigate();
const reportModal = useReportModal();
- const dCaptcha = useCaptchaModal('delete');
+ const dCaptcha = useCaptchaPlugin('delete');
const refreshQuestion = () => {
callback?.('default');
@@ -88,6 +89,55 @@ const Index: FC<IProps> = ({
});
};
+ const submitDeleteQuestion = () => {
+ const req = {
+ id: qid,
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ dCaptcha?.resolveCaptchaReq(req);
+
+ deleteQuestion(req)
+ .then(async () => {
+ await dCaptcha?.close();
+ toast.onShow({
+ msg: t('post_deleted', { keyPrefix: 'messages' }),
+ variant: 'success',
+ });
+ callback?.('delete_question');
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ dCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
+ };
+
+ const submitDeleteAnswer = () => {
+ const req = {
+ id: aid,
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ dCaptcha?.resolveCaptchaReq(req);
+
+ deleteAnswer(req)
+ .then(async () => {
+ await dCaptcha?.close();
+ // refresh page
+ toast.onShow({
+ msg: t('tip_answer_deleted'),
+ variant: 'success',
+ });
+ callback?.('delete_answer');
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ dCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
+ };
+
const handleDelete = () => {
if (type === 'question') {
Modal.confirm({
@@ -97,28 +147,12 @@ const Index: FC<IProps> = ({
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => {
+ if (!dCaptcha) {
+ submitDeleteQuestion();
+ return;
+ }
dCaptcha.check(() => {
- const req = {
- id: qid,
- captcha_code: undefined,
- captcha_id: undefined,
- };
- dCaptcha.resolveCaptchaReq(req);
-
- deleteQuestion(req)
- .then(async () => {
- await dCaptcha.close();
- toast.onShow({
- msg: t('post_deleted', { keyPrefix: 'messages' }),
- variant: 'success',
- });
- callback?.('delete_question');
- })
- .catch((ex) => {
- if (ex.isError) {
- dCaptcha.handleCaptchaError(ex.list);
- }
- });
+ submitDeleteQuestion();
});
},
});
@@ -132,29 +166,12 @@ const Index: FC<IProps> = ({
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => {
+ if (!dCaptcha) {
+ submitDeleteAnswer();
+ return;
+ }
dCaptcha.check(() => {
- const req = {
- id: aid,
- captcha_code: undefined,
- captcha_id: undefined,
- };
- dCaptcha.resolveCaptchaReq(req);
-
- deleteAnswer(req)
- .then(async () => {
- await dCaptcha.close();
- // refresh page
- toast.onShow({
- msg: t('tip_answer_deleted'),
- variant: 'success',
- });
- callback?.('delete_answer');
- })
- .catch((ex) => {
- if (ex.isError) {
- dCaptcha.handleCaptchaError(ex.list);
- }
- });
+ submitDeleteAnswer();
});
},
});
diff --git a/ui/src/components/PageTags/index.tsx b/ui/src/components/PageTags/index.tsx
index 6bed2125..86d9a979 100644
--- a/ui/src/components/PageTags/index.tsx
+++ b/ui/src/components/PageTags/index.tsx
@@ -32,6 +32,7 @@ const Index: FC = () => {
);
const appVersion = siteInfoStore((_) => _.version);
const hashVersion = siteInfoStore((_) => _.revision);
+ const siteName = siteInfoStore((_) => _.siteInfo).name;
const setAppGenerator = () => {
if (!appVersion) {
return;
@@ -61,6 +62,11 @@ const Index: FC = () => {
);
}
};
+ // properties used for social media tags
+ const openGraphType = 'website';
+ const twitterType = 'summary';
+ const { href } = window.location;
+ const { hostname } = new URL(href);
useEffect(() => {
setDocLang();
@@ -86,6 +92,25 @@ const Index: FC = () => {
{doInsertCustomCSS && (
<link rel="stylesheet" href={`${process.env.PUBLIC_URL}/custom.css`} />
)}
+ {/* Social media meta share tags start here */}
+ <meta property="og:type" content={openGraphType} />
+ <meta property="og:title" name="twitter:title" content={pageTitle} />
+ <meta property="og:site_name" content={siteName} />
+ <meta property="og:url" content={href} />
+ {description && <meta property="og:description" content={description} />}
+ <meta
+ property="og:image"
+ itemProp="image primaryImageOfPage"
+ content={square_icon || favicon || '/favicon.ico'}
+ />
+ <meta name="twitter:card" content={twitterType} />
+ <meta name="twitter:domain" content={hostname} />
+ {description && <meta name="twitter:description" content={description} />}
+ <meta
+ name="twitter:image"
+ content={square_icon || favicon || '/favicon.ico'}
+ />
+ {/* Social media meta share tags end here */}
</Helmet>
);
};
diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx
index d0afb692..51fc4655 100644
--- a/ui/src/components/SchemaForm/index.tsx
+++ b/ui/src/components/SchemaForm/index.tsx
@@ -29,6 +29,7 @@ import { useTranslation } from 'react-i18next';
import isEmpty from 'lodash/isEmpty';
import classnames from 'classnames';
+import { scrollToElementTop } from '@/utils';
import type * as Type from '@/common/interface';
import type {
@@ -201,6 +202,7 @@ const SchemaForm: ForwardRefRenderFunction<FormRef, FormProps> = (
if (onChange instanceof Function) {
onChange({ ...formData });
}
+ scrollToElementTop(document.getElementById(errors[0]));
return false;
}
const syncErrors = await syncValidator();
@@ -216,6 +218,7 @@ const SchemaForm: ForwardRefRenderFunction<FormRef, FormProps> = (
if (onChange instanceof Function) {
onChange({ ...formData });
}
+ scrollToElementTop(document.getElementById(syncErrors[0].key));
return false;
}
return true;
diff --git a/ui/src/components/SideNav/index.scss b/ui/src/components/SideNav/index.scss
index d8bcc8f1..1fb0e7b1 100644
--- a/ui/src/components/SideNav/index.scss
+++ b/ui/src/components/SideNav/index.scss
@@ -33,6 +33,9 @@
.nav-link {
color: var(--an-side-nav-link);
}
+ .nav-link:focus-visible {
+ box-shadow: none;
+ }
.nav-link:hover {
color: var(--an-side-nav-link-hover-color);
background-color: var(--bs-gray-100);
@@ -48,7 +51,6 @@
width: 1px;
height: 100%;
background-color: var(--bs-border-color);
- min-height: calc(100vh - 62px - 74px);
}
}
diff --git a/ui/src/components/Unactivate/index.tsx b/ui/src/components/Unactivate/index.tsx
index e96e2875..163da2bd 100644
--- a/ui/src/components/Unactivate/index.tsx
+++ b/ui/src/components/Unactivate/index.tsx
@@ -26,7 +26,7 @@ import type { ImgCodeReq, FormDataType } from '@/common/interface';
import { loggedUserInfoStore } from '@/stores';
import { resendEmail } from '@/services';
import { handleFormError } from '@/utils';
-import { useCaptchaModal } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
interface IProps {
visible?: boolean;
@@ -44,12 +44,12 @@ const Index: React.FC<IProps> = () => {
},
});
- const emailCaptcha = useCaptchaModal('email');
+ const emailCaptcha = useCaptchaPlugin('email');
const submit = () => {
let req: ImgCodeReq = {};
- const imgCode = emailCaptcha.getCaptcha();
- if (imgCode.verify) {
+ const imgCode = emailCaptcha?.getCaptcha();
+ if (imgCode?.verify) {
req = {
captcha_code: imgCode.captcha_code,
captcha_id: imgCode.captcha_id,
@@ -57,12 +57,12 @@ const Index: React.FC<IProps> = () => {
}
resendEmail(req)
.then(async () => {
- await emailCaptcha.close();
+ await emailCaptcha?.close();
setSuccess(true);
})
.catch((err) => {
if (err.isError) {
- emailCaptcha.handleCaptchaError(err.list);
+ emailCaptcha?.handleCaptchaError(err.list);
const data = handleFormError(err, formData);
setFormData({ ...data });
}
@@ -71,6 +71,10 @@ const Index: React.FC<IProps> = () => {
const onSentEmail = (evt) => {
evt.preventDefault();
+ if (!emailCaptcha) {
+ submit();
+ return;
+ }
emailCaptcha.check(() => {
submit();
});
diff --git a/ui/src/hooks/usePrompt/index.tsx b/ui/src/hooks/usePrompt/index.tsx
index 7655adce..095dbd53 100644
--- a/ui/src/hooks/usePrompt/index.tsx
+++ b/ui/src/hooks/usePrompt/index.tsx
@@ -36,6 +36,7 @@ const usePromptWithUnload = ({
beforeUnload = true,
}: PromptProps) => {
const { t } = useTranslation('translation', { keyPrefix: 'prompt' });
+
usePrompt({
when,
message: `${t('leave_page')} ${t('changes_not_save')}`,
diff --git a/ui/src/hooks/useReportModal/index.tsx b/ui/src/hooks/useReportModal/index.tsx
index e20b8eb6..843472cd 100644
--- a/ui/src/hooks/useReportModal/index.tsx
+++ b/ui/src/hooks/useReportModal/index.tsx
@@ -23,7 +23,8 @@ import { useTranslation } from 'react-i18next';
import ReactDOM from 'react-dom/client';
-import { useToast, useCaptchaModal } from '@/hooks';
+import { useToast } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import type * as Type from '@/common/interface';
import {
reportList,
@@ -65,7 +66,7 @@ const useReportModal = (callback?: () => void) => {
const [show, setShow] = useState(false);
const [list, setList] = useState<any[]>([]);
- const rCaptcha = useCaptchaModal('report');
+ const rCaptcha = useCaptchaPlugin('report');
useEffect(() => {
const div = document.createElement('div');
@@ -168,6 +169,34 @@ const useReportModal = (callback?: () => void) => {
return true;
};
+ const submitReport = (data) => {
+ const flagReq = {
+ source: data.type,
+ report_type: reportType.type,
+ object_id: data.id,
+ content: content.value,
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ rCaptcha?.resolveCaptchaReq(flagReq);
+
+ postReport(flagReq)
+ .then(async () => {
+ await rCaptcha?.close();
+ toast.onShow({
+ msg: t('flag_success', { keyPrefix: 'toast' }),
+ variant: 'warning',
+ });
+ onClose();
+ asyncCallback();
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ rCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
+ };
+
const handleSubmit = () => {
if (!params) {
return;
@@ -205,32 +234,12 @@ const useReportModal = (callback?: () => void) => {
return;
}
if (!params.isBackend && params.action === 'flag') {
+ if (!rCaptcha) {
+ submitReport(params);
+ return;
+ }
rCaptcha.check(() => {
- const flagReq = {
- source: params.type,
- report_type: reportType.type,
- object_id: params.id,
- content: content.value,
- captcha_code: undefined,
- captcha_id: undefined,
- };
- rCaptcha.resolveCaptchaReq(flagReq);
-
- postReport(flagReq)
- .then(async () => {
- await rCaptcha.close();
- toast.onShow({
- msg: t('flag_success', { keyPrefix: 'toast' }),
- variant: 'warning',
- });
- onClose();
- asyncCallback();
- })
- .catch((ex) => {
- if (ex.isError) {
- rCaptcha.handleCaptchaError(ex.list);
- }
- });
+ submitReport(params);
});
}
diff --git a/ui/src/i18n/init.ts b/ui/src/i18n/init.ts
index 03f775cb..86188900 100644
--- a/ui/src/i18n/init.ts
+++ b/ui/src/i18n/init.ts
@@ -21,7 +21,7 @@ import { initReactI18next } from 'react-i18next';
import i18next from 'i18next';
import en_US from '@i18n/en_US.yaml';
-import zh_CN from '@i18n/zh_CN.yaml';
+// import zh_CN from '@i18n/zh_CN.yaml';
import { DEFAULT_LANG, LANG_RESOURCE_STORAGE_KEY } from '@/common/constants';
import Storage from '@/utils/storage';
@@ -34,9 +34,9 @@ const initResources = {
en_US: {
translation: en_US.ui,
},
- zh_CN: {
- translation: zh_CN.ui,
- },
+ // zh_CN: {
+ // translation: zh_CN.ui,
+ // },
};
const storageLang = Storage.get(LANG_RESOURCE_STORAGE_KEY);
@@ -44,8 +44,7 @@ if (
storageLang &&
storageLang.resources &&
storageLang.lng &&
- storageLang.lng !== 'en_US' &&
- storageLang.lng !== 'zh_CN'
+ storageLang.lng !== 'en_US'
) {
initResources[storageLang.lng] = {
translation: storageLang.resources,
diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx
index 091fe2db..42339263 100644
--- a/ui/src/pages/Admin/Answers/index.tsx
+++ b/ui/src/pages/Admin/Answers/index.tsx
@@ -76,7 +76,7 @@ const Answers: FC = () => {
return (
<>
<h3 className="mb-4">{t('page_title')}</h3>
- <div className="d-flex justify-content-between align-items-center mb-3">
+ <div className="d-flex flex-wrap justify-content-between align-items-center mb-3">
<QueryGroup
data={answerFilterItems}
currentSort={curFilter}
@@ -91,12 +91,13 @@ const Answers: FC = () => {
type="search"
placeholder={t('filter.placeholder')}
style={{ width: '12.25rem' }}
+ className="mt-3 mt-sm-0"
/>
</div>
<Table responsive>
<thead>
<tr>
- <th>{t('post')}</th>
+ <th className="min-w-15">{t('post')}</th>
<th style={{ width: '11%' }}>{t('votes')}</th>
<th style={{ width: '14%' }}>{t('created')}</th>
<th style={{ width: '11%' }}>{t('status')}</th>
@@ -127,9 +128,7 @@ const Answers: FC = () => {
className="ms-2 text-success"
/>
)}
- <div
- className="text-truncate-2 small"
- style={{ maxWidth: '30rem' }}>
+ <div className="text-truncate-2 small max-w-30">
{escapeRemove(li.description)}
</div>
</td>
diff --git a/ui/src/pages/Admin/Branding/index.tsx b/ui/src/pages/Admin/Branding/index.tsx
index 74c3d40e..bec7fc0f 100644
--- a/ui/src/pages/Admin/Branding/index.tsx
+++ b/ui/src/pages/Admin/Branding/index.tsx
@@ -25,7 +25,7 @@ import { FormDataType } from '@/common/interface';
import { brandSetting, getBrandSetting } from '@/services';
import { brandingStore } from '@/stores';
import { useToast } from '@/hooks';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
const uploadType = 'branding';
const Index: FC = () => {
@@ -145,6 +145,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/CssAndHtml/index.tsx b/ui/src/pages/Admin/CssAndHtml/index.tsx
index ae64a697..de376ad7 100644
--- a/ui/src/pages/Admin/CssAndHtml/index.tsx
+++ b/ui/src/pages/Admin/CssAndHtml/index.tsx
@@ -24,7 +24,7 @@ import type * as Type from '@/common/interface';
import { getPageCustom, putPageCustom } from '@/services';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { customizeStore } from '@/stores';
const Index: FC = () => {
@@ -124,6 +124,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/General/index.tsx b/ui/src/pages/Admin/General/index.tsx
index 3c6ff51a..6a1b080f 100644
--- a/ui/src/pages/Admin/General/index.tsx
+++ b/ui/src/pages/Admin/General/index.tsx
@@ -26,7 +26,7 @@ import { useToast } from '@/hooks';
import { siteInfoStore } from '@/stores';
import { useGeneralSetting, updateGeneralSetting } from '@/services';
import Pattern from '@/common/pattern';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
const General: FC = () => {
const { t } = useTranslation('translation', {
@@ -157,6 +157,8 @@ const General: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Interface/index.tsx b/ui/src/pages/Admin/Interface/index.tsx
index f4808f97..9c622048 100644
--- a/ui/src/pages/Admin/Interface/index.tsx
+++ b/ui/src/pages/Admin/Interface/index.tsx
@@ -39,7 +39,7 @@ import {
loadLanguageOptions,
setupAppTimeZone,
} from '@/utils/localize';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
const Interface: FC = () => {
const { t } = useTranslation('translation', {
@@ -141,6 +141,8 @@ const Interface: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Legal/index.tsx b/ui/src/pages/Admin/Legal/index.tsx
index d0dd2e7a..83561566 100644
--- a/ui/src/pages/Admin/Legal/index.tsx
+++ b/ui/src/pages/Admin/Legal/index.tsx
@@ -26,7 +26,7 @@ import type * as Type from '@/common/interface';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
import { getLegalSetting, putLegalSetting } from '@/services';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
const Legal: FC = () => {
const { t } = useTranslation('translation', {
@@ -90,6 +90,8 @@ const Legal: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Login/index.tsx b/ui/src/pages/Admin/Login/index.tsx
index c58a293b..c43cfcff 100644
--- a/ui/src/pages/Admin/Login/index.tsx
+++ b/ui/src/pages/Admin/Login/index.tsx
@@ -24,7 +24,7 @@ import type * as Type from '@/common/interface';
import { getLoginSetting, putLoginSetting } from '@/services';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { loginSettingStore } from '@/stores';
const Index: FC = () => {
@@ -132,6 +132,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Plugins/Installed/index.tsx b/ui/src/pages/Admin/Plugins/Installed/index.tsx
index 6df6d716..f1ced442 100644
--- a/ui/src/pages/Admin/Plugins/Installed/index.tsx
+++ b/ui/src/pages/Admin/Plugins/Installed/index.tsx
@@ -103,10 +103,10 @@ const Users: FC = () => {
/>
</Stack>
</div>
- <Table>
+ <Table responsive>
<thead>
<tr>
- <th>{t('name')}</th>
+ <th className="min-w-15">{t('name')}</th>
<th style={{ width: '17%' }}>{t('version')}</th>
<th style={{ width: '11%' }}>{t('status')}</th>
{curFilter !== 'deleted' ? (
diff --git a/ui/src/pages/Admin/Privileges/index.tsx b/ui/src/pages/Admin/Privileges/index.tsx
index dbe58f0c..5276912a 100644
--- a/ui/src/pages/Admin/Privileges/index.tsx
+++ b/ui/src/pages/Admin/Privileges/index.tsx
@@ -29,7 +29,7 @@ import {
AdminSettingsPrivilege,
AdminSettingsPrivilegeReq,
} from '@/services';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { ADMIN_PRIVILEGE_CUSTOM_LEVEL } from '@/common/constants';
const Index: FC = () => {
@@ -83,10 +83,10 @@ const Index: FC = () => {
validator: (value: string) => {
const val = Number(value);
if (Number.isNaN(val)) {
- return 'the input should be number';
+ return t('msg.should_be_number');
}
if (val < 1) {
- return 'number should be equal or larger than 1';
+ return t('msg.number_larger_1');
}
return true;
},
@@ -148,6 +148,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx
index 716120d3..4a3c669a 100644
--- a/ui/src/pages/Admin/Questions/index.tsx
+++ b/ui/src/pages/Admin/Questions/index.tsx
@@ -74,7 +74,7 @@ const Questions: FC = () => {
return (
<>
<h3 className="mb-4">{t('page_title')}</h3>
- <div className="d-flex justify-content-between align-items-center mb-3">
+ <div className="d-flex flex-wrap justify-content-between align-items-center mb-3">
<QueryGroup
data={questionFilterItems}
currentSort={curFilter}
@@ -89,12 +89,13 @@ const Questions: FC = () => {
placeholder={t('filter.placeholder')}
onChange={handleFilter}
style={{ width: '12.25rem' }}
+ className="mt-3 mt-sm-0"
/>
</div>
- <Table>
+ <Table responsive>
<thead>
<tr>
- <th>{t('post')}</th>
+ <th className="min-w-15">{t('post')}</th>
<th style={{ width: '8%' }}>{t('votes')}</th>
<th style={{ width: '8%' }}>{t('answers')}</th>
<th style={{ width: '15%' }}>{t('created')}</th>
diff --git a/ui/src/pages/Admin/Seo/index.tsx b/ui/src/pages/Admin/Seo/index.tsx
index 0424eae8..e539595b 100644
--- a/ui/src/pages/Admin/Seo/index.tsx
+++ b/ui/src/pages/Admin/Seo/index.tsx
@@ -24,7 +24,7 @@ import type * as Type from '@/common/interface';
import { getSeoSetting, putSeoSetting } from '@/services';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { seoSettingStore } from '@/stores';
const Index: FC = () => {
@@ -90,6 +90,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/SettingsUsers/index.tsx b/ui/src/pages/Admin/SettingsUsers/index.tsx
index a83c2e3b..e2714790 100644
--- a/ui/src/pages/Admin/SettingsUsers/index.tsx
+++ b/ui/src/pages/Admin/SettingsUsers/index.tsx
@@ -29,7 +29,7 @@ import {
putUsersSetting,
AdminSettingsUsers,
} from '@/services';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import * as Type from '@/common/interface';
import { siteInfoStore } from '@/stores';
@@ -168,6 +168,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Smtp/index.tsx b/ui/src/pages/Admin/Smtp/index.tsx
index 3103461f..40725c31 100644
--- a/ui/src/pages/Admin/Smtp/index.tsx
+++ b/ui/src/pages/Admin/Smtp/index.tsx
@@ -25,7 +25,7 @@ import { useToast } from '@/hooks';
import { useSmtpSetting, updateSmtpSetting } from '@/services';
import pattern from '@/common/pattern';
import { SchemaForm, JSONSchema, UISchema, initFormData } from '@/components';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
const Smtp: FC = () => {
const { t } = useTranslation('translation', {
@@ -182,6 +182,8 @@ const Smtp: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Themes/index.tsx b/ui/src/pages/Admin/Themes/index.tsx
index 346e2e05..0d219816 100644
--- a/ui/src/pages/Admin/Themes/index.tsx
+++ b/ui/src/pages/Admin/Themes/index.tsx
@@ -24,7 +24,7 @@ import type * as Type from '@/common/interface';
import { getThemeSetting, putThemeSetting } from '@/services';
import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components';
import { useToast } from '@/hooks';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { themeSettingStore } from '@/stores';
import { setupAppTheme } from '@/utils/localize';
@@ -135,6 +135,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx
index c119e864..c8f4fab4 100644
--- a/ui/src/pages/Admin/Users/index.tsx
+++ b/ui/src/pages/Admin/Users/index.tsx
@@ -165,7 +165,7 @@ const Users: FC = () => {
return (
<>
<h3 className="mb-4">{t('title')}</h3>
- <div className="d-flex justify-content-between align-items-center mb-3">
+ <div className="d-flex flex-wrap justify-content-between align-items-center mb-3">
<Stack direction="horizontal" gap={3}>
<QueryGroup
data={UserFilterKeys}
@@ -190,14 +190,17 @@ const Users: FC = () => {
onChange={handleFilter}
placeholder={t('filter.placeholder')}
style={{ width: '12.25rem' }}
+ className="mt-3 mt-sm-0"
/>
</div>
- <Table>
+ <Table responsive>
<thead>
<tr>
<th>{t('name')}</th>
<th style={{ width: '12%' }}>{t('reputation')}</th>
- <th style={{ width: '20%' }}>{t('email')}</th>
+ <th style={{ width: '20%' }} className="min-w-15">
+ {t('email')}
+ </th>
<th className="text-nowrap" style={{ width: '15%' }}>
{t('created_at')}
</th>
diff --git a/ui/src/pages/Admin/Write/index.tsx b/ui/src/pages/Admin/Write/index.tsx
index 7c837435..42806111 100644
--- a/ui/src/pages/Admin/Write/index.tsx
+++ b/ui/src/pages/Admin/Write/index.tsx
@@ -27,7 +27,7 @@ import {
getRequireAndReservedTag,
postRequireAndReservedTag,
} from '@/services';
-import { handleFormError } from '@/utils';
+import { handleFormError, scrollToElementTop } from '@/utils';
import { writeSettingStore } from '@/stores';
const Index: FC = () => {
@@ -121,6 +121,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
}
});
};
diff --git a/ui/src/pages/Admin/index.scss b/ui/src/pages/Admin/index.scss
index c723b8ac..27a9af01 100644
--- a/ui/src/pages/Admin/index.scss
+++ b/ui/src/pages/Admin/index.scss
@@ -21,3 +21,21 @@
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
+
+.min-w-15 {
+ min-width: 15rem;
+}
+
+.max-w-30 {
+ max-width: 30rem;
+}
+
+@media screen and (max-width: 768px) {
+ .max-w-30 {
+ max-width: 15rem;
+ }
+}
+
+.table tr th {
+ white-space: nowrap;
+}
diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx
index 69723d29..14a2b30b 100644
--- a/ui/src/pages/Install/index.tsx
+++ b/ui/src/pages/Install/index.tsx
@@ -29,7 +29,12 @@ import {
installBaseInfo,
checkConfigFileExists,
} from '@/services';
-import { Storage, handleFormError, scrollToDocTop } from '@/utils';
+import {
+ Storage,
+ handleFormError,
+ scrollToDocTop,
+ scrollToElementTop,
+} from '@/utils';
import { CURRENT_LANG_STORAGE_KEY } from '@/common/constants';
import {
@@ -201,6 +206,8 @@ const Index: FC = () => {
if (err.isError) {
const data = handleFormError(err, formData);
setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
} else {
handleErr(err);
}
diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx
index f0a061bd..cf7a2f1b 100644
--- a/ui/src/pages/Layout/index.tsx
+++ b/ui/src/pages/Layout/index.tsx
@@ -75,7 +75,7 @@ const Layout: FC = () => {
}}>
<Header />
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
- <div className="position-relative page-wrap">
+ <div className="position-relative page-wrap d-flex flex-column flex-fill">
{httpStatusCode ? (
<HttpErrorContent httpCode={httpStatusCode} />
) : (
diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx
index 9a1ff14a..30939a2e 100644
--- a/ui/src/pages/Questions/Ask/index.tsx
+++ b/ui/src/pages/Questions/Ask/index.tsx
@@ -27,7 +27,7 @@ import classNames from 'classnames';
import isEqual from 'lodash/isEqual';
import debounce from 'lodash/debounce';
-import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
+import { usePageTags, usePromptWithUnload } from '@/hooks';
import { Editor, EditorRef, TagSelector } from '@/components';
import type * as Type from '@/common/interface';
import { DRAFT_QUESTION_STORAGE_KEY } from '@/common/constants';
@@ -40,8 +40,14 @@ import {
getTagsBySlugName,
saveQuestionWithAnswer,
} from '@/services';
-import { handleFormError, SaveDraft, storageExpires } from '@/utils';
+import {
+ handleFormError,
+ SaveDraft,
+ storageExpires,
+ scrollToElementTop,
+} from '@/utils';
import { pathFactory } from '@/router/pathFactory';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import SearchQuestion from './components/SearchQuestion';
@@ -87,7 +93,7 @@ const Ask = () => {
const [formData, setFormData] = useState<FormDataItem>(initFormData);
const [immData, setImmData] = useState<FormDataItem>(initFormData);
const [checked, setCheckState] = useState(false);
- const contentChangedRef = useRef(false);
+ const [blockState, setBlockState] = useState(false);
const [focusType, setForceType] = useState('');
const [hasDraft, setHasDraft] = useState(false);
const resetForm = () => {
@@ -120,8 +126,8 @@ const Ask = () => {
const isEdit = qid !== undefined;
- const saveCaptcha = useCaptchaModal('question');
- const editCaptcha = useCaptchaModal('edit');
+ const saveCaptcha = useCaptchaPlugin('question');
+ const editCaptcha = useCaptchaPlugin('edit');
const removeDraft = () => {
saveDraft.save.cancel();
@@ -165,9 +171,9 @@ const Ask = () => {
tags.value.map((v) => v.slug_name),
)
) {
- contentChangedRef.current = true;
+ setBlockState(true);
} else {
- contentChangedRef.current = false;
+ setBlockState(false);
}
return;
}
@@ -188,15 +194,15 @@ const Ask = () => {
},
callback: () => setHasDraft(true),
});
- contentChangedRef.current = true;
+ setBlockState(true);
} else {
removeDraft();
- contentChangedRef.current = false;
+ setBlockState(false);
}
}, [formData]);
usePromptWithUnload({
- when: contentChangedRef.current,
+ when: blockState,
});
const { data: revisions = [] } = useQueryRevisions(qid);
@@ -276,6 +282,83 @@ const Ask = () => {
}
};
+ const submitModifyQuestion = (params) => {
+ const ep = {
+ ...params,
+ id: qid,
+ edit_summary: formData.edit_summary.value,
+ };
+ const imgCode = editCaptcha?.getCaptcha();
+ if (imgCode?.verify) {
+ ep.captcha_code = imgCode.captcha_code;
+ ep.captcha_id = imgCode.captcha_id;
+ }
+ modifyQuestion(ep)
+ .then(async (res) => {
+ await editCaptcha?.close();
+ navigate(pathFactory.questionLanding(qid, res?.url_title), {
+ state: { isReview: res?.wait_for_review },
+ });
+ })
+ .catch((err) => {
+ if (err.isError) {
+ editCaptcha?.handleCaptchaError(err.list);
+ const data = handleFormError(err, formData);
+ setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
+ }
+ });
+ };
+
+ const submitQuestion = async (params) => {
+ const imgCode = saveCaptcha?.getCaptcha();
+ if (imgCode?.verify) {
+ params.captcha_code = imgCode.captcha_code;
+ params.captcha_id = imgCode.captcha_id;
+ }
+ let res;
+ if (checked) {
+ res = await saveQuestionWithAnswer({
+ ...params,
+ answer_content: formData.answer_content.value,
+ }).catch((err) => {
+ if (err.isError) {
+ const captchaErr = saveCaptcha?.handleCaptchaError(err.list);
+ if (!(captchaErr && err.list.length === 1)) {
+ const data = handleFormError(err, formData);
+ setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
+ }
+ }
+ });
+ } else {
+ res = await saveQuestion(params).catch((err) => {
+ if (err.isError) {
+ const captchaErr = saveCaptcha?.handleCaptchaError(err.list);
+ if (!(captchaErr && err.list.length === 1)) {
+ const data = handleFormError(err, formData);
+ setFormData({ ...data });
+ const ele = document.getElementById(err.list[0].error_field);
+ scrollToElementTop(ele);
+ }
+ }
+ });
+ }
+
+ const id = res?.id || res?.question?.id;
+ if (id) {
+ await saveCaptcha?.close();
+ if (checked) {
+ navigate(pathFactory.questionLanding(id, res?.question?.url_title));
+ } else {
+ navigate(pathFactory.questionLanding(id, res?.url_title));
+ }
+ }
+ removeDraft();
+ };
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();
@@ -287,77 +370,22 @@ const Ask = () => {
};
if (isEdit) {
+ if (!editCaptcha) {
+ setBlockState(false);
+ submitModifyQuestion(params);
+ return;
+ }
editCaptcha.check(() => {
- contentChangedRef.current = false;
- const ep = {
- ...params,
- id: qid,
- edit_summary: formData.edit_summary.value,
- };
- const imgCode = editCaptcha.getCaptcha();
- if (imgCode.verify) {
- ep.captcha_code = imgCode.captcha_code;
- ep.captcha_id = imgCode.captcha_id;
- }
- modifyQuestion(ep)
- .then(async (res) => {
- await editCaptcha.close();
- navigate(pathFactory.questionLanding(qid, res?.url_title), {
- state: { isReview: res?.wait_for_review },
- });
- })
- .catch((err) => {
- if (err.isError) {
- editCaptcha.handleCaptchaError(err.list);
- const data = handleFormError(err, formData);
- setFormData({ ...data });
- }
- });
+ submitModifyQuestion(params);
});
} else {
- saveCaptcha.check(async () => {
- contentChangedRef.current = false;
- const imgCode = saveCaptcha.getCaptcha();
- if (imgCode.verify) {
- params.captcha_code = imgCode.captcha_code;
- params.captcha_id = imgCode.captcha_id;
- }
- let res;
- if (checked) {
- res = await saveQuestionWithAnswer({
- ...params,
- answer_content: formData.answer_content.value,
- }).catch((err) => {
- if (err.isError) {
- const captchaErr = saveCaptcha.handleCaptchaError(err.list);
- if (!(captchaErr && err.list.length === 1)) {
- const data = handleFormError(err, formData);
- setFormData({ ...data });
- }
- }
- });
- } else {
- res = await saveQuestion(params).catch((err) => {
- if (err.isError) {
- const captchaErr = saveCaptcha.handleCaptchaError(err.list);
- if (!(captchaErr && err.list.length === 1)) {
- const data = handleFormError(err, formData);
- setFormData({ ...data });
- }
- }
- });
- }
-
- const id = res?.id || res?.question?.id;
- if (id) {
- await saveCaptcha.close();
- if (checked) {
- navigate(pathFactory.questionLanding(id, res?.question?.url_title));
- } else {
- navigate(pathFactory.questionLanding(id, res?.url_title));
- }
- }
- removeDraft();
+ if (!saveCaptcha) {
+ setBlockState(false);
+ submitQuestion(params);
+ return;
+ }
+ saveCaptcha?.check(async () => {
+ submitQuestion(params);
});
}
};
@@ -426,7 +454,7 @@ const Ask = () => {
</Form.Control.Feedback>
{bool && <SearchQuestion similarQuestions={similarQuestions} />}
</Form.Group>
- <Form.Group controlId="body">
+ <Form.Group controlId="content">
<Form.Label>{t('form.fields.body.label')}</Form.Label>
<Form.Control
defaultValue={formData.content.value}
diff --git a/ui/src/pages/Questions/Detail/components/InviteToAnswer/index.tsx b/ui/src/pages/Questions/Detail/components/InviteToAnswer/index.tsx
index 82f9fa27..b8e29942 100644
--- a/ui/src/pages/Questions/Detail/components/InviteToAnswer/index.tsx
+++ b/ui/src/pages/Questions/Detail/components/InviteToAnswer/index.tsx
@@ -27,7 +27,7 @@ import classNames from 'classnames';
import { Avatar } from '@/components';
import { getInviteUser, putInviteUser } from '@/services';
import type * as Type from '@/common/interface';
-import { useCaptchaModal } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import PeopleDropdown from './PeopleDropdown';
@@ -44,7 +44,7 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
const [editing, setEditing] = useState(false);
const [users, setUsers] = useState<Type.UserInfoBase[]>([]);
- const iaCaptcha = useCaptchaModal('invitation_answer');
+ const iaCaptcha = useCaptchaPlugin('invitation_answer');
const initInviteUsers = () => {
if (!questionId) {
@@ -74,28 +74,35 @@ const Index: FC<Props> = ({ questionId, readOnly = false }) => {
setUsers(userList);
};
+ const submitInviteUser = () => {
+ const names = users.map((_) => {
+ return _.username;
+ });
+ const imgCode: Type.ImgCodeReq = {};
+ iaCaptcha?.resolveCaptchaReq(imgCode);
+ putInviteUser(questionId, names, imgCode)
+ .then(async () => {
+ await iaCaptcha?.close();
+ setEditing(false);
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ iaCaptcha?.handleCaptchaError(ex.list);
+ }
+ });
+ };
+
const saveInviteUsers = () => {
if (!users) {
return;
}
- const names = users.map((_) => {
- return _.username;
- });
- iaCaptcha.check(() => {
- const imgCode: Type.ImgCodeReq = {};
- iaCaptcha.resolveCaptchaReq(imgCode);
- putInviteUser(questionId, names, imgCode)
- .then(async () => {
- await iaCaptcha.close();
- setEditing(false);
- })
- .catch((ex) => {
- if (ex.isError) {
- iaCaptcha.handleCaptchaError(ex.list);
- }
- console.error('putInviteUser error: ', ex);
- });
- });
+
+ if (!iaCaptcha) {
+ submitInviteUser();
+ return;
+ }
+
+ iaCaptcha.check(() => submitInviteUser());
};
useEffect(() => {
diff --git a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx
index 9bc8be44..456fbe37 100644
--- a/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx
+++ b/ui/src/pages/Questions/Detail/components/WriteAnswer/index.tsx
@@ -25,7 +25,8 @@ import { Link } from 'react-router-dom';
import { marked } from 'marked';
import classNames from 'classnames';
-import { usePromptWithUnload, useCaptchaModal } from '@/hooks';
+import { usePromptWithUnload } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import { Editor, Modal, TextArea } from '@/components';
import { FormDataType, PostAnswerReq } from '@/common/interface';
import { postAnswer } from '@/services';
@@ -63,8 +64,9 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
const [editorFocusState, setEditorFocusState] = useState(false);
const [hasDraft, setHasDraft] = useState(false);
const [showTips, setShowTips] = useState(data.loggedUserRank < 100);
- const aCaptcha = useCaptchaModal('answer');
+ const aCaptcha = useCaptchaPlugin('answer');
const writeInfo = writeSettingStore((state) => state.write);
+ const [editorCanSave, setEditorCanSave] = useState(false);
usePromptWithUnload({
when: Boolean(formData.content.value),
@@ -80,6 +82,7 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
useEffect(() => {
const draft = storageExpires.get(DRAFT_ANSWER_STORAGE_KEY);
if (draft?.questionId === data.qid && draft?.content) {
+ setShowEditor(true);
setFormData({
content: {
value: draft.content,
@@ -87,9 +90,11 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
errorMsg: '',
},
});
- setShowEditor(true);
setHasDraft(true);
}
+ setTimeout(() => {
+ setEditorCanSave(true);
+ }, 100);
}, []);
useEffect(() => {
@@ -152,6 +157,40 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
}
};
+ const submitAnswer = () => {
+ const params: PostAnswerReq = {
+ question_id: data?.qid,
+ content: formData.content.value,
+ html: marked.parse(formData.content.value),
+ };
+ const imgCode = aCaptcha?.getCaptcha();
+ if (imgCode?.verify) {
+ params.captcha_code = imgCode.captcha_code;
+ params.captcha_id = imgCode.captcha_id;
+ }
+ postAnswer(params)
+ .then(async (res) => {
+ await aCaptcha?.close();
+ setShowEditor(false);
+ setFormData({
+ content: {
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ },
+ });
+ removeDraft();
+ callback?.(res.info);
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ aCaptcha?.handleCaptchaError(ex.list);
+ const stateData = handleFormError(ex, formData);
+ setFormData({ ...stateData });
+ }
+ });
+ };
+
const handleSubmit = () => {
if (!guard.tryNormalLogged(true)) {
return;
@@ -159,40 +198,11 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
if (!checkValidated()) {
return;
}
-
- aCaptcha.check(() => {
- const params: PostAnswerReq = {
- question_id: data?.qid,
- content: formData.content.value,
- html: marked.parse(formData.content.value),
- };
- const imgCode = aCaptcha.getCaptcha();
- if (imgCode.verify) {
- params.captcha_code = imgCode.captcha_code;
- params.captcha_id = imgCode.captcha_id;
- }
- postAnswer(params)
- .then(async (res) => {
- await aCaptcha.close();
- setShowEditor(false);
- setFormData({
- content: {
- value: '',
- isInvalid: false,
- errorMsg: '',
- },
- });
- removeDraft();
- callback?.(res.info);
- })
- .catch((ex) => {
- if (ex.isError) {
- aCaptcha.handleCaptchaError(ex.list);
- const stateData = handleFormError(ex, formData);
- setFormData({ ...stateData });
- }
- });
- });
+ if (!aCaptcha) {
+ submitAnswer();
+ return;
+ }
+ aCaptcha.check(() => submitAnswer());
};
const clickBtn = () => {
@@ -260,13 +270,15 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
value={formData.content.value}
autoFocus={editorFocusState}
onChange={(val) => {
- setFormData({
- content: {
- value: val,
- isInvalid: false,
- errorMsg: '',
- },
- });
+ if (editorCanSave) {
+ setFormData({
+ content: {
+ value: val,
+ isInvalid: false,
+ errorMsg: '',
+ },
+ });
+ }
}}
onFocus={() => {
setFocusType('answer');
diff --git a/ui/src/pages/Questions/EditAnswer/index.tsx b/ui/src/pages/Questions/EditAnswer/index.tsx
index fbb3e327..49e7b33a 100644
--- a/ui/src/pages/Questions/EditAnswer/index.tsx
+++ b/ui/src/pages/Questions/EditAnswer/index.tsx
@@ -26,7 +26,8 @@ import dayjs from 'dayjs';
import classNames from 'classnames';
import { handleFormError, scrollToDocTop } from '@/utils';
-import { usePageTags, usePromptWithUnload, useCaptchaModal } from '@/hooks';
+import { usePageTags, usePromptWithUnload } from '@/hooks';
+import { useCaptchaPlugin, useRenderHtmlPlugin } from '@/utils/pluginKit';
import { pathFactory } from '@/router/pathFactory';
import { Editor, EditorRef, Icon, htmlRender } from '@/components';
import type * as Type from '@/common/interface';
@@ -35,7 +36,6 @@ import {
modifyAnswer,
useQueryRevisions,
} from '@/services';
-import { useRenderHtmlPlugin } from '@/utils/pluginKit';
import './index.scss';
@@ -71,7 +71,7 @@ const Index = () => {
const [formData, setFormData] = useState<FormDataItem>(initFormData);
const [immData, setImmData] = useState(initFormData);
const [contentChanged, setContentChanged] = useState(false);
- const editCaptcha = useCaptchaModal('edit');
+ const editCaptcha = useCaptchaPlugin('edit');
useEffect(() => {
if (data?.info?.content) {
@@ -153,6 +153,39 @@ const Index = () => {
return bol;
};
+ const submitEditAnswer = () => {
+ const params: Type.AnswerParams = {
+ content: formData.content.value,
+ html: editorRef.current.getHtml(),
+ question_id: qid,
+ id: aid,
+ edit_summary: formData.description.value,
+ };
+ editCaptcha?.resolveCaptchaReq(params);
+
+ modifyAnswer(params)
+ .then(async (res) => {
+ await editCaptcha?.close();
+ navigate(
+ pathFactory.answerLanding({
+ questionId: qid,
+ slugTitle: data?.question?.url_title,
+ answerId: aid,
+ }),
+ {
+ state: { isReview: res?.wait_for_review },
+ },
+ );
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ editCaptcha?.handleCaptchaError(ex.list);
+ const stateData = handleFormError(ex, formData);
+ setFormData({ ...stateData });
+ }
+ });
+ };
+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
setContentChanged(false);
@@ -163,38 +196,11 @@ const Index = () => {
return;
}
- editCaptcha.check(() => {
- const params: Type.AnswerParams = {
- content: formData.content.value,
- html: editorRef.current.getHtml(),
- question_id: qid,
- id: aid,
- edit_summary: formData.description.value,
- };
- editCaptcha.resolveCaptchaReq(params);
-
- modifyAnswer(params)
- .then(async (res) => {
- await editCaptcha.close();
- navigate(
- pathFactory.answerLanding({
- questionId: qid,
- slugTitle: data?.question?.url_title,
- answerId: aid,
- }),
- {
- state: { isReview: res?.wait_for_review },
- },
- );
- })
- .catch((ex) => {
- if (ex.isError) {
- editCaptcha.handleCaptchaError(ex.list);
- const stateData = handleFormError(ex, formData);
- setFormData({ ...stateData });
- }
- });
- });
+ if (!editCaptcha) {
+ submitEditAnswer();
+ return;
+ }
+ editCaptcha.check(() => submitEditAnswer());
};
const handleSelectedRevision = (e) => {
const index = e.target.value;
diff --git a/ui/src/pages/Questions/index.tsx b/ui/src/pages/Questions/index.tsx
index e0bf27ff..f1b26c18 100644
--- a/ui/src/pages/Questions/index.tsx
+++ b/ui/src/pages/Questions/index.tsx
@@ -53,7 +53,7 @@ const Questions: FC = () => {
Storage.set(QUESTIONS_ORDER_STORAGE_KEY, curOrder);
}
const reqParams: Type.QueryQuestionsReq = {
- page_size: 20,
+ page_size: 10,
page: curPage,
order: curOrder as Type.QuestionOrderBy,
};
diff --git a/ui/src/pages/Review/components/ApproveDropdown/index.tsx b/ui/src/pages/Review/components/ApproveDropdown/index.tsx
index f6a62471..b2c05c96 100644
--- a/ui/src/pages/Review/components/ApproveDropdown/index.tsx
+++ b/ui/src/pages/Review/components/ApproveDropdown/index.tsx
@@ -23,7 +23,8 @@ import { useTranslation } from 'react-i18next';
import { Modal } from '@/components';
import { putFlagReviewAction } from '@/services';
-import { useCaptchaModal, useReportModal, useToast } from '@/hooks';
+import { useReportModal, useToast } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import type * as Type from '@/common/interface';
import EditPostModal from '../EditPostModal';
@@ -46,12 +47,52 @@ const Index: FC<IProps> = ({
const [showEditPostModal, setShowEditPostModal] = useState(false);
const closeModal = useReportModal(approveCallback);
const toast = useToast();
- const dCaptcha = useCaptchaModal('delete');
+ const dCaptcha = useCaptchaPlugin('delete');
const handleEditPostModalState = () => {
setShowEditPostModal(!showEditPostModal);
};
+ const submitReviewAction = () => {
+ const req: Type.PutFlagReviewParams = {
+ operation_type: 'delete_post',
+ flag_id: String(itemData?.flag_id),
+ captcha_code: undefined,
+ captcha_id: undefined,
+ };
+ dCaptcha?.resolveCaptchaReq(req);
+
+ delete req.captcha_code;
+ delete req.captcha_id;
+
+ putFlagReviewAction(req)
+ .then(async () => {
+ await dCaptcha?.close();
+ let msg = '';
+ if (objectType === 'question') {
+ msg = t('post_deleted', { keyPrefix: 'messages' });
+ }
+ if (objectType === 'answer') {
+ msg = t('tip_answer_deleted');
+ }
+ if (objectType === 'answer' || objectType === 'question') {
+ toast.onShow({
+ msg,
+ variant: 'success',
+ });
+ }
+ approveCallback();
+ })
+ .catch((ex) => {
+ if (ex.isError) {
+ dCaptcha?.handleCaptchaError(ex.list);
+ }
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ };
+
const handleDelete = () => {
let content = '';
@@ -78,45 +119,11 @@ const Index: FC<IProps> = ({
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => {
- dCaptcha.check(() => {
- const req: Type.PutFlagReviewParams = {
- operation_type: 'delete_post',
- flag_id: String(itemData?.flag_id),
- captcha_code: undefined,
- captcha_id: undefined,
- };
- dCaptcha.resolveCaptchaReq(req);
-
- delete req.captcha_code;
- delete req.captcha_id;
-
- putFlagReviewAction(req)
- .then(async () => {
- await dCaptcha.close();
- let msg = '';
- if (objectType === 'question') {
- msg = t('post_deleted', { keyPrefix: 'messages' });
- }
- if (objectType === 'answer') {
- msg = t('tip_answer_deleted');
- }
- if (objectType === 'answer' || objectType === 'question') {
- toast.onShow({
- msg,
- variant: 'success',
- });
- }
- approveCallback();
- })
- .catch((ex) => {
- if (ex.isError) {
- dCaptcha.handleCaptchaError(ex.list);
- }
- })
- .finally(() => {
- setIsLoading(false);
- });
- });
+ if (!dCaptcha) {
+ submitReviewAction();
+ return;
+ }
+ dCaptcha.check(() => submitReviewAction());
},
onCancel: () => {
setIsLoading(false);
diff --git a/ui/src/pages/Review/components/EditPostModal/index.tsx b/ui/src/pages/Review/components/EditPostModal/index.tsx
index 0eefd311..9e7ae5e9 100644
--- a/ui/src/pages/Review/components/EditPostModal/index.tsx
+++ b/ui/src/pages/Review/components/EditPostModal/index.tsx
@@ -24,13 +24,15 @@ import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { putFlagReviewAction } from '@/services';
-import { useCaptchaModal, usePageUsers } from '@/hooks';
+import { usePageUsers } from '@/hooks';
+import { useCaptchaPlugin } from '@/utils/pluginKit';
import { Editor, TagSelector, Mentions, TextArea } from '@/components';
import {
// matchedUsers,
parseUserInfo,
handleFormError,
parseEditMentionUser,
+ scrollToElementTop,
} from '@/utils';
import type * as Type from '@/common/interface';
@@ -89,7 +91,7 @@ const Index: FC<Props> = ({
const [loaded, setLoaded] = useState(false);
const pageUsers = usePageUsers();
- const editCaptcha = useCaptchaModal('edit');
+ const editCaptcha = useCaptchaPlugin('edit');
const onClose = (bol) => {
if (bol) {
@@ -157,17 +159,19 @@ const Index: FC<Props> = ({
setFormData({
...formData,
});
- return bol;
- };
-
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
- event.preventDefault();
- event.stopPropagation();
- if (!checkValidated()) {
- return;
+ if (!bol) {
+ const errObj = Object.keys(formData).filter(
+ (key) => formData[key].isInvalid,
+ );
+ const ele = document.getElementById(errObj[0]);
+ scrollToElementTop(ele);
}
+ return bol;
+ };
+
+ const submitFlagReviewAction = () => {
const params: Type.PutFlagReviewParams = {
title: formData.title.value,
content: formData.content.value,
@@ -191,28 +195,44 @@ const Index: FC<Props> = ({
delete params.title;
delete params.tags;
}
+ if (objectType === 'question') {
+ const imgCode = editCaptcha?.getCaptcha();
+ if (imgCode?.verify) {
+ params.captcha_code = imgCode.captcha_code;
+ params.captcha_id = imgCode.captcha_id;
+ }
+ }
+ putFlagReviewAction(params)
+ .then(async () => {
+ await editCaptcha?.close();
+ onClose(true);
+ })
+ .catch((err) => {
+ if (err.isError) {
+ editCaptcha?.handleCaptchaError(err.list);
+ const data = handleFormError(err, formData);
+ setFormData({ ...data });
- editCaptcha.check(() => {
... 1315 lines suppressed ...