From 666ab706a66f7c39c8bb758c0abc2c08c15d4967 Mon Sep 17 00:00:00 2001
From: Dinesht04
Date: Thu, 13 Nov 2025 02:22:58 +0530
Subject: [PATCH 01/92] feat(ui): add min values for inputs and context-based
keyboards for inputs
---
ui/src/components/SchemaForm/components/Input.tsx | 15 +++++++++++++++
ui/src/components/SchemaForm/index.tsx | 3 +++
ui/src/components/SchemaForm/types.ts | 10 ++++++++++
ui/src/pages/Admin/Privileges/index.tsx | 2 ++
ui/src/pages/Admin/Write/index.tsx | 10 ++++++++++
5 files changed, 40 insertions(+)
diff --git a/ui/src/components/SchemaForm/components/Input.tsx b/ui/src/components/SchemaForm/components/Input.tsx
index 1a3fe319b..c0fd084ca 100644
--- a/ui/src/components/SchemaForm/components/Input.tsx
+++ b/ui/src/components/SchemaForm/components/Input.tsx
@@ -29,6 +29,17 @@ interface Props {
onChange?: (fd: Type.FormDataType) => void;
formData: Type.FormDataType;
readOnly: boolean;
+ minValue?: number;
+ inputMode?:
+ | 'text'
+ | 'search'
+ | 'none'
+ | 'tel'
+ | 'url'
+ | 'email'
+ | 'numeric'
+ | 'decimal'
+ | undefined;
}
const Index: FC = ({
type = 'text',
@@ -37,6 +48,8 @@ const Index: FC = ({
onChange,
formData,
readOnly = false,
+ minValue = 0,
+ inputMode = 'text',
}) => {
const fieldObject = formData[fieldName];
const handleChange = (evt: React.ChangeEvent) => {
@@ -60,6 +73,8 @@ const Index: FC = ({
placeholder={placeholder}
type={type}
value={fieldObject?.value || ''}
+ min={minValue}
+ inputMode={inputMode}
onChange={handleChange}
disabled={readOnly}
isInvalid={fieldObject?.isInvalid}
diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx
index 421e0b1e1..23b193b48 100644
--- a/ui/src/components/SchemaForm/index.tsx
+++ b/ui/src/components/SchemaForm/index.tsx
@@ -368,6 +368,9 @@ const SchemaForm: ForwardRefRenderFunction = (
{widget === 'input' ? (
{
}
return true;
},
+ inputType: 'number',
+ inputMode: 'numeric',
},
};
});
diff --git a/ui/src/pages/Admin/Write/index.tsx b/ui/src/pages/Admin/Write/index.tsx
index 86d44d6d9..fee2d28cc 100644
--- a/ui/src/pages/Admin/Write/index.tsx
+++ b/ui/src/pages/Admin/Write/index.tsx
@@ -261,6 +261,8 @@ const Index: FC = () => {
{t('min_tags.label')}
{
@@ -302,6 +304,8 @@ const Index: FC = () => {
{t('min_content.label')}
{
@@ -344,6 +348,8 @@ const Index: FC = () => {
{t('image_size.label')}
{
@@ -366,6 +372,8 @@ const Index: FC = () => {
{t('attachment_size.label')}
{
@@ -388,6 +396,8 @@ const Index: FC = () => {
{t('image_megapixels.label')}
{
From 157dbfd08a1474534fe3548ef02243bf421ba020 Mon Sep 17 00:00:00 2001
From: Dinesht04
Date: Fri, 14 Nov 2025 02:28:46 +0530
Subject: [PATCH 02/92] feat(ui): add types, logic and defaults for min, max
value of input component of form
---
ui/src/components/SchemaForm/components/Input.tsx | 9 ++++++---
ui/src/components/SchemaForm/index.tsx | 9 +++++++++
ui/src/components/SchemaForm/types.ts | 2 ++
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/ui/src/components/SchemaForm/components/Input.tsx b/ui/src/components/SchemaForm/components/Input.tsx
index c0fd084ca..3a2c0397d 100644
--- a/ui/src/components/SchemaForm/components/Input.tsx
+++ b/ui/src/components/SchemaForm/components/Input.tsx
@@ -29,7 +29,8 @@ interface Props {
onChange?: (fd: Type.FormDataType) => void;
formData: Type.FormDataType;
readOnly: boolean;
- minValue?: number;
+ min?: number;
+ max?: number;
inputMode?:
| 'text'
| 'search'
@@ -48,7 +49,8 @@ const Index: FC = ({
onChange,
formData,
readOnly = false,
- minValue = 0,
+ min = 0,
+ max = 65355,
inputMode = 'text',
}) => {
const fieldObject = formData[fieldName];
@@ -73,7 +75,8 @@ const Index: FC = ({
placeholder={placeholder}
type={type}
value={fieldObject?.value || ''}
- min={minValue}
+ min={min}
+ max={max}
inputMode={inputMode}
onChange={handleChange}
disabled={readOnly}
diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx
index 23b193b48..5bde2fdc8 100644
--- a/ui/src/components/SchemaForm/index.tsx
+++ b/ui/src/components/SchemaForm/index.tsx
@@ -260,6 +260,8 @@ const SchemaForm: ForwardRefRenderFunction = (
enum: enumValues = [],
enumNames = [],
max_length = 0,
+ max = 65355,
+ min = 0,
} = properties[key];
const { 'ui:widget': widget = 'input', 'ui:options': uiOpt } =
uiSchema?.[key] || {};
@@ -374,6 +376,8 @@ const SchemaForm: ForwardRefRenderFunction = (
placeholder={
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
}
+ min={min}
+ max={max}
fieldName={key}
onChange={onChange}
formData={formData}
@@ -408,9 +412,14 @@ const SchemaForm: ForwardRefRenderFunction = (
type={
uiOpt && 'inputType' in uiOpt ? uiOpt.inputType : 'text'
}
+ inputMode={
+ uiOpt && 'inputMode' in uiOpt ? uiOpt.inputMode : 'text'
+ }
placeholder={
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
}
+ min={min}
+ max={max}
fieldName={key}
onChange={onChange}
formData={formData}
diff --git a/ui/src/components/SchemaForm/types.ts b/ui/src/components/SchemaForm/types.ts
index eaedf902a..55cf8e99d 100644
--- a/ui/src/components/SchemaForm/types.ts
+++ b/ui/src/components/SchemaForm/types.ts
@@ -49,6 +49,8 @@ export interface JSONSchema {
description?: string;
enum?: Array;
enumNames?: string[];
+ min?: number;
+ max?: number;
default?: string | boolean | number | any[];
max_length?: number;
};
From 54e602c5e6ad9a8c49187eadc4d480fbd7892a97 Mon Sep 17 00:00:00 2001
From: Dinesht04
Date: Wed, 19 Nov 2025 15:28:58 +0530
Subject: [PATCH 03/92] fix(ui): refactor number input props (min,max)
---
ui/src/components/SchemaForm/components/Input.tsx | 9 ++++++---
ui/src/components/SchemaForm/index.tsx | 2 +-
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/ui/src/components/SchemaForm/components/Input.tsx b/ui/src/components/SchemaForm/components/Input.tsx
index 3a2c0397d..d84d7d55a 100644
--- a/ui/src/components/SchemaForm/components/Input.tsx
+++ b/ui/src/components/SchemaForm/components/Input.tsx
@@ -50,10 +50,14 @@ const Index: FC = ({
formData,
readOnly = false,
min = 0,
- max = 65355,
+ max,
inputMode = 'text',
}) => {
const fieldObject = formData[fieldName];
+ const numberInputProps =
+ type === 'number'
+ ? { min, ...(max != null && max > 0 ? { max } : {}) }
+ : {};
const handleChange = (evt: React.ChangeEvent) => {
const { name, value } = evt.currentTarget;
const state = {
@@ -75,8 +79,7 @@ const Index: FC = ({
placeholder={placeholder}
type={type}
value={fieldObject?.value || ''}
- min={min}
- max={max}
+ {...numberInputProps}
inputMode={inputMode}
onChange={handleChange}
disabled={readOnly}
diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx
index 5bde2fdc8..171b5f675 100644
--- a/ui/src/components/SchemaForm/index.tsx
+++ b/ui/src/components/SchemaForm/index.tsx
@@ -260,7 +260,7 @@ const SchemaForm: ForwardRefRenderFunction = (
enum: enumValues = [],
enumNames = [],
max_length = 0,
- max = 65355,
+ max,
min = 0,
} = properties[key];
const { 'ui:widget': widget = 'input', 'ui:options': uiOpt } =
From 9a7eb0f450d31042c8badf080354754a8704e429 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Thu, 20 Nov 2025 17:42:35 +0800
Subject: [PATCH 04/92] fix(service): set default language to "en_US" if
invalid language is provided
---
internal/service/siteinfo/siteinfo_service.go | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go
index a0f4891c4..92b3e0c71 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -40,7 +40,6 @@ import (
tagcommon "github.com/apache/answer/internal/service/tag_common"
"github.com/apache/answer/plugin"
"github.com/jinzhu/copier"
- "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
)
@@ -159,10 +158,9 @@ func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGe
}
func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) {
- // check language
+ // If the language is invalid, set it to the default language "en_US"
if !translator.CheckLanguageIsValid(req.Language) {
- err = errors.BadRequest(reason.LangNotFound)
- return
+ req.Language = "en_US"
}
content, _ := json.Marshal(req)
From 09b6b34b0540b1aa6f344b20df8507f56e6fb406 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Thu, 20 Nov 2025 17:50:57 +0800
Subject: [PATCH 05/92] fix(comment): decode CommentID using DeShortID for
consistency
---
internal/controller/comment_controller.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go
index 5b807d935..65fbedf04 100644
--- a/internal/controller/comment_controller.go
+++ b/internal/controller/comment_controller.go
@@ -246,6 +246,7 @@ func (cc *CommentController) GetCommentWithPage(ctx *gin.Context) {
return
}
req.ObjectID = uid.DeShortID(req.ObjectID)
+ req.CommentID = uid.DeShortID(req.CommentID)
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.CommentEdit,
From a15dd41550c282a4de44259c69a57dd52eeb0e9c Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Sat, 22 Nov 2025 12:43:55 +0100
Subject: [PATCH 06/92] refactor(internal): compile regex once while clearing
text
Regex and Replacer can be reused.
No need to recreate for each invocation.
Signed-off-by: ferhat elmas
---
pkg/htmltext/htmltext.go | 48 ++++++++++++++++++----------------------
1 file changed, 22 insertions(+), 26 deletions(-)
diff --git a/pkg/htmltext/htmltext.go b/pkg/htmltext/htmltext.go
index 49049109d..0b84cda49 100644
--- a/pkg/htmltext/htmltext.go
+++ b/pkg/htmltext/htmltext.go
@@ -34,38 +34,34 @@ import (
"github.com/mozillazg/go-pinyin"
)
+var (
+ reCode = regexp.MustCompile(`(?ism)<(pre)>.*<\/pre>`)
+ reCodeReplace = "{code...}"
+ reLink = regexp.MustCompile(`(?ism)(.*)?<\/a>`)
+ reLinkReplace = " [$1] "
+ reSpace = regexp.MustCompile(` +`)
+ reSpaceReplace = " "
+
+ spaceReplacer = strings.NewReplacer(
+ "\n", " ",
+ "\r", " ",
+ "\t", " ",
+ )
+)
+
// ClearText clear HTML, get the clear text
-func ClearText(html string) (text string) {
- if len(html) == 0 {
- text = html
- return
+func ClearText(html string) string {
+ if html == "" {
+ return html
}
- var (
- re *regexp.Regexp
- codeReg = `(?ism)<(pre)>.*<\/pre>`
- codeRepl = "{code...}"
- linkReg = `(?ism)(.*)?<\/a>`
- linkRepl = " [$1] "
- spaceReg = ` +`
- spaceRepl = " "
- )
- re = regexp.MustCompile(codeReg)
- html = re.ReplaceAllString(html, codeRepl)
+ html = reCode.ReplaceAllString(html, reCodeReplace)
+ html = reLink.ReplaceAllString(html, reLinkReplace)
- re = regexp.MustCompile(linkReg)
- html = re.ReplaceAllString(html, linkRepl)
-
- text = strings.NewReplacer(
- "\n", " ",
- "\r", " ",
- "\t", " ",
- ).Replace(strip.StripTags(html))
+ text := spaceReplacer.Replace(strip.StripTags(html))
// replace multiple spaces to one space
- re = regexp.MustCompile(spaceReg)
- text = strings.TrimSpace(re.ReplaceAllString(text, spaceRepl))
- return
+ return strings.TrimSpace(reSpace.ReplaceAllString(text, reSpaceReplace))
}
func UrlTitle(title string) (text string) {
From ce053ccfa6620cc4c8263ba3b54dfcd988a1e6e9 Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Tue, 25 Nov 2025 00:34:24 +0100
Subject: [PATCH 07/92] fix: multi byte run boundary for cut long title
Signed-off-by: ferhat elmas
---
pkg/htmltext/htmltext.go | 12 +++++++++---
pkg/htmltext/htmltext_test.go | 22 ++++++++++++++++++++++
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/pkg/htmltext/htmltext.go b/pkg/htmltext/htmltext.go
index 0b84cda49..707c20a80 100644
--- a/pkg/htmltext/htmltext.go
+++ b/pkg/htmltext/htmltext.go
@@ -96,10 +96,16 @@ func convertChinese(content string) string {
}
func cutLongTitle(title string) string {
- if len(title) > 150 {
- return title[0:150]
+ maxBytes := 150
+ if len(title) <= maxBytes {
+ return title
}
- return title
+
+ truncated := title[:maxBytes]
+ for len(truncated) > 0 && !utf8.ValidString(truncated) {
+ truncated = truncated[:len(truncated)-1]
+ }
+ return truncated
}
// FetchExcerpt return the excerpt from the HTML string
diff --git a/pkg/htmltext/htmltext_test.go b/pkg/htmltext/htmltext_test.go
index d549d8874..63866eb28 100644
--- a/pkg/htmltext/htmltext_test.go
+++ b/pkg/htmltext/htmltext_test.go
@@ -21,6 +21,7 @@ package htmltext
import (
"fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -178,6 +179,27 @@ func TestFetchRangedExcerpt(t *testing.T) {
assert.Equal(t, expected, actual)
}
+func TestCutLongTitle(t *testing.T) {
+ // Short title, no cutting needed
+ short := "hello"
+ assert.Equal(t, short, cutLongTitle(short))
+
+ // Exactly max bytes, no cutting needed
+ exact150 := strings.Repeat("a", 150)
+ assert.Equal(t, 150, len(cutLongTitle(exact150)))
+
+ // Just over max bytes, should be cut
+ exact151 := strings.Repeat("a", 151)
+ assert.Equal(t, 150, len(cutLongTitle(exact151)))
+
+ // Multi-byte rune at boundary gets removed properly
+ asciiPart := strings.Repeat("a", 149) // 149 bytes
+ multiByteChar := "中" // 3 bytes - will span bytes 149-151
+ title := asciiPart + multiByteChar // 152 bytes total
+
+ assert.Equal(t, asciiPart, cutLongTitle(title))
+}
+
func TestFetchMatchedExcerpt(t *testing.T) {
var (
expected,
From 0777291e80c804cb836b5059d7e0fad5f2b7fe79 Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Wed, 26 Nov 2025 01:00:42 +0100
Subject: [PATCH 08/92] fix(ui): null pointer access if get branding fails
Signed-off-by: ferhat elmas
---
internal/router/ui.go | 6 ++--
internal/router/ui_test.go | 59 ++++++++++++++++++++++++++++++++++++++
2 files changed, 62 insertions(+), 3 deletions(-)
create mode 100644 internal/router/ui_test.go
diff --git a/internal/router/ui.go b/internal/router/ui.go
index 0b5ee3f96..14f71f5fc 100644
--- a/internal/router/ui.go
+++ b/internal/router/ui.go
@@ -22,7 +22,6 @@ package router
import (
"embed"
"fmt"
- "github.com/apache/answer/plugin"
"io/fs"
"net/http"
"os"
@@ -31,6 +30,7 @@ import (
"github.com/apache/answer/internal/controller"
"github.com/apache/answer/internal/service/siteinfo_common"
"github.com/apache/answer/pkg/htmltext"
+ "github.com/apache/answer/plugin"
"github.com/apache/answer/ui"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/log"
@@ -111,10 +111,10 @@ func (a *UIRouter) Register(r *gin.Engine, baseURLPath string) {
if err != nil {
log.Error(err)
}
- if branding.Favicon != "" {
+ if branding != nil && branding.Favicon != "" {
c.String(http.StatusOK, htmltext.GetPicByUrl(branding.Favicon))
return
- } else if branding.SquareIcon != "" {
+ } else if branding != nil && branding.SquareIcon != "" {
c.String(http.StatusOK, htmltext.GetPicByUrl(branding.SquareIcon))
return
} else {
diff --git a/internal/router/ui_test.go b/internal/router/ui_test.go
new file mode 100644
index 000000000..645529338
--- /dev/null
+++ b/internal/router/ui_test.go
@@ -0,0 +1,59 @@
+/*
+ * 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 router
+
+import (
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/apache/answer/internal/service/mock"
+ "github.com/gin-gonic/gin"
+ "github.com/stretchr/testify/assert"
+ "go.uber.org/mock/gomock"
+)
+
+func TestUIRouter_FaviconWithNilBranding(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+
+ mockSiteInfoService := mock.NewMockSiteInfoCommonService(ctrl)
+
+ // Simulate a database error
+ mockSiteInfoService.EXPECT().
+ GetSiteBranding(gomock.Any()).
+ Return(nil, errors.New("database connection failed"))
+
+ router := &UIRouter{
+ siteInfoService: mockSiteInfoService,
+ }
+
+ gin.SetMode(gin.TestMode)
+ r := gin.New()
+ router.Register(r, "")
+
+ req := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil)
+ w := httptest.NewRecorder()
+
+ r.ServeHTTP(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+}
From bc629db13266dfcd9560da38b39d71a615a1664f Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Thu, 27 Nov 2025 22:05:14 +0100
Subject: [PATCH 09/92] chore(deps): bump mockgen to 0.6.0 for go1.25 support
Signed-off-by: ferhat elmas
---
Makefile | 4 ++--
README.md | 2 +-
cmd/wire_gen.go | 8 ++++----
docs/docs.go | 32 +++++++++++++++++++++++---------
docs/swagger.json | 32 +++++++++++++++++++++++---------
docs/swagger.yaml | 28 +++++++++++++++++++---------
go.mod | 14 +++++++-------
go.sum | 36 ++++++++++++++++++------------------
8 files changed, 97 insertions(+), 59 deletions(-)
diff --git a/Makefile b/Makefile
index 6e9679a89..8ab23cce3 100644
--- a/Makefile
+++ b/Makefile
@@ -23,10 +23,10 @@ universal: generate
generate:
@$(GO) get github.com/swaggo/swag/cmd/swag@v1.16.3
@$(GO) get github.com/google/wire/cmd/wire@v0.5.0
- @$(GO) get go.uber.org/mock/mockgen@v0.5.0
+ @$(GO) get go.uber.org/mock/mockgen@v0.6.0
@$(GO) install github.com/swaggo/swag/cmd/swag@v1.16.3
@$(GO) install github.com/google/wire/cmd/wire@v0.5.0
- @$(GO) install go.uber.org/mock/mockgen@v0.5.0
+ @$(GO) install go.uber.org/mock/mockgen@v0.6.0
@$(GO) generate ./...
@$(GO) mod tidy
diff --git a/README.md b/README.md
index 8d201500e..362365496 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ You can also check out the [plugins here](https://answer.apache.org/plugins).
- Golang >= 1.23
- Node.js >= 20
- pnpm >= 9
-- [mockgen](https://github.com/uber-go/mock?tab=readme-ov-file#installation) >= 1.6.0
+- [mockgen](https://github.com/uber-go/mock?tab=readme-ov-file#installation) >= 0.6.0
- [wire](https://github.com/google/wire/) >= 0.5.0
### Build
diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index bda555bca..aae1c6af6 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -192,22 +192,22 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
notificationQueueService := notice_queue.NewNotificationQueueService()
externalNotificationQueueService := notice_queue.NewNewQuestionNotificationQueueService()
+ reviewRepo := review.NewReviewRepo(dataData)
+ reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalNotificationQueueService, tagCommonService, questionCommon, notificationQueueService, siteInfoCommonService, commentCommonRepo)
+ commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService, eventQueueService, reviewService)
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
limitRepo := limit.NewRateLimitRepo(dataData)
rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo)
+ commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware)
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService)
answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService, userExternalLoginRepo, siteInfoCommonService)
- reviewRepo := review.NewReviewRepo(dataData)
- reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalNotificationQueueService, tagCommonService, questionCommon, notificationQueueService, siteInfoCommonService, commentCommonRepo)
questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService, eventQueueService, reviewRepo)
answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService, eventQueueService)
- commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService, eventQueueService, reviewService)
- commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware)
reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService)
reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventQueueService)
reportController := controller.NewReportController(reportService, rankService, captchaService)
diff --git a/docs/docs.go b/docs/docs.go
index 263e6775e..c87313917 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -9780,8 +9780,6 @@ const docTemplate = `{
"schema.QuestionAdd": {
"type": "object",
"required": [
- "content",
- "tags",
"title"
],
"properties": {
@@ -9796,7 +9794,7 @@ const docTemplate = `{
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"tags": {
"description": "tags",
@@ -9817,8 +9815,6 @@ const docTemplate = `{
"type": "object",
"required": [
"answer_content",
- "content",
- "tags",
"title"
],
"properties": {
@@ -9838,7 +9834,7 @@ const docTemplate = `{
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"mention_username_list": {
"type": "array",
@@ -10119,9 +10115,7 @@ const docTemplate = `{
"schema.QuestionUpdate": {
"type": "object",
"required": [
- "content",
"id",
- "tags",
"title"
],
"properties": {
@@ -10136,7 +10130,7 @@ const docTemplate = `{
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"edit_summary": {
"description": "edit summary",
@@ -11047,6 +11041,16 @@ const docTemplate = `{
"max_image_size": {
"type": "integer"
},
+ "min_content": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 0
+ },
+ "min_tags": {
+ "type": "integer",
+ "maximum": 5,
+ "minimum": 0
+ },
"recommend_tags": {
"type": "array",
"items": {
@@ -11091,6 +11095,16 @@ const docTemplate = `{
"max_image_size": {
"type": "integer"
},
+ "min_content": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 0
+ },
+ "min_tags": {
+ "type": "integer",
+ "maximum": 5,
+ "minimum": 0
+ },
"recommend_tags": {
"type": "array",
"items": {
diff --git a/docs/swagger.json b/docs/swagger.json
index 8cc85e263..9f89cb157 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -9753,8 +9753,6 @@
"schema.QuestionAdd": {
"type": "object",
"required": [
- "content",
- "tags",
"title"
],
"properties": {
@@ -9769,7 +9767,7 @@
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"tags": {
"description": "tags",
@@ -9790,8 +9788,6 @@
"type": "object",
"required": [
"answer_content",
- "content",
- "tags",
"title"
],
"properties": {
@@ -9811,7 +9807,7 @@
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"mention_username_list": {
"type": "array",
@@ -10092,9 +10088,7 @@
"schema.QuestionUpdate": {
"type": "object",
"required": [
- "content",
"id",
- "tags",
"title"
],
"properties": {
@@ -10109,7 +10103,7 @@
"description": "content",
"type": "string",
"maxLength": 65535,
- "minLength": 6
+ "minLength": 0
},
"edit_summary": {
"description": "edit summary",
@@ -11020,6 +11014,16 @@
"max_image_size": {
"type": "integer"
},
+ "min_content": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 0
+ },
+ "min_tags": {
+ "type": "integer",
+ "maximum": 5,
+ "minimum": 0
+ },
"recommend_tags": {
"type": "array",
"items": {
@@ -11064,6 +11068,16 @@
"max_image_size": {
"type": "integer"
},
+ "min_content": {
+ "type": "integer",
+ "maximum": 65535,
+ "minimum": 0
+ },
+ "min_tags": {
+ "type": "integer",
+ "maximum": 5,
+ "minimum": 0
+ },
"recommend_tags": {
"type": "array",
"items": {
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index 9cdf248e1..d2dd076cf 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1589,7 +1589,7 @@ definitions:
content:
description: content
maxLength: 65535
- minLength: 6
+ minLength: 0
type: string
tags:
description: tags
@@ -1602,8 +1602,6 @@ definitions:
minLength: 6
type: string
required:
- - content
- - tags
- title
type: object
schema.QuestionAddByAnswer:
@@ -1620,7 +1618,7 @@ definitions:
content:
description: content
maxLength: 65535
- minLength: 6
+ minLength: 0
type: string
mention_username_list:
items:
@@ -1638,8 +1636,6 @@ definitions:
type: string
required:
- answer_content
- - content
- - tags
- title
type: object
schema.QuestionInfoResp:
@@ -1826,7 +1822,7 @@ definitions:
content:
description: content
maxLength: 65535
- minLength: 6
+ minLength: 0
type: string
edit_summary:
description: edit summary
@@ -1849,9 +1845,7 @@ definitions:
minLength: 6
type: string
required:
- - content
- id
- - tags
- title
type: object
schema.QuestionUpdateInviteUser:
@@ -2453,6 +2447,14 @@ definitions:
type: integer
max_image_size:
type: integer
+ min_content:
+ maximum: 65535
+ minimum: 0
+ type: integer
+ min_tags:
+ maximum: 5
+ minimum: 0
+ type: integer
recommend_tags:
items:
$ref: '#/definitions/schema.SiteWriteTag'
@@ -2482,6 +2484,14 @@ definitions:
type: integer
max_image_size:
type: integer
+ min_content:
+ maximum: 65535
+ minimum: 0
+ type: integer
+ min_tags:
+ maximum: 5
+ minimum: 0
+ type: integer
recommend_tags:
items:
$ref: '#/definitions/schema.SiteWriteTag'
diff --git a/go.mod b/go.mod
index 417688120..969d9ebbc 100644
--- a/go.mod
+++ b/go.mod
@@ -56,12 +56,12 @@ require (
github.com/swaggo/swag v1.16.3
github.com/tidwall/gjson v1.17.3
github.com/yuin/goldmark v1.7.4
- go.uber.org/mock v0.5.0
- golang.org/x/crypto v0.36.0
+ go.uber.org/mock v0.6.0
+ golang.org/x/crypto v0.41.0
golang.org/x/image v0.20.0
- golang.org/x/net v0.38.0
- golang.org/x/term v0.30.0
- golang.org/x/text v0.23.0
+ golang.org/x/net v0.43.0
+ golang.org/x/term v0.34.0
+ golang.org/x/text v0.28.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.33.0
@@ -161,8 +161,8 @@ require (
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
- golang.org/x/sys v0.31.0 // indirect
- golang.org/x/tools v0.25.0 // indirect
+ golang.org/x/sys v0.35.0 // indirect
+ golang.org/x/tools v0.36.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index b45d881fe..35db004db 100644
--- a/go.sum
+++ b/go.sum
@@ -654,8 +654,8 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
-go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
+go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
+go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
@@ -685,8 +685,8 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
@@ -703,8 +703,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -731,8 +731,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -743,8 +743,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -779,14 +779,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
+golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -795,8 +795,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -820,8 +820,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
-golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
+golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
+golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
From fc2a1d8afeb86602215d831c2767c2a1a56f30a2 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Fri, 28 Nov 2025 10:49:10 +0800
Subject: [PATCH 10/92] fix(answer): update QuestionID handling in answer
update process
---
internal/controller/answer_controller.go | 1 -
internal/schema/answer_schema.go | 1 -
internal/service/content/answer_service.go | 19 +++++++++----------
3 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go
index 7c5aca1db..0e43121c5 100644
--- a/internal/controller/answer_controller.go
+++ b/internal/controller/answer_controller.go
@@ -318,7 +318,6 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
handler.HandleResponse(ctx, err, nil)
return
}
- req.QuestionID = uid.DeShortID(req.QuestionID)
linkUrlLimitUser := canList[2]
isAdmin := middleware.GetUserIsAdminModerator(ctx)
if !isAdmin || !linkUrlLimitUser {
diff --git a/internal/schema/answer_schema.go b/internal/schema/answer_schema.go
index 015e26ac5..9ac4bcad7 100644
--- a/internal/schema/answer_schema.go
+++ b/internal/schema/answer_schema.go
@@ -78,7 +78,6 @@ type GetAnswerInfoResp struct {
type AnswerUpdateReq struct {
ID string `json:"id"`
- QuestionID string `json:"question_id"`
Title string `json:"title"`
Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"`
EditSummary string `validate:"omitempty" json:"edit_summary"`
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go
index b9d45b522..982bbf88a 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -346,24 +346,23 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", errors.BadRequest(reason.AnswerCannotUpdate)
}
- questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
+ answerInfo, exist, err := as.answerRepo.GetByID(ctx, req.ID)
if err != nil {
return "", err
}
if !exist {
- return "", errors.BadRequest(reason.QuestionNotFound)
+ return "", errors.BadRequest(reason.AnswerNotFound)
+ }
+ if answerInfo.Status == entity.AnswerStatusDeleted {
+ return "", errors.BadRequest(reason.AnswerCannotUpdate)
}
- answerInfo, exist, err := as.answerRepo.GetByID(ctx, req.ID)
+ questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, answerInfo.QuestionID)
if err != nil {
return "", err
}
if !exist {
- return "", errors.BadRequest(reason.AnswerNotFound)
- }
-
- if answerInfo.Status == entity.AnswerStatusDeleted {
- return "", errors.BadRequest(reason.AnswerCannotUpdate)
+ return "", errors.BadRequest(reason.QuestionNotFound)
}
//If the content is the same, ignore it
@@ -374,7 +373,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
insertData := &entity.Answer{}
insertData.ID = req.ID
insertData.UserID = answerInfo.UserID
- insertData.QuestionID = req.QuestionID
+ insertData.QuestionID = questionInfo.ID
insertData.OriginalText = req.Content
insertData.ParsedText = req.HTML
insertData.UpdatedAt = time.Now()
@@ -403,7 +402,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
if err = as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "updated_at", "last_edit_user_id"}); err != nil {
return "", err
}
- err = as.questionCommon.UpdatePostTime(ctx, req.QuestionID)
+ err = as.questionCommon.UpdatePostTime(ctx, questionInfo.ID)
if err != nil {
return insertData.ID, err
}
From f723d120d924dcf7e65b969ce18f8111cac40343 Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Fri, 28 Nov 2025 23:11:57 +0100
Subject: [PATCH 11/92] feat: add golangci-lint into lint target
* replace empty interface with any
* run fmt via golangci-lint
related to #1432
Signed-off-by: ferhat elmas
---
.gitignore | 2 +-
.golangci.yaml | 30 +++++++++++++++++++
Makefile | 17 +++++++++--
docs/docs.go | 7 ++---
docs/swagger.json | 7 ++---
docs/swagger.yaml | 6 ++--
internal/base/handler/handler.go | 6 ++--
internal/base/handler/response.go | 4 +--
internal/base/pager/pager.go | 2 +-
internal/base/pager/pagination.go | 6 ++--
internal/base/server/http_funcmap.go | 6 ++--
internal/base/translator/provider.go | 8 ++---
internal/base/validator/validator.go | 4 +--
internal/install/install_from_env.go | 2 +-
internal/migrations/init.go | 10 +++----
internal/migrations/init_data.go | 2 +-
internal/migrations/v25.go | 2 +-
internal/migrations/v6.go | 2 +-
internal/repo/meta/meta_repo.go | 2 +-
.../plugin_config/plugin_user_config_repo.go | 2 +-
internal/repo/revision/revision_repo.go | 2 +-
internal/repo/role/user_role_rel_repo.go | 2 +-
internal/repo/search_common/search_repo.go | 26 ++++++++--------
internal/repo/user/user_repo.go | 2 +-
internal/schema/question_schema.go | 28 ++++++++---------
internal/schema/revision_schema.go | 2 +-
internal/schema/siteinfo_schema.go | 14 ++++-----
internal/schema/user_schema.go | 2 +-
internal/service/question_common/question.go | 2 +-
internal/service/siteinfo/siteinfo_service.go | 2 +-
.../siteinfo_common/siteinfo_service.go | 4 +--
pkg/converter/str.go | 2 +-
32 files changed, 125 insertions(+), 90 deletions(-)
create mode 100644 .golangci.yaml
diff --git a/.gitignore b/.gitignore
index 1fc116a74..257ef31d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,7 +28,7 @@ vendor/
/answer-data/
/answer
/new_answer
-
+build/tools/
dist/
# Lint setup generated file
diff --git a/.golangci.yaml b/.golangci.yaml
new file mode 100644
index 000000000..af00efc3b
--- /dev/null
+++ b/.golangci.yaml
@@ -0,0 +1,30 @@
+# 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.
+
+version: "2"
+linters:
+ default: none
+
+formatters:
+ enable:
+ - gofmt
+ settings:
+ gofmt:
+ simplify: true
+ rewrite-rules:
+ - pattern: 'interface{}'
+ replacement: 'any'
diff --git a/Makefile b/Makefile
index 8ab23cce3..fd3f044fa 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,15 @@ Revision=$(shell git rev-parse --short HEAD 2>/dev/null || echo "")
GO_FLAGS=-ldflags="-X github.com/apache/answer/cmd.Version=$(VERSION) -X 'github.com/apache/answer/cmd.Revision=$(Revision)' -X 'github.com/apache/answer/cmd.Time=`date +%s`' -extldflags -static"
GO=$(GO_ENV) "$(shell which go)"
+GOLANGCI_VERSION ?= v2.6.2
+TOOLS_BIN := $(shell mkdir -p build/tools && realpath build/tools)
+
+GOLANGCI = $(TOOLS_BIN)/golangci-lint-$(GOLANGCI_VERSION)
+$(GOLANGCI):
+ rm -f $(TOOLS_BIN)/golangci-lint*
+ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_VERSION)/install.sh | sh -s -- -b $(TOOLS_BIN) $(GOLANGCI_VERSION)
+ mv $(TOOLS_BIN)/golangci-lint $(TOOLS_BIN)/golangci-lint-$(GOLANGCI_VERSION)
+
build: generate
@$(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
@@ -50,8 +59,12 @@ install-ui-packages:
ui:
@cd ui && pnpm pre-install && pnpm build && cd -
-lint: generate
+lint: generate $(GOLANGCI)
+ @bash ./script/check-asf-header.sh
+ $(GOLANGCI) run
+
+lint-fix: generate $(GOLANGCI)
@bash ./script/check-asf-header.sh
- @gofmt -w -l .
+ $(GOLANGCI) run --fix
all: clean build
diff --git a/docs/docs.go b/docs/docs.go
index c87313917..5e9d5b39d 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -8080,9 +8080,6 @@ const docTemplate = `{
"id": {
"type": "string"
},
- "question_id": {
- "type": "string"
- },
"title": {
"type": "string"
}
@@ -10920,7 +10917,7 @@ const docTemplate = `{
},
"theme_config": {
"type": "object",
- "additionalProperties": true
+ "additionalProperties": {}
}
}
},
@@ -10935,7 +10932,7 @@ const docTemplate = `{
},
"theme_config": {
"type": "object",
- "additionalProperties": true
+ "additionalProperties": {}
},
"theme_options": {
"type": "array",
diff --git a/docs/swagger.json b/docs/swagger.json
index 9f89cb157..e0f6378e4 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -8053,9 +8053,6 @@
"id": {
"type": "string"
},
- "question_id": {
- "type": "string"
- },
"title": {
"type": "string"
}
@@ -10893,7 +10890,7 @@
},
"theme_config": {
"type": "object",
- "additionalProperties": true
+ "additionalProperties": {}
}
}
},
@@ -10908,7 +10905,7 @@
},
"theme_config": {
"type": "object",
- "additionalProperties": true
+ "additionalProperties": {}
},
"theme_options": {
"type": "array",
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index d2dd076cf..e0244083b 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -399,8 +399,6 @@ definitions:
type: string
id:
type: string
- question_id:
- type: string
title:
type: string
required:
@@ -2364,7 +2362,7 @@ definitions:
maxLength: 255
type: string
theme_config:
- additionalProperties: true
+ additionalProperties: {}
type: object
required:
- theme
@@ -2376,7 +2374,7 @@ definitions:
theme:
type: string
theme_config:
- additionalProperties: true
+ additionalProperties: {}
type: object
theme_options:
items:
diff --git a/internal/base/handler/handler.go b/internal/base/handler/handler.go
index 7670feea7..5a4961a3e 100644
--- a/internal/base/handler/handler.go
+++ b/internal/base/handler/handler.go
@@ -31,7 +31,7 @@ import (
)
// HandleResponse Handle response body
-func HandleResponse(ctx *gin.Context, err error, data interface{}) {
+func HandleResponse(ctx *gin.Context, err error, data any) {
lang := GetLang(ctx)
// no error
if err == nil {
@@ -61,7 +61,7 @@ func HandleResponse(ctx *gin.Context, err error, data interface{}) {
}
// BindAndCheck bind request and check
-func BindAndCheck(ctx *gin.Context, data interface{}) bool {
+func BindAndCheck(ctx *gin.Context, data any) bool {
lang := GetLang(ctx)
ctx.Set(constant.AcceptLanguageFlag, lang)
if err := ctx.ShouldBind(data); err != nil {
@@ -79,7 +79,7 @@ func BindAndCheck(ctx *gin.Context, data interface{}) bool {
}
// BindAndCheckReturnErr bind request and check
-func BindAndCheckReturnErr(ctx *gin.Context, data interface{}) (errFields []*validator.FormErrorField) {
+func BindAndCheckReturnErr(ctx *gin.Context, data any) (errFields []*validator.FormErrorField) {
lang := GetLang(ctx)
if err := ctx.ShouldBind(data); err != nil {
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
diff --git a/internal/base/handler/response.go b/internal/base/handler/response.go
index 827e0b362..51be8a8a1 100644
--- a/internal/base/handler/response.go
+++ b/internal/base/handler/response.go
@@ -34,7 +34,7 @@ type RespBody struct {
// response message
Message string `json:"msg"`
// response data
- Data interface{} `json:"data"`
+ Data any `json:"data"`
}
// TrMsg translate the reason cause as a message
@@ -63,7 +63,7 @@ func NewRespBodyFromError(e *errors.Error) *RespBody {
}
// NewRespBodyData new response body with data
-func NewRespBodyData(code int, reason string, data interface{}) *RespBody {
+func NewRespBodyData(code int, reason string, data any) *RespBody {
return &RespBody{
Code: code,
Reason: reason,
diff --git a/internal/base/pager/pager.go b/internal/base/pager/pager.go
index d7a4caa14..b14d99bfa 100644
--- a/internal/base/pager/pager.go
+++ b/internal/base/pager/pager.go
@@ -27,7 +27,7 @@ import (
)
// Help xorm page helper
-func Help(page, pageSize int, rowsSlicePtr interface{}, rowElement interface{}, session *xorm.Session) (total int64, err error) {
+func Help(page, pageSize int, rowsSlicePtr any, rowElement any, session *xorm.Session) (total int64, err error) {
page, pageSize = ValPageAndPageSize(page, pageSize)
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
diff --git a/internal/base/pager/pagination.go b/internal/base/pager/pagination.go
index 36849fed5..1b09e1f21 100644
--- a/internal/base/pager/pagination.go
+++ b/internal/base/pager/pagination.go
@@ -25,8 +25,8 @@ import (
// PageModel page model
type PageModel struct {
- Count int64 `json:"count"`
- List interface{} `json:"list"`
+ Count int64 `json:"count"`
+ List any `json:"list"`
}
// PageCond page condition
@@ -36,7 +36,7 @@ type PageCond struct {
}
// NewPageModel new page model
-func NewPageModel(totalRecords int64, records interface{}) *PageModel {
+func NewPageModel(totalRecords int64, records any) *PageModel {
sliceValue := reflect.Indirect(reflect.ValueOf(records))
if sliceValue.Kind() != reflect.Slice {
panic("not a slice")
diff --git a/internal/base/server/http_funcmap.go b/internal/base/server/http_funcmap.go
index 9d8e98f96..8f6cac5fc 100644
--- a/internal/base/server/http_funcmap.go
+++ b/internal/base/server/http_funcmap.go
@@ -64,7 +64,7 @@ var funcMap = template.FuncMap{
"formatLinkNofollow": func(data string) template.HTML {
return template.HTML(FormatLinkNofollow(data))
},
- "translator": func(la i18n.Language, data string, params ...interface{}) string {
+ "translator": func(la i18n.Language, data string, params ...any) string {
trans := translator.GlobalTrans.Tr(la, data)
if len(params) > 0 && len(params)%2 == 0 {
@@ -128,8 +128,8 @@ var funcMap = template.FuncMap{
trans = translator.GlobalTrans.Tr(la, "ui.dates.long_date_with_year")
return day.Format(timestamp, trans, tz)
},
- "wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} {
- return map[string]interface{}{
+ "wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]any {
+ return map[string]any{
"comments": comments,
"language": la,
"timezone": tz,
diff --git a/internal/base/translator/provider.go b/internal/base/translator/provider.go
index 9838d185d..47212e84f 100644
--- a/internal/base/translator/provider.go
+++ b/internal/base/translator/provider.go
@@ -76,14 +76,14 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
// parse the backend translation
originalTr := struct {
- Backend map[string]map[string]interface{} `yaml:"backend"`
- UI map[string]interface{} `yaml:"ui"`
- Plugin map[string]interface{} `yaml:"plugin"`
+ Backend map[string]map[string]any `yaml:"backend"`
+ UI map[string]any `yaml:"ui"`
+ Plugin map[string]any `yaml:"plugin"`
}{}
if err = yaml.Unmarshal(buf, &originalTr); err != nil {
return nil, err
}
- translation := make(map[string]interface{}, 0)
+ translation := make(map[string]any, 0)
for k, v := range originalTr.Backend {
translation[k] = v
}
diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go
index 70c7be2e9..22761c521 100644
--- a/internal/base/validator/validator.go
+++ b/internal/base/validator/validator.go
@@ -187,7 +187,7 @@ func GetValidatorByLang(lang i18n.Language) *MyValidator {
}
// Check /
-func (m *MyValidator) Check(value interface{}) (errFields []*FormErrorField, err error) {
+func (m *MyValidator) Check(value any) (errFields []*FormErrorField, err error) {
defer func() {
if len(errFields) == 0 {
return
@@ -261,7 +261,7 @@ type Checker interface {
Check() (errField []*FormErrorField, err error)
}
-func getObjectTagByFieldName(obj interface{}, fieldName string) (tag string) {
+func getObjectTagByFieldName(obj any, fieldName string) (tag string) {
defer func() {
if err := recover(); err != nil {
log.Error(err)
diff --git a/internal/install/install_from_env.go b/internal/install/install_from_env.go
index a6b668bab..c05d2aaba 100644
--- a/internal/install/install_from_env.go
+++ b/internal/install/install_from_env.go
@@ -133,7 +133,7 @@ func initBaseInfo(env *Env) (err error) {
return requestAPI(req, "POST", "/installation/base-info", InitBaseInfo)
}
-func requestAPI(req interface{}, method, url string, handlerFunc gin.HandlerFunc) error {
+func requestAPI(req any, method, url string, handlerFunc gin.HandlerFunc) error {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
body, _ := json.Marshal(req)
diff --git a/internal/migrations/init.go b/internal/migrations/init.go
index 392ecb2c6..184c986b9 100644
--- a/internal/migrations/init.go
+++ b/internal/migrations/init.go
@@ -208,7 +208,7 @@ func (m *Mentor) initSiteInfoGeneralData() {
}
func (m *Mentor) initSiteInfoLoginConfig() {
- loginConfig := map[string]interface{}{
+ loginConfig := map[string]any{
"allow_new_registrations": true,
"allow_email_registrations": true,
"allow_password_login": true,
@@ -223,7 +223,7 @@ func (m *Mentor) initSiteInfoLoginConfig() {
}
func (m *Mentor) initSiteInfoLegalConfig() {
- legalConfig := map[string]interface{}{
+ legalConfig := map[string]any{
"external_content_display": m.userData.ExternalContentDisplay,
}
legalConfigDataBytes, _ := json.Marshal(legalConfig)
@@ -244,7 +244,7 @@ func (m *Mentor) initSiteInfoThemeConfig() {
}
func (m *Mentor) initSiteInfoSEOConfig() {
- seoData := map[string]interface{}{
+ seoData := map[string]any{
"permalink": constant.PermalinkQuestionID,
"robots": defaultSEORobotTxt + m.userData.SiteURL + "/sitemap.xml",
}
@@ -276,7 +276,7 @@ func (m *Mentor) initSiteInfoUsersConfig() {
}
func (m *Mentor) initSiteInfoPrivilegeRank() {
- privilegeRankData := map[string]interface{}{
+ privilegeRankData := map[string]any{
"level": schema.PrivilegeLevel2,
}
privilegeRankDataBytes, _ := json.Marshal(privilegeRankData)
@@ -288,7 +288,7 @@ func (m *Mentor) initSiteInfoPrivilegeRank() {
}
func (m *Mentor) initSiteInfoWrite() {
- writeData := map[string]interface{}{
+ writeData := map[string]any{
"min_content": 6,
"restrict_answer": true,
"min_tags": 1,
diff --git a/internal/migrations/init_data.go b/internal/migrations/init_data.go
index 96151625d..356a915a7 100644
--- a/internal/migrations/init_data.go
+++ b/internal/migrations/init_data.go
@@ -43,7 +43,7 @@ Sitemap: `
)
var (
- tables = []interface{}{
+ tables = []any{
&entity.Activity{},
&entity.Answer{},
&entity.Collection{},
diff --git a/internal/migrations/v25.go b/internal/migrations/v25.go
index 560a852ac..228c2ef26 100644
--- a/internal/migrations/v25.go
+++ b/internal/migrations/v25.go
@@ -39,7 +39,7 @@ func addFileRecord(ctx context.Context, x *xorm.Engine) error {
if err != nil {
return fmt.Errorf("get legal config failed: %w", err)
}
- legalConfig := make(map[string]interface{})
+ legalConfig := make(map[string]any)
if exist {
if err := json.Unmarshal([]byte(legalInfo.Content), &legalConfig); err != nil {
return fmt.Errorf("unmarshal legal config failed: %w", err)
diff --git a/internal/migrations/v6.go b/internal/migrations/v6.go
index 9171ad47a..88fb58497 100644
--- a/internal/migrations/v6.go
+++ b/internal/migrations/v6.go
@@ -45,7 +45,7 @@ func addNewAnswerNotification(ctx context.Context, x *xorm.Engine) error {
}
}
- m := make(map[string]interface{})
+ m := make(map[string]any)
_ = json.Unmarshal([]byte(cond.Value), &m)
m["new_answer_title"] = "[{{.SiteName}}] {{.DisplayName}} answered your question"
m["new_answer_body"] = "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \nView it on {{.SiteName}} \n\nYou are receiving this because you authored the thread. Unsubscribe "
diff --git a/internal/repo/meta/meta_repo.go b/internal/repo/meta/meta_repo.go
index 767bd04c7..9680fb419 100644
--- a/internal/repo/meta/meta_repo.go
+++ b/internal/repo/meta/meta_repo.go
@@ -72,7 +72,7 @@ func (mr *metaRepo) UpdateMeta(ctx context.Context, meta *entity.Meta) (err erro
// AddOrUpdateMetaByObjectIdAndKey if exist record with same objectID and key, update it. Or create a new one
func (mr *metaRepo) AddOrUpdateMetaByObjectIdAndKey(ctx context.Context, objectId, key string, f func(*entity.Meta, bool) (*entity.Meta, error)) error {
- _, err := mr.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
+ _, err := mr.data.DB.Transaction(func(session *xorm.Session) (any, error) {
session = session.Context(ctx)
// 1. acquire meta entity with target object id and key
diff --git a/internal/repo/plugin_config/plugin_user_config_repo.go b/internal/repo/plugin_config/plugin_user_config_repo.go
index 19d6af5f9..83da8e758 100644
--- a/internal/repo/plugin_config/plugin_user_config_repo.go
+++ b/internal/repo/plugin_config/plugin_user_config_repo.go
@@ -44,7 +44,7 @@ func NewPluginUserConfigRepo(data *data.Data) plugin_common.PluginUserConfigRepo
func (ur *pluginUserConfigRepo) SaveUserPluginConfig(ctx context.Context, userID string,
pluginSlugName, configValue string) (err error) {
- _, err = ur.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
+ _, err = ur.data.DB.Transaction(func(session *xorm.Session) (any, error) {
session = session.Context(ctx)
old := &entity.PluginUserConfig{
UserID: userID,
diff --git a/internal/repo/revision/revision_repo.go b/internal/repo/revision/revision_repo.go
index 09ba1aacd..8b9e08400 100644
--- a/internal/repo/revision/revision_repo.go
+++ b/internal/repo/revision/revision_repo.go
@@ -64,7 +64,7 @@ func (rr *revisionRepo) AddRevision(ctx context.Context, revision *entity.Revisi
if !rr.allowRecord(revision.ObjectType) {
return nil
}
- _, err = rr.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
+ _, err = rr.data.DB.Transaction(func(session *xorm.Session) (any, error) {
session = session.Context(ctx)
_, err = session.Insert(revision)
if err != nil {
diff --git a/internal/repo/role/user_role_rel_repo.go b/internal/repo/role/user_role_rel_repo.go
index 7bd14ecea..1925339c0 100644
--- a/internal/repo/role/user_role_rel_repo.go
+++ b/internal/repo/role/user_role_rel_repo.go
@@ -45,7 +45,7 @@ func NewUserRoleRelRepo(data *data.Data) role.UserRoleRelRepo {
// SaveUserRoleRel save user role rel
func (ur *userRoleRelRepo) SaveUserRoleRel(ctx context.Context, userID string, roleID int) (err error) {
- _, err = ur.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
+ _, err = ur.data.DB.Transaction(func(session *xorm.Session) (any, error) {
session = session.Context(ctx)
item := &entity.UserRoleRel{UserID: userID}
exist, err := session.Get(item)
diff --git a/internal/repo/search_common/search_repo.go b/internal/repo/search_common/search_repo.go
index 314c51878..806517234 100644
--- a/internal/repo/search_common/search_repo.go
+++ b/internal/repo/search_common/search_repo.go
@@ -107,8 +107,8 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
ub *builder.Builder
qfs = qFields
afs = aFields
- argsQ = []interface{}{}
- argsA = []interface{}{}
+ argsQ = []any{}
+ argsA = []any{}
)
if order == "relevance" {
@@ -212,8 +212,8 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
return
}
- queryArgs := []interface{}{}
- countArgs := []interface{}{}
+ queryArgs := []any{}
+ countArgs := []any{}
queryArgs = append(queryArgs, querySQL)
queryArgs = append(queryArgs, argsQ...)
@@ -246,7 +246,7 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
words = filterWords(words)
var (
qfs = qFields
- args = []interface{}{}
+ args = []any{}
)
if order == "relevance" {
if len(words) > 0 {
@@ -313,8 +313,8 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
args = append(args, answers)
}
- queryArgs := []interface{}{}
- countArgs := []interface{}{}
+ queryArgs := []any{}
+ countArgs := []any{}
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
if err != nil {
@@ -358,7 +358,7 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
var (
afs = aFields
- args = []interface{}{}
+ args = []any{}
)
if order == "relevance" {
if len(words) > 0 {
@@ -409,8 +409,8 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
args = append(args, questionID)
}
- queryArgs := []interface{}{}
- countArgs := []interface{}{}
+ queryArgs := []any{}
+ countArgs := []any{}
countSQL, _, err := builder.MySQL().Select("count(*) total").From(b, "c").ToSQL()
if err != nil {
@@ -575,9 +575,9 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte,
return resultList, nil
}
-func addRelevanceField(searchFields, words, fields []string) (res []string, args []interface{}) {
+func addRelevanceField(searchFields, words, fields []string) (res []string, args []any) {
relevanceRes := []string{}
- args = []interface{}{}
+ args = []any{}
for _, searchField := range searchFields {
var (
@@ -585,7 +585,7 @@ func addRelevanceField(searchFields, words, fields []string) (res []string, args
replacement = "REPLACE(%s, ?, '')"
replaceField = searchField
replaced string
- argsField = []interface{}{}
+ argsField = []any{}
)
res = fields
diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go
index a85cd79a1..1533cc5e8 100644
--- a/internal/repo/user/user_repo.go
+++ b/internal/repo/user/user_repo.go
@@ -51,7 +51,7 @@ func NewUserRepo(data *data.Data) usercommon.UserRepo {
// AddUser add user
func (ur *userRepo) AddUser(ctx context.Context, user *entity.User) (err error) {
- _, err = ur.data.DB.Transaction(func(session *xorm.Session) (interface{}, error) {
+ _, err = ur.data.DB.Transaction(func(session *xorm.Session) (any, error) {
session = session.Context(ctx)
userInfo := &entity.User{}
exist, err := session.Where("username = ?", user.Username).Get(userInfo)
diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go
index 84b97b830..133208286 100644
--- a/internal/schema/question_schema.go
+++ b/internal/schema/question_schema.go
@@ -330,24 +330,24 @@ type UserAnswerInfo struct {
CreateTime int `json:"create_time"`
UpdateTime int `json:"update_time"`
QuestionInfo struct {
- Title string `json:"title"`
- UrlTitle string `json:"url_title"`
- Tags []interface{} `json:"tags"`
+ Title string `json:"title"`
+ UrlTitle string `json:"url_title"`
+ Tags []any `json:"tags"`
} `json:"question_info"`
}
type UserQuestionInfo struct {
- ID string `json:"question_id"`
- Title string `json:"title"`
- UrlTitle string `json:"url_title"`
- VoteCount int `json:"vote_count"`
- Tags []interface{} `json:"tags"`
- ViewCount int `json:"view_count"`
- AnswerCount int `json:"answer_count"`
- CollectionCount int `json:"collection_count"`
- CreatedAt int64 `json:"created_at"`
- AcceptedAnswerID string `json:"accepted_answer_id"`
- Status string `json:"status"`
+ ID string `json:"question_id"`
+ Title string `json:"title"`
+ UrlTitle string `json:"url_title"`
+ VoteCount int `json:"vote_count"`
+ Tags []any `json:"tags"`
+ ViewCount int `json:"view_count"`
+ AnswerCount int `json:"answer_count"`
+ CollectionCount int `json:"collection_count"`
+ CreatedAt int64 `json:"created_at"`
+ AcceptedAnswerID string `json:"accepted_answer_id"`
+ Status string `json:"status"`
}
const (
diff --git a/internal/schema/revision_schema.go b/internal/schema/revision_schema.go
index 946d11653..b3ac0aadd 100644
--- a/internal/schema/revision_schema.go
+++ b/internal/schema/revision_schema.go
@@ -97,7 +97,7 @@ type GetRevisionResp struct {
Title string `json:"title"`
UrlTitle string `json:"url_title"`
Content string `json:"-"`
- ContentParsed interface{} `json:"content"`
+ ContentParsed any `json:"content"`
Status int `json:"status"`
CreatedAt time.Time `json:"-"`
CreatedAtParsed int64 `json:"create_at"`
diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go
index 76f66cb07..0e43bb420 100644
--- a/internal/schema/siteinfo_schema.go
+++ b/internal/schema/siteinfo_schema.go
@@ -178,9 +178,9 @@ type SiteCustomCssHTMLReq struct {
// SiteThemeReq site theme config
type SiteThemeReq struct {
- Theme string `validate:"required,gt=0,lte=255" json:"theme"`
- ThemeConfig map[string]interface{} `validate:"omitempty" json:"theme_config"`
- ColorScheme string `validate:"omitempty,gt=0,lte=100" json:"color_scheme"`
+ Theme string `validate:"required,gt=0,lte=255" json:"theme"`
+ ThemeConfig map[string]any `validate:"omitempty" json:"theme_config"`
+ ColorScheme string `validate:"omitempty,gt=0,lte=100" json:"color_scheme"`
}
type SiteSeoReq struct {
@@ -213,10 +213,10 @@ type SiteUsersResp SiteUsersReq
// SiteThemeResp site theme response
type SiteThemeResp struct {
- ThemeOptions []*ThemeOption `json:"theme_options"`
- Theme string `json:"theme"`
- ThemeConfig map[string]interface{} `json:"theme_config"`
- ColorScheme string `json:"color_scheme"`
+ ThemeOptions []*ThemeOption `json:"theme_options"`
+ Theme string `json:"theme"`
+ ThemeConfig map[string]any `json:"theme_config"`
+ ColorScheme string `json:"color_scheme"`
}
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go
index 7ba8817a8..d209c8f58 100644
--- a/internal/schema/user_schema.go
+++ b/internal/schema/user_schema.go
@@ -200,7 +200,7 @@ func (r *GetOtherUserInfoByUsernameResp) ConvertFromUserEntityWithLang(ctx conte
r.SuspendedUntil = userInfo.SuspendedUntil.Unix()
trans := translator.GlobalTrans.Tr(lang, "ui.dates.long_date_with_time")
suspendedUntilFormatted := day.Format(userInfo.SuspendedUntil.Unix(), trans, "UTC")
- r.StatusMsg = translator.TrWithData(lang, reason.UserStatusSuspendedUntil, map[string]interface{}{
+ r.StatusMsg = translator.TrWithData(lang, reason.UserStatusSuspendedUntil, map[string]any{
"SuspendedUntil": suspendedUntilFormatted,
})
}
diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go
index feb5626ed..333806445 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -622,7 +622,7 @@ func (qs *QuestionCommon) SitemapCron(ctx context.Context) {
}
}
-func (qs *QuestionCommon) SetCache(ctx context.Context, cachekey string, info interface{}) error {
+func (qs *QuestionCommon) SetCache(ctx context.Context, cachekey string, info any) error {
infoStr, err := json.Marshal(info)
if err != nil {
return errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go
index 92b3e0c71..cf43d68d5 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -183,7 +183,7 @@ func (s *SiteInfoService) SaveSiteBranding(ctx context.Context, req *schema.Site
}
// SaveSiteWrite save site configuration about write
-func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWriteReq) (resp interface{}, err error) {
+func (s *SiteInfoService) SaveSiteWrite(ctx context.Context, req *schema.SiteWriteReq) (resp any, err error) {
recommendTags, reservedTags := make([]string, 0), make([]string, 0)
recommendTagMapping, reservedTagMapping := make(map[string]bool), make(map[string]bool)
for _, tag := range req.ReservedTags {
diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go
index ef4869cc4..fda117229 100644
--- a/internal/service/siteinfo_common/siteinfo_service.go
+++ b/internal/service/siteinfo_common/siteinfo_service.go
@@ -56,7 +56,7 @@ type SiteInfoCommonService interface {
GetSiteCustomCssHTML(ctx context.Context) (resp *schema.SiteCustomCssHTMLResp, err error)
GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error)
GetSiteSeo(ctx context.Context) (resp *schema.SiteSeoResp, err error)
- GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error)
+ GetSiteInfoByType(ctx context.Context, siteType string, resp any) (err error)
IsBrandingFileUsed(ctx context.Context, filePath string) bool
}
@@ -224,7 +224,7 @@ func (s *siteInfoCommonService) EnableShortID(ctx context.Context) (enabled bool
return siteSeo.IsShortLink()
}
-func (s *siteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp interface{}) (err error) {
+func (s *siteInfoCommonService) GetSiteInfoByType(ctx context.Context, siteType string, resp any) (err error) {
siteInfo, exist, err := s.siteInfoRepo.GetByType(ctx, siteType)
if err != nil {
return err
diff --git a/pkg/converter/str.go b/pkg/converter/str.go
index 40c147fdc..5164609ca 100644
--- a/pkg/converter/str.go
+++ b/pkg/converter/str.go
@@ -47,7 +47,7 @@ func IntToString(data int64) string {
// InterfaceToString converts data to string
// It will be used in template render
-func InterfaceToString(data interface{}) string {
+func InterfaceToString(data any) string {
switch t := data.(type) {
case int:
i := data.(int)
From 9540ef60050fb6e2598d8d72ab23514f6e6d4f93 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Mon, 1 Dec 2025 11:30:42 +0800
Subject: [PATCH 12/92] refactor(goimports): add goimports to golangci-lint
configuration #1432
---
.golangci.yaml | 1 +
internal/base/handler/handler.go | 3 ++-
internal/base/middleware/accept_language.go | 3 ++-
internal/base/middleware/rate_limit.go | 1 +
internal/cli/config.go | 1 +
internal/cli/i18n.go | 7 ++++---
internal/controller/template_render/comment.go | 1 +
internal/controller/user_plugin_controller.go | 3 ++-
internal/entity/badge_entity.go | 3 ++-
internal/entity/config_entity.go | 1 +
internal/migrations/v1.go | 1 +
internal/migrations/v13.go | 1 +
internal/migrations/v14.go | 1 +
internal/migrations/v15.go | 1 +
internal/migrations/v16.go | 1 +
internal/migrations/v17.go | 1 +
internal/migrations/v18.go | 1 +
internal/migrations/v19.go | 1 +
internal/migrations/v2.go | 1 +
internal/migrations/v21.go | 1 +
internal/migrations/v22.go | 1 +
internal/migrations/v24.go | 1 +
internal/repo/activity/answer_repo.go | 3 ++-
internal/repo/activity/user_active_repo.go | 1 +
internal/repo/activity_common/vote.go | 1 +
internal/repo/auth/auth.go | 1 +
internal/repo/badge/badge_event_rule.go | 3 ++-
internal/repo/badge/badge_repo.go | 1 +
internal/repo/badge_award/badge_award_repo.go | 1 +
internal/repo/badge_group/badge_group_repo.go | 1 +
internal/repo/collection/collection_repo.go | 1 +
internal/repo/export/email_repo.go | 3 ++-
internal/repo/limit/limit.go | 3 ++-
internal/repo/meta/meta_repo.go | 2 +-
internal/repo/plugin_config/plugin_user_config_repo.go | 1 +
internal/repo/search_sync/search_sync.go | 1 +
.../user_notification_config_repo.go | 1 +
internal/schema/email_template.go | 1 +
internal/schema/notification_schema.go | 3 ++-
internal/schema/user_notification_schema.go | 1 +
internal/service/activity/activity.go | 2 +-
internal/service/activity/answer_activity_service.go | 1 +
internal/service/badge/badge_award_service.go | 1 +
internal/service/badge/badge_event_handler.go | 1 +
internal/service/badge/badge_group_service.go | 1 +
internal/service/badge/badge_service.go | 3 ++-
internal/service/content/question_hottest_service.go | 5 +++--
internal/service/content/vote_service.go | 3 ++-
internal/service/export/email_service.go | 3 ++-
internal/service/meta/meta_service.go | 3 ++-
internal/service/notification/notification_service.go | 1 +
internal/service/permission/answer_permission.go | 1 +
internal/service/permission/question_permission.go | 1 +
internal/service/provider.go | 2 +-
internal/service/report/report_service.go | 1 +
internal/service/search_parser/search_parser.go | 3 ++-
.../user_notification_config_service.go | 1 +
pkg/converter/str.go | 3 ++-
pkg/day/day_test.go | 3 ++-
pkg/gravatar/gravatar_test.go | 3 ++-
60 files changed, 82 insertions(+), 25 deletions(-)
diff --git a/.golangci.yaml b/.golangci.yaml
index af00efc3b..263f63f70 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -22,6 +22,7 @@ linters:
formatters:
enable:
- gofmt
+ - goimports
settings:
gofmt:
simplify: true
diff --git a/internal/base/handler/handler.go b/internal/base/handler/handler.go
index 5a4961a3e..b545b5e01 100644
--- a/internal/base/handler/handler.go
+++ b/internal/base/handler/handler.go
@@ -21,13 +21,14 @@ package handler
import (
"errors"
+ "net/http"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/validator"
"github.com/gin-gonic/gin"
myErrors "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
- "net/http"
)
// HandleResponse Handle response body
diff --git a/internal/base/middleware/accept_language.go b/internal/base/middleware/accept_language.go
index 7a8ee391a..ca8a1f903 100644
--- a/internal/base/middleware/accept_language.go
+++ b/internal/base/middleware/accept_language.go
@@ -20,13 +20,14 @@
package middleware
import (
+ "strings"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/translator"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/i18n"
"golang.org/x/text/language"
- "strings"
)
// ExtractAndSetAcceptLanguage extract accept language from header and set to context
diff --git a/internal/base/middleware/rate_limit.go b/internal/base/middleware/rate_limit.go
index 29b961757..d376a6d50 100644
--- a/internal/base/middleware/rate_limit.go
+++ b/internal/base/middleware/rate_limit.go
@@ -22,6 +22,7 @@ package middleware
import (
"encoding/json"
"fmt"
+
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/repo/limit"
diff --git a/internal/cli/config.go b/internal/cli/config.go
index 93cb8eab2..ecb62a13c 100644
--- a/internal/cli/config.go
+++ b/internal/cli/config.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
diff --git a/internal/cli/i18n.go b/internal/cli/i18n.go
index faef0f28a..819ebd753 100644
--- a/internal/cli/i18n.go
+++ b/internal/cli/i18n.go
@@ -21,13 +21,14 @@ package cli
import (
"fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
"github.com/apache/answer/i18n"
"github.com/apache/answer/pkg/dir"
"github.com/apache/answer/pkg/writer"
"gopkg.in/yaml.v3"
- "os"
- "path/filepath"
- "strings"
)
type YamlPluginContent struct {
diff --git a/internal/controller/template_render/comment.go b/internal/controller/template_render/comment.go
index 67efd2129..2862ad8dd 100644
--- a/internal/controller/template_render/comment.go
+++ b/internal/controller/template_render/comment.go
@@ -21,6 +21,7 @@ package templaterender
import (
"context"
+
"github.com/apache/answer/internal/base/pager"
"github.com/apache/answer/internal/schema"
)
diff --git a/internal/controller/user_plugin_controller.go b/internal/controller/user_plugin_controller.go
index 310215253..1c4c041c5 100644
--- a/internal/controller/user_plugin_controller.go
+++ b/internal/controller/user_plugin_controller.go
@@ -21,10 +21,11 @@ package controller
import (
"encoding/json"
+ "net/http"
+
"github.com/apache/answer/internal/base/middleware"
"github.com/apache/answer/internal/base/reason"
"github.com/segmentfault/pacman/errors"
- "net/http"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/schema"
diff --git a/internal/entity/badge_entity.go b/internal/entity/badge_entity.go
index a370e2750..367bb7456 100644
--- a/internal/entity/badge_entity.go
+++ b/internal/entity/badge_entity.go
@@ -20,8 +20,9 @@
package entity
import (
- "github.com/tidwall/gjson"
"time"
+
+ "github.com/tidwall/gjson"
)
type BadgeLevel int
diff --git a/internal/entity/config_entity.go b/internal/entity/config_entity.go
index 95a02be48..d39af7b2d 100644
--- a/internal/entity/config_entity.go
+++ b/internal/entity/config_entity.go
@@ -21,6 +21,7 @@ package entity
import (
"encoding/json"
+
"github.com/segmentfault/pacman/log"
"github.com/apache/answer/pkg/converter"
diff --git a/internal/migrations/v1.go b/internal/migrations/v1.go
index c5e731a0e..688732fa7 100644
--- a/internal/migrations/v1.go
+++ b/internal/migrations/v1.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"xorm.io/xorm"
)
diff --git a/internal/migrations/v13.go b/internal/migrations/v13.go
index 30bfbb542..57d248482 100644
--- a/internal/migrations/v13.go
+++ b/internal/migrations/v13.go
@@ -24,6 +24,7 @@ import (
"encoding/json"
"fmt"
"time"
+
"xorm.io/builder"
"github.com/apache/answer/internal/base/constant"
diff --git a/internal/migrations/v14.go b/internal/migrations/v14.go
index ee3a3d0d6..5e125c98e 100644
--- a/internal/migrations/v14.go
+++ b/internal/migrations/v14.go
@@ -22,6 +22,7 @@ package migrations
import (
"context"
"time"
+
"xorm.io/xorm/schemas"
"xorm.io/xorm"
diff --git a/internal/migrations/v15.go b/internal/migrations/v15.go
index 5195c105a..5858d0097 100644
--- a/internal/migrations/v15.go
+++ b/internal/migrations/v15.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"github.com/apache/answer/internal/entity"
"xorm.io/xorm"
)
diff --git a/internal/migrations/v16.go b/internal/migrations/v16.go
index 11643600c..11ae36843 100644
--- a/internal/migrations/v16.go
+++ b/internal/migrations/v16.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"github.com/segmentfault/pacman/log"
diff --git a/internal/migrations/v17.go b/internal/migrations/v17.go
index 655e547a2..3fa2c7b31 100644
--- a/internal/migrations/v17.go
+++ b/internal/migrations/v17.go
@@ -22,6 +22,7 @@ package migrations
import (
"context"
"fmt"
+
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/service/permission"
"github.com/segmentfault/pacman/log"
diff --git a/internal/migrations/v18.go b/internal/migrations/v18.go
index 89db524f5..7178c704e 100644
--- a/internal/migrations/v18.go
+++ b/internal/migrations/v18.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
diff --git a/internal/migrations/v19.go b/internal/migrations/v19.go
index b45d374bb..b50c89f14 100644
--- a/internal/migrations/v19.go
+++ b/internal/migrations/v19.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"github.com/apache/answer/internal/entity"
"xorm.io/xorm"
)
diff --git a/internal/migrations/v2.go b/internal/migrations/v2.go
index 4e17597dc..601ff98b2 100644
--- a/internal/migrations/v2.go
+++ b/internal/migrations/v2.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"xorm.io/xorm"
)
diff --git a/internal/migrations/v21.go b/internal/migrations/v21.go
index 880852f8e..de38bffbd 100644
--- a/internal/migrations/v21.go
+++ b/internal/migrations/v21.go
@@ -21,6 +21,7 @@ package migrations
import (
"context"
+
"xorm.io/xorm"
)
diff --git a/internal/migrations/v22.go b/internal/migrations/v22.go
index 14292a1e7..e7177deae 100644
--- a/internal/migrations/v22.go
+++ b/internal/migrations/v22.go
@@ -22,6 +22,7 @@ package migrations
import (
"context"
"fmt"
+
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/unique"
diff --git a/internal/migrations/v24.go b/internal/migrations/v24.go
index 19358af45..a488679f6 100644
--- a/internal/migrations/v24.go
+++ b/internal/migrations/v24.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
diff --git a/internal/repo/activity/answer_repo.go b/internal/repo/activity/answer_repo.go
index db6e3bde4..4aca874a7 100644
--- a/internal/repo/activity/answer_repo.go
+++ b/internal/repo/activity/answer_repo.go
@@ -22,8 +22,9 @@ package activity
import (
"context"
"fmt"
- "github.com/segmentfault/pacman/log"
"time"
+
+ "github.com/segmentfault/pacman/log"
"xorm.io/builder"
"github.com/apache/answer/internal/base/constant"
diff --git a/internal/repo/activity/user_active_repo.go b/internal/repo/activity/user_active_repo.go
index 2452bcf83..68dfec7d7 100644
--- a/internal/repo/activity/user_active_repo.go
+++ b/internal/repo/activity/user_active_repo.go
@@ -22,6 +22,7 @@ package activity
import (
"context"
"fmt"
+
"xorm.io/builder"
"github.com/apache/answer/internal/base/data"
diff --git a/internal/repo/activity_common/vote.go b/internal/repo/activity_common/vote.go
index cb7d23d00..506578424 100644
--- a/internal/repo/activity_common/vote.go
+++ b/internal/repo/activity_common/vote.go
@@ -21,6 +21,7 @@ package activity_common
import (
"context"
+
"github.com/apache/answer/pkg/uid"
"github.com/apache/answer/internal/base/data"
diff --git a/internal/repo/auth/auth.go b/internal/repo/auth/auth.go
index a1e358f9a..597352b23 100644
--- a/internal/repo/auth/auth.go
+++ b/internal/repo/auth/auth.go
@@ -22,6 +22,7 @@ package auth
import (
"context"
"encoding/json"
+
"github.com/apache/answer/internal/service/auth"
"github.com/apache/answer/internal/base/constant"
diff --git a/internal/repo/badge/badge_event_rule.go b/internal/repo/badge/badge_event_rule.go
index 8c4656db3..786d988ac 100644
--- a/internal/repo/badge/badge_event_rule.go
+++ b/internal/repo/badge/badge_event_rule.go
@@ -21,6 +21,8 @@ package badge
import (
"context"
+ "strconv"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
@@ -29,7 +31,6 @@ import (
"github.com/apache/answer/internal/service/badge"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
- "strconv"
)
// eventRuleRepo event rule repo
diff --git a/internal/repo/badge/badge_repo.go b/internal/repo/badge/badge_repo.go
index 257caef81..baff45d8a 100644
--- a/internal/repo/badge/badge_repo.go
+++ b/internal/repo/badge/badge_repo.go
@@ -21,6 +21,7 @@ package badge
import (
"context"
+
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/pager"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/repo/badge_award/badge_award_repo.go b/internal/repo/badge_award/badge_award_repo.go
index eda5d80c2..11429e570 100644
--- a/internal/repo/badge_award/badge_award_repo.go
+++ b/internal/repo/badge_award/badge_award_repo.go
@@ -22,6 +22,7 @@ package badge_award
import (
"context"
"fmt"
+
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/pager"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/repo/badge_group/badge_group_repo.go b/internal/repo/badge_group/badge_group_repo.go
index 839ba4691..8f1111a48 100644
--- a/internal/repo/badge_group/badge_group_repo.go
+++ b/internal/repo/badge_group/badge_group_repo.go
@@ -21,6 +21,7 @@ package badge_group
import (
"context"
+
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/service/badge"
diff --git a/internal/repo/collection/collection_repo.go b/internal/repo/collection/collection_repo.go
index a3faacdb5..f30692c97 100644
--- a/internal/repo/collection/collection_repo.go
+++ b/internal/repo/collection/collection_repo.go
@@ -21,6 +21,7 @@ package collection
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/handler"
diff --git a/internal/repo/export/email_repo.go b/internal/repo/export/email_repo.go
index 1f8e1ce83..a3e619712 100644
--- a/internal/repo/export/email_repo.go
+++ b/internal/repo/export/email_repo.go
@@ -21,9 +21,10 @@ package export
import (
"context"
+ "time"
+
"github.com/apache/answer/internal/base/constant"
"github.com/tidwall/gjson"
- "time"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/repo/limit/limit.go b/internal/repo/limit/limit.go
index 4868accd5..524ad12bc 100644
--- a/internal/repo/limit/limit.go
+++ b/internal/repo/limit/limit.go
@@ -22,11 +22,12 @@ package limit
import (
"context"
"fmt"
+ "time"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
"github.com/segmentfault/pacman/errors"
- "time"
)
// LimitRepo auth repository
diff --git a/internal/repo/meta/meta_repo.go b/internal/repo/meta/meta_repo.go
index 9680fb419..fecd7bd5d 100644
--- a/internal/repo/meta/meta_repo.go
+++ b/internal/repo/meta/meta_repo.go
@@ -25,7 +25,7 @@ import (
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/entity"
- "github.com/apache/answer/internal/service/meta_common"
+ metacommon "github.com/apache/answer/internal/service/meta_common"
"github.com/segmentfault/pacman/errors"
"xorm.io/builder"
"xorm.io/xorm"
diff --git a/internal/repo/plugin_config/plugin_user_config_repo.go b/internal/repo/plugin_config/plugin_user_config_repo.go
index 83da8e758..d14442a56 100644
--- a/internal/repo/plugin_config/plugin_user_config_repo.go
+++ b/internal/repo/plugin_config/plugin_user_config_repo.go
@@ -21,6 +21,7 @@ package plugin_config
import (
"context"
+
"github.com/apache/answer/internal/base/pager"
"xorm.io/xorm"
diff --git a/internal/repo/search_sync/search_sync.go b/internal/repo/search_sync/search_sync.go
index 5c0adc0f9..889ffe5be 100644
--- a/internal/repo/search_sync/search_sync.go
+++ b/internal/repo/search_sync/search_sync.go
@@ -21,6 +21,7 @@ package search_sync
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
diff --git a/internal/repo/user_notification_config/user_notification_config_repo.go b/internal/repo/user_notification_config/user_notification_config_repo.go
index 8ea2b065b..0e761514b 100644
--- a/internal/repo/user_notification_config/user_notification_config_repo.go
+++ b/internal/repo/user_notification_config/user_notification_config_repo.go
@@ -21,6 +21,7 @@ package user_notification_config
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/schema/email_template.go b/internal/schema/email_template.go
index 1fcdfbce3..d7e4b929a 100644
--- a/internal/schema/email_template.go
+++ b/internal/schema/email_template.go
@@ -21,6 +21,7 @@ package schema
import (
"encoding/json"
+
"github.com/apache/answer/internal/base/constant"
)
diff --git a/internal/schema/notification_schema.go b/internal/schema/notification_schema.go
index a68328ace..e5d60615e 100644
--- a/internal/schema/notification_schema.go
+++ b/internal/schema/notification_schema.go
@@ -21,8 +21,9 @@ package schema
import (
"encoding/json"
- "github.com/apache/answer/internal/entity"
"sort"
+
+ "github.com/apache/answer/internal/entity"
)
const (
diff --git a/internal/schema/user_notification_schema.go b/internal/schema/user_notification_schema.go
index eca97e81c..60cc4a27b 100644
--- a/internal/schema/user_notification_schema.go
+++ b/internal/schema/user_notification_schema.go
@@ -21,6 +21,7 @@ package schema
import (
"encoding/json"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
)
diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go
index 2d384f34c..061d84493 100644
--- a/internal/service/activity/activity.go
+++ b/internal/service/activity/activity.go
@@ -26,7 +26,7 @@ import (
"strings"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/meta_common"
+ metacommon "github.com/apache/answer/internal/service/meta_common"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
diff --git a/internal/service/activity/answer_activity_service.go b/internal/service/activity/answer_activity_service.go
index 169e7eb39..48b5f3ef9 100644
--- a/internal/service/activity/answer_activity_service.go
+++ b/internal/service/activity/answer_activity_service.go
@@ -21,6 +21,7 @@ package activity
import (
"context"
+
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity_type"
"github.com/apache/answer/internal/service/config"
diff --git a/internal/service/badge/badge_award_service.go b/internal/service/badge/badge_award_service.go
index 397a7471a..982c1d1a4 100644
--- a/internal/service/badge/badge_award_service.go
+++ b/internal/service/badge/badge_award_service.go
@@ -21,6 +21,7 @@ package badge
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/service/badge/badge_event_handler.go b/internal/service/badge/badge_event_handler.go
index 219822947..cc161f6ad 100644
--- a/internal/service/badge/badge_event_handler.go
+++ b/internal/service/badge/badge_event_handler.go
@@ -21,6 +21,7 @@ package badge
import (
"context"
+
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
diff --git a/internal/service/badge/badge_group_service.go b/internal/service/badge/badge_group_service.go
index e0dab6e89..7c220a0be 100644
--- a/internal/service/badge/badge_group_service.go
+++ b/internal/service/badge/badge_group_service.go
@@ -21,6 +21,7 @@ package badge
import (
"context"
+
"github.com/apache/answer/internal/entity"
)
diff --git a/internal/service/badge/badge_service.go b/internal/service/badge/badge_service.go
index 7bc9ffe21..03b8a8774 100644
--- a/internal/service/badge/badge_service.go
+++ b/internal/service/badge/badge_service.go
@@ -21,6 +21,8 @@ package badge
import (
"context"
+ "strings"
+
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/translator"
@@ -32,7 +34,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
- "strings"
)
type BadgeRepo interface {
diff --git a/internal/service/content/question_hottest_service.go b/internal/service/content/question_hottest_service.go
index a33b155fb..085b36709 100644
--- a/internal/service/content/question_hottest_service.go
+++ b/internal/service/content/question_hottest_service.go
@@ -21,11 +21,12 @@ package content
import (
"context"
+ "math"
+ "time"
+
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
"github.com/segmentfault/pacman/log"
- "math"
- "time"
)
func (q *QuestionService) RefreshHottestCron(ctx context.Context) {
diff --git a/internal/service/content/vote_service.go b/internal/service/content/vote_service.go
index ff3ee5974..92f0c9962 100644
--- a/internal/service/content/vote_service.go
+++ b/internal/service/content/vote_service.go
@@ -22,9 +22,10 @@ package content
import (
"context"
"fmt"
- "github.com/apache/answer/internal/service/event_queue"
"strings"
+ "github.com/apache/answer/internal/service/event_queue"
+
"github.com/apache/answer/internal/service/activity_common"
"github.com/apache/answer/internal/base/constant"
diff --git a/internal/service/export/email_service.go b/internal/service/export/email_service.go
index b00ee093b..ddf31b348 100644
--- a/internal/service/export/email_service.go
+++ b/internal/service/export/email_service.go
@@ -23,12 +23,13 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
- "github.com/apache/answer/pkg/display"
"mime"
"os"
"strings"
"time"
+ "github.com/apache/answer/pkg/display"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/service/meta/meta_service.go b/internal/service/meta/meta_service.go
index 9e4f07410..4b3104197 100644
--- a/internal/service/meta/meta_service.go
+++ b/internal/service/meta/meta_service.go
@@ -23,10 +23,11 @@ import (
"context"
"encoding/json"
"errors"
- "github.com/apache/answer/internal/service/event_queue"
"strconv"
"strings"
+ "github.com/apache/answer/internal/service/event_queue"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go
index 598212aa1..09d871351 100644
--- a/internal/service/notification/notification_service.go
+++ b/internal/service/notification/notification_service.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"fmt"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/base/handler"
diff --git a/internal/service/permission/answer_permission.go b/internal/service/permission/answer_permission.go
index 4eb563a5a..340fca8db 100644
--- a/internal/service/permission/answer_permission.go
+++ b/internal/service/permission/answer_permission.go
@@ -21,6 +21,7 @@ package permission
import (
"context"
+
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/base/handler"
diff --git a/internal/service/permission/question_permission.go b/internal/service/permission/question_permission.go
index b6750beae..eca0a58f2 100644
--- a/internal/service/permission/question_permission.go
+++ b/internal/service/permission/question_permission.go
@@ -21,6 +21,7 @@ package permission
import (
"context"
+
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/base/handler"
diff --git a/internal/service/provider.go b/internal/service/provider.go
index 4b1b64276..65535f41b 100644
--- a/internal/service/provider.go
+++ b/internal/service/provider.go
@@ -40,7 +40,7 @@ import (
"github.com/apache/answer/internal/service/follow"
"github.com/apache/answer/internal/service/importer"
"github.com/apache/answer/internal/service/meta"
- "github.com/apache/answer/internal/service/meta_common"
+ metacommon "github.com/apache/answer/internal/service/meta_common"
"github.com/apache/answer/internal/service/notice_queue"
"github.com/apache/answer/internal/service/notification"
notficationcommon "github.com/apache/answer/internal/service/notification_common"
diff --git a/internal/service/report/report_service.go b/internal/service/report/report_service.go
index 7dcc1d689..d32ccdabf 100644
--- a/internal/service/report/report_service.go
+++ b/internal/service/report/report_service.go
@@ -21,6 +21,7 @@ package report
import (
"encoding/json"
+
"github.com/apache/answer/internal/service/event_queue"
"github.com/apache/answer/internal/base/constant"
diff --git a/internal/service/search_parser/search_parser.go b/internal/service/search_parser/search_parser.go
index e87efaf6f..3e6182e15 100644
--- a/internal/service/search_parser/search_parser.go
+++ b/internal/service/search_parser/search_parser.go
@@ -22,10 +22,11 @@ package search_parser
import (
"context"
"fmt"
- "github.com/apache/answer/internal/base/constant"
"regexp"
"strings"
+ "github.com/apache/answer/internal/base/constant"
+
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/tag_common"
usercommon "github.com/apache/answer/internal/service/user_common"
diff --git a/internal/service/user_notification_config/user_notification_config_service.go b/internal/service/user_notification_config/user_notification_config_service.go
index 7c54df0aa..01da3ee2f 100644
--- a/internal/service/user_notification_config/user_notification_config_service.go
+++ b/internal/service/user_notification_config/user_notification_config_service.go
@@ -21,6 +21,7 @@ package user_notification_config
import (
"context"
+
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
diff --git a/pkg/converter/str.go b/pkg/converter/str.go
index 5164609ca..8553975be 100644
--- a/pkg/converter/str.go
+++ b/pkg/converter/str.go
@@ -21,8 +21,9 @@ package converter
import (
"fmt"
- "github.com/segmentfault/pacman/log"
"strconv"
+
+ "github.com/segmentfault/pacman/log"
)
func StringToInt64(str string) int64 {
diff --git a/pkg/day/day_test.go b/pkg/day/day_test.go
index 73e49aca3..4f945d88f 100644
--- a/pkg/day/day_test.go
+++ b/pkg/day/day_test.go
@@ -20,9 +20,10 @@
package day
import (
- "github.com/stretchr/testify/assert"
"testing"
"time"
+
+ "github.com/stretchr/testify/assert"
)
func TestFormat(t *testing.T) {
diff --git a/pkg/gravatar/gravatar_test.go b/pkg/gravatar/gravatar_test.go
index bf504e68f..b88a69649 100644
--- a/pkg/gravatar/gravatar_test.go
+++ b/pkg/gravatar/gravatar_test.go
@@ -20,9 +20,10 @@
package gravatar
import (
- "github.com/apache/answer/internal/base/constant"
"testing"
+ "github.com/apache/answer/internal/base/constant"
+
"github.com/stretchr/testify/assert"
)
From 5e705a124b3ae2d927f8084e545c3809ae70f4a8 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Mon, 1 Dec 2025 12:23:50 +0800
Subject: [PATCH 13/92] refactor(lint): improve error handling and code
consistency across multiple files
---
.golangci.yaml | 13 ++++-
cmd/wire.go | 3 +-
internal/base/conf/conf.go | 4 +-
internal/base/constant/ctx_flag.go | 7 +++
internal/base/data/data.go | 2 +-
internal/base/handler/lang.go | 2 +-
internal/base/handler/short_id.go | 2 +-
internal/base/server/http_funcmap.go | 9 ++--
internal/base/validator/validator.go | 2 +-
internal/cli/build.go | 52 -------------------
internal/cli/config.go | 6 ++-
internal/cli/dump.go | 4 +-
internal/cli/install_check.go | 8 ++-
internal/cli/reset_password.go | 4 +-
internal/controller/template_controller.go | 43 ++++++---------
.../controller/template_render/controller.go | 1 -
internal/controller/user_controller.go | 7 +--
internal/install/install_from_env.go | 3 +-
internal/migrations/migrations.go | 4 +-
internal/migrations/v22.go | 3 ++
internal/migrations/v24.go | 3 --
internal/repo/answer/answer_repo.go | 1 +
.../plugin_config/plugin_user_config_repo.go | 2 +-
internal/repo/repo_test/repo_main_test.go | 2 +-
internal/repo/tag/tag_rel_repo.go | 1 +
internal/service/action/captcha_service.go | 7 ++-
internal/service/action/captcha_strategy.go | 27 +++++-----
internal/service/badge/badge_event_handler.go | 1 -
internal/service/badge/badge_service.go | 26 +++-------
internal/service/content/question_service.go | 22 ++++----
internal/service/content/revision_service.go | 5 +-
internal/service/content/search_service.go | 3 ++
internal/service/content/vote_service.go | 3 --
.../service/dashboard/dashboard_service.go | 8 +--
.../file_record/file_record_service.go | 1 -
internal/service/importer/importer_service.go | 5 +-
internal/service/meta/meta_service.go | 12 +++--
.../invite_answer_notification.go | 2 +-
.../notification/new_answer_notification.go | 2 +-
.../notification/new_comment_notification.go | 2 +-
.../notification/new_question_notification.go | 2 +-
.../notification/notification_service.go | 4 +-
.../notification_common/notification.go | 3 ++
.../service/report_handle/report_handle.go | 6 +++
internal/service/review/review_service.go | 1 -
internal/service/siteinfo/siteinfo_service.go | 4 +-
internal/service/uploader/upload.go | 28 +++++++---
internal/service/user_admin/user_backyard.go | 11 ++--
.../user_notification_config_service.go | 8 +--
pkg/checker/file_type.go | 4 +-
pkg/checker/password.go | 3 +-
pkg/converter/markdown.go | 7 ++-
pkg/htmltext/htmltext.go | 4 +-
plugin/kv_storage.go | 6 +--
plugin/plugin_test/plugin_main_test.go | 2 +-
ui/static.go | 1 -
56 files changed, 194 insertions(+), 214 deletions(-)
diff --git a/.golangci.yaml b/.golangci.yaml
index 263f63f70..611b49bc2 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -17,7 +17,18 @@
version: "2"
linters:
- default: none
+ exclusions:
+ paths:
+ - answer-data
+ - ui
+ - i18n
+ enable:
+ - asasalint # checks for pass []any as any in variadic func(...any)
+ - asciicheck # checks that your code does not contain non-ASCII identifiers
+ - bidichk # checks for dangerous unicode character sequences
+ - bodyclose # checks whether HTTP response body is closed successfully
+ - canonicalheader # checks whether net/http.Header uses canonical header
+ - copyloopvar # detects places where loop variables are copied (Go 1.22+)
formatters:
enable:
diff --git a/cmd/wire.go b/cmd/wire.go
index b25026e5b..3979ecbf5 100644
--- a/cmd/wire.go
+++ b/cmd/wire.go
@@ -1,5 +1,4 @@
//go:build wireinject
-// +build wireinject
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -32,7 +31,7 @@ import (
"github.com/apache/answer/internal/base/server"
"github.com/apache/answer/internal/base/translator"
"github.com/apache/answer/internal/controller"
- "github.com/apache/answer/internal/controller/template_render"
+ templaterender "github.com/apache/answer/internal/controller/template_render"
"github.com/apache/answer/internal/controller_admin"
"github.com/apache/answer/internal/repo"
"github.com/apache/answer/internal/router"
diff --git a/internal/base/conf/conf.go b/internal/base/conf/conf.go
index db83862b0..04e3a19ba 100644
--- a/internal/base/conf/conf.go
+++ b/internal/base/conf/conf.go
@@ -117,7 +117,9 @@ func ReadConfig(configFilePath string) (c *AllConfig, err error) {
func RewriteConfig(configFilePath string, allConfig *AllConfig) error {
buf := bytes.Buffer{}
enc := yaml.NewEncoder(&buf)
- defer enc.Close()
+ defer func() {
+ _ = enc.Close()
+ }()
enc.SetIndent(2)
if err := enc.Encode(allConfig); err != nil {
return err
diff --git a/internal/base/constant/ctx_flag.go b/internal/base/constant/ctx_flag.go
index 2a757fa87..450491bb3 100644
--- a/internal/base/constant/ctx_flag.go
+++ b/internal/base/constant/ctx_flag.go
@@ -23,3 +23,10 @@ const (
AcceptLanguageFlag = "Accept-Language"
ShortIDFlag = "Short-ID-Enabled"
)
+
+type ContextKey string
+
+const (
+ AcceptLanguageContextKey ContextKey = ContextKey(AcceptLanguageFlag)
+ ShortIDContextKey ContextKey = ContextKey(ShortIDFlag)
+)
diff --git a/internal/base/data/data.go b/internal/base/data/data.go
index 1d24d7184..7696d8f56 100644
--- a/internal/base/data/data.go
+++ b/internal/base/data/data.go
@@ -47,7 +47,7 @@ type Data struct {
func NewData(db *xorm.Engine, cache cache.Cache) (*Data, func(), error) {
cleanup := func() {
log.Info("closing the data resources")
- db.Close()
+ _ = db.Close()
}
return &Data{DB: db, Cache: cache}, cleanup, nil
}
diff --git a/internal/base/handler/lang.go b/internal/base/handler/lang.go
index a676e5bc2..4ff1ac7f1 100644
--- a/internal/base/handler/lang.go
+++ b/internal/base/handler/lang.go
@@ -38,7 +38,7 @@ func GetLang(ctx *gin.Context) i18n.Language {
// GetLangByCtx get language from header
func GetLangByCtx(ctx context.Context) i18n.Language {
- acceptLanguage, ok := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
+ acceptLanguage, ok := ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
if ok {
return acceptLanguage
}
diff --git a/internal/base/handler/short_id.go b/internal/base/handler/short_id.go
index c763bf944..8f9a2a7e5 100644
--- a/internal/base/handler/short_id.go
+++ b/internal/base/handler/short_id.go
@@ -27,7 +27,7 @@ import (
// GetEnableShortID get language from header
func GetEnableShortID(ctx context.Context) bool {
- flag, ok := ctx.Value(constant.ShortIDFlag).(bool)
+ flag, ok := ctx.Value(constant.ShortIDContextKey).(bool)
if ok {
return flag
}
diff --git a/internal/base/server/http_funcmap.go b/internal/base/server/http_funcmap.go
index 8f6cac5fc..db4604572 100644
--- a/internal/base/server/http_funcmap.go
+++ b/internal/base/server/http_funcmap.go
@@ -21,7 +21,6 @@ package server
import (
"html/template"
- "math"
"regexp"
"strconv"
"strings"
@@ -107,15 +106,15 @@ var funcMap = template.FuncMap{
}
if between >= 60 && between < 3600 {
- min := math.Floor(float64(between / 60))
+ min := between / 60
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_minutes_ago")
- return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(min, 'f', 0, 64))
+ return strings.ReplaceAll(trans, "{{count}}", strconv.FormatInt(min, 10))
}
if between >= 3600 && between < 3600*24 {
- h := math.Floor(float64(between / 3600))
+ h := between / 3600
trans = translator.GlobalTrans.Tr(la, "ui.dates.x_hours_ago")
- return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(h, 'f', 0, 64))
+ return strings.ReplaceAll(trans, "{{count}}", strconv.FormatInt(h, 10))
}
if between >= 3600*24 &&
diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go
index 22761c521..9c7f6ec41 100644
--- a/internal/base/validator/validator.go
+++ b/internal/base/validator/validator.go
@@ -142,7 +142,7 @@ func Sanitizer(fl validator.FieldLevel) (res bool) {
switch field.Kind() {
case reflect.String:
filter := bluemonday.UGCPolicy()
- content := strings.Replace(filter.Sanitize(field.String()), "&", "&", -1)
+ content := strings.ReplaceAll(filter.Sanitize(field.String()), "&", "&")
field.SetString(content)
return true
case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
diff --git a/internal/cli/build.go b/internal/cli/build.go
index a5a4d938e..efc876e31 100644
--- a/internal/cli/build.go
+++ b/internal/cli/build.go
@@ -34,7 +34,6 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/apache/answer/pkg/dir"
"github.com/apache/answer/pkg/writer"
- "github.com/apache/answer/ui"
"github.com/segmentfault/pacman/log"
"gopkg.in/yaml.v3"
)
@@ -300,50 +299,6 @@ func copyUIFiles(b *buildingMaterial) (err error) {
return nil
}
-// overwriteIndexTs overwrites index.ts file in ui/src/plugins/ dir
-func overwriteIndexTs(b *buildingMaterial) (err error) {
- localUIPluginDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui/src/plugins/")
-
- folders, err := getFolders(localUIPluginDir)
- if err != nil {
- return fmt.Errorf("failed to get folders: %w", err)
- }
-
- content := generateIndexTsContent(folders)
- err = os.WriteFile(filepath.Join(localUIPluginDir, "index.ts"), []byte(content), 0644)
- if err != nil {
- return fmt.Errorf("failed to write index.ts: %w", err)
- }
- return nil
-}
-
-func getFolders(dir string) ([]string, error) {
- var folders []string
- files, err := os.ReadDir(dir)
- if err != nil {
- return nil, err
- }
- for _, file := range files {
- if file.IsDir() && file.Name() != "builtin" {
- folders = append(folders, file.Name())
- }
- }
- return folders, nil
-}
-
-func generateIndexTsContent(folders []string) string {
- builder := &strings.Builder{}
- builder.WriteString("export default null;\n")
- // Line 2:1: Delete `⏎` prettier/prettier
- if len(folders) > 0 {
- builder.WriteString("\n")
- }
- for _, folder := range folders {
- builder.WriteString(fmt.Sprintf("export { default as %s } from '%s';\n", folder, folder))
- }
- return builder.String()
-}
-
// buildUI run pnpm install and pnpm build commands to build ui
func buildUI(b *buildingMaterial) (err error) {
localUIBuildDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui")
@@ -362,13 +317,6 @@ func buildUI(b *buildingMaterial) (err error) {
return nil
}
-func replaceNecessaryFile(b *buildingMaterial) (err error) {
- fmt.Printf("try to replace ui build directory\n")
- uiBuildDir := filepath.Join(b.tmpDir, "vendor/github.com/apache/answer/ui")
- err = copyDirEntries(ui.Build, ".", uiBuildDir)
- return err
-}
-
// mergeI18nFiles merge i18n files
func mergeI18nFiles(b *buildingMaterial) (err error) {
fmt.Printf("try to merge i18n files\n")
diff --git a/internal/cli/config.go b/internal/cli/config.go
index ecb62a13c..e2445c590 100644
--- a/internal/cli/config.go
+++ b/internal/cli/config.go
@@ -42,7 +42,9 @@ func SetDefaultConfig(dbConf *data.Database, cacheConf *data.CacheConf, field *C
if err != nil {
return err
}
- defer db.Close()
+ defer func() {
+ _ = db.Close()
+ }()
cache, cacheCleanup, err := data.NewCache(cacheConf)
if err != nil {
@@ -50,7 +52,7 @@ func SetDefaultConfig(dbConf *data.Database, cacheConf *data.CacheConf, field *C
}
defer func() {
if cache != nil {
- cache.Flush(context.Background())
+ _ = cache.Flush(context.Background())
cacheCleanup()
}
}()
diff --git a/internal/cli/dump.go b/internal/cli/dump.go
index e5d528212..e63ef1a93 100644
--- a/internal/cli/dump.go
+++ b/internal/cli/dump.go
@@ -34,7 +34,9 @@ func DumpAllData(dataConf *data.Database, dumpDataPath string) error {
if err != nil {
return err
}
- defer db.Close()
+ defer func() {
+ _ = db.Close()
+ }()
if err = db.Ping(); err != nil {
return err
}
diff --git a/internal/cli/install_check.go b/internal/cli/install_check.go
index 9326e069f..c3fadcaba 100644
--- a/internal/cli/install_check.go
+++ b/internal/cli/install_check.go
@@ -43,7 +43,9 @@ func CheckDBConnection(dataConf *data.Database) bool {
fmt.Printf("connection database failed: %s\n", err)
return false
}
- defer db.Close()
+ defer func() {
+ _ = db.Close()
+ }()
if err = db.Ping(); err != nil {
fmt.Printf("connection ping database failed: %s\n", err)
return false
@@ -59,7 +61,9 @@ func CheckDBTableExist(dataConf *data.Database) bool {
fmt.Printf("connection database failed: %s\n", err)
return false
}
- defer db.Close()
+ defer func() {
+ _ = db.Close()
+ }()
if err = db.Ping(); err != nil {
fmt.Printf("connection ping database failed: %s\n", err)
return false
diff --git a/internal/cli/reset_password.go b/internal/cli/reset_password.go
index 2a7d1af4c..dbb3422af 100644
--- a/internal/cli/reset_password.go
+++ b/internal/cli/reset_password.go
@@ -77,7 +77,9 @@ func ResetPassword(ctx context.Context, dataDirPath string, opts *ResetPasswordO
if err != nil {
return fmt.Errorf("connect database failed: %w", err)
}
- defer db.Close()
+ defer func() {
+ _ = db.Close()
+ }()
cache, cacheCleanup, err := data.NewCache(config.Data.Cache)
if err != nil {
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index 83cd58c32..801e129c2 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -162,11 +162,9 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = siteInfo.General.SiteUrl
- UrlUseTitle := false
- if siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
- siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID {
- UrlUseTitle = true
- }
+ UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
+ siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
+
siteInfo.Title = ""
tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
"data": data,
@@ -205,11 +203,9 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
siteInfo.Canonical = fmt.Sprintf("%s/questions?page=%d", siteInfo.General.SiteUrl, page)
}
- UrlUseTitle := false
- if siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
- siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID {
- UrlUseTitle = true
- }
+ UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
+ siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
+
siteInfo.Title = fmt.Sprintf("%s - %s", translator.Tr(handler.GetLang(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
"data": data,
@@ -371,11 +367,8 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
return
}
- UrlUseTitle := false
- if siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
- siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID {
- UrlUseTitle = true
- }
+ UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
+ siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
//related question
userID := middleware.GetLoginUserIDFromContext(ctx)
@@ -434,7 +427,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
for _, tag := range detail.Tags {
tags = append(tags, tag.DisplayName)
}
- siteInfo.Keywords = strings.Replace(strings.Trim(fmt.Sprint(tags), "[]"), " ", ",", -1)
+ siteInfo.Keywords = strings.ReplaceAll(strings.Trim(fmt.Sprint(tags), "[]"), " ", ",")
siteInfo.Title = fmt.Sprintf("%s - %s", detail.Title, siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "question-detail.html", siteInfo, gin.H{
"id": id,
@@ -504,11 +497,9 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
}
siteInfo.Keywords = tagInfo.DisplayName
- UrlUseTitle := false
- if siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
- siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID {
- UrlUseTitle = true
- }
+ UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
+ siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
+
siteInfo.Title = fmt.Sprintf("'%s' %s - %s", tagInfo.DisplayName, translator.Tr(handler.GetLang(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "tag-detail.html", siteInfo, gin.H{
"tag": tagInfo,
@@ -570,11 +561,9 @@ func (tc *TemplateController) Page404(ctx *gin.Context) {
}
func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteInfo *schema.TemplateSiteInfoResp, data gin.H) {
- var (
- prefix = ""
- cssPath = ""
- scriptPath = make([]string, len(tc.scriptPath))
- )
+ prefix := ""
+ cssPath := ""
+ scriptPath := make([]string, len(tc.scriptPath))
_ = plugin.CallCDN(func(fn plugin.CDN) error {
prefix = fn.GetStaticPrefix()
@@ -612,7 +601,7 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
data["description"] = siteInfo.Description
data["language"] = handler.GetLang(ctx)
data["timezone"] = siteInfo.Interface.TimeZone
- language := strings.Replace(siteInfo.Interface.Language, "_", "-", -1)
+ language := strings.ReplaceAll(siteInfo.Interface.Language, "_", "-")
data["lang"] = language
data["HeadCode"] = siteInfo.CustomCssHtml.CustomHead
data["HeaderCode"] = siteInfo.CustomCssHtml.CustomHeader
diff --git a/internal/controller/template_render/controller.go b/internal/controller/template_render/controller.go
index 5f802fa76..3412010db 100644
--- a/internal/controller/template_render/controller.go
+++ b/internal/controller/template_render/controller.go
@@ -101,7 +101,6 @@ func Paginator(page, pageSize int, nums int64) *schema.Paginator {
case page >= 3 && totalpages > 5:
start := page - 3 + 1
pages = make([]int, 5)
- prevpage = page - 3
for i := range pages {
pages[i] = start + i
}
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index 49b9b23c6..e4a3b3d3b 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -151,7 +151,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
resp, err := uc.userService.EmailLogin(ctx, req)
if err != nil {
- _, _ = uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionPassword, ctx.ClientIP())
+ uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionPassword, ctx.ClientIP())
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "e_mail",
ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
@@ -404,10 +404,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
}
- _, err := uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEditUserinfo, req.UserID)
- if err != nil {
- log.Error(err)
- }
+ uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionEditUserinfo, req.UserID)
}
oldPassVerification, err := uc.userService.UserModifyPassWordVerification(ctx, req)
diff --git a/internal/install/install_from_env.go b/internal/install/install_from_env.go
index c05d2aaba..02bc76381 100644
--- a/internal/install/install_from_env.go
+++ b/internal/install/install_from_env.go
@@ -22,6 +22,7 @@ package install
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"net/http/httptest"
@@ -143,7 +144,7 @@ func requestAPI(req any, method, url string, handlerFunc gin.HandlerFunc) error
}
handlerFunc(c)
if w.Code != http.StatusOK {
- return fmt.Errorf(gjson.Get(w.Body.String(), "msg").String())
+ return errors.New(gjson.Get(w.Body.String(), "msg").String())
}
return nil
}
diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go
index 9caa28ed1..2fbfbb7fd 100644
--- a/internal/migrations/migrations.go
+++ b/internal/migrations/migrations.go
@@ -147,7 +147,9 @@ func Migrate(debug bool, dbConf *data.Database, cacheConf *data.CacheConf, upgra
fmt.Println("new database failed: ", err.Error())
return err
}
- defer engine.Close()
+ defer func() {
+ _ = engine.Close()
+ }()
currentDBVersion, err := GetCurrentDBVersion(engine)
if err != nil {
diff --git a/internal/migrations/v22.go b/internal/migrations/v22.go
index e7177deae..5367f5d46 100644
--- a/internal/migrations/v22.go
+++ b/internal/migrations/v22.go
@@ -61,6 +61,9 @@ func addBadges(ctx context.Context, x *xorm.Engine) (err error) {
if exist {
badge.ID = beans.ID
_, err = x.Context(ctx).ID(beans.ID).Update(badge)
+ if err != nil {
+ return fmt.Errorf("update badge failed: %w", err)
+ }
continue
}
badge.ID, err = uniqueIDRepo.GenUniqueIDStr(ctx, new(entity.Badge).TableName())
diff --git a/internal/migrations/v24.go b/internal/migrations/v24.go
index a488679f6..86d624254 100644
--- a/internal/migrations/v24.go
+++ b/internal/migrations/v24.go
@@ -66,8 +66,5 @@ func addQuestionLinkedCount(ctx context.Context, x *xorm.Engine) error {
}
}
- type Question struct {
- LinkedCount int `xorm:"not null default 0 INT(11) linked_count"`
- }
return x.Context(ctx).Sync(new(entity.Question))
}
diff --git a/internal/repo/answer/answer_repo.go b/internal/repo/answer/answer_repo.go
index c5447befc..0f1ae8146 100644
--- a/internal/repo/answer/answer_repo.go
+++ b/internal/repo/answer/answer_repo.go
@@ -503,6 +503,7 @@ func (ar *answerRepo) updateSearch(ctx context.Context, answerID string) (err er
err = st.Find(&tagListList)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return
}
for _, tag := range tagListList {
tags = append(tags, tag.TagID)
diff --git a/internal/repo/plugin_config/plugin_user_config_repo.go b/internal/repo/plugin_config/plugin_user_config_repo.go
index d14442a56..df5ae29b8 100644
--- a/internal/repo/plugin_config/plugin_user_config_repo.go
+++ b/internal/repo/plugin_config/plugin_user_config_repo.go
@@ -71,7 +71,7 @@ func (ur *pluginUserConfigRepo) SaveUserPluginConfig(ctx context.Context, userID
return nil, nil
})
if err != nil {
- err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
return nil
}
diff --git a/internal/repo/repo_test/repo_main_test.go b/internal/repo/repo_test/repo_main_test.go
index a59ae6269..919ca94e8 100644
--- a/internal/repo/repo_test/repo_main_test.go
+++ b/internal/repo/repo_test/repo_main_test.go
@@ -77,7 +77,7 @@ func TestMain(t *testing.M) {
dbSetting = dbSettingMapping[string(schemas.SQLITE)]
}
if dbSetting.Driver == string(schemas.SQLITE) {
- os.RemoveAll(dbSetting.Connection)
+ _ = os.RemoveAll(dbSetting.Connection)
}
defer func() {
diff --git a/internal/repo/tag/tag_rel_repo.go b/internal/repo/tag/tag_rel_repo.go
index 3634c97a7..a52b1bf54 100644
--- a/internal/repo/tag/tag_rel_repo.go
+++ b/internal/repo/tag/tag_rel_repo.go
@@ -198,6 +198,7 @@ func (tr *tagRelRepo) GetTagRelDefaultStatusByObjectID(ctx context.Context, obje
exist, err := tr.data.DB.Context(ctx).ID(objectID).Cols("show", "status").Get(&question)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
+ return
}
if exist && (question.Show == entity.QuestionHide || question.Status == entity.QuestionStatusDeleted) {
return entity.TagRelStatusHide, nil
diff --git a/internal/service/action/captcha_service.go b/internal/service/action/captcha_service.go
index 04f8b16ac..aefb70519 100644
--- a/internal/service/action/captcha_service.go
+++ b/internal/service/action/captcha_service.go
@@ -106,11 +106,11 @@ func (cs *CaptchaService) ActionRecordVerifyCaptcha(
return pass
}
-func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, unit string) (int, error) {
+func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string, unit string) {
info, err := cs.captchaRepo.GetActionType(ctx, unit, actionType)
if err != nil {
log.Error(err)
- return 0, err
+ return
}
amount := 1
if info != nil {
@@ -118,9 +118,8 @@ func (cs *CaptchaService) ActionRecordAdd(ctx context.Context, actionType string
}
err = cs.captchaRepo.SetActionType(ctx, unit, actionType, "", amount)
if err != nil {
- return 0, err
+ log.Error(err)
}
- return amount, nil
}
func (cs *CaptchaService) ActionRecordDel(ctx context.Context, actionType string, unit string) {
diff --git a/internal/service/action/captcha_strategy.go b/internal/service/action/captcha_strategy.go
index 3befda821..b423b583b 100644
--- a/internal/service/action/captcha_strategy.go
+++ b/internal/service/action/captcha_strategy.go
@@ -89,7 +89,9 @@ func (cs *CaptchaService) CaptchaActionPassword(ctx context.Context, unit string
return false
}
if now-actionInfo.LastTime != 0 && now-actionInfo.LastTime > setTime {
- cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionPassword, "", 0)
+ if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionPassword, "", 0); err != nil {
+ log.Error(err)
+ }
}
return true
}
@@ -105,7 +107,9 @@ func (cs *CaptchaService) CaptchaActionEditUserinfo(ctx context.Context, unit st
return false
}
if now-actionInfo.LastTime != 0 && now-actionInfo.LastTime > setTime {
- cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionEditUserinfo, "", 0)
+ if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionEditUserinfo, "", 0); err != nil {
+ log.Error(err)
+ }
}
return true
}
@@ -154,10 +158,7 @@ func (cs *CaptchaService) CaptchaActionEdit(ctx context.Context, unit string, ac
return true
}
setNum := 10
- if actionInfo.Num >= setNum {
- return false
- }
- return true
+ return actionInfo.Num < setNum
}
func (cs *CaptchaService) CaptchaActionInvitationAnswer(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
@@ -165,10 +166,7 @@ func (cs *CaptchaService) CaptchaActionInvitationAnswer(ctx context.Context, uni
return true
}
setNum := 30
- if actionInfo.Num >= setNum {
- return false
- }
- return true
+ return actionInfo.Num < setNum
}
func (cs *CaptchaService) CaptchaActionSearch(ctx context.Context, unit string, actionInfo *entity.ActionRecordInfo) bool {
@@ -182,7 +180,9 @@ func (cs *CaptchaService) CaptchaActionSearch(ctx context.Context, unit string,
return false
}
if now-actionInfo.LastTime > setTime {
- cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionSearch, "", 0)
+ if err := cs.captchaRepo.SetActionType(ctx, unit, entity.CaptchaActionSearch, "", 0); err != nil {
+ log.Error(err)
+ }
}
return true
}
@@ -218,8 +218,5 @@ func (cs *CaptchaService) CaptchaActionVote(ctx context.Context, unit string, ac
return true
}
setNum := 40
- if actionInfo.Num >= setNum {
- return false
- }
- return true
+ return actionInfo.Num < setNum
}
diff --git a/internal/service/badge/badge_event_handler.go b/internal/service/badge/badge_event_handler.go
index cc161f6ad..24cabf29b 100644
--- a/internal/service/badge/badge_event_handler.go
+++ b/internal/service/badge/badge_event_handler.go
@@ -32,7 +32,6 @@ import (
type BadgeEventService struct {
data *data.Data
eventQueueService event_queue.EventQueueService
- badgeAwardRepo BadgeAwardRepo
badgeRepo BadgeRepo
eventRuleRepo EventRuleRepo
badgeAwardService *BadgeAwardService
diff --git a/internal/service/badge/badge_service.go b/internal/service/badge/badge_service.go
index 03b8a8774..ebb90450a 100644
--- a/internal/service/badge/badge_service.go
+++ b/internal/service/badge/badge_service.go
@@ -206,10 +206,8 @@ func (b *BadgeService) ListPaged(ctx context.Context, req *schema.GetBadgeListPa
resp = make([]*schema.GetBadgeListPagedResp, len(badges))
general, siteErr := b.siteInfoCommonService.GetSiteGeneral(ctx)
- var baseURL = ""
- if siteErr != nil {
- baseURL = ""
- } else {
+ baseURL := ""
+ if siteErr == nil {
baseURL = general.SiteUrl
}
@@ -246,31 +244,23 @@ func (b *BadgeService) searchByName(ctx context.Context, name string) (result []
// GetBadgeInfo get badge info
func (b *BadgeService) GetBadgeInfo(ctx *gin.Context, id string, userID string) (info *schema.GetBadgeInfoResp, err error) {
- var (
- badge *entity.Badge
- earnedTotal int64 = 0
- exists = false
- )
-
- badge, exists, err = b.badgeRepo.GetByID(ctx, id)
+ badge, exists, err := b.badgeRepo.GetByID(ctx, id)
if err != nil {
- return
+ return nil, err
}
if !exists || badge.Status == entity.BadgeStatusInactive {
- err = errors.BadRequest(reason.BadgeObjectNotFound)
- return
+ return nil, errors.BadRequest(reason.BadgeObjectNotFound)
}
+ var earnedTotal int64
if len(userID) > 0 {
earnedTotal = b.badgeAwardRepo.CountByUserIdAndBadgeId(ctx, userID, badge.ID)
}
+ baseURL := ""
general, siteErr := b.siteInfoCommonService.GetSiteGeneral(ctx)
- var baseURL = ""
- if siteErr != nil {
- baseURL = ""
- } else {
+ if siteErr == nil {
baseURL = general.SiteUrl
}
diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go
index 1d1d1af39..da15fae5d 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -277,7 +277,7 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que
if tagerr != nil {
return errorlist, tagerr
}
- if !req.QuestionPermission.CanUseReservedTag {
+ if !req.CanUseReservedTag {
taglist, err := qs.AddQuestionCheckTags(ctx, Tags)
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(taglist, ","))
@@ -350,7 +350,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
if tagerr != nil {
return questionInfo, tagerr
}
- if !req.QuestionPermission.CanUseReservedTag {
+ if !req.CanUseReservedTag {
taglist, err := qs.AddQuestionCheckTags(ctx, tags)
errMsg := fmt.Sprintf(`"%s" can only be used by moderators.`,
strings.Join(taglist, ","))
@@ -1397,13 +1397,17 @@ func (qs *QuestionService) GetQuestionsByTitle(ctx context.Context, title string
for _, question := range res {
questionIDs = append(questionIDs, question.ID)
}
- questions, err = qs.questionRepo.FindByID(ctx, questionIDs)
+ var questionErr error
+ questions, questionErr = qs.questionRepo.FindByID(ctx, questionIDs)
+ if questionErr != nil {
+ return resp, questionErr
+ }
} else {
- questions, err = qs.questionRepo.GetQuestionsByTitle(ctx, title, 10)
- }
-
- if err != nil {
- return resp, err
+ var questionErr error
+ questions, questionErr = qs.questionRepo.GetQuestionsByTitle(ctx, title, 10)
+ if questionErr != nil {
+ return resp, questionErr
+ }
}
for _, question := range questions {
item := &schema.QuestionBaseInfo{}
@@ -1723,7 +1727,7 @@ func (qs *QuestionService) SitemapCron(ctx context.Context) {
log.Error(err)
return
}
- ctx = context.WithValue(ctx, constant.ShortIDFlag, siteSeo.IsShortLink())
+ ctx = context.WithValue(ctx, constant.ShortIDContextKey, siteSeo.IsShortLink())
qs.questioncommon.SitemapCron(ctx)
}
diff --git a/internal/service/content/revision_service.go b/internal/service/content/revision_service.go
index 1aed4cf82..a5cefeb41 100644
--- a/internal/service/content/revision_service.go
+++ b/internal/service/content/revision_service.go
@@ -41,7 +41,6 @@ import (
"github.com/apache/answer/internal/service/review"
"github.com/apache/answer/internal/service/revision"
"github.com/apache/answer/internal/service/tag_common"
- tagcommon "github.com/apache/answer/internal/service/tag_common"
usercommon "github.com/apache/answer/internal/service/user_common"
"github.com/apache/answer/pkg/converter"
"github.com/apache/answer/pkg/htmltext"
@@ -62,7 +61,7 @@ type RevisionService struct {
questionRepo questioncommon.QuestionRepo
answerRepo answercommon.AnswerRepo
tagRepo tag_common.TagRepo
- tagCommon *tagcommon.TagCommonService
+ tagCommon *tag_common.TagCommonService
notificationQueueService notice_queue.NotificationQueueService
activityQueueService activity_queue.ActivityQueueService
reportRepo report_common.ReportRepo
@@ -79,7 +78,7 @@ func NewRevisionService(
questionRepo questioncommon.QuestionRepo,
answerRepo answercommon.AnswerRepo,
tagRepo tag_common.TagRepo,
- tagCommon *tagcommon.TagCommonService,
+ tagCommon *tag_common.TagCommonService,
notificationQueueService notice_queue.NotificationQueueService,
activityQueueService activity_queue.ActivityQueueService,
reportRepo report_common.ReportRepo,
diff --git a/internal/service/content/search_service.go b/internal/service/content/search_service.go
index 98add0938..ccafcd82c 100644
--- a/internal/service/content/search_service.go
+++ b/internal/service/content/search_service.go
@@ -93,6 +93,9 @@ func (ss *SearchService) searchByPlugin(ctx context.Context, finder plugin.Searc
} else if cond.SearchAnswer() {
res, resp.Total, err = finder.SearchAnswers(ctx, cond.Convert2PluginSearchCond(dto.Page, dto.Size, dto.Order))
}
+ if err != nil {
+ return resp, err
+ }
resp.SearchResults, err = ss.searchRepo.ParseSearchPluginResult(ctx, res, cond.Words)
return resp, err
diff --git a/internal/service/content/vote_service.go b/internal/service/content/vote_service.go
index 92f0c9962..aa6150497 100644
--- a/internal/service/content/vote_service.go
+++ b/internal/service/content/vote_service.go
@@ -26,8 +26,6 @@ import (
"github.com/apache/answer/internal/service/event_queue"
- "github.com/apache/answer/internal/service/activity_common"
-
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/pager"
@@ -64,7 +62,6 @@ type VoteService struct {
answerRepo answercommon.AnswerRepo
commentCommonRepo comment_common.CommentCommonRepo
objectService *object_info.ObjService
- activityRepo activity_common.ActivityRepo
eventQueueService event_queue.EventQueueService
}
diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go
index d9198e6db..91f0e338a 100644
--- a/internal/service/dashboard/dashboard_service.go
+++ b/internal/service/dashboard/dashboard_service.go
@@ -274,7 +274,9 @@ func (ds *dashboardService) remoteVersion(ctx context.Context) string {
log.Errorf("request remote version failed: %s", err)
return ""
}
- defer resp.Body.Close()
+ defer func() {
+ _ = resp.Body.Close()
+ }()
respByte, err := io.ReadAll(resp.Body)
if err != nil {
@@ -358,7 +360,7 @@ func (ds *dashboardService) GetDatabaseSize() (dbSize string) {
if err != nil {
log.Warnf("get db size failed: %s", err)
} else {
- if res != nil && len(res) > 0 && res[0]["db_size"] != nil {
+ if len(res) > 0 && res[0]["db_size"] != nil {
dbSizeStr, _ := res[0]["db_size"].(string)
dbSize = dir.FormatFileSize(converter.StringToInt64(dbSizeStr))
}
@@ -370,7 +372,7 @@ func (ds *dashboardService) GetDatabaseSize() (dbSize string) {
if err != nil {
log.Warnf("get db size failed: %s", err)
} else {
- if res != nil && len(res) > 0 && res[0]["db_size"] != nil {
+ if len(res) > 0 && res[0]["db_size"] != nil {
dbSizeStr, _ := res[0]["db_size"].(int32)
dbSize = dir.FormatFileSize(int64(dbSizeStr))
}
diff --git a/internal/service/file_record/file_record_service.go b/internal/service/file_record/file_record_service.go
index 29097ba8c..aa526f014 100644
--- a/internal/service/file_record/file_record_service.go
+++ b/internal/service/file_record/file_record_service.go
@@ -174,7 +174,6 @@ func (fs *FileRecordService) PurgeDeletedFiles(ctx context.Context) {
if err != nil {
log.Errorf("create deleted directory error: %v", err)
}
- return
}
func (fs *FileRecordService) DeleteAndMoveFileRecord(ctx context.Context, fileRecord *entity.FileRecord) error {
diff --git a/internal/service/importer/importer_service.go b/internal/service/importer/importer_service.go
index 45aabf39b..c7673ffb5 100644
--- a/internal/service/importer/importer_service.go
+++ b/internal/service/importer/importer_service.go
@@ -62,8 +62,7 @@ type ImporterFunc struct {
}
func (ipfunc *ImporterFunc) AddQuestion(ctx context.Context, questionInfo plugin.QuestionImporterInfo) (err error) {
- ipfunc.importerService.ImportQuestion(ctx, questionInfo)
- return nil
+ return ipfunc.importerService.ImportQuestion(ctx, questionInfo)
}
func (ip *ImporterService) NewImporterFunc() plugin.ImporterFunc {
@@ -84,7 +83,7 @@ func (ip *ImporterService) ImportQuestion(ctx context.Context, questionInfo plug
return err
}
if !exist {
- return fmt.Errorf("User not found")
+ return fmt.Errorf("user not found")
}
// To limit rate, remove the following code from comment: Part 2/2
diff --git a/internal/service/meta/meta_service.go b/internal/service/meta/meta_service.go
index 4b3104197..c1ca7c619 100644
--- a/internal/service/meta/meta_service.go
+++ b/internal/service/meta/meta_service.go
@@ -97,7 +97,8 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda
return nil, err
}
var event *schema.EventMsg
- if objectType == constant.AnswerObjectType {
+ switch objectType {
+ case constant.AnswerObjectType:
answerInfo, exist, err := ms.answerRepo.GetAnswer(ctx, req.ObjectID)
if err != nil {
return nil, err
@@ -107,7 +108,7 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda
}
event = schema.NewEvent(constant.EventAnswerReact, req.UserID).TID(answerInfo.ID).
AID(answerInfo.ID, answerInfo.UserID)
- } else if objectType == constant.QuestionObjectType {
+ case constant.QuestionObjectType:
questionInfo, exist, err := ms.questionRepo.GetQuestion(ctx, req.ObjectID)
if err != nil {
return nil, err
@@ -117,7 +118,7 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda
}
event = schema.NewEvent(constant.EventQuestionReact, req.UserID).TID(questionInfo.ID).
QID(questionInfo.ID, questionInfo.UserID)
- } else {
+ default:
return nil, myErrors.BadRequest(reason.ObjectNotFound)
}
@@ -159,9 +160,10 @@ func (ms *MetaService) AddOrUpdateReaction(ctx context.Context, req *schema.Upda
// updateReaction update reaction
func (ms *MetaService) updateReaction(req *schema.UpdateReactionReq, reactions *schema.ReactionsSummaryMeta) {
- if req.Reaction == "activate" {
+ switch req.Reaction {
+ case "activate":
reactions.AddReactionSummary(req.Emoji, req.UserID)
- } else if req.Reaction == "deactivate" {
+ case "deactivate":
reactions.RemoveReactionSummary(req.Emoji, req.UserID)
}
}
diff --git a/internal/service/notification/invite_answer_notification.go b/internal/service/notification/invite_answer_notification.go
index 4e7c051cd..f68feb067 100644
--- a/internal/service/notification/invite_answer_notification.go
+++ b/internal/service/notification/invite_answer_notification.go
@@ -70,7 +70,7 @@ func (ns *ExternalNotificationService) sendInviteAnswerNotificationEmail(ctx con
// If receiver has set language, use it to send email.
if len(lang) > 0 {
- ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(lang))
+ ctx = context.WithValue(ctx, constant.AcceptLanguageContextKey, i18n.Language(lang))
}
title, body, err := ns.emailService.NewInviteAnswerTemplate(ctx, rawData)
if err != nil {
diff --git a/internal/service/notification/new_answer_notification.go b/internal/service/notification/new_answer_notification.go
index 91b7e2ae3..c54fd961c 100644
--- a/internal/service/notification/new_answer_notification.go
+++ b/internal/service/notification/new_answer_notification.go
@@ -70,7 +70,7 @@ func (ns *ExternalNotificationService) sendNewAnswerNotificationEmail(ctx contex
// If receiver has set language, use it to send email.
if len(lang) > 0 {
- ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(lang))
+ ctx = context.WithValue(ctx, constant.AcceptLanguageContextKey, i18n.Language(lang))
}
title, body, err := ns.emailService.NewAnswerTemplate(ctx, rawData)
if err != nil {
diff --git a/internal/service/notification/new_comment_notification.go b/internal/service/notification/new_comment_notification.go
index 3ec99d13c..e622ed4f7 100644
--- a/internal/service/notification/new_comment_notification.go
+++ b/internal/service/notification/new_comment_notification.go
@@ -69,7 +69,7 @@ func (ns *ExternalNotificationService) sendNewCommentNotificationEmail(ctx conte
}
// If receiver has set language, use it to send email.
if len(lang) > 0 {
- ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(lang))
+ ctx = context.WithValue(ctx, constant.AcceptLanguageContextKey, i18n.Language(lang))
}
title, body, err := ns.emailService.NewCommentTemplate(ctx, rawData)
if err != nil {
diff --git a/internal/service/notification/new_question_notification.go b/internal/service/notification/new_question_notification.go
index 8a12d0b8c..debfb8c27 100644
--- a/internal/service/notification/new_question_notification.go
+++ b/internal/service/notification/new_question_notification.go
@@ -176,7 +176,7 @@ func (ns *ExternalNotificationService) sendNewQuestionNotificationEmail(ctx cont
}
// If receiver has set language, use it to send email.
if len(userInfo.Language) > 0 {
- ctx = context.WithValue(ctx, constant.AcceptLanguageFlag, i18n.Language(userInfo.Language))
+ ctx = context.WithValue(ctx, constant.AcceptLanguageContextKey, i18n.Language(userInfo.Language))
}
title, body, err := ns.emailService.NewQuestionTemplate(ctx, rawData)
if err != nil {
diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go
index 09d871351..0369d4557 100644
--- a/internal/service/notification/notification_service.go
+++ b/internal/service/notification/notification_service.go
@@ -82,8 +82,8 @@ func (ns *NotificationService) GetRedDot(ctx context.Context, req *schema.GetRed
achievementKey := fmt.Sprintf(constant.RedDotCacheKey, constant.NotificationTypeAchievement, req.UserID)
redBot := &schema.RedDot{}
- redBot.Inbox, _, err = ns.data.Cache.GetInt64(ctx, inboxKey)
- redBot.Achievement, _, err = ns.data.Cache.GetInt64(ctx, achievementKey)
+ redBot.Inbox, _, _ = ns.data.Cache.GetInt64(ctx, inboxKey)
+ redBot.Achievement, _, _ = ns.data.Cache.GetInt64(ctx, achievementKey)
// get review amount
if req.CanReviewAnswer || req.CanReviewQuestion || req.CanReviewTag {
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 2bceb6298..55d638424 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -206,6 +206,9 @@ func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.N
}
if req.ObjectInfo.ObjectType == constant.BadgeAwardObjectType {
err = ns.AddBadgeAwardAlertCache(ctx, info.UserID, info.ID, req.ObjectInfo.ObjectMap["badge_id"])
+ if err != nil {
+ log.Error("AddBadgeAwardAlertCache Error", err.Error())
+ }
}
go ns.SendNotificationToAllFollower(ctx, msg, questionID)
diff --git a/internal/service/report_handle/report_handle.go b/internal/service/report_handle/report_handle.go
index 27cffb111..a04681663 100644
--- a/internal/service/report_handle/report_handle.go
+++ b/internal/service/report_handle/report_handle.go
@@ -112,6 +112,9 @@ func (rh *ReportHandle) updateReportedAnswerReport(ctx context.Context, report *
NoNeedReview: true,
})
}
+ if err != nil {
+ return err
+ }
return nil
}
@@ -128,5 +131,8 @@ func (rh *ReportHandle) updateReportedCommentReport(ctx context.Context, report
UserID: req.UserID,
})
}
+ if err != nil {
+ return err
+ }
return nil
}
diff --git a/internal/service/review/review_service.go b/internal/service/review/review_service.go
index 40089fb2d..a23b9ee43 100644
--- a/internal/service/review/review_service.go
+++ b/internal/service/review/review_service.go
@@ -469,7 +469,6 @@ func (cs *ReviewService) notificationCommentOnTheQuestion(ctx context.Context, c
cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID,
objInfo.ObjectCreatorUserID, comment.ID, comment.UserID, htmltext.FetchExcerpt(comment.ParsedText, "...", 240))
}
- return
}
func (cs *ReviewService) notificationCommentReply(ctx context.Context, replyUserID, commentID, commentUserID,
diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go
index cf43d68d5..f355d09f4 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -345,7 +345,7 @@ func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema
return nil, err
}
privilegeOptions := schema.DefaultPrivilegeOptions
- if privilege.CustomPrivileges != nil && len(privilege.CustomPrivileges) > 0 {
+ if len(privilege.CustomPrivileges) > 0 {
privilegeOptions = append(privilegeOptions, &schema.PrivilegeOption{
Level: schema.PrivilegeLevelCustom,
LevelDesc: reason.PrivilegeLevelCustomDesc,
@@ -358,7 +358,7 @@ func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema
Options: s.translatePrivilegeOptions(ctx, privilegeOptions),
SelectedLevel: schema.PrivilegeLevel3,
}
- if privilege != nil && privilege.Level > 0 {
+ if privilege.Level > 0 {
resp.SelectedLevel = privilege.Level
}
return resp, nil
diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go
index 2ae5369df..30029193b 100644
--- a/internal/service/uploader/upload.go
+++ b/internal/service/uploader/upload.go
@@ -119,7 +119,9 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context, userID string) (ur
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
- file.Close()
+ defer func() {
+ _ = file.Close()
+ }()
fileExt := strings.ToLower(path.Ext(fileHeader.Filename))
if _, ok := plugin.DefaultFileTypeCheckMapping[plugin.UserAvatar][fileExt]; !ok {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
@@ -148,12 +150,12 @@ func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, si
thumbFileName := fmt.Sprintf("%d_%d@%s", size, size, fileName)
thumbFilePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, constant.AvatarThumbSubPath, thumbFileName)
- avatarFile, err := os.ReadFile(thumbFilePath)
+ _, err = os.ReadFile(thumbFilePath)
if err == nil {
return thumbFilePath, nil
}
filePath := fmt.Sprintf("%s/%s/%s", us.serviceConfig.UploadPath, constant.AvatarSubPath, fileName)
- avatarFile, err = os.ReadFile(filePath)
+ avatarFile, err := os.ReadFile(filePath)
if err != nil {
return "", errors.NotFound(reason.UnknownError).WithError(err)
}
@@ -179,7 +181,9 @@ func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, si
if err != nil {
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
- defer out.Close()
+ defer func() {
+ _ = out.Close()
+ }()
thumbReader := bytes.NewReader(buf.Bytes())
if _, err = io.Copy(out, thumbReader); err != nil {
@@ -208,7 +212,9 @@ func (us *uploaderService) UploadPostFile(ctx *gin.Context, userID string) (
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
- defer file.Close()
+ defer func() {
+ _ = file.Close()
+ }()
if checker.IsUnAuthorizedExtension(fileHeader.Filename, siteWrite.AuthorizedImageExtensions) {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
@@ -244,7 +250,9 @@ func (us *uploaderService) UploadPostAttachment(ctx *gin.Context, userID string)
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
- defer file.Close()
+ defer func() {
+ _ = file.Close()
+ }()
if checker.IsUnAuthorizedExtension(fileHeader.Filename, resp.AuthorizedAttachmentExtensions) {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
@@ -280,7 +288,9 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context, userID string) (
if err != nil {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
}
- file.Close()
+ defer func() {
+ _ = file.Close()
+ }()
fileExt := strings.ToLower(path.Ext(fileHeader.Filename))
if _, ok := plugin.DefaultFileTypeCheckMapping[plugin.AdminBranding][fileExt]; !ok {
return "", errors.BadRequest(reason.RequestFormatError).WithError(err)
@@ -316,7 +326,9 @@ func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.Fil
if err != nil {
return "", errors.InternalServer(reason.UnknownError).WithError(err).WithStack()
}
- defer src.Close()
+ defer func() {
+ _ = src.Close()
+ }()
if !checker.DecodeAndCheckImageFile(filePath, siteWrite.GetMaxImageMegapixel()) {
return "", errors.BadRequest(reason.UploadFileUnsupportedFileFormat)
diff --git a/internal/service/user_admin/user_backyard.go b/internal/service/user_admin/user_backyard.go
index 0f6ec81ed..6fce161ce 100644
--- a/internal/service/user_admin/user_backyard.go
+++ b/internal/service/user_admin/user_backyard.go
@@ -400,7 +400,7 @@ func (us *UserAdminService) EditUserProfile(ctx context.Context, req *schema.Edi
if req.UserID == req.LoginUserID {
return nil, errors.BadRequest(reason.AdminCannotEditTheirProfile)
}
- userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
+ _, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return nil, err
}
@@ -415,7 +415,7 @@ func (us *UserAdminService) EditUserProfile(ctx context.Context, req *schema.Edi
}), errors.BadRequest(reason.UsernameInvalid)
}
- userInfo, exist, err = us.userCommonService.GetByUsername(ctx, req.Username)
+ userInfo, exist, err := us.userCommonService.GetByUsername(ctx, req.Username)
if err != nil {
return nil, err
}
@@ -620,11 +620,12 @@ func (us *UserAdminService) SendUserActivation(ctx context.Context, req *schema.
}
func (us *UserAdminService) DeletePermanently(ctx context.Context, req *schema.DeletePermanentlyReq) (err error) {
- if req.Type == constant.DeletePermanentlyUsers {
+ switch req.Type {
+ case constant.DeletePermanentlyUsers:
return us.userRepo.DeletePermanentlyUsers(ctx)
- } else if req.Type == constant.DeletePermanentlyQuestions {
+ case constant.DeletePermanentlyQuestions:
return us.questionCommonRepo.DeletePermanentlyQuestions(ctx)
- } else if req.Type == constant.DeletePermanentlyAnswers {
+ case constant.DeletePermanentlyAnswers:
return us.answerCommonRepo.DeletePermanentlyAnswers(ctx)
}
diff --git a/internal/service/user_notification_config/user_notification_config_service.go b/internal/service/user_notification_config/user_notification_config_service.go
index 01da3ee2f..c40df55cf 100644
--- a/internal/service/user_notification_config/user_notification_config_service.go
+++ b/internal/service/user_notification_config/user_notification_config_service.go
@@ -68,21 +68,21 @@ func (us *UserNotificationConfigService) GetUserNotificationConfig(ctx context.C
func (us *UserNotificationConfigService) UpdateUserNotificationConfig(
ctx context.Context, req *schema.UpdateUserNotificationConfigReq) (err error) {
- req.NotificationConfig.Format()
+ req.Format()
err = us.userNotificationConfigRepo.Save(ctx,
- us.convertToEntity(ctx, req.UserID, constant.InboxSource, req.NotificationConfig.Inbox))
+ us.convertToEntity(ctx, req.UserID, constant.InboxSource, req.Inbox))
if err != nil {
return err
}
err = us.userNotificationConfigRepo.Save(ctx,
- us.convertToEntity(ctx, req.UserID, constant.AllNewQuestionSource, req.NotificationConfig.AllNewQuestion))
+ us.convertToEntity(ctx, req.UserID, constant.AllNewQuestionSource, req.AllNewQuestion))
if err != nil {
return err
}
err = us.userNotificationConfigRepo.Save(ctx,
us.convertToEntity(ctx, req.UserID, constant.AllNewQuestionForFollowingTagsSource,
- req.NotificationConfig.AllNewQuestionForFollowingTags))
+ req.AllNewQuestionForFollowingTags))
if err != nil {
return err
}
diff --git a/pkg/checker/file_type.go b/pkg/checker/file_type.go
index 51f687d6c..ac1fbcaf9 100644
--- a/pkg/checker/file_type.go
+++ b/pkg/checker/file_type.go
@@ -75,7 +75,9 @@ func decodeAndCheckImageFile(localFilePath string, maxImageMegapixel int, checke
log.Errorf("open file error: %v", err)
return false
}
- defer file.Close()
+ defer func() {
+ _ = file.Close()
+ }()
if err = checker(file, maxImageMegapixel); err != nil {
log.Errorf("check image format error: %v", err)
diff --git a/pkg/checker/password.go b/pkg/checker/password.go
index ac274f352..ac99fd433 100644
--- a/pkg/checker/password.go
+++ b/pkg/checker/password.go
@@ -20,6 +20,7 @@
package checker
import (
+ "errors"
"fmt"
"regexp"
"strings"
@@ -40,7 +41,7 @@ const (
// CheckPassword checks the password strength
func CheckPassword(password string) error {
if strings.Contains(password, " ") {
- return fmt.Errorf(PasswordCannotContainSpaces)
+ return errors.New(PasswordCannotContainSpaces)
}
// TODO Currently there is no requirement for password strength
diff --git a/pkg/converter/markdown.go b/pkg/converter/markdown.go
index 1789a32cd..d16915a84 100644
--- a/pkg/converter/markdown.go
+++ b/pkg/converter/markdown.go
@@ -32,7 +32,6 @@ import (
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
- "github.com/yuin/goldmark/renderer/html"
goldmarkHTML "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
)
@@ -141,7 +140,7 @@ func (r *DangerousHTMLRenderer) renderLink(w util.BufWriter, source []byte, node
if entering && r.renderLinkIsUrl(string(n.Destination)) {
_, _ = w.WriteString("')
} else {
@@ -175,7 +174,7 @@ func (r *DangerousHTMLRenderer) renderAutoLink(w util.BufWriter, source []byte,
_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
if n.Attributes() != nil {
_ = w.WriteByte('"')
- html.RenderAttributes(w, n, html.LinkAttributeFilter)
+ goldmarkHTML.RenderAttributes(w, n, goldmarkHTML.LinkAttributeFilter)
_ = w.WriteByte('>')
} else {
_, _ = w.WriteString(`">`)
diff --git a/pkg/htmltext/htmltext.go b/pkg/htmltext/htmltext.go
index 707c20a80..56db4d2b2 100644
--- a/pkg/htmltext/htmltext.go
+++ b/pkg/htmltext/htmltext.go
@@ -194,7 +194,9 @@ func GetPicByUrl(Url string) string {
if err != nil {
return ""
}
- defer res.Body.Close()
+ defer func() {
+ _ = res.Body.Close()
+ }()
pix, err := io.ReadAll(res.Body)
if err != nil {
return ""
diff --git a/plugin/kv_storage.go b/plugin/kv_storage.go
index d1ed3eaa6..204e5ee68 100644
--- a/plugin/kv_storage.go
+++ b/plugin/kv_storage.go
@@ -88,7 +88,7 @@ func (kv *KVOperator) getSession(ctx context.Context) (*xorm.Session, func()) {
session = kv.data.DB.NewSession().Context(ctx)
cleanup = func() {
if session != nil {
- session.Close()
+ _ = session.Close()
}
}
}
@@ -281,7 +281,7 @@ func (kv *KVOperator) Tx(ctx context.Context, fn func(ctx context.Context, kv *K
if kv.session == nil {
session := kv.data.DB.NewSession().Context(ctx)
if err := session.Begin(); err != nil {
- session.Close()
+ _ = session.Close()
return fmt.Errorf("%w: begin transaction failed: %v", ErrKVTransactionFailed, err)
}
@@ -291,7 +291,7 @@ func (kv *KVOperator) Tx(ctx context.Context, fn func(ctx context.Context, kv *K
log.Errorf("rollback failed: %v", rollbackErr)
}
}
- session.Close()
+ _ = session.Close()
}()
txKv = &KVOperator{
diff --git a/plugin/plugin_test/plugin_main_test.go b/plugin/plugin_test/plugin_main_test.go
index add11460b..fd9015c86 100644
--- a/plugin/plugin_test/plugin_main_test.go
+++ b/plugin/plugin_test/plugin_main_test.go
@@ -78,7 +78,7 @@ func TestMain(t *testing.M) {
dbSetting = dbSettingMapping[string(schemas.SQLITE)]
}
if dbSetting.Driver == string(schemas.SQLITE) {
- os.RemoveAll(dbSetting.Connection)
+ _ = os.RemoveAll(dbSetting.Connection)
}
defer func() {
diff --git a/ui/static.go b/ui/static.go
index dc79a1785..65caae461 100644
--- a/ui/static.go
+++ b/ui/static.go
@@ -21,7 +21,6 @@ package ui
import (
"embed"
- _ "embed"
)
//go:embed build
From 670aa323254ca8f2a5d3927353afb83b7724e424 Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Mon, 1 Dec 2025 21:22:18 +0100
Subject: [PATCH 14/92] refactor(lint): add new linters and fix their issues
* gocritic
* misspell
* modernize (aside, bumping go would be nice)
* testifylint
* unconvert
* unparam
* whitespace
related to #1432
Signed-off-by: ferhat elmas
---
.golangci.yaml | 7 +++
i18n/cs_CZ.yaml | 2 +-
i18n/cy_GB.yaml | 2 +-
i18n/da_DK.yaml | 2 +-
i18n/de_DE.yaml | 2 +-
i18n/en_US.yaml | 2 +-
i18n/es_ES.yaml | 2 +-
i18n/fa_IR.yaml | 2 +-
i18n/fr_FR.yaml | 2 +-
i18n/hi_IN.yaml | 2 +-
i18n/id_ID.yaml | 2 +-
i18n/it_IT.yaml | 2 +-
i18n/ja_JP.yaml | 2 +-
i18n/ko_KR.yaml | 2 +-
i18n/ml_IN.yaml | 2 +-
i18n/pl_PL.yaml | 2 +-
i18n/pt_PT.yaml | 2 +-
i18n/ro_RO.yaml | 2 +-
i18n/ru_RU.yaml | 2 +-
i18n/sk_SK.yaml | 2 +-
i18n/sv_SE.yaml | 10 ++--
i18n/te_IN.yaml | 2 +-
i18n/tr_TR.yaml | 2 +-
i18n/uk_UA.yaml | 2 +-
i18n/vi_VN.yaml | 2 +-
i18n/zh_CN.yaml | 2 +-
i18n/zh_TW.yaml | 2 +-
internal/base/middleware/avatar.go | 1 -
internal/base/reason/reason.go | 2 +-
internal/base/server/http.go | 1 -
internal/base/server/http_funcmap.go | 4 +-
internal/controller/question_controller.go | 3 +-
internal/controller/template_controller.go | 8 +--
.../controller/template_render/comment.go | 1 -
.../controller/template_render/controller.go | 10 ++--
internal/controller/user_controller.go | 1 -
internal/entity/collection_entity.go | 4 +-
internal/install/install_req.go | 9 +--
internal/migrations/v12.go | 1 -
internal/migrations/v13.go | 4 +-
internal/repo/activity/follow_repo.go | 2 +-
internal/repo/activity_common/follow.go | 6 +-
internal/repo/answer/answer_repo.go | 2 +-
internal/repo/collection/collection_repo.go | 4 +-
internal/repo/question/question_repo.go | 8 +--
internal/repo/rank/user_rank_repo.go | 2 +-
internal/repo/repo_test/auth_test.go | 31 +++++-----
internal/repo/repo_test/captcha_test.go | 11 ++--
internal/repo/repo_test/comment_repo_test.go | 27 ++++-----
internal/repo/repo_test/email_repo_test.go | 5 +-
internal/repo/repo_test/meta_repo_test.go | 31 +++++-----
.../repo/repo_test/notification_repo_test.go | 33 ++++++-----
internal/repo/repo_test/reason_repo_test.go | 5 +-
internal/repo/repo_test/recommend_test.go | 27 ++++-----
internal/repo/repo_test/repo_main_test.go | 2 +-
internal/repo/repo_test/revision_repo_test.go | 17 +++---
internal/repo/repo_test/siteinfo_repo_test.go | 9 +--
internal/repo/repo_test/tag_rel_repo_test.go | 25 ++++----
internal/repo/repo_test/tag_repo_test.go | 33 ++++++-----
.../repo/repo_test/user_backyard_repo_test.go | 15 ++---
internal/repo/repo_test/user_repo_test.go | 35 +++++------
internal/repo/search_common/search_repo.go | 4 +-
internal/schema/backyard_user_schema.go | 2 +-
internal/schema/meta_schema.go | 16 ++---
internal/schema/notification_schema.go | 6 +-
internal/schema/revision_schema.go | 2 +-
internal/schema/siteinfo_schema.go | 2 +-
internal/schema/tag_schema.go | 2 +-
internal/service/action/captcha_strategy.go | 21 ++++---
internal/service/auth/auth.go | 2 +-
internal/service/comment/comment_service.go | 15 +++--
internal/service/content/answer_service.go | 13 ++---
.../content/question_hottest_service.go | 1 -
internal/service/content/question_service.go | 58 ++++++++-----------
internal/service/content/revision_service.go | 1 -
internal/service/content/search_service.go | 14 +++--
internal/service/content/user_service.go | 7 +--
.../service/dashboard/dashboard_service.go | 18 +++---
.../invite_answer_notification.go | 3 +-
.../notification/new_answer_notification.go | 3 +-
.../notification/new_comment_notification.go | 3 +-
.../notification/new_question_notification.go | 3 +-
.../notification/notification_service.go | 13 ++---
.../notification_common/notification.go | 2 +-
.../plugin_common/plugin_common_service.go | 1 -
internal/service/question_common/question.go | 11 ++--
.../siteinfo_common/siteinfo_service_test.go | 5 +-
internal/service/tag/tag_service.go | 4 +-
internal/service/tag_common/tag_common.go | 8 +--
internal/service/uploader/upload.go | 2 -
internal/service/user_admin/user_backyard.go | 23 ++++----
internal/service/user_common/user.go | 4 +-
.../user_notification_config_service.go | 2 +-
pkg/checker/file_type.go | 8 +--
pkg/checker/path_ignore.go | 15 +----
pkg/checker/question_link.go | 11 ++--
pkg/converter/markdown.go | 18 +++---
pkg/day/day.go | 7 ++-
pkg/dir/dir.go | 16 ++---
pkg/htmltext/htmltext.go | 14 ++---
pkg/htmltext/htmltext_test.go | 4 +-
plugin/plugin_test/plugin_main_test.go | 13 ++---
102 files changed, 392 insertions(+), 431 deletions(-)
diff --git a/.golangci.yaml b/.golangci.yaml
index 611b49bc2..09fca51c5 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -29,6 +29,13 @@ linters:
- bodyclose # checks whether HTTP response body is closed successfully
- canonicalheader # checks whether net/http.Header uses canonical header
- copyloopvar # detects places where loop variables are copied (Go 1.22+)
+ - gocritic # provides diagnostics that check for bugs, performance and style issues
+ - misspell # finds commonly misspelled English words in comments and strings
+ - modernize # detects code that can be modernized to use newer Go features
+ - testifylint # checks usage of github.com/stretchr/testify
+ - unconvert # removes unnecessary type conversions
+ - unparam # reports unused function parameters
+ - whitespace # detects leading and trailing whitespace
formatters:
enable:
diff --git a/i18n/cs_CZ.yaml b/i18n/cs_CZ.yaml
index d9f972a63..ab53cb635 100644
--- a/i18n/cs_CZ.yaml
+++ b/i18n/cs_CZ.yaml
@@ -234,7 +234,7 @@ backend:
other: Nemáte oprávnění pro aktualizaci.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/cy_GB.yaml b/i18n/cy_GB.yaml
index 267070a08..5a5b9a654 100644
--- a/i18n/cy_GB.yaml
+++ b/i18n/cy_GB.yaml
@@ -234,7 +234,7 @@ backend:
other: Dim caniatâd i ddiweddaru.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/da_DK.yaml b/i18n/da_DK.yaml
index d3f410e45..37adc7db0 100644
--- a/i18n/da_DK.yaml
+++ b/i18n/da_DK.yaml
@@ -234,7 +234,7 @@ backend:
other: Ingen tilladelse til at opdatere.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/de_DE.yaml b/i18n/de_DE.yaml
index d7683f03b..542dca9b8 100644
--- a/i18n/de_DE.yaml
+++ b/i18n/de_DE.yaml
@@ -234,7 +234,7 @@ backend:
other: Keine Berechtigung zum Aktualisieren.
content_cannot_empty:
other: Der Inhalt darf nicht leer sein.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 40017b75a..6ff6ef3c6 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -235,7 +235,7 @@ backend:
other: No permission to update.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/es_ES.yaml b/i18n/es_ES.yaml
index 99071e2fe..f53a2810c 100644
--- a/i18n/es_ES.yaml
+++ b/i18n/es_ES.yaml
@@ -234,7 +234,7 @@ backend:
other: Sin permiso para actualizar.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/fa_IR.yaml b/i18n/fa_IR.yaml
index 600d5cc11..d013d0172 100644
--- a/i18n/fa_IR.yaml
+++ b/i18n/fa_IR.yaml
@@ -234,7 +234,7 @@ backend:
other: اجازه بروزرسانی ندارید.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/fr_FR.yaml b/i18n/fr_FR.yaml
index ecb87bef1..879906fa5 100644
--- a/i18n/fr_FR.yaml
+++ b/i18n/fr_FR.yaml
@@ -234,7 +234,7 @@ backend:
other: Pas de permission pour mettre à jour.
content_cannot_empty:
other: Le contenu ne peut pas être vide.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/hi_IN.yaml b/i18n/hi_IN.yaml
index 09be62019..eb0cb50bd 100644
--- a/i18n/hi_IN.yaml
+++ b/i18n/hi_IN.yaml
@@ -234,7 +234,7 @@ backend:
other: No permission to update.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/id_ID.yaml b/i18n/id_ID.yaml
index f8a4a8a88..43c0d70d1 100644
--- a/i18n/id_ID.yaml
+++ b/i18n/id_ID.yaml
@@ -234,7 +234,7 @@ backend:
other: Tidak diizinkan untuk memperbarui.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/it_IT.yaml b/i18n/it_IT.yaml
index 29cd1f521..50f60aa6a 100644
--- a/i18n/it_IT.yaml
+++ b/i18n/it_IT.yaml
@@ -234,7 +234,7 @@ backend:
other: Nessun permesso per l'aggiornamento.
content_cannot_empty:
other: Il contenuto non può essere vuoto.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/ja_JP.yaml b/i18n/ja_JP.yaml
index 8e2fb52c3..4bfe45c97 100644
--- a/i18n/ja_JP.yaml
+++ b/i18n/ja_JP.yaml
@@ -234,7 +234,7 @@ backend:
other: 更新する権限がありません。
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/ko_KR.yaml b/i18n/ko_KR.yaml
index be1807457..0ddefa61f 100644
--- a/i18n/ko_KR.yaml
+++ b/i18n/ko_KR.yaml
@@ -234,7 +234,7 @@ backend:
other: 업데이트 권한이 없습니다.
content_cannot_empty:
other: 내용은 비워둘 수 없습니다.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/ml_IN.yaml b/i18n/ml_IN.yaml
index 421350311..e2cac0e0b 100644
--- a/i18n/ml_IN.yaml
+++ b/i18n/ml_IN.yaml
@@ -234,7 +234,7 @@ backend:
other: No permission to update.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/pl_PL.yaml b/i18n/pl_PL.yaml
index ae26c6d1a..e5972d9b3 100644
--- a/i18n/pl_PL.yaml
+++ b/i18n/pl_PL.yaml
@@ -234,7 +234,7 @@ backend:
other: Brak uprawnień do edycji.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/pt_PT.yaml b/i18n/pt_PT.yaml
index 997bb0552..5989071e8 100644
--- a/i18n/pt_PT.yaml
+++ b/i18n/pt_PT.yaml
@@ -234,7 +234,7 @@ backend:
other: Sem permissão para atualizar.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/ro_RO.yaml b/i18n/ro_RO.yaml
index cd0afb8a3..43af2180b 100644
--- a/i18n/ro_RO.yaml
+++ b/i18n/ro_RO.yaml
@@ -234,7 +234,7 @@ backend:
other: Nu aveți permisiunea de a actualiza.
content_cannot_empty:
other: Conținutul nu poate fi gol.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/ru_RU.yaml b/i18n/ru_RU.yaml
index c554ac8d7..458fd2e66 100644
--- a/i18n/ru_RU.yaml
+++ b/i18n/ru_RU.yaml
@@ -234,7 +234,7 @@ backend:
other: Нет разрешения на обновление.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/sk_SK.yaml b/i18n/sk_SK.yaml
index 72bd4726e..cd629fc94 100644
--- a/i18n/sk_SK.yaml
+++ b/i18n/sk_SK.yaml
@@ -234,7 +234,7 @@ backend:
other: Žiadne povolenie na aktualizáciu.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/sv_SE.yaml b/i18n/sv_SE.yaml
index 243703ce0..abf58112d 100644
--- a/i18n/sv_SE.yaml
+++ b/i18n/sv_SE.yaml
@@ -162,7 +162,7 @@ backend:
cannot_modify_self_status:
other: Du får inte ändra din status.
email_or_password_wrong:
- other: Fel e-post eller lösenord.
+ other: Fel e-post eller lösenord.
answer:
not_found:
other: Svar hittades inte.
@@ -189,12 +189,12 @@ backend:
need_to_be_verified:
other: E-postadressen ska vara verifierad.
verify_url_expired:
- other: Länken för att verifiera e-postadressen har gått ut. Vänligen skicka igen.
+ other: Länken för att verifiera e-postadressen har gått ut. Vänligen skicka igen.
illegal_email_domain_error:
- other: E-post från den domänen tillåts inte. Vänligen använt en annan.
+ other: E-post från den domänen tillåts inte. Vänligen använt en annan.
lang:
not_found:
- other: Språkfilen hittas inte.
+ other: Språkfilen hittas inte.
object:
captcha_verification_failed:
other: Fel Captcha.
@@ -234,7 +234,7 @@ backend:
other: No permission to update.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/te_IN.yaml b/i18n/te_IN.yaml
index dbf4acb14..9e80aa8d8 100644
--- a/i18n/te_IN.yaml
+++ b/i18n/te_IN.yaml
@@ -234,7 +234,7 @@ backend:
other: No permission to update.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/tr_TR.yaml b/i18n/tr_TR.yaml
index fdecee532..92d97ebe2 100644
--- a/i18n/tr_TR.yaml
+++ b/i18n/tr_TR.yaml
@@ -234,7 +234,7 @@ backend:
other: Güncelleme izni yok.
content_cannot_empty:
other: İçerik boş olamaz.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/uk_UA.yaml b/i18n/uk_UA.yaml
index ea0e4591c..67496262a 100644
--- a/i18n/uk_UA.yaml
+++ b/i18n/uk_UA.yaml
@@ -234,7 +234,7 @@ backend:
other: Немає дозволу на оновлення.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/vi_VN.yaml b/i18n/vi_VN.yaml
index 6f5e5cbbb..af01b1da4 100644
--- a/i18n/vi_VN.yaml
+++ b/i18n/vi_VN.yaml
@@ -234,7 +234,7 @@ backend:
other: Không có quyền cập nhật.
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 0f7a25154..35321ac1f 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -234,7 +234,7 @@ backend:
other: 没有更新权限。
content_cannot_empty:
other: 内容不能为空。
- content_less_than_minumum:
+ content_less_than_minimum:
other: 输入的内容不足。
rank:
fail_to_meet_the_condition:
diff --git a/i18n/zh_TW.yaml b/i18n/zh_TW.yaml
index cc362ea1e..4b239c1e1 100644
--- a/i18n/zh_TW.yaml
+++ b/i18n/zh_TW.yaml
@@ -234,7 +234,7 @@ backend:
other: 無更新權限。
content_cannot_empty:
other: Content cannot be empty.
- content_less_than_minumum:
+ content_less_than_minimum:
other: Not enough content entered.
rank:
fail_to_meet_the_condition:
diff --git a/internal/base/middleware/avatar.go b/internal/base/middleware/avatar.go
index 98430638b..42b28da99 100644
--- a/internal/base/middleware/avatar.go
+++ b/internal/base/middleware/avatar.go
@@ -80,7 +80,6 @@ func (am *AvatarMiddleware) AvatarThumb() gin.HandlerFunc {
}
ctx.Abort()
return
-
} else {
urlInfo, err := url.Parse(uri)
if err != nil {
diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go
index 42e29f4c5..97aaa75a9 100644
--- a/internal/base/reason/reason.go
+++ b/internal/base/reason/reason.go
@@ -47,7 +47,7 @@ const (
QuestionAlreadyDeleted = "error.question.already_deleted"
QuestionUnderReview = "error.question.under_review"
QuestionContentCannotEmpty = "error.question.content_cannot_empty"
- QuestionContentLessThanMinimum = "error.question.content_less_than_minumum"
+ QuestionContentLessThanMinimum = "error.question.content_less_than_minimum"
AnswerNotFound = "error.answer.not_found"
AnswerCannotDeleted = "error.answer.cannot_deleted"
AnswerCannotUpdate = "error.answer.cannot_update"
diff --git a/internal/base/server/http.go b/internal/base/server/http.go
index 088bbc9e4..4fbb04cb2 100644
--- a/internal/base/server/http.go
+++ b/internal/base/server/http.go
@@ -44,7 +44,6 @@ func NewHTTPServer(debug bool,
pluginAPIRouter *router.PluginAPIRouter,
uiConf *UI,
) *gin.Engine {
-
if debug {
gin.SetMode(gin.DebugMode)
} else {
diff --git a/internal/base/server/http_funcmap.go b/internal/base/server/http_funcmap.go
index db4604572..a5d530b50 100644
--- a/internal/base/server/http_funcmap.go
+++ b/internal/base/server/http_funcmap.go
@@ -134,9 +134,7 @@ var funcMap = template.FuncMap{
"timezone": tz,
}
},
- "urlTitle": func(title string) string {
- return htmltext.UrlTitle(title)
- },
+ "urlTitle": htmltext.UrlTitle,
}
func FormatLinkNofollow(html string) string {
diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go
index 4b1869fa7..581cf548b 100644
--- a/internal/controller/question_controller.go
+++ b/internal/controller/question_controller.go
@@ -287,7 +287,6 @@ func (qc *QuestionController) GetQuestionInviteUserInfo(ctx *gin.Context) {
questionID := uid.DeShortID(ctx.Query("id"))
resp, err := qc.questionService.InviteUserInfo(ctx, questionID)
handler.HandleResponse(ctx, err, resp)
-
}
// SimilarQuestion godoc
@@ -578,7 +577,7 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) {
handler.HandleResponse(ctx, errors.BadRequest(reason.RequestFormatError), errFields)
return
}
- //add the question id to the answer
+ // add the question id to the answer
questionInfo, ok := resp.(*schema.QuestionInfoResp)
if ok {
answerReq := &schema.AnswerAddReq{}
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index 801e129c2..f6a442c21 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -263,14 +263,13 @@ func (tc *TemplateController) QuestionInfoRedirect(ctx *gin.Context, siteInfo *s
if needChangeShortID {
return true, url
}
- //not have title
+ // not have title
if titleIsAnswerID || len(title) == 0 {
return false, ""
}
return true, url
} else {
-
detail, err := tc.templateRenderController.QuestionDetail(ctx, questionID)
if err != nil {
tc.Page404(ctx)
@@ -284,7 +283,7 @@ func (tc *TemplateController) QuestionInfoRedirect(ctx *gin.Context, siteInfo *s
if len(ctx.Request.URL.Query()) > 0 {
url = fmt.Sprintf("%s?%s", url, ctx.Request.URL.RawQuery)
}
- //have title
+ // have title
if len(title) > 0 && !titleIsAnswerID && correctTitle {
if needChangeShortID {
return true, url
@@ -370,7 +369,7 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
- //related question
+ // related question
userID := middleware.GetLoginUserIDFromContext(ctx)
relatedQuestion, _, _ := tc.questionService.SimilarQuestion(ctx, id, userID)
@@ -553,7 +552,6 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
"topQuestions": questionList,
"topAnswers": answerList,
})
-
}
func (tc *TemplateController) Page404(ctx *gin.Context) {
diff --git a/internal/controller/template_render/comment.go b/internal/controller/template_render/comment.go
index 2862ad8dd..c1265bd05 100644
--- a/internal/controller/template_render/comment.go
+++ b/internal/controller/template_render/comment.go
@@ -33,7 +33,6 @@ func (t *TemplateRenderController) CommentList(
comments map[string][]*schema.GetCommentResp,
err error,
) {
-
comments = make(map[string][]*schema.GetCommentResp, len(objectIDs))
for _, objectID := range objectIDs {
diff --git a/internal/controller/template_render/controller.go b/internal/controller/template_render/controller.go
index 3412010db..297b3f9a6 100644
--- a/internal/controller/template_render/controller.go
+++ b/internal/controller/template_render/controller.go
@@ -78,10 +78,10 @@ func Paginator(page, pageSize int, nums int64) *schema.Paginator {
pageSize = 10
}
- var prevpage int //Previous page address
- var nextpage int //Address on the last page
- //Generate the total number of pages based on the total number of nums and the number of prepage pages
- totalpages := int(math.Ceil(float64(nums) / float64(pageSize))) //Total number of Pages
+ var prevpage int // Previous page address
+ var nextpage int // Address on the last page
+ // Generate the total number of pages based on the total number of nums and the number of prepage pages
+ totalpages := int(math.Ceil(float64(nums) / float64(pageSize))) // Total number of Pages
if page > totalpages {
page = totalpages
}
@@ -90,7 +90,7 @@ func Paginator(page, pageSize int, nums int64) *schema.Paginator {
}
var pages []int
switch {
- case page >= totalpages-5 && totalpages > 5: //The last 5 pages
+ case page >= totalpages-5 && totalpages > 5: // The last 5 pages
start := totalpages - 5 + 1
prevpage = page - 1
nextpage = int(math.Min(float64(totalpages), float64(page+1)))
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index e4a3b3d3b..cc89caf1a 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -509,7 +509,6 @@ func (uc *UserController) ActionRecord(ctx *gin.Context) {
resp, err := uc.actionService.ActionRecord(ctx, req)
handler.HandleResponse(ctx, err, resp)
}
-
}
// GetUserNotificationConfig get user's notification config
diff --git a/internal/entity/collection_entity.go b/internal/entity/collection_entity.go
index 5fbd5b0eb..24ae232f4 100644
--- a/internal/entity/collection_entity.go
+++ b/internal/entity/collection_entity.go
@@ -33,8 +33,8 @@ type Collection struct {
type CollectionSearch struct {
Collection
- Page int `json:"page" form:"page"` //Query number of pages
- PageSize int `json:"page_size" form:"page_size"` //Search page size
+ Page int `json:"page" form:"page"` // Query number of pages
+ PageSize int `json:"page_size" form:"page_size"` // Search page size
}
// TableName collection table name
diff --git a/internal/install/install_req.go b/internal/install/install_req.go
index e5b8839e9..39657a546 100644
--- a/internal/install/install_req.go
+++ b/internal/install/install_req.go
@@ -65,13 +65,14 @@ func (r *CheckDatabaseReq) GetConnection() string {
}
if r.DbType == string(schemas.POSTGRES) {
host, port := parsePgSQLHostPort(r.DbHost)
- if !r.Ssl {
+ switch {
+ case !r.Ssl:
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
host, port, r.DbUsername, r.DbPassword, r.DbName)
- } else if r.SslMode == "require" {
+ case r.SslMode == "require":
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode)
- } else if r.SslMode == "verify-ca" || r.SslMode == "verify-full" {
+ case r.SslMode == "verify-ca" || r.SslMode == "verify-full":
connection := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode)
if len(r.SslRootCert) > 0 && dir.CheckFileExist(r.SslRootCert) {
@@ -150,7 +151,7 @@ func (r *InitBaseInfoReq) FormatSiteUrl() {
}
r.SiteURL = fmt.Sprintf("%s://%s", parsedUrl.Scheme, parsedUrl.Host)
if len(parsedUrl.Path) > 0 {
- r.SiteURL = r.SiteURL + parsedUrl.Path
+ r.SiteURL += parsedUrl.Path
r.SiteURL = strings.TrimSuffix(r.SiteURL, "/")
}
}
diff --git a/internal/migrations/v12.go b/internal/migrations/v12.go
index 2dd3ce71e..23e8acdcd 100644
--- a/internal/migrations/v12.go
+++ b/internal/migrations/v12.go
@@ -75,7 +75,6 @@ func updateQuestionPostTime(ctx context.Context, x *xorm.Engine) error {
return fmt.Errorf("update question failed: %w", err)
}
}
-
}
return nil
diff --git a/internal/migrations/v13.go b/internal/migrations/v13.go
index 57d248482..16ba177e8 100644
--- a/internal/migrations/v13.go
+++ b/internal/migrations/v13.go
@@ -136,7 +136,7 @@ func addPrivilegeForInviteSomeoneToAnswer(ctx context.Context, x *xorm.Engine) e
}
func updateQuestionCount(ctx context.Context, x *xorm.Engine) error {
- //question answer count
+ // question answer count
answers := make([]AnswerV13, 0)
err := x.Context(ctx).Find(&answers, &AnswerV13{Status: entity.AnswerStatusAvailable})
if err != nil {
@@ -216,7 +216,7 @@ func updateTagCount(ctx context.Context, x *xorm.Engine) error {
}
}
- //select tag count
+ // select tag count
newTagRelList := make([]entity.TagRel, 0)
err = x.Context(ctx).Find(&newTagRelList, &entity.TagRel{Status: entity.TagRelStatusAvailable})
if err != nil {
diff --git a/internal/repo/activity/follow_repo.go b/internal/repo/activity/follow_repo.go
index 7af2189a6..4c6fc431b 100644
--- a/internal/repo/activity/follow_repo.go
+++ b/internal/repo/activity/follow_repo.go
@@ -168,7 +168,7 @@ func (ar *FollowRepo) FollowCancel(ctx context.Context, objectID, userID string)
return err
}
-func (ar *FollowRepo) updateFollows(ctx context.Context, session *xorm.Session, objectID string, follows int) error {
+func (ar *FollowRepo) updateFollows(_ context.Context, session *xorm.Session, objectID string, follows int) error {
objectType, err := obj.GetObjectTypeStrByObjectID(objectID)
if err != nil {
return err
diff --git a/internal/repo/activity_common/follow.go b/internal/repo/activity_common/follow.go
index 99e5a6e67..a185c3d8a 100644
--- a/internal/repo/activity_common/follow.go
+++ b/internal/repo/activity_common/follow.go
@@ -66,19 +66,19 @@ func (ar *FollowRepo) GetFollowAmount(ctx context.Context, objectID string) (fol
model := &entity.Question{}
_, err = ar.data.DB.Context(ctx).Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
- follows = int(model.FollowCount)
+ follows = model.FollowCount
}
case "user":
model := &entity.User{}
_, err = ar.data.DB.Context(ctx).Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
- follows = int(model.FollowCount)
+ follows = model.FollowCount
}
case "tag":
model := &entity.Tag{}
_, err = ar.data.DB.Context(ctx).Where("id = ?", objectID).Cols("`follow_count`").Get(model)
if err == nil {
- follows = int(model.FollowCount)
+ follows = model.FollowCount
}
default:
err = errors.InternalServer(reason.DisallowFollow).WithMsg("this object can't be followed")
diff --git a/internal/repo/answer/answer_repo.go b/internal/repo/answer/answer_repo.go
index 0f1ae8146..52963c44c 100644
--- a/internal/repo/answer/answer_repo.go
+++ b/internal/repo/answer/answer_repo.go
@@ -335,7 +335,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
var err error
rows := make([]*entity.Answer, 0)
if search.Page > 0 {
- search.Page = search.Page - 1
+ search.Page--
} else {
search.Page = 0
}
diff --git a/internal/repo/collection/collection_repo.go b/internal/repo/collection/collection_repo.go
index f30692c97..482cb075d 100644
--- a/internal/repo/collection/collection_repo.go
+++ b/internal/repo/collection/collection_repo.go
@@ -167,7 +167,7 @@ func (cr *collectionRepo) GetCollectionPage(ctx context.Context, page, pageSize
// SearchObjectCollected check object is collected or not
func (cr *collectionRepo) SearchObjectCollected(ctx context.Context, userID string, objectIds []string) (map[string]bool, error) {
- for i := 0; i < len(objectIds); i++ {
+ for i := range objectIds {
objectIds[i] = uid.DeShortID(objectIds[i])
}
@@ -193,7 +193,7 @@ func (cr *collectionRepo) SearchList(ctx context.Context, search *entity.Collect
var err error
rows := make([]*entity.Collection, 0)
if search.Page > 0 {
- search.Page = search.Page - 1
+ search.Page--
} else {
search.Page = 0
}
diff --git a/internal/repo/question/question_repo.go b/internal/repo/question/question_repo.go
index 3935e6102..3449efb8b 100644
--- a/internal/repo/question/question_repo.go
+++ b/internal/repo/question/question_repo.go
@@ -89,9 +89,9 @@ func (qr *questionRepo) RemoveQuestion(ctx context.Context, id string) (err erro
}
// UpdateQuestion update question
-func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Question, Cols []string) (err error) {
+func (qr *questionRepo) UpdateQuestion(ctx context.Context, question *entity.Question, cols []string) (err error) {
question.ID = uid.DeShortID(question.ID)
- _, err = qr.data.DB.Context(ctx).Where("id =?", question.ID).Cols(Cols...).Update(question)
+ _, err = qr.data.DB.Context(ctx).Where("id =?", question.ID).Cols(cols...).Update(question)
if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
@@ -344,7 +344,7 @@ func (qr *questionRepo) GetUserQuestionCount(ctx context.Context, userID string,
func (qr *questionRepo) SitemapQuestions(ctx context.Context, page, pageSize int) (
questionIDList []*schema.SiteMapQuestionInfo, err error) {
- page = page - 1
+ page--
questionIDList = make([]*schema.SiteMapQuestionInfo, 0)
// try to get sitemap data from cache
@@ -524,7 +524,7 @@ func (qr *questionRepo) AdminQuestionPage(ctx context.Context, search *schema.Ad
rows := make([]*entity.Question, 0)
if search.Page > 0 {
- search.Page = search.Page - 1
+ search.Page--
} else {
search.Page = 0
}
diff --git a/internal/repo/rank/user_rank_repo.go b/internal/repo/rank/user_rank_repo.go
index cb9ca1b3d..5e86f5929 100644
--- a/internal/repo/rank/user_rank_repo.go
+++ b/internal/repo/rank/user_rank_repo.go
@@ -141,7 +141,7 @@ func (ur *UserRankRepo) TriggerUserRank(ctx context.Context,
return false, nil
}
-func (ur *UserRankRepo) checkUserMinRank(ctx context.Context, session *xorm.Session, userID string, deltaRank int) (
+func (ur *UserRankRepo) checkUserMinRank(_ context.Context, session *xorm.Session, userID string, deltaRank int) (
isReachStandard bool, err error,
) {
bean := &entity.User{ID: userID}
diff --git a/internal/repo/repo_test/auth_test.go b/internal/repo/repo_test/auth_test.go
index 387332399..0c9919020 100644
--- a/internal/repo/repo_test/auth_test.go
+++ b/internal/repo/repo_test/auth_test.go
@@ -26,6 +26,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/auth"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
@@ -38,10 +39,10 @@ func Test_authRepo_SetUserCacheInfo(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetUserCacheInfo(context.TODO(), accessToken, visitToken, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
cacheInfo, err := authRepo.GetUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, userID, cacheInfo.UserID)
}
@@ -49,13 +50,13 @@ func Test_authRepo_RemoveUserCacheInfo(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetUserCacheInfo(context.TODO(), accessToken, visitToken, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
err = authRepo.RemoveUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
userInfo, err := authRepo.GetUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Nil(t, userInfo)
}
@@ -63,23 +64,23 @@ func Test_authRepo_SetUserStatus(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetUserStatus(context.TODO(), userID, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
cacheInfo, err := authRepo.GetUserStatus(context.TODO(), userID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, userID, cacheInfo.UserID)
}
func Test_authRepo_RemoveUserStatus(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetUserStatus(context.TODO(), userID, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
err = authRepo.RemoveUserStatus(context.TODO(), userID)
- assert.NoError(t, err)
+ require.NoError(t, err)
userInfo, err := authRepo.GetUserStatus(context.TODO(), userID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Nil(t, userInfo)
}
@@ -87,10 +88,10 @@ func Test_authRepo_SetAdminUserCacheInfo(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetAdminUserCacheInfo(context.TODO(), accessToken, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
cacheInfo, err := authRepo.GetAdminUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, userID, cacheInfo.UserID)
}
@@ -98,12 +99,12 @@ func Test_authRepo_RemoveAdminUserCacheInfo(t *testing.T) {
authRepo := auth.NewAuthRepo(testDataSource)
err := authRepo.SetAdminUserCacheInfo(context.TODO(), accessToken, &entity.UserCacheInfo{UserID: userID})
- assert.NoError(t, err)
+ require.NoError(t, err)
err = authRepo.RemoveAdminUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
userInfo, err := authRepo.GetAdminUserCacheInfo(context.TODO(), accessToken)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Nil(t, userInfo)
}
diff --git a/internal/repo/repo_test/captcha_test.go b/internal/repo/repo_test/captcha_test.go
index 48e9dd806..e6954a232 100644
--- a/internal/repo/repo_test/captcha_test.go
+++ b/internal/repo/repo_test/captcha_test.go
@@ -25,6 +25,7 @@ import (
"github.com/apache/answer/internal/repo/captcha"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
@@ -36,23 +37,23 @@ var (
func Test_captchaRepo_DelActionType(t *testing.T) {
captchaRepo := captcha.NewCaptchaRepo(testDataSource)
err := captchaRepo.SetActionType(context.TODO(), ip, actionType, "", amount)
- assert.NoError(t, err)
+ require.NoError(t, err)
actionInfo, err := captchaRepo.GetActionType(context.TODO(), ip, actionType)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, amount, actionInfo.Num)
err = captchaRepo.DelActionType(context.TODO(), ip, actionType)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_captchaRepo_SetCaptcha(t *testing.T) {
captchaRepo := captcha.NewCaptchaRepo(testDataSource)
key, capt := "key", "1234"
err := captchaRepo.SetCaptcha(context.TODO(), key, capt)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotCaptcha, err := captchaRepo.GetCaptcha(context.TODO(), key)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, capt, gotCaptcha)
}
diff --git a/internal/repo/repo_test/comment_repo_test.go b/internal/repo/repo_test/comment_repo_test.go
index 3de154817..41ba04464 100644
--- a/internal/repo/repo_test/comment_repo_test.go
+++ b/internal/repo/repo_test/comment_repo_test.go
@@ -29,6 +29,7 @@ import (
"github.com/apache/answer/internal/repo/unique"
commentService "github.com/apache/answer/internal/service/comment"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func buildCommentEntity() *entity.Comment {
@@ -48,10 +49,10 @@ func Test_commentRepo_AddComment(t *testing.T) {
commentRepo := comment.NewCommentRepo(testDataSource, uniqueIDRepo)
testCommentEntity := buildCommentEntity()
err := commentRepo.AddComment(context.TODO(), testCommentEntity)
- assert.NoError(t, err)
+ require.NoError(t, err)
err = commentRepo.RemoveComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_commentRepo_GetCommentPage(t *testing.T) {
@@ -59,7 +60,7 @@ func Test_commentRepo_GetCommentPage(t *testing.T) {
commentRepo := comment.NewCommentRepo(testDataSource, uniqueIDRepo)
testCommentEntity := buildCommentEntity()
err := commentRepo.AddComment(context.TODO(), testCommentEntity)
- assert.NoError(t, err)
+ require.NoError(t, err)
resp, total, err := commentRepo.GetCommentPage(context.TODO(), &commentService.CommentQuery{
PageCond: pager.PageCond{
@@ -67,12 +68,12 @@ func Test_commentRepo_GetCommentPage(t *testing.T) {
PageSize: 10,
},
})
- assert.NoError(t, err)
- assert.Equal(t, total, int64(1))
+ require.NoError(t, err)
+ assert.Equal(t, int64(1), total)
assert.Equal(t, resp[0].ID, testCommentEntity.ID)
err = commentRepo.RemoveComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_commentRepo_UpdateComment(t *testing.T) {
@@ -81,19 +82,19 @@ func Test_commentRepo_UpdateComment(t *testing.T) {
commonCommentRepo := comment.NewCommentCommonRepo(testDataSource, uniqueIDRepo)
testCommentEntity := buildCommentEntity()
err := commentRepo.AddComment(context.TODO(), testCommentEntity)
- assert.NoError(t, err)
+ require.NoError(t, err)
testCommentEntity.ParsedText = "test"
err = commentRepo.UpdateCommentContent(context.TODO(), testCommentEntity.ID, "test", "test")
- assert.NoError(t, err)
+ require.NoError(t, err)
newComment, exist, err := commonCommentRepo.GetComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, testCommentEntity.ParsedText, newComment.ParsedText)
err = commentRepo.RemoveComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_commentRepo_CannotGetDeletedComment(t *testing.T) {
@@ -102,12 +103,12 @@ func Test_commentRepo_CannotGetDeletedComment(t *testing.T) {
testCommentEntity := buildCommentEntity()
err := commentRepo.AddComment(context.TODO(), testCommentEntity)
- assert.NoError(t, err)
+ require.NoError(t, err)
err = commentRepo.RemoveComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
_, exist, err := commentRepo.GetComment(context.TODO(), testCommentEntity.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.False(t, exist)
}
diff --git a/internal/repo/repo_test/email_repo_test.go b/internal/repo/repo_test/email_repo_test.go
index 82fc6c571..03ca9efe3 100644
--- a/internal/repo/repo_test/email_repo_test.go
+++ b/internal/repo/repo_test/email_repo_test.go
@@ -26,15 +26,16 @@ import (
"github.com/apache/answer/internal/repo/export"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_emailRepo_VerifyCode(t *testing.T) {
emailRepo := export.NewEmailRepo(testDataSource)
code, content := "1111", "{\"source_type\":\"\",\"e_mail\":\"\",\"user_id\":\"1\",\"skip_validation_latest_code\":false}"
err := emailRepo.SetCode(context.TODO(), "1", code, content, time.Minute)
- assert.NoError(t, err)
+ require.NoError(t, err)
verifyContent, err := emailRepo.VerifyCode(context.TODO(), code)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, content, verifyContent)
}
diff --git a/internal/repo/repo_test/meta_repo_test.go b/internal/repo/repo_test/meta_repo_test.go
index e910dab3f..68965f22b 100644
--- a/internal/repo/repo_test/meta_repo_test.go
+++ b/internal/repo/repo_test/meta_repo_test.go
@@ -26,6 +26,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/meta"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func buildMetaEntity() *entity.Meta {
@@ -41,15 +42,15 @@ func Test_metaRepo_GetMetaByObjectIdAndKey(t *testing.T) {
metaEnt := buildMetaEntity()
err := metaRepo.AddMeta(context.TODO(), metaEnt)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotMeta, exist, err := metaRepo.GetMetaByObjectIdAndKey(context.TODO(), metaEnt.ObjectID, metaEnt.Key)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, metaEnt.ID, gotMeta.ID)
err = metaRepo.RemoveMeta(context.TODO(), metaEnt.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_metaRepo_GetMetaList(t *testing.T) {
@@ -57,15 +58,15 @@ func Test_metaRepo_GetMetaList(t *testing.T) {
metaEnt := buildMetaEntity()
err := metaRepo.AddMeta(context.TODO(), metaEnt)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotMetaList, err := metaRepo.GetMetaList(context.TODO(), metaEnt)
- assert.NoError(t, err)
- assert.Equal(t, len(gotMetaList), 1)
+ require.NoError(t, err)
+ assert.Len(t, gotMetaList, 1)
assert.Equal(t, gotMetaList[0].ID, metaEnt.ID)
err = metaRepo.RemoveMeta(context.TODO(), metaEnt.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_metaRepo_GetMetaPage(t *testing.T) {
@@ -73,15 +74,15 @@ func Test_metaRepo_GetMetaPage(t *testing.T) {
metaEnt := buildMetaEntity()
err := metaRepo.AddMeta(context.TODO(), metaEnt)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotMetaList, err := metaRepo.GetMetaList(context.TODO(), metaEnt)
- assert.NoError(t, err)
- assert.Equal(t, len(gotMetaList), 1)
+ require.NoError(t, err)
+ assert.Len(t, gotMetaList, 1)
assert.Equal(t, gotMetaList[0].ID, metaEnt.ID)
err = metaRepo.RemoveMeta(context.TODO(), metaEnt.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_metaRepo_UpdateMeta(t *testing.T) {
@@ -89,17 +90,17 @@ func Test_metaRepo_UpdateMeta(t *testing.T) {
metaEnt := buildMetaEntity()
err := metaRepo.AddMeta(context.TODO(), metaEnt)
- assert.NoError(t, err)
+ require.NoError(t, err)
metaEnt.Value = "testing"
err = metaRepo.UpdateMeta(context.TODO(), metaEnt)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotMeta, exist, err := metaRepo.GetMetaByObjectIdAndKey(context.TODO(), metaEnt.ObjectID, metaEnt.Key)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, gotMeta.Value, metaEnt.Value)
err = metaRepo.RemoveMeta(context.TODO(), metaEnt.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
diff --git a/internal/repo/repo_test/notification_repo_test.go b/internal/repo/repo_test/notification_repo_test.go
index a05913ba1..4f843e777 100644
--- a/internal/repo/repo_test/notification_repo_test.go
+++ b/internal/repo/repo_test/notification_repo_test.go
@@ -27,6 +27,7 @@ import (
"github.com/apache/answer/internal/repo/notification"
"github.com/apache/answer/internal/schema"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func buildNotificationEntity() *entity.Notification {
@@ -44,13 +45,13 @@ func Test_notificationRepo_ClearIDUnRead(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
err = notificationRepo.ClearIDUnRead(context.TODO(), ent.UserID, ent.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exists, err := notificationRepo.GetById(context.TODO(), ent.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.Equal(t, schema.NotificationRead, got.IsRead)
}
@@ -59,13 +60,13 @@ func Test_notificationRepo_ClearUnRead(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
err = notificationRepo.ClearUnRead(context.TODO(), ent.UserID, ent.Type)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exists, err := notificationRepo.GetById(context.TODO(), ent.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.Equal(t, schema.NotificationRead, got.IsRead)
}
@@ -74,10 +75,10 @@ func Test_notificationRepo_GetById(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exists, err := notificationRepo.GetById(context.TODO(), ent.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.Equal(t, got.ID, ent.ID)
}
@@ -86,10 +87,10 @@ func Test_notificationRepo_GetByUserIdObjectIdTypeId(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exists, err := notificationRepo.GetByUserIdObjectIdTypeId(context.TODO(), ent.UserID, ent.ObjectID, ent.Type)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.Equal(t, got.ObjectID, ent.ObjectID)
}
@@ -98,11 +99,11 @@ func Test_notificationRepo_GetNotificationPage(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
notificationPage, total, err := notificationRepo.GetNotificationPage(context.TODO(), &schema.NotificationSearch{UserID: ent.UserID})
- assert.NoError(t, err)
- assert.True(t, total > 0)
+ require.NoError(t, err)
+ assert.Positive(t, total)
assert.Equal(t, notificationPage[0].UserID, ent.UserID)
}
@@ -110,14 +111,14 @@ func Test_notificationRepo_UpdateNotificationContent(t *testing.T) {
notificationRepo := notification.NewNotificationRepo(testDataSource)
ent := buildNotificationEntity()
err := notificationRepo.AddNotification(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
ent.Content = "test"
err = notificationRepo.UpdateNotificationContent(context.TODO(), ent)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exists, err := notificationRepo.GetById(context.TODO(), ent.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.Equal(t, got.Content, ent.Content)
}
diff --git a/internal/repo/repo_test/reason_repo_test.go b/internal/repo/repo_test/reason_repo_test.go
index 636f8d278..acb8c861a 100644
--- a/internal/repo/repo_test/reason_repo_test.go
+++ b/internal/repo/repo_test/reason_repo_test.go
@@ -28,12 +28,13 @@ import (
"github.com/apache/answer/internal/repo/reason"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_reasonRepo_ListReasons(t *testing.T) {
configRepo := config.NewConfigRepo(testDataSource)
reasonRepo := reason.NewReasonRepo(serviceconfig.NewConfigService(configRepo))
reasonItems, err := reasonRepo.ListReasons(context.TODO(), "question", "close")
- assert.NoError(t, err)
- assert.Equal(t, 4, len(reasonItems))
+ require.NoError(t, err)
+ assert.Len(t, reasonItems, 4)
}
diff --git a/internal/repo/repo_test/recommend_test.go b/internal/repo/repo_test/recommend_test.go
index a21693886..7675aa204 100644
--- a/internal/repo/repo_test/recommend_test.go
+++ b/internal/repo/repo_test/recommend_test.go
@@ -34,6 +34,7 @@ import (
"github.com/apache/answer/internal/repo/user"
config2 "github.com/apache/answer/internal/service/config"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_questionRepo_GetRecommend(t *testing.T) {
@@ -61,8 +62,8 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
IsAdmin: false,
}
err := userRepo.AddUser(context.TODO(), user)
- assert.NoError(t, err)
- assert.NotEqual(t, "", user.ID)
+ require.NoError(t, err)
+ assert.NotEmpty(t, user.ID)
questions := make([]*entity.Question, 0)
// tag, unjoin, unfollow
@@ -140,8 +141,8 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
for _, question := range questions {
err = questionRepo.AddQuestion(context.TODO(), question)
- assert.NoError(t, err)
- assert.NotEqual(t, "", question.ID)
+ require.NoError(t, err)
+ assert.NotEmpty(t, question.ID)
}
tags := []*entity.Tag{
@@ -161,7 +162,7 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
},
}
err = tagCommenRepo.AddTagList(context.TODO(), tags)
- assert.NoError(t, err)
+ require.NoError(t, err)
tagRels := make([]*entity.TagRel, 0)
for i, question := range questions {
@@ -173,7 +174,7 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
tagRels = append(tagRels, tagRel)
}
err = tagRelRepo.AddTagRelList(context.TODO(), tagRels)
- assert.NoError(t, err)
+ require.NoError(t, err)
followQuestionIDs := make([]string, 0)
for i := range questions {
@@ -181,15 +182,15 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
continue
}
err = followRepo.Follow(context.TODO(), questions[i].ID, user.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
followQuestionIDs = append(followQuestionIDs, questions[i].ID)
}
// get recommend
questionList, total, err := questionRepo.GetRecommendQuestionPageByTags(context.TODO(), user.ID, []string{tags[0].ID}, followQuestionIDs, 1, 20)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, int64(5), total)
- assert.Equal(t, 5, len(questionList))
+ assert.Len(t, questionList, 5)
// recovery
t.Cleanup(func() {
@@ -197,19 +198,19 @@ func Test_questionRepo_GetRecommend(t *testing.T) {
for i, tagRel := range tagRels {
if i%2 == 1 {
err = followRepo.FollowCancel(context.TODO(), questions[i].ID, user.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
tagRelIDs = append(tagRelIDs, tagRel.ID)
}
err = tagRelRepo.RemoveTagRelListByIDs(context.TODO(), tagRelIDs)
- assert.NoError(t, err)
+ require.NoError(t, err)
for _, tag := range tags {
err = tagRepo.RemoveTag(context.TODO(), tag.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
for _, q := range questions {
err = questionRepo.RemoveQuestion(context.TODO(), q.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
})
}
diff --git a/internal/repo/repo_test/repo_main_test.go b/internal/repo/repo_test/repo_main_test.go
index 919ca94e8..e2f40f276 100644
--- a/internal/repo/repo_test/repo_main_test.go
+++ b/internal/repo/repo_test/repo_main_test.go
@@ -153,7 +153,7 @@ func initDatabaseImage(dbSetting TestDBSetting) (connection string, cleanup func
return "", nil, fmt.Errorf("could not connect to docker: %s", err)
}
- //resource, err := pool.Run(dbSetting.ImageName, dbSetting.ImageVersion, dbSetting.ENV)
+ // resource, err := pool.Run(dbSetting.ImageName, dbSetting.ImageVersion, dbSetting.ENV)
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: dbSetting.ImageName,
Tag: dbSetting.ImageVersion,
diff --git a/internal/repo/repo_test/revision_repo_test.go b/internal/repo/repo_test/revision_repo_test.go
index 01d262d2b..6fd067b30 100644
--- a/internal/repo/repo_test/revision_repo_test.go
+++ b/internal/repo/repo_test/revision_repo_test.go
@@ -29,6 +29,7 @@ import (
"github.com/apache/answer/internal/repo/revision"
"github.com/apache/answer/internal/repo/unique"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var q = &entity.Question{
@@ -69,29 +70,29 @@ func Test_revisionRepo_AddRevision(t *testing.T) {
// create question
err := questionRepo.AddQuestion(context.TODO(), q)
- assert.NoError(t, err)
- assert.NotEqual(t, "", q.ID)
+ require.NoError(t, err)
+ assert.NotEmpty(t, q.ID)
content, err := json.Marshal(q)
- assert.NoError(t, err)
+ require.NoError(t, err)
// auto update false
rev := getRev(q.ID, q.Title, string(content))
err = revisionRepo.AddRevision(context.TODO(), rev, false)
- assert.NoError(t, err)
+ require.NoError(t, err)
qr, _, _ := questionRepo.GetQuestion(context.TODO(), q.ID)
assert.NotEqual(t, rev.ID, qr.RevisionID)
// auto update false
rev = getRev(q.ID, q.Title, string(content))
err = revisionRepo.AddRevision(context.TODO(), rev, true)
- assert.NoError(t, err)
+ require.NoError(t, err)
qr, _, _ = questionRepo.GetQuestion(context.TODO(), q.ID)
assert.Equal(t, rev.ID, qr.RevisionID)
// recovery
t.Cleanup(func() {
err = questionRepo.RemoveQuestion(context.TODO(), q.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
})
}
@@ -103,7 +104,7 @@ func Test_revisionRepo_GetLastRevisionByObjectID(t *testing.T) {
Test_revisionRepo_AddRevision(t)
rev, exists, err := revisionRepo.GetLastRevisionByObjectID(context.TODO(), q.ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exists)
assert.NotNil(t, rev)
}
@@ -115,6 +116,6 @@ func Test_revisionRepo_GetRevisionList(t *testing.T) {
)
Test_revisionRepo_AddRevision(t)
revs, err := revisionRepo.GetRevisionList(context.TODO(), &entity.Revision{ObjectID: q.ID})
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.GreaterOrEqual(t, len(revs), 1)
}
diff --git a/internal/repo/repo_test/siteinfo_repo_test.go b/internal/repo/repo_test/siteinfo_repo_test.go
index cb5d8fc0d..9fa59d177 100644
--- a/internal/repo/repo_test/siteinfo_repo_test.go
+++ b/internal/repo/repo_test/siteinfo_repo_test.go
@@ -26,6 +26,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/site_info"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_siteInfoRepo_SaveByType(t *testing.T) {
@@ -34,19 +35,19 @@ func Test_siteInfoRepo_SaveByType(t *testing.T) {
data := &entity.SiteInfo{Content: "site_info", Type: "test"}
err := siteInfoRepo.SaveByType(context.TODO(), data.Type, data)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err := siteInfoRepo.GetByType(context.TODO(), data.Type)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, data.Content, got.Content)
data.Content = "new site_info"
err = siteInfoRepo.SaveByType(context.TODO(), data.Type, data)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err = siteInfoRepo.GetByType(context.TODO(), data.Type)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, data.Content, got.Content)
}
diff --git a/internal/repo/repo_test/tag_rel_repo_test.go b/internal/repo/repo_test/tag_rel_repo_test.go
index e3af67a14..47c09a6a3 100644
--- a/internal/repo/repo_test/tag_rel_repo_test.go
+++ b/internal/repo/repo_test/tag_rel_repo_test.go
@@ -30,6 +30,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/tag"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
@@ -61,15 +62,15 @@ func Test_tagListRepo_BatchGetObjectTagRelList(t *testing.T) {
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
relList, err :=
tagRelRepo.BatchGetObjectTagRelList(context.TODO(), []string{testTagRelList[0].ObjectID, testTagRelList[1].ObjectID})
- assert.NoError(t, err)
- assert.Equal(t, 2, len(relList))
+ require.NoError(t, err)
+ assert.Len(t, relList, 2)
}
func Test_tagListRepo_CountTagRelByTagID(t *testing.T) {
tagRelOnce.Do(addTagRelList)
tagRelRepo := tag.NewTagRelRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, int64(1), count)
}
@@ -79,8 +80,8 @@ func Test_tagListRepo_GetObjectTagRelList(t *testing.T) {
relList, err :=
tagRelRepo.GetObjectTagRelList(context.TODO(), testTagRelList[0].ObjectID)
- assert.NoError(t, err)
- assert.Equal(t, 1, len(relList))
+ require.NoError(t, err)
+ assert.Len(t, relList, 1)
}
func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
@@ -89,25 +90,25 @@ func Test_tagListRepo_GetObjectTagRelWithoutStatus(t *testing.T) {
relList, err :=
tagRelRepo.BatchGetObjectTagRelList(context.TODO(), []string{testTagRelList[0].ObjectID, testTagRelList[1].ObjectID})
- assert.NoError(t, err)
- assert.Equal(t, 2, len(relList))
+ require.NoError(t, err)
+ assert.Len(t, relList, 2)
ids := []int64{relList[0].ID, relList[1].ID}
err = tagRelRepo.RemoveTagRelListByIDs(context.TODO(), ids)
- assert.NoError(t, err)
+ require.NoError(t, err)
count, err := tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, int64(0), count)
_, exist, err := tagRelRepo.GetObjectTagRelWithoutStatus(context.TODO(), relList[0].ObjectID, relList[0].TagID)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
err = tagRelRepo.EnableTagRelByIDs(context.TODO(), ids, false)
- assert.NoError(t, err)
+ require.NoError(t, err)
count, err = tagRelRepo.CountTagRelByTagID(context.TODO(), "10030000000000101")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, int64(1), count)
}
diff --git a/internal/repo/repo_test/tag_repo_test.go b/internal/repo/repo_test/tag_repo_test.go
index b2475a227..aa1ec1a25 100644
--- a/internal/repo/repo_test/tag_repo_test.go
+++ b/internal/repo/repo_test/tag_repo_test.go
@@ -32,6 +32,7 @@ import (
"github.com/apache/answer/internal/repo/unique"
"github.com/apache/answer/pkg/converter"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
var (
@@ -75,7 +76,7 @@ func Test_tagRepo_GetTagByID(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID, true)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, testTagList[0].SlugName, gotTag.SlugName)
}
@@ -85,7 +86,7 @@ func Test_tagRepo_GetTagBySlugName(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTag, exist, err := tagCommonRepo.GetTagBySlugName(context.TODO(), testTagList[0].SlugName)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, testTagList[0].SlugName, gotTag.SlugName)
}
@@ -95,7 +96,7 @@ func Test_tagRepo_GetTagList(t *testing.T) {
tagRepo := tag.NewTagRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTags, err := tagRepo.GetTagList(context.TODO(), &entity.Tag{ID: testTagList[0].ID})
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
}
@@ -104,7 +105,7 @@ func Test_tagRepo_GetTagListByIDs(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTags, err := tagCommonRepo.GetTagListByIDs(context.TODO(), []string{testTagList[0].ID})
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
}
@@ -113,7 +114,7 @@ func Test_tagRepo_GetTagListByName(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTags, err := tagCommonRepo.GetTagListByName(context.TODO(), testTagList[0].SlugName, false, false)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
}
@@ -122,7 +123,7 @@ func Test_tagRepo_GetTagListByNames(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTags, err := tagCommonRepo.GetTagListByNames(context.TODO(), []string{testTagList[0].SlugName})
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
}
@@ -131,7 +132,7 @@ func Test_tagRepo_GetTagPage(t *testing.T) {
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTags, _, err := tagCommonRepo.GetTagPage(context.TODO(), 1, 1, &entity.Tag{SlugName: testTagList[0].SlugName}, "")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, testTagList[0].SlugName, gotTags[0].SlugName)
}
@@ -140,12 +141,12 @@ func Test_tagRepo_RemoveTag(t *testing.T) {
uniqueIDRepo := unique.NewUniqueIDRepo(testDataSource)
tagRepo := tag.NewTagRepo(testDataSource, uniqueIDRepo)
err := tagRepo.RemoveTag(context.TODO(), testTagList[1].ID)
- assert.NoError(t, err)
+ require.NoError(t, err)
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
_, exist, err := tagCommonRepo.GetTagBySlugName(context.TODO(), testTagList[1].SlugName)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.False(t, exist)
}
@@ -155,12 +156,12 @@ func Test_tagRepo_UpdateTag(t *testing.T) {
testTagList[0].DisplayName = "golang"
err := tagRepo.UpdateTag(context.TODO(), testTagList[0])
- assert.NoError(t, err)
+ require.NoError(t, err)
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID, true)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, testTagList[0].DisplayName, gotTag.DisplayName)
}
@@ -170,10 +171,10 @@ func Test_tagRepo_UpdateTagQuestionCount(t *testing.T) {
testTagList[0].DisplayName = "golang"
err := tagCommonRepo.UpdateTagQuestionCount(context.TODO(), testTagList[0].ID, 100)
- assert.NoError(t, err)
+ require.NoError(t, err)
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[0].ID, true)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, 100, gotTag.QuestionCount)
}
@@ -184,16 +185,16 @@ func Test_tagRepo_UpdateTagSynonym(t *testing.T) {
testTagList[0].DisplayName = "golang"
err := tagRepo.UpdateTag(context.TODO(), testTagList[0])
- assert.NoError(t, err)
+ require.NoError(t, err)
err = tagRepo.UpdateTagSynonym(context.TODO(), []string{testTagList[2].SlugName},
converter.StringToInt64(testTagList[0].ID), testTagList[0].SlugName)
- assert.NoError(t, err)
+ require.NoError(t, err)
tagCommonRepo := tag_common.NewTagCommonRepo(testDataSource, unique.NewUniqueIDRepo(testDataSource))
gotTag, exist, err := tagCommonRepo.GetTagByID(context.TODO(), testTagList[2].ID, true)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, testTagList[0].ID, fmt.Sprintf("%d", gotTag.MainTagID))
}
diff --git a/internal/repo/repo_test/user_backyard_repo_test.go b/internal/repo/repo_test/user_backyard_repo_test.go
index 073a600cf..eb4db102b 100644
--- a/internal/repo/repo_test/user_backyard_repo_test.go
+++ b/internal/repo/repo_test/user_backyard_repo_test.go
@@ -28,12 +28,13 @@ import (
"github.com/apache/answer/internal/repo/auth"
"github.com/apache/answer/internal/repo/user"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_userAdminRepo_GetUserInfo(t *testing.T) {
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
got, exist, err := userAdminRepo.GetUserInfo(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, "1", got.ID)
}
@@ -41,7 +42,7 @@ func Test_userAdminRepo_GetUserInfo(t *testing.T) {
func Test_userAdminRepo_GetUserPage(t *testing.T) {
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
got, total, err := userAdminRepo.GetUserPage(context.TODO(), 1, 1, &entity.User{Username: "admin"}, "", false)
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Equal(t, "1", got[0].ID)
}
@@ -49,25 +50,25 @@ func Test_userAdminRepo_GetUserPage(t *testing.T) {
func Test_userAdminRepo_UpdateUserStatus(t *testing.T) {
userAdminRepo := user.NewUserAdminRepo(testDataSource, auth.NewAuthRepo(testDataSource))
got, exist, err := userAdminRepo.GetUserInfo(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, entity.UserStatusAvailable, got.Status)
err = userAdminRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusSuspended, entity.EmailStatusAvailable,
"admin@admin.com", time.Now().Add(time.Minute*5))
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err = userAdminRepo.GetUserInfo(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, entity.UserStatusSuspended, got.Status)
err = userAdminRepo.UpdateUserStatus(context.TODO(), "1", entity.UserStatusAvailable, entity.EmailStatusAvailable,
"admin@admin.com", time.Time{})
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err = userAdminRepo.GetUserInfo(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, entity.UserStatusAvailable, got.Status)
}
diff --git a/internal/repo/repo_test/user_repo_test.go b/internal/repo/repo_test/user_repo_test.go
index 7b18833dd..95a85ab99 100644
--- a/internal/repo/repo_test/user_repo_test.go
+++ b/internal/repo/repo_test/user_repo_test.go
@@ -26,6 +26,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/repo/user"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func Test_userRepo_AddUser(t *testing.T) {
@@ -40,21 +41,21 @@ func Test_userRepo_AddUser(t *testing.T) {
IsAdmin: false,
}
err := userRepo.AddUser(context.TODO(), userInfo)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_userRepo_BatchGetByID(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
got, err := userRepo.BatchGetByID(context.TODO(), []string{"1"})
- assert.NoError(t, err)
- assert.Equal(t, 1, len(got))
+ require.NoError(t, err)
+ assert.Len(t, got, 1)
assert.Equal(t, "admin", got[0].Username)
}
func Test_userRepo_GetByEmail(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
got, exist, err := userRepo.GetByEmail(context.TODO(), "admin@admin.com")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, "admin", got.Username)
}
@@ -62,7 +63,7 @@ func Test_userRepo_GetByEmail(t *testing.T) {
func Test_userRepo_GetByUserID(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
got, exist, err := userRepo.GetByUserID(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, "admin", got.Username)
}
@@ -70,7 +71,7 @@ func Test_userRepo_GetByUserID(t *testing.T) {
func Test_userRepo_GetByUsername(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
got, exist, err := userRepo.GetByUsername(context.TODO(), "admin")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, "admin", got.Username)
}
@@ -78,10 +79,10 @@ func Test_userRepo_GetByUsername(t *testing.T) {
func Test_userRepo_IncreaseAnswerCount(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.IncreaseAnswerCount(context.TODO(), "1", 1)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err := userRepo.GetByUserID(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, 1, got.AnswerCount)
}
@@ -89,10 +90,10 @@ func Test_userRepo_IncreaseAnswerCount(t *testing.T) {
func Test_userRepo_IncreaseQuestionCount(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.IncreaseQuestionCount(context.TODO(), "1", 1)
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err := userRepo.GetByUserID(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, 1, got.AnswerCount)
}
@@ -100,22 +101,22 @@ func Test_userRepo_IncreaseQuestionCount(t *testing.T) {
func Test_userRepo_UpdateEmail(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdateEmail(context.TODO(), "1", "admin@admin.com")
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_userRepo_UpdateEmailStatus(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdateEmailStatus(context.TODO(), "1", entity.EmailStatusToBeVerified)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_userRepo_UpdateInfo(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdateInfo(context.TODO(), &entity.User{ID: "1", Bio: "test"})
- assert.NoError(t, err)
+ require.NoError(t, err)
got, exist, err := userRepo.GetByUserID(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
assert.True(t, exist)
assert.Equal(t, "test", got.Bio)
}
@@ -123,17 +124,17 @@ func Test_userRepo_UpdateInfo(t *testing.T) {
func Test_userRepo_UpdateLastLoginDate(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdateLastLoginDate(context.TODO(), "1")
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_userRepo_UpdateNoticeStatus(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdateNoticeStatus(context.TODO(), "1", 1)
- assert.NoError(t, err)
+ require.NoError(t, err)
}
func Test_userRepo_UpdatePass(t *testing.T) {
userRepo := user.NewUserRepo(testDataSource)
err := userRepo.UpdatePass(context.TODO(), "1", "admin")
- assert.NoError(t, err)
+ require.NoError(t, err)
}
diff --git a/internal/repo/search_common/search_repo.go b/internal/repo/search_common/search_repo.go
index 806517234..a56b8edb6 100644
--- a/internal/repo/search_common/search_repo.go
+++ b/internal/repo/search_common/search_repo.go
@@ -190,7 +190,7 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
argsA = append(argsA, votes)
}
- //b = b.Union("all", ub)
+ // b = b.Union("all", ub)
ubSQL, _, err := ub.ToSQL()
if err != nil {
return
@@ -447,7 +447,7 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
return
}
-func (sr *searchRepo) parseOrder(ctx context.Context, order string) (res string) {
+func (sr *searchRepo) parseOrder(_ context.Context, order string) (res string) {
switch order {
case "newest":
res = "created_at desc"
diff --git a/internal/schema/backyard_user_schema.go b/internal/schema/backyard_user_schema.go
index 6ec848721..d36ff8083 100644
--- a/internal/schema/backyard_user_schema.go
+++ b/internal/schema/backyard_user_schema.go
@@ -222,7 +222,7 @@ func (req *AddUsersReq) ParseUsers(ctx context.Context) (errFields []*validator.
}
// check users amount
- if len(req.Users) <= 0 || len(req.Users) > constant.DefaultBulkUser {
+ if len(req.Users) == 0 || len(req.Users) > constant.DefaultBulkUser {
errFields = append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "users",
ErrorMsg: translator.TrWithData(handler.GetLangByCtx(ctx), reason.AddBulkUsersAmountError,
diff --git a/internal/schema/meta_schema.go b/internal/schema/meta_schema.go
index 286e2e7d6..e5a072529 100644
--- a/internal/schema/meta_schema.go
+++ b/internal/schema/meta_schema.go
@@ -19,6 +19,8 @@
package schema
+import "slices"
+
type UpdateReactionReq struct {
ObjectID string `validate:"required" json:"object_id"`
Emoji string `validate:"required,oneof=heart smile frown" json:"emoji"`
@@ -48,13 +50,7 @@ func (r *ReactionsSummaryMeta) AddReactionSummary(emoji, userID string) {
if reaction.Emoji != emoji {
continue
}
- exist := false
- for _, id := range reaction.UserIDs {
- if id == userID {
- exist = true
- break
- }
- }
+ exist := slices.Contains(reaction.UserIDs, userID)
if !exist {
reaction.UserIDs = append(reaction.UserIDs, userID)
}
@@ -94,10 +90,8 @@ func (r *ReactionsSummaryMeta) CheckUserInReactionSummary(emoji, userID string)
if reaction.Emoji != emoji {
continue
}
- for _, id := range reaction.UserIDs {
- if id == userID {
- return true
- }
+ if slices.Contains(reaction.UserIDs, userID) {
+ return true
}
}
return false
diff --git a/internal/schema/notification_schema.go b/internal/schema/notification_schema.go
index e5d60615e..b9e029389 100644
--- a/internal/schema/notification_schema.go
+++ b/internal/schema/notification_schema.go
@@ -53,7 +53,7 @@ var NotificationInboxType = map[string]int{
type NotificationContent struct {
ID string `json:"id"`
- TriggerUserID string `json:"-"` //show userid
+ TriggerUserID string `json:"-"` // show userid
ReceiverUserID string `json:"-"` // receiver userid
UserInfo *UserBasicInfo `json:"user_info,omitempty"`
ObjectInfo ObjectInfo `json:"object_info"`
@@ -169,8 +169,8 @@ func (r *RedDotBadgeAwardCache) RemoveBadgeAward(notificationID string) {
}
type NotificationSearch struct {
- Page int `json:"page" form:"page"` //Query number of pages
- PageSize int `json:"page_size" form:"page_size"` //Search page size
+ Page int `json:"page" form:"page"` // Query number of pages
+ PageSize int `json:"page_size" form:"page_size"` // Search page size
Type int `json:"-" form:"-"`
TypeStr string `json:"type" form:"type"` // inbox achievement
InboxTypeStr string `json:"inbox_type" form:"inbox_type"` // inbox achievement
diff --git a/internal/schema/revision_schema.go b/internal/schema/revision_schema.go
index b3ac0aadd..6f3246e51 100644
--- a/internal/schema/revision_schema.go
+++ b/internal/schema/revision_schema.go
@@ -53,7 +53,7 @@ const RevisionAuditReject = "reject"
type RevisionAuditReq struct {
// object id
ID string `validate:"required" comment:"id" form:"id"`
- Operation string `validate:"required" comment:"operation" form:"operation"` //approve or reject
+ Operation string `validate:"required" comment:"operation" form:"operation"` // approve or reject
UserID string `json:"-"`
CanReviewQuestion bool `json:"-"`
CanReviewAnswer bool `json:"-"`
diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go
index 0e43bb420..7ab657512 100644
--- a/internal/schema/siteinfo_schema.go
+++ b/internal/schema/siteinfo_schema.go
@@ -52,7 +52,7 @@ func (r *SiteGeneralReq) FormatSiteUrl() {
}
r.SiteUrl = fmt.Sprintf("%s://%s", parsedUrl.Scheme, parsedUrl.Host)
if len(parsedUrl.Path) > 0 {
- r.SiteUrl = r.SiteUrl + parsedUrl.Path
+ r.SiteUrl += parsedUrl.Path
r.SiteUrl = strings.TrimSuffix(r.SiteUrl, "/")
}
}
diff --git a/internal/schema/tag_schema.go b/internal/schema/tag_schema.go
index a51b2d2d5..8eb3212c4 100644
--- a/internal/schema/tag_schema.go
+++ b/internal/schema/tag_schema.go
@@ -109,7 +109,7 @@ type GetTagPageResp struct {
DisplayName string `json:"display_name"`
// excerpt
Excerpt string `json:"excerpt"`
- //description
+ // description
Description string `json:"description"`
// original text
OriginalText string `json:"original_text"`
diff --git a/internal/service/action/captcha_strategy.go b/internal/service/action/captcha_strategy.go
index b423b583b..edda73252 100644
--- a/internal/service/action/captcha_strategy.go
+++ b/internal/service/action/captcha_strategy.go
@@ -67,9 +67,8 @@ func (cs *CaptchaService) ValidationStrategy(ctx context.Context, unit, actionTy
return cs.CaptchaActionDelete(ctx, unit, info)
case entity.CaptchaActionVote:
return cs.CaptchaActionVote(ctx, unit, info)
-
}
- //actionType not found
+ // actionType not found
return false
}
@@ -83,7 +82,7 @@ func (cs *CaptchaService) CaptchaActionPassword(ctx context.Context, unit string
return true
}
setNum := 3
- setTime := int64(60 * 30) //seconds
+ setTime := int64(60 * 30) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
return false
@@ -101,7 +100,7 @@ func (cs *CaptchaService) CaptchaActionEditUserinfo(ctx context.Context, unit st
return true
}
setNum := 3
- setTime := int64(60 * 30) //seconds
+ setTime := int64(60 * 30) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
return false
@@ -119,7 +118,7 @@ func (cs *CaptchaService) CaptchaActionQuestion(ctx context.Context, unit string
return true
}
setNum := 10
- setTime := int64(5) //seconds
+ setTime := int64(5) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
return false
@@ -132,7 +131,7 @@ func (cs *CaptchaService) CaptchaActionAnswer(ctx context.Context, unit string,
return true
}
setNum := 10
- setTime := int64(5) //seconds
+ setTime := int64(5) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
return false
@@ -145,7 +144,7 @@ func (cs *CaptchaService) CaptchaActionComment(ctx context.Context, unit string,
return true
}
setNum := 30
- setTime := int64(1) //seconds
+ setTime := int64(1) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
return false
@@ -175,8 +174,8 @@ func (cs *CaptchaService) CaptchaActionSearch(ctx context.Context, unit string,
}
now := time.Now().Unix()
setNum := 20
- setTime := int64(60) //seconds
- if now-int64(actionInfo.LastTime) <= setTime && actionInfo.Num >= setNum {
+ setTime := int64(60) // seconds
+ if now-actionInfo.LastTime <= setTime && actionInfo.Num >= setNum {
return false
}
if now-actionInfo.LastTime > setTime {
@@ -192,7 +191,7 @@ func (cs *CaptchaService) CaptchaActionReport(ctx context.Context, unit string,
return true
}
setNum := 30
- setTime := int64(1) //seconds
+ setTime := int64(1) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
return false
@@ -205,7 +204,7 @@ func (cs *CaptchaService) CaptchaActionDelete(ctx context.Context, unit string,
return true
}
setNum := 5
- setTime := int64(5) //seconds
+ setTime := int64(5) // seconds
now := time.Now().Unix()
if now-actionInfo.LastTime <= setTime || actionInfo.Num >= setNum {
return false
diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go
index 9ee5f3ee0..b94f7912a 100644
--- a/internal/service/auth/auth.go
+++ b/internal/service/auth/auth.go
@@ -138,7 +138,7 @@ func (as *AuthService) RemoveTokensExceptCurrentUser(ctx context.Context, userID
as.authRepo.RemoveUserTokens(ctx, userID, accessToken)
}
-//Admin
+// Admin
func (as *AuthService) GetAdminUserCacheInfo(ctx context.Context, accessToken string) (userInfo *entity.UserCacheInfo, err error) {
return as.authRepo.GetAdminUserCacheInfo(ctx, accessToken)
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go
index 3a5b3e6c9..dc599e6df 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -178,9 +178,8 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
time.Now(), req.CanEdit, req.CanDelete)
if comment.Status == entity.CommentStatusAvailable {
- commentResp, err := cs.addCommentNotification(ctx, req, resp, comment, objInfo)
- if err != nil {
- return commentResp, err
+ if err := cs.addCommentNotification(ctx, req, resp, comment, objInfo); err != nil {
+ return nil, err
}
}
@@ -220,7 +219,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
func (cs *CommentService) addCommentNotification(
ctx context.Context, req *schema.AddCommentReq, resp *schema.GetCommentResp,
- comment *entity.Comment, objInfo *schema.SimpleObjectInfo) (*schema.GetCommentResp, error) {
+ comment *entity.Comment, objInfo *schema.SimpleObjectInfo) error {
// The priority of the notification
// 1. reply to user
// 2. comment mention to user
@@ -231,7 +230,7 @@ func (cs *CommentService) addCommentNotification(
if len(resp.ReplyUserID) > 0 && resp.ReplyUserID != req.UserID {
replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
if err != nil {
- return nil, err
+ return err
}
if exist {
resp.ReplyUsername = replyUser.Username
@@ -241,7 +240,7 @@ func (cs *CommentService) addCommentNotification(
cs.notificationCommentReply(ctx, replyUser.ID, comment.ID, req.UserID,
objInfo.QuestionID, objInfo.Title, htmltext.FetchExcerpt(comment.ParsedText, "...", 240))
alreadyNotifiedUserID[replyUser.ID] = true
- return nil, nil
+ return nil
}
if len(req.MentionUsernameList) > 0 {
@@ -250,7 +249,7 @@ func (cs *CommentService) addCommentNotification(
for _, userID := range alreadyNotifiedUserIDs {
alreadyNotifiedUserID[userID] = true
}
- return nil, nil
+ return nil
}
if objInfo.ObjectType == constant.QuestionObjectType && !alreadyNotifiedUserID[objInfo.ObjectCreatorUserID] {
@@ -260,7 +259,7 @@ func (cs *CommentService) addCommentNotification(
cs.notificationAnswerComment(ctx, objInfo.QuestionID, objInfo.Title, objInfo.AnswerID,
objInfo.ObjectCreatorUserID, comment.ID, req.UserID, htmltext.FetchExcerpt(comment.ParsedText, "...", 240))
}
- return nil, nil
+ return nil
}
// RemoveComment delete comment
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go
index 982bbf88a..f904b82f0 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -146,7 +146,6 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
if !exist {
return errors.BadRequest(reason.AnswerCannotDeleted)
}
-
}
err = as.answerRepo.RemoveAnswer(ctx, req.ID)
@@ -180,10 +179,10 @@ func (as *AnswerService) RemoveAnswer(ctx context.Context, req *schema.RemoveAns
// #2372 In order to simplify the process and complexity, as well as to consider if it is in-house,
// facing the problem of recovery.
- //err = as.answerActivityService.DeleteAnswer(ctx, answerInfo.ID, answerInfo.CreatedAt, answerInfo.VoteCount)
- //if err != nil {
- // log.Errorf("delete answer activity change failed: %s", err.Error())
- //}
+ // err = as.answerActivityService.DeleteAnswer(ctx, answerInfo.ID, answerInfo.CreatedAt, answerInfo.VoteCount)
+ // if err != nil {
+ // log.Errorf("delete answer activity change failed: %s", err.Error())
+ // }
as.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: req.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
@@ -264,7 +263,7 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
insertData.RevisionID = "0"
insertData.LastEditUserID = "0"
insertData.Status = entity.AnswerStatusPending
- //insertData.UpdatedAt = now
+ // insertData.UpdatedAt = now
if err = as.answerRepo.AddAnswer(ctx, insertData); err != nil {
return "", err
}
@@ -365,7 +364,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", errors.BadRequest(reason.QuestionNotFound)
}
- //If the content is the same, ignore it
+ // If the content is the same, ignore it
if answerInfo.OriginalText == req.Content {
return "", nil
}
diff --git a/internal/service/content/question_hottest_service.go b/internal/service/content/question_hottest_service.go
index 085b36709..b73e7a30d 100644
--- a/internal/service/content/question_hottest_service.go
+++ b/internal/service/content/question_hottest_service.go
@@ -30,7 +30,6 @@ import (
)
func (q *QuestionService) RefreshHottestCron(ctx context.Context) {
-
var (
page = 1
pageSize = 100
diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go
index da15fae5d..b8372a72e 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -216,9 +216,9 @@ func (qs *QuestionService) ReopenQuestion(ctx context.Context, req *schema.Reope
return nil
}
-func (qs *QuestionService) AddQuestionCheckTags(ctx context.Context, Tags []*entity.Tag) ([]string, error) {
+func (qs *QuestionService) AddQuestionCheckTags(ctx context.Context, tags []*entity.Tag) ([]string, error) {
list := make([]string, 0)
- for _, tag := range Tags {
+ for _, tag := range tags {
if tag.Reserved {
list = append(list, tag.DisplayName)
}
@@ -374,14 +374,14 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
question.AcceptedAnswerID = "0"
question.LastAnswerID = "0"
question.LastEditUserID = "0"
- //question.PostUpdateTime = nil
+ // question.PostUpdateTime = nil
question.Status = entity.QuestionStatusPending
question.RevisionID = "0"
question.CreatedAt = now
question.PostUpdateTime = now
question.Pin = entity.QuestionUnPin
question.Show = entity.QuestionShow
- //question.UpdatedAt = nil
+ // question.UpdatedAt = nil
err = qs.questionRepo.AddQuestion(ctx, question)
if err != nil {
return
@@ -416,10 +416,7 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
Title: question.Title,
}
- questionWithTagsRevision, err := qs.changeQuestionToRevision(ctx, question, tags)
- if err != nil {
- return nil, err
- }
+ questionWithTagsRevision := qs.changeQuestionToRevision(ctx, question, tags)
infoJSON, _ := json.Marshal(questionWithTagsRevision)
revisionDTO.Content = string(infoJSON)
revisionID, err := qs.revisionService.AddRevision(ctx, revisionDTO, true)
@@ -554,7 +551,7 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
if err != nil {
return err
}
- //if the status is deleted, return directly
+ // if the status is deleted, return directly
if questionInfo.Status == entity.QuestionStatusDeleted {
return nil
}
@@ -613,7 +610,7 @@ func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.Remov
}
}
- //tag count
+ // tag count
tagIDs := make([]string, 0)
Tags, tagerr := qs.tagCommon.GetObjectEntityTag(ctx, req.ID)
if tagerr != nil {
@@ -684,7 +681,7 @@ func (qs *QuestionService) UpdateQuestionCheckTags(ctx context.Context, req *sch
isChange := qs.tagCommon.CheckTagsIsChange(ctx, tagNameList, oldtagNameList)
- //If the content is the same, ignore it
+ // If the content is the same, ignore it
if dbinfo.Title == req.Title && dbinfo.OriginalText == req.Content && !isChange {
return
}
@@ -798,7 +795,7 @@ func (qs *QuestionService) UpdateQuestionInviteUser(ctx context.Context, req *sc
return errors.BadRequest(reason.QuestionNotFound)
}
- //verify invite user
+ // verify invite user
inviteUserInfoList, err := qs.userCommon.BatchGetUserBasicInfoByUserNames(ctx, req.InviteUser)
if err != nil {
log.Error("BatchGetUserBasicInfoByUserNames error", err.Error())
@@ -826,7 +823,7 @@ func (qs *QuestionService) UpdateQuestionInviteUser(ctx context.Context, req *sc
if saveerr != nil {
return saveerr
}
- //send notification
+ // send notification
oldInviteUserIDsStr := originQuestion.InviteUserID
oldInviteUserIDs := make([]string, 0)
needSendNotificationUserIDs := make([]string, 0)
@@ -964,7 +961,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
isChange := qs.tagCommon.CheckTagsIsChange(ctx, tagNameList, oldtagNameList)
- //If the content is the same, ignore it
+ // If the content is the same, ignore it
if dbinfo.Title == req.Title && dbinfo.OriginalText == req.Content && !isChange {
return
}
@@ -1015,7 +1012,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
return errorlist, err
}
- //Administrators and themselves do not need to be audited
+ // Administrators and themselves do not need to be audited
revisionDTO := &schema.AddRevisionDTO{
UserID: question.UserID,
@@ -1031,11 +1028,11 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
// It's not you or the administrator that needs to be reviewed
if !canUpdate {
revisionDTO.Status = entity.RevisionUnreviewedStatus
- revisionDTO.UserID = req.UserID //use revision userid
+ revisionDTO.UserID = req.UserID // use revision userid
} else {
- //Direct modification
+ // Direct modification
revisionDTO.Status = entity.RevisionReviewPassStatus
- //update question to db
+ // update question to db
question.ParsedText, err = qs.questioncommon.UpdateQuestionLink(ctx, question.ID, "", question.ParsedText, question.OriginalText)
if err != nil {
return questionInfo, err
@@ -1054,10 +1051,7 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
}
}
- questionWithTagsRevision, err := qs.changeQuestionToRevision(ctx, question, Tags)
- if err != nil {
- return nil, err
- }
+ questionWithTagsRevision := qs.changeQuestionToRevision(ctx, question, Tags)
infoJSON, _ := json.Marshal(questionWithTagsRevision)
revisionDTO.Content = string(infoJSON)
revisionID, err := qs.revisionService.AddRevision(ctx, revisionDTO, true)
@@ -1165,7 +1159,6 @@ func (qs *QuestionService) CheckChangeReservedTag(ctx context.Context, oldobject
// PersonalQuestionPage get question list by user
func (qs *QuestionService) PersonalQuestionPage(ctx context.Context, req *schema.PersonalQuestionPageReq) (
pageModel *pager.PageModel, err error) {
-
userinfo, exist, err := qs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
if err != nil {
return nil, err
@@ -1250,7 +1243,6 @@ func (qs *QuestionService) PersonalAnswerPage(ctx context.Context, req *schema.P
info.QuestionID = item.QuestionID
if item.QuestionInfo.Status == entity.QuestionStatusDeleted {
info.QuestionInfo.Title = "Deleted question"
-
}
userAnswerlist = append(userAnswerlist, info)
}
@@ -1489,7 +1481,8 @@ func (qs *QuestionService) GetQuestionPage(ctx context.Context, req *schema.Ques
if err != nil {
return nil, 0, err
}
- tagIDs = append(synTagIds, tagInfo.ID)
+ tagIDs = append(tagIDs, synTagIds...)
+ tagIDs = append(tagIDs, tagInfo.ID)
} else {
return questions, 0, nil
}
@@ -1585,10 +1578,10 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, req *sche
if setStatus == entity.QuestionStatusDeleted {
// #2372 In order to simplify the process and complexity, as well as to consider if it is in-house,
// facing the problem of recovery.
- //err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount)
- //if err != nil {
- // log.Errorf("admin delete question then rank rollback error %s", err.Error())
- //}
+ // err = qs.answerActivityService.DeleteQuestion(ctx, questionInfo.ID, questionInfo.CreatedAt, questionInfo.VoteCount)
+ // if err != nil {
+ // log.Errorf("admin delete question then rank rollback error %s", err.Error())
+ // }
qs.activityQueueService.Send(ctx, &schema.ActivityMsg{
UserID: questionInfo.UserID,
TriggerUserID: converter.StringToInt64(req.UserID),
@@ -1642,7 +1635,6 @@ func (qs *QuestionService) AdminSetQuestionStatus(ctx context.Context, req *sche
func (qs *QuestionService) AdminQuestionPage(
ctx context.Context, req *schema.AdminQuestionPageReq) (
resp *pager.PageModel, err error) {
-
list := make([]*schema.AdminQuestionInfo, 0)
questionList, count, err := qs.questionRepo.AdminQuestionPage(ctx, req)
if err != nil {
@@ -1708,8 +1700,8 @@ func (qs *QuestionService) AdminAnswerPage(ctx context.Context, req *schema.Admi
return pager.NewPageModel(count, answerResp), nil
}
-func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questionInfo *entity.Question, tags []*entity.Tag) (
- questionRevision *entity.QuestionWithTagsRevision, err error) {
+func (qs *QuestionService) changeQuestionToRevision(_ context.Context, questionInfo *entity.Question, tags []*entity.Tag) (
+ questionRevision *entity.QuestionWithTagsRevision) {
questionRevision = &entity.QuestionWithTagsRevision{}
questionRevision.Question = *questionInfo
@@ -1718,7 +1710,7 @@ func (qs *QuestionService) changeQuestionToRevision(ctx context.Context, questio
_ = copier.Copy(item, tag)
questionRevision.Tags = append(questionRevision.Tags, item)
}
- return questionRevision, nil
+ return questionRevision
}
func (qs *QuestionService) SitemapCron(ctx context.Context) {
diff --git a/internal/service/content/revision_service.go b/internal/service/content/revision_service.go
index a5cefeb41..4ac08e769 100644
--- a/internal/service/content/revision_service.go
+++ b/internal/service/content/revision_service.go
@@ -235,7 +235,6 @@ func (rs *RevisionService) revisionAuditQuestion(ctx context.Context, revisionit
func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem *schema.GetRevisionResp) (err error) {
answerinfo, ok := revisionitem.ContentParsed.(*schema.AnswerInfo)
if ok {
-
var PostUpdateTime time.Time
dbquestion, exist, dberr := rs.questionRepo.GetQuestion(ctx, answerinfo.QuestionID)
if dberr != nil || !exist {
diff --git a/internal/service/content/search_service.go b/internal/service/content/search_service.go
index ccafcd82c..866f1c6e2 100644
--- a/internal/service/content/search_service.go
+++ b/internal/service/content/search_service.go
@@ -68,13 +68,14 @@ func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (res
resp = &schema.SearchResp{}
// search plugin is not found, call system search
if finder == nil {
- if cond.SearchAll() {
+ switch {
+ case cond.SearchAll():
resp.SearchResults, resp.Total, err =
ss.searchRepo.SearchContents(ctx, cond.Words, cond.Tags, cond.UserID, cond.VoteAmount, dto.Page, dto.Size, dto.Order)
- } else if cond.SearchQuestion() {
+ case cond.SearchQuestion():
resp.SearchResults, resp.Total, err =
ss.searchRepo.SearchQuestions(ctx, cond.Words, cond.Tags, cond.NotAccepted, cond.Views, cond.AnswerAmount, dto.Page, dto.Size, dto.Order)
- } else if cond.SearchAnswer() {
+ case cond.SearchAnswer():
resp.SearchResults, resp.Total, err =
ss.searchRepo.SearchAnswers(ctx, cond.Words, cond.Tags, cond.Accepted, cond.QuestionID, dto.Page, dto.Size, dto.Order)
}
@@ -86,11 +87,12 @@ func (ss *SearchService) Search(ctx context.Context, dto *schema.SearchDTO) (res
func (ss *SearchService) searchByPlugin(ctx context.Context, finder plugin.Search, cond *schema.SearchCondition, dto *schema.SearchDTO) (resp *schema.SearchResp, err error) {
var res []plugin.SearchResult
resp = &schema.SearchResp{}
- if cond.SearchAll() {
+ switch {
+ case cond.SearchAll():
res, resp.Total, err = finder.SearchContents(ctx, cond.Convert2PluginSearchCond(dto.Page, dto.Size, dto.Order))
- } else if cond.SearchQuestion() {
+ case cond.SearchQuestion():
res, resp.Total, err = finder.SearchQuestions(ctx, cond.Convert2PluginSearchCond(dto.Page, dto.Size, dto.Order))
- } else if cond.SearchAnswer() {
+ case cond.SearchAnswer():
res, resp.Total, err = finder.SearchAnswers(ctx, cond.Convert2PluginSearchCond(dto.Page, dto.Size, dto.Order))
}
if err != nil {
diff --git a/internal/service/content/user_service.go b/internal/service/content/user_service.go
index 81f7ac824..e9cc35788 100644
--- a/internal/service/content/user_service.go
+++ b/internal/service/content/user_service.go
@@ -314,7 +314,6 @@ func (us *UserService) UserModifyPassword(ctx context.Context, req *schema.UserM
// UpdateInfo update user info
func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) (
errFields []*validator.FormErrorField, err error) {
-
if len(req.Username) > 0 {
if checker.IsInvalidUsername(req.Username) {
return append(errFields, &validator.FormErrorField{
@@ -598,7 +597,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
// verifyPassword
// Compare whether the password is correct
-func (us *UserService) verifyPassword(ctx context.Context, loginPass, userPass string) bool {
+func (us *UserService) verifyPassword(_ context.Context, loginPass, userPass string) bool {
if len(loginPass) == 0 && len(userPass) == 0 {
return true
}
@@ -608,8 +607,8 @@ func (us *UserService) verifyPassword(ctx context.Context, loginPass, userPass s
// encryptPassword
// The password does irreversible encryption.
-func (us *UserService) encryptPassword(ctx context.Context, Pass string) (string, error) {
- hashPwd, err := bcrypt.GenerateFromPassword([]byte(Pass), bcrypt.DefaultCost)
+func (us *UserService) encryptPassword(_ context.Context, pass string) (string, error) {
+ hashPwd, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
// This encrypted string can be saved to the database and can be used as password matching verification
return string(hashPwd), err
}
diff --git a/internal/service/dashboard/dashboard_service.go b/internal/service/dashboard/dashboard_service.go
index 91f0e338a..8b08ba02f 100644
--- a/internal/service/dashboard/dashboard_service.go
+++ b/internal/service/dashboard/dashboard_service.go
@@ -264,7 +264,7 @@ func (ds *dashboardService) voteCount(ctx context.Context) int64 {
return voteCount
}
-func (ds *dashboardService) remoteVersion(ctx context.Context) string {
+func (ds *dashboardService) remoteVersion(_ context.Context) string {
req, _ := http.NewRequest("GET", "https://answer.apache.org/data/latest.json?from_version="+constant.Version, nil)
req.Header.Set("User-Agent", "Answer/"+constant.Version)
httpClient := &http.Client{}
@@ -359,11 +359,9 @@ func (ds *dashboardService) GetDatabaseSize() (dbSize string) {
res, err := ds.data.DB.QueryInterface(sql)
if err != nil {
log.Warnf("get db size failed: %s", err)
- } else {
- if len(res) > 0 && res[0]["db_size"] != nil {
- dbSizeStr, _ := res[0]["db_size"].(string)
- dbSize = dir.FormatFileSize(converter.StringToInt64(dbSizeStr))
- }
+ } else if len(res) > 0 && res[0]["db_size"] != nil {
+ dbSizeStr, _ := res[0]["db_size"].(string)
+ dbSize = dir.FormatFileSize(converter.StringToInt64(dbSizeStr))
}
case schemas.POSTGRES:
sql := fmt.Sprintf("SELECT pg_database_size('%s') AS db_size",
@@ -371,11 +369,9 @@ func (ds *dashboardService) GetDatabaseSize() (dbSize string) {
res, err := ds.data.DB.QueryInterface(sql)
if err != nil {
log.Warnf("get db size failed: %s", err)
- } else {
- if len(res) > 0 && res[0]["db_size"] != nil {
- dbSizeStr, _ := res[0]["db_size"].(int32)
- dbSize = dir.FormatFileSize(int64(dbSizeStr))
- }
+ } else if len(res) > 0 && res[0]["db_size"] != nil {
+ dbSizeStr, _ := res[0]["db_size"].(int32)
+ dbSize = dir.FormatFileSize(int64(dbSizeStr))
}
case schemas.SQLITE:
dirSize, err := dir.DirSize(ds.data.DB.DataSourceName())
diff --git a/internal/service/notification/invite_answer_notification.go b/internal/service/notification/invite_answer_notification.go
index f68feb067..6b0407f9f 100644
--- a/internal/service/notification/invite_answer_notification.go
+++ b/internal/service/notification/invite_answer_notification.go
@@ -45,8 +45,7 @@ func (ns *ExternalNotificationService) handleInviteAnswerNotification(ctx contex
if !channel.Enable {
continue
}
- switch channel.Key {
- case constant.EmailChannel:
+ if channel.Key == constant.EmailChannel {
ns.sendInviteAnswerNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewInviteAnswerTemplateRawData)
}
}
diff --git a/internal/service/notification/new_answer_notification.go b/internal/service/notification/new_answer_notification.go
index c54fd961c..4ae9ca9ea 100644
--- a/internal/service/notification/new_answer_notification.go
+++ b/internal/service/notification/new_answer_notification.go
@@ -45,8 +45,7 @@ func (ns *ExternalNotificationService) handleNewAnswerNotification(ctx context.C
if !channel.Enable {
continue
}
- switch channel.Key {
- case constant.EmailChannel:
+ if channel.Key == constant.EmailChannel {
ns.sendNewAnswerNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewAnswerTemplateRawData)
}
}
diff --git a/internal/service/notification/new_comment_notification.go b/internal/service/notification/new_comment_notification.go
index e622ed4f7..9734e54e5 100644
--- a/internal/service/notification/new_comment_notification.go
+++ b/internal/service/notification/new_comment_notification.go
@@ -45,8 +45,7 @@ func (ns *ExternalNotificationService) handleNewCommentNotification(ctx context.
if !channel.Enable {
continue
}
- switch channel.Key {
- case constant.EmailChannel:
+ if channel.Key == constant.EmailChannel {
ns.sendNewCommentNotificationEmail(ctx, msg.ReceiverUserID, msg.ReceiverEmail, msg.ReceiverLang, msg.NewCommentTemplateRawData)
}
}
diff --git a/internal/service/notification/new_question_notification.go b/internal/service/notification/new_question_notification.go
index debfb8c27..2f83042b2 100644
--- a/internal/service/notification/new_question_notification.go
+++ b/internal/service/notification/new_question_notification.go
@@ -55,8 +55,7 @@ func (ns *ExternalNotificationService) handleNewQuestionNotification(ctx context
if !channel.Enable {
continue
}
- switch channel.Key {
- case constant.EmailChannel:
+ if channel.Key == constant.EmailChannel {
ns.sendNewQuestionNotificationEmail(ctx, subscriber.UserID, &schema.NewQuestionTemplateRawData{
QuestionTitle: msg.NewQuestionTemplateRawData.QuestionTitle,
QuestionID: msg.NewQuestionTemplateRawData.QuestionID,
diff --git a/internal/service/notification/notification_service.go b/internal/service/notification/notification_service.go
index 0369d4557..6a69cbaef 100644
--- a/internal/service/notification/notification_service.go
+++ b/internal/service/notification/notification_service.go
@@ -226,15 +226,12 @@ func (ns *NotificationService) GetNotificationPage(ctx context.Context, searchCo
if err != nil {
return nil, err
}
- resp, err = ns.formatNotificationPage(ctx, notifications)
- if err != nil {
- return nil, err
- }
+ resp = ns.formatNotificationPage(ctx, notifications)
return pager.NewPageModel(total, resp), nil
}
func (ns *NotificationService) formatNotificationPage(ctx context.Context, notifications []*entity.Notification) (
- resp []*schema.NotificationContent, err error) {
+ resp []*schema.NotificationContent) {
lang := handler.GetLangByCtx(ctx)
enableShortID := handler.GetEnableShortID(ctx)
userIDs := make([]string, 0)
@@ -287,13 +284,13 @@ func (ns *NotificationService) formatNotificationPage(ctx context.Context, notif
}
if len(userIDs) == 0 {
- return resp, nil
+ return resp
}
users, err := ns.userRepo.BatchGetByID(ctx, userIDs)
if err != nil {
log.Error(err)
- return resp, nil
+ return resp
}
userIDMapping := make(map[string]*entity.User, len(users))
for _, user := range users {
@@ -314,5 +311,5 @@ func (ns *NotificationService) formatNotificationPage(ctx context.Context, notif
}
}
}
- return resp, nil
+ return resp
}
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 55d638424..0bbd1865f 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -155,7 +155,7 @@ func (ns *NotificationCommon) AddNotification(ctx context.Context, msg *schema.N
}
req.Rank = rank
if exist {
- //modify notification
+ // modify notification
updateContent := &schema.NotificationContent{}
err := json.Unmarshal([]byte(notificationInfo.Content), updateContent)
if err != nil {
diff --git a/internal/service/plugin_common/plugin_common_service.go b/internal/service/plugin_common/plugin_common_service.go
index d3aa839b2..7d39a5aa0 100644
--- a/internal/service/plugin_common/plugin_common_service.go
+++ b/internal/service/plugin_common/plugin_common_service.go
@@ -69,7 +69,6 @@ func NewPluginCommonService(
data *data.Data,
importerService *importer.ImporterService,
) *PluginCommonService {
-
p := &PluginCommonService{
configService: configService,
pluginConfigRepo: pluginConfigRepo,
diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go
index 333806445..846dea894 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -179,17 +179,17 @@ func (qs *QuestionCommon) UpdateCollectionCount(ctx context.Context, questionID
return qs.questionRepo.UpdateCollectionCount(ctx, questionID)
}
-func (qs *QuestionCommon) UpdateAccepted(ctx context.Context, questionID, AnswerID string) error {
+func (qs *QuestionCommon) UpdateAccepted(ctx context.Context, questionID, answerID string) error {
question := &entity.Question{}
question.ID = questionID
- question.AcceptedAnswerID = AnswerID
+ question.AcceptedAnswerID = answerID
return qs.questionRepo.UpdateAccepted(ctx, question)
}
-func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionID, AnswerID string) error {
+func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionID, answerID string) error {
question := &entity.Question{}
question.ID = questionID
- question.LastAnswerID = AnswerID
+ question.LastAnswerID = answerID
return qs.questionRepo.UpdateLastAnswer(ctx, question)
}
@@ -232,7 +232,7 @@ func (qs *QuestionCommon) InviteUserInfo(ctx context.Context, questionID string)
if !has {
return InviteUserInfo, errors.NotFound(reason.QuestionNotFound)
}
- //InviteUser
+ // InviteUser
if dbinfo.InviteUserID != "" {
InviteUserIDs := make([]string, 0)
err := json.Unmarshal([]byte(dbinfo.InviteUserID), &InviteUserIDs)
@@ -681,7 +681,6 @@ func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question)
info.LastAnsweredUserID = answerInfo.UserID
}
}
-
}
info.Tags = make([]*schema.TagResp, 0)
return &info
diff --git a/internal/service/siteinfo_common/siteinfo_service_test.go b/internal/service/siteinfo_common/siteinfo_service_test.go
index 3a567dcb9..a87d427f2 100644
--- a/internal/service/siteinfo_common/siteinfo_service_test.go
+++ b/internal/service/siteinfo_common/siteinfo_service_test.go
@@ -27,6 +27,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/service/mock"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
@@ -46,6 +47,6 @@ func TestSiteInfoCommonService_GetSiteGeneral(t *testing.T) {
mockInit(ctl)
siteInfoCommonService := NewSiteInfoCommonService(mockSiteInfoRepo)
resp, err := siteInfoCommonService.GetSiteGeneral(context.TODO())
- assert.NoError(t, err)
- assert.Equal(t, resp.Name, "name")
+ require.NoError(t, err)
+ assert.Equal(t, "name", resp.Name)
}
diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go
index 577199ec8..e61bfa06e 100644
--- a/internal/service/tag/tag_service.go
+++ b/internal/service/tag/tag_service.go
@@ -74,7 +74,7 @@ func NewTagService(
// RemoveTag delete tag
func (ts *TagService) RemoveTag(ctx context.Context, req *schema.RemoveTagReq) (err error) {
- //If the tag has associated problems, it cannot be deleted
+ // If the tag has associated problems, it cannot be deleted
tagCount, err := ts.tagCommonService.CountTagRelByTagID(ctx, req.TagID)
if err != nil {
return err
@@ -83,7 +83,7 @@ func (ts *TagService) RemoveTag(ctx context.Context, req *schema.RemoveTagReq) (
return errors.BadRequest(reason.TagIsUsedCannotDelete)
}
- //If the tag has associated problems, it cannot be deleted
+ // If the tag has associated problems, it cannot be deleted
tagSynonymCount, err := ts.tagRepo.GetTagSynonymCount(ctx, req.TagID)
if err != nil {
return err
diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go
index fed5dfbfb..87c10bcc9 100644
--- a/internal/service/tag_common/tag_common.go
+++ b/internal/service/tag_common/tag_common.go
@@ -591,7 +591,6 @@ func (ts *TagCommonService) CheckTag(ctx context.Context, tags []string, userID
err = errors.BadRequest(reason.TagNotFound).WithMsg(fmt.Sprintf("tag [%s] does not exist",
strings.Join(addTagMsgList, ",")))
return err
-
}
return nil
@@ -660,9 +659,8 @@ func (ts *TagCommonService) CheckChangeReservedTag(ctx context.Context, oldobjec
// ObjectChangeTag change object tag list
func (ts *TagCommonService) ObjectChangeTag(ctx context.Context, objectTagData *schema.TagChange, minimumTags int) (errorlist []*validator.FormErrorField, err error) {
- //checks if the tags sent in the put req are less than the minimum, if so, tag changes are not applied
+ // checks if the tags sent in the put req are less than the minimum, if so, tag changes are not applied
if len(objectTagData.Tags) < minimumTags {
-
errorlist := make([]*validator.FormErrorField, 0)
errorlist = append(errorlist, &validator.FormErrorField{
ErrorField: "tags",
@@ -884,11 +882,11 @@ func (ts *TagCommonService) UpdateTag(ctx context.Context, req *schema.UpdateTag
return errors.BadRequest(reason.TagNotFound)
}
- //Adding equivalent slug formatting for tag update
+ // 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 the content is the same, ignore it
if tagInfo.OriginalText == req.OriginalText &&
tagInfo.DisplayName == req.DisplayName &&
tagInfo.SlugName == slugName {
diff --git a/internal/service/uploader/upload.go b/internal/service/uploader/upload.go
index 30029193b..8dea746ce 100644
--- a/internal/service/uploader/upload.go
+++ b/internal/service/uploader/upload.go
@@ -135,7 +135,6 @@ func (us *uploaderService) UploadAvatarFile(ctx *gin.Context, userID string) (ur
}
us.fileRecordService.AddFileRecord(ctx, userID, avatarFilePath, url, string(plugin.UserAvatar))
return url, nil
-
}
func (us *uploaderService) AvatarThumbFile(ctx *gin.Context, fileName string, size int) (url string, err error) {
@@ -304,7 +303,6 @@ func (us *uploaderService) UploadBrandingFile(ctx *gin.Context, userID string) (
}
us.fileRecordService.AddFileRecord(ctx, userID, avatarFilePath, url, string(plugin.AdminBranding))
return url, nil
-
}
func (us *uploaderService) uploadImageFile(ctx *gin.Context, file *multipart.FileHeader, fileSubPath string) (
diff --git a/internal/service/user_admin/user_backyard.go b/internal/service/user_admin/user_backyard.go
index 6fce161ce..fcced1c8b 100644
--- a/internal/service/user_admin/user_backyard.go
+++ b/internal/service/user_admin/user_backyard.go
@@ -470,14 +470,15 @@ func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUser
user := &entity.User{}
_ = copier.Copy(user, req)
- if req.IsInactive() {
+ switch {
+ case req.IsInactive():
user.MailStatus = entity.EmailStatusToBeVerified
user.Status = entity.UserStatusAvailable
- } else if req.IsSuspended() {
+ case req.IsSuspended():
user.Status = entity.UserStatusSuspended
- } else if req.IsDeleted() {
+ case req.IsDeleted():
user.Status = entity.UserStatusDeleted
- } else {
+ default:
user.MailStatus = entity.EmailStatusAvailable
user.Status = entity.UserStatusAvailable
}
@@ -486,8 +487,8 @@ func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUser
if email, e := mail.ParseAddress(req.Query); e == nil {
user.EMail = email.Address
req.Query = ""
- } else if strings.HasPrefix(req.Query, "user:") {
- id := strings.TrimSpace(strings.TrimPrefix(req.Query, "user:"))
+ } else if after, ok := strings.CutPrefix(req.Query, "user:"); ok {
+ id := strings.TrimSpace(after)
idSearch := true
for _, r := range id {
if !unicode.IsDigit(r) {
@@ -521,18 +522,19 @@ func (us *UserAdminService) GetUserPage(ctx context.Context, req *schema.GetUser
DisplayName: u.DisplayName,
Avatar: avatarMapping[u.ID].GetURL(),
}
- if u.Status == entity.UserStatusDeleted {
+ switch {
+ case u.Status == entity.UserStatusDeleted:
t.Status = constant.UserDeleted
t.DeletedAt = u.DeletedAt.Unix()
- } else if u.Status == entity.UserStatusSuspended {
+ case u.Status == entity.UserStatusSuspended:
t.Status = constant.UserSuspended
t.SuspendedAt = u.SuspendedAt.Unix()
if !u.SuspendedUntil.IsZero() {
t.SuspendedUntil = u.SuspendedUntil.Unix()
}
- } else if u.MailStatus == entity.EmailStatusToBeVerified {
+ case u.MailStatus == entity.EmailStatusToBeVerified:
t.Status = constant.UserInactive
- } else {
+ default:
t.Status = constant.UserNormal
}
resp = append(resp, t)
@@ -646,7 +648,6 @@ func (us *UserAdminService) CheckAndUnsuspendExpiredUsers(ctx context.Context) e
if user.Status == entity.UserStatusSuspended &&
!user.SuspendedUntil.IsZero() &&
user.SuspendedUntil.Before(now) {
-
log.Infof("Unsuspending user %s (ID: %s) - suspension expired at %v",
user.Username, user.ID, user.SuspendedUntil)
diff --git a/internal/service/user_common/user.go b/internal/service/user_common/user.go
index 124972a16..2e777f14c 100644
--- a/internal/service/user_common/user.go
+++ b/internal/service/user_common/user.go
@@ -85,9 +85,9 @@ func NewUserCommon(
}
}
-func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (
+func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, id string) (
userBasicInfo *schema.UserBasicInfo, exist bool, err error) {
- userInfo, exist, err := us.userRepo.GetByUserID(ctx, ID)
+ userInfo, exist, err := us.userRepo.GetByUserID(ctx, id)
if err != nil {
return nil, exist, err
}
diff --git a/internal/service/user_notification_config/user_notification_config_service.go b/internal/service/user_notification_config/user_notification_config_service.go
index c40df55cf..8ab72fa94 100644
--- a/internal/service/user_notification_config/user_notification_config_service.go
+++ b/internal/service/user_notification_config/user_notification_config_service.go
@@ -96,7 +96,7 @@ func (us *UserNotificationConfigService) SetDefaultUserNotificationConfig(ctx co
string(constant.InboxSource), `[{"key":"email","enable":true}]`)
}
-func (us *UserNotificationConfigService) convertToEntity(ctx context.Context, userID string,
+func (us *UserNotificationConfigService) convertToEntity(_ context.Context, userID string,
source constant.NotificationSource, channel schema.NotificationChannelConfig) (c *entity.UserNotificationConfig) {
var channels schema.NotificationChannels
channels = append(channels, &channel)
diff --git a/pkg/checker/file_type.go b/pkg/checker/file_type.go
index ac1fbcaf9..8eaabcbfc 100644
--- a/pkg/checker/file_type.go
+++ b/pkg/checker/file_type.go
@@ -28,6 +28,7 @@ import (
"io"
"os"
"path/filepath"
+ "slices"
"strings"
"github.com/segmentfault/pacman/log"
@@ -38,12 +39,7 @@ import (
// WANING Only checks the file extension is not reliable, but `http.DetectContentType` and `mimetype` are not reliable for all file types.
func IsUnAuthorizedExtension(fileName string, allowedExtensions []string) bool {
ext := strings.ToLower(strings.Trim(filepath.Ext(fileName), "."))
- for _, extension := range allowedExtensions {
- if extension == ext {
- return false
- }
- }
- return true
+ return !slices.Contains(allowedExtensions, ext)
}
// DecodeAndCheckImageFile currently answers support image type is
diff --git a/pkg/checker/path_ignore.go b/pkg/checker/path_ignore.go
index 8be757be5..24b092f7c 100644
--- a/pkg/checker/path_ignore.go
+++ b/pkg/checker/path_ignore.go
@@ -20,6 +20,7 @@
package checker
import (
+ "slices"
"sync"
"github.com/apache/answer/configs"
@@ -46,21 +47,11 @@ func initPathIgnore() {
// IsUsersIgnorePath checks whether the username is in ignore path
func IsUsersIgnorePath(username string) bool {
ignorePathInit.Do(initPathIgnore)
- for _, u := range pathIgnore.Users {
- if u == username {
- return true
- }
- }
- return false
+ return slices.Contains(pathIgnore.Users, username)
}
// IsQuestionsIgnorePath checks whether the questionID is in ignore path
func IsQuestionsIgnorePath(questionID string) bool {
ignorePathInit.Do(initPathIgnore)
- for _, u := range pathIgnore.Questions {
- if u == questionID {
- return true
- }
- }
- return false
+ return slices.Contains(pathIgnore.Questions, questionID)
}
diff --git a/pkg/checker/question_link.go b/pkg/checker/question_link.go
index 41b246c36..efd19b3c3 100644
--- a/pkg/checker/question_link.go
+++ b/pkg/checker/question_link.go
@@ -44,15 +44,16 @@ func GetQuestionLink(content string) []QuestionLink {
left, right := 0, 0
for right < len(content) {
// find "/questions/" or "#"
- if right+11 < len(content) && content[right:right+11] == "/questions/" {
+ switch {
+ case right+11 < len(content) && content[right:right+11] == "/questions/":
left = right
right += 11
processURL(content, &left, &right, uniqueIDs, &questionLinks)
- } else if content[right] == '#' {
+ case content[right] == '#':
left = right + 1
right = left
processID(content, &left, &right, uniqueIDs, &questionLinks)
- } else {
+ default:
right++
}
}
@@ -101,9 +102,7 @@ func addUniqueID(questionID, answerID string, linkType int, uniqueIDs map[string
objectType, err := obj.GetObjectTypeStrByObjectID(uid.DeShortID(answerID))
if err != nil {
answerID = ""
- }
-
- if objectType == constant.AnswerObjectType {
+ } else if objectType == constant.AnswerObjectType {
if _, ok := uniqueIDs[answerID]; !ok {
uniqueIDs[answerID] = struct{}{}
isAdd = true
diff --git a/pkg/converter/markdown.go b/pkg/converter/markdown.go
index d16915a84..adae3faf6 100644
--- a/pkg/converter/markdown.go
+++ b/pkg/converter/markdown.go
@@ -107,7 +107,7 @@ func (r *DangerousHTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, n
}
n := node.(*ast.RawHTML)
l := n.Segments.Len()
- for i := 0; i < l; i++ {
+ for i := range l {
segment := n.Segments.At(i)
if string(source[segment.Start:segment.Stop]) == "" || string(source[segment.Start:segment.Stop]) == " " {
_, _ = w.Write(segment.Value(source))
@@ -122,15 +122,13 @@ func (r *DangerousHTMLRenderer) renderHTMLBlock(w util.BufWriter, source []byte,
n := node.(*ast.HTMLBlock)
if entering {
l := n.Lines().Len()
- for i := 0; i < l; i++ {
+ for i := range l {
line := n.Lines().At(i)
r.Writer.SecureWrite(w, line.Value(source))
}
- } else {
- if n.HasClosure() {
- closure := n.ClosureLine
- r.Writer.SecureWrite(w, closure.Value(source))
- }
+ } else if n.HasClosure() {
+ closure := n.ClosureLine
+ r.Writer.SecureWrite(w, closure.Value(source))
}
return ast.WalkContinue, nil
}
@@ -184,8 +182,8 @@ func (r *DangerousHTMLRenderer) renderAutoLink(w util.BufWriter, source []byte,
return ast.WalkContinue, nil
}
-func (r *DangerousHTMLRenderer) renderLinkIsUrl(verifyUrl string) bool {
- isURL := govalidator.IsURL(verifyUrl)
- isPath, _ := regexp.MatchString(`^/`, verifyUrl)
+func (r *DangerousHTMLRenderer) renderLinkIsUrl(verifyURL string) bool {
+ isURL := govalidator.IsURL(verifyURL)
+ isPath, _ := regexp.MatchString(`^/`, verifyURL)
return isURL || isPath
}
diff --git a/pkg/day/day.go b/pkg/day/day.go
index 90afac0ad..2fd86a6c9 100644
--- a/pkg/day/day.go
+++ b/pkg/day/day.go
@@ -20,6 +20,7 @@
package day
import (
+ "strings"
"time"
)
@@ -50,16 +51,16 @@ func Format(unix int64, format, tz string) (formatted string) {
for i := l; i >= 0; i-- {
format = strings.ReplaceAll(format, placeholders[i].old, placeholders[i].new)
}*/
- toFormat := ""
+ var toFormat strings.Builder
from := []rune(format)
for len(from) > 0 {
to, suffix := nextStdChunk(from)
- toFormat += string(to)
+ toFormat.WriteString(string(to))
from = suffix
}
_, _ = time.LoadLocation(tz)
- formatted = time.Unix(unix, 0).Format(toFormat)
+ formatted = time.Unix(unix, 0).Format(toFormat.String())
return
}
diff --git a/pkg/dir/dir.go b/pkg/dir/dir.go
index 928883c2e..09591f6ca 100644
--- a/pkg/dir/dir.go
+++ b/pkg/dir/dir.go
@@ -51,19 +51,19 @@ func DirSize(path string) (int64, error) {
}
func FormatFileSize(fileSize int64) (size string) {
- if fileSize < 1024 {
- //return strconv.FormatInt(fileSize, 10) + "B"
+ switch {
+ case fileSize < 1024:
+ // return strconv.FormatInt(fileSize, 10) + "B"
return fmt.Sprintf("%.2f B", float64(fileSize)/float64(1))
- } else if fileSize < (1024 * 1024) {
+ case fileSize < (1024 * 1024):
return fmt.Sprintf("%.2f KB", float64(fileSize)/float64(1024))
- } else if fileSize < (1024 * 1024 * 1024) {
+ case fileSize < (1024 * 1024 * 1024):
return fmt.Sprintf("%.2f MB", float64(fileSize)/float64(1024*1024))
- } else if fileSize < (1024 * 1024 * 1024 * 1024) {
+ case fileSize < (1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2f GB", float64(fileSize)/float64(1024*1024*1024))
- } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) {
+ case fileSize < (1024 * 1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2f TB", float64(fileSize)/float64(1024*1024*1024*1024))
- } else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
+ default: // if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024)
return fmt.Sprintf("%.2f EB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
-
}
diff --git a/pkg/htmltext/htmltext.go b/pkg/htmltext/htmltext.go
index 56db4d2b2..e2e017c8d 100644
--- a/pkg/htmltext/htmltext.go
+++ b/pkg/htmltext/htmltext.go
@@ -77,14 +77,14 @@ func UrlTitle(title string) (text string) {
}
func clearEmoji(s string) string {
- ret := ""
+ var ret strings.Builder
rs := []rune(s)
- for i := 0; i < len(rs); i++ {
+ for i := range rs {
if len(string(rs[i])) != 4 {
- ret += string(rs[i])
+ ret.WriteString(string(rs[i]))
}
}
- return ret
+ return ret.String()
}
func convertChinese(content string) string {
@@ -164,7 +164,7 @@ func FetchRangedExcerpt(html, trimMarker string, offset int, limit int) (text st
text = trimMarker + text
}
if end < len(runeText) {
- text = text + trimMarker
+ text += trimMarker
}
return
@@ -189,8 +189,8 @@ func FetchMatchedExcerpt(html string, words []string, trimMarker string, trimLen
return FetchRangedExcerpt(html, trimMarker, runeOffset, runeLimit)
}
-func GetPicByUrl(Url string) string {
- res, err := http.Get(Url)
+func GetPicByUrl(url string) string {
+ res, err := http.Get(url)
if err != nil {
return ""
}
diff --git a/pkg/htmltext/htmltext_test.go b/pkg/htmltext/htmltext_test.go
index 63866eb28..39de9e960 100644
--- a/pkg/htmltext/htmltext_test.go
+++ b/pkg/htmltext/htmltext_test.go
@@ -186,11 +186,11 @@ func TestCutLongTitle(t *testing.T) {
// Exactly max bytes, no cutting needed
exact150 := strings.Repeat("a", 150)
- assert.Equal(t, 150, len(cutLongTitle(exact150)))
+ assert.Len(t, cutLongTitle(exact150), 150)
// Just over max bytes, should be cut
exact151 := strings.Repeat("a", 151)
- assert.Equal(t, 150, len(cutLongTitle(exact151)))
+ assert.Len(t, cutLongTitle(exact151), 150)
// Multi-byte rune at boundary gets removed properly
asciiPart := strings.Repeat("a", 149) // 149 bytes
diff --git a/plugin/plugin_test/plugin_main_test.go b/plugin/plugin_test/plugin_main_test.go
index fd9015c86..7ba6f8ae3 100644
--- a/plugin/plugin_test/plugin_main_test.go
+++ b/plugin/plugin_test/plugin_main_test.go
@@ -81,19 +81,16 @@ func TestMain(t *testing.M) {
_ = os.RemoveAll(dbSetting.Connection)
}
- defer func() {
- if tearDown != nil {
- tearDown()
- }
- }()
if err := initTestDataSource(dbSetting); err != nil {
panic(err)
}
log.Info("init test database successfully")
- if ret := t.Run(); ret != 0 {
- os.Exit(ret)
+ ret := t.Run()
+ if tearDown != nil {
+ tearDown()
}
+ os.Exit(ret)
}
type TestDBSetting struct {
@@ -155,7 +152,7 @@ func initDatabaseImage(dbSetting TestDBSetting) (connection string, cleanup func
return "", nil, fmt.Errorf("could not connect to docker: %s", err)
}
- //resource, err := pool.Run(dbSetting.ImageName, dbSetting.ImageVersion, dbSetting.ENV)
+ // resource, err := pool.Run(dbSetting.ImageName, dbSetting.ImageVersion, dbSetting.ENV)
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
Repository: dbSetting.ImageName,
Tag: dbSetting.ImageVersion,
From 6660cdf6e25df42a6e86deca5d25e9817d67a5d2 Mon Sep 17 00:00:00 2001
From: Krypt0n123 <352600525@qq.com>
Date: Tue, 2 Dec 2025 22:59:33 +0800
Subject: [PATCH 15/92] fix: add missing revision data for default content
(fixes #1436)
---
internal/migrations/init.go | 95 ++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 1 deletion(-)
diff --git a/internal/migrations/init.go b/internal/migrations/init.go
index 184c986b9..8a72794fe 100644
--- a/internal/migrations/init.go
+++ b/internal/migrations/init.go
@@ -28,6 +28,7 @@ import (
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/data"
+ "github.com/apache/answer/internal/repo/revision"
"github.com/apache/answer/internal/repo/unique"
"github.com/apache/answer/internal/schema"
"github.com/segmentfault/pacman/log"
@@ -311,6 +312,7 @@ func (m *Mentor) initSiteInfoWrite() {
func (m *Mentor) initDefaultContent() {
uniqueIDRepo := unique.NewUniqueIDRepo(&data.Data{DB: m.engine})
+ revisionRepo := revision.NewRevisionRepo(&data.Data{DB: m.engine}, uniqueIDRepo)
now := time.Now()
tagId, err := uniqueIDRepo.GenUniqueIDStr(m.ctx, entity.Tag{}.TableName())
@@ -343,7 +345,7 @@ func (m *Mentor) initDefaultContent() {
return
}
- tag := entity.Tag{
+ tag := &entity.Tag{
ID: tagId,
SlugName: "support",
DisplayName: "support",
@@ -419,16 +421,74 @@ func (m *Mentor) initDefaultContent() {
if m.err != nil {
return
}
+ tagContent, err := json.Marshal(tag)
+ if err != nil {
+ m.err = err
+ return
+ }
+ m.err = revisionRepo.AddRevision(m.ctx, &entity.Revision{
+ UserID: tag.UserID,
+ ObjectID: tag.ID,
+ Title: tag.SlugName,
+ Content: string(tagContent),
+ Status: entity.RevisionReviewPassStatus,
+ }, true)
+ if m.err != nil {
+ return
+ }
+ tagForRevision := &entity.TagSimpleInfoForRevision{
+ ID: tag.ID,
+ MainTagID: tag.MainTagID,
+ MainTagSlugName: tag.MainTagSlugName,
+ SlugName: tag.SlugName,
+ DisplayName: tag.DisplayName,
+ Recommend: tag.Recommend,
+ Reserved: tag.Reserved,
+ RevisionID: tag.RevisionID,
+ }
_, m.err = m.engine.Context(m.ctx).Insert(q1)
if m.err != nil {
return
}
+ q1Revision := &entity.QuestionWithTagsRevision{
+ Question: *q1,
+ Tags: []*entity.TagSimpleInfoForRevision{tagForRevision},
+ }
+ q1Content, err := json.Marshal(q1Revision)
+ if err != nil {
+ m.err = err
+ return
+ }
+ m.err = revisionRepo.AddRevision(m.ctx, &entity.Revision{
+ UserID: q1.UserID,
+ ObjectID: q1.ID,
+ Title: q1.Title,
+ Content: string(q1Content),
+ Status: entity.RevisionReviewPassStatus,
+ }, true)
+ if m.err != nil {
+ return
+ }
_, m.err = m.engine.Context(m.ctx).Insert(a1)
if m.err != nil {
return
}
+ a1Content, err := json.Marshal(a1)
+ if err != nil {
+ m.err = err
+ return
+ }
+ m.err = revisionRepo.AddRevision(m.ctx, &entity.Revision{
+ UserID: a1.UserID,
+ ObjectID: a1.ID,
+ Content: string(a1Content),
+ Status: entity.RevisionReviewPassStatus,
+ }, true)
+ if m.err != nil {
+ return
+ }
_, m.err = m.engine.Context(m.ctx).Insert(entity.TagRel{
ObjectID: q1.ID,
@@ -443,11 +503,44 @@ func (m *Mentor) initDefaultContent() {
if m.err != nil {
return
}
+ q2Revision := &entity.QuestionWithTagsRevision{
+ Question: *q2,
+ Tags: []*entity.TagSimpleInfoForRevision{tagForRevision},
+ }
+ q2Content, err := json.Marshal(q2Revision)
+ if err != nil {
+ m.err = err
+ return
+ }
+ m.err = revisionRepo.AddRevision(m.ctx, &entity.Revision{
+ UserID: q2.UserID,
+ ObjectID: q2.ID,
+ Title: q2.Title,
+ Content: string(q2Content),
+ Status: entity.RevisionReviewPassStatus,
+ }, true)
+ if m.err != nil {
+ return
+ }
_, m.err = m.engine.Context(m.ctx).Insert(a2)
if m.err != nil {
return
}
+ a2Content, err := json.Marshal(a2)
+ if err != nil {
+ m.err = err
+ return
+ }
+ m.err = revisionRepo.AddRevision(m.ctx, &entity.Revision{
+ UserID: a2.UserID,
+ ObjectID: a2.ID,
+ Content: string(a2Content),
+ Status: entity.RevisionReviewPassStatus,
+ }, true)
+ if m.err != nil {
+ return
+ }
_, m.err = m.engine.Context(m.ctx).Insert(entity.TagRel{
ObjectID: q2.ID,
From 740ac61bb228ec48bf3d15231e4ec6ccc30a8bfc Mon Sep 17 00:00:00 2001
From: liruohrh <2372221537@qq.com>
Date: Tue, 2 Dec 2025 01:21:49 +0800
Subject: [PATCH 16/92] fix: get right lang
---
internal/base/handler/handler.go | 8 +++----
internal/base/handler/lang.go | 10 ---------
internal/base/middleware/accept_language.go | 5 ++---
internal/controller/answer_controller.go | 6 ++---
internal/controller/comment_controller.go | 6 ++---
internal/controller/lang_controller.go | 2 +-
internal/controller/question_controller.go | 14 ++++++------
internal/controller/report_controller.go | 2 +-
internal/controller/search_controller.go | 2 +-
internal/controller/template_controller.go | 10 ++++-----
internal/controller/user_controller.go | 22 +++++++++----------
internal/controller/vote_controller.go | 8 +++----
.../user_backyard_controller.go | 2 +-
internal/service/importer/importer_service.go | 2 +-
plugin/plugin.go | 2 +-
15 files changed, 44 insertions(+), 57 deletions(-)
diff --git a/internal/base/handler/handler.go b/internal/base/handler/handler.go
index b545b5e01..0c2fe8f4f 100644
--- a/internal/base/handler/handler.go
+++ b/internal/base/handler/handler.go
@@ -23,7 +23,6 @@ import (
"errors"
"net/http"
- "github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/base/validator"
"github.com/gin-gonic/gin"
@@ -33,7 +32,7 @@ import (
// HandleResponse Handle response body
func HandleResponse(ctx *gin.Context, err error, data any) {
- lang := GetLang(ctx)
+ lang := GetLangByCtx(ctx)
// no error
if err == nil {
ctx.JSON(http.StatusOK, NewRespBodyData(http.StatusOK, reason.Success, data).TrMsg(lang))
@@ -63,8 +62,7 @@ func HandleResponse(ctx *gin.Context, err error, data any) {
// BindAndCheck bind request and check
func BindAndCheck(ctx *gin.Context, data any) bool {
- lang := GetLang(ctx)
- ctx.Set(constant.AcceptLanguageFlag, lang)
+ lang := GetLangByCtx(ctx)
if err := ctx.ShouldBind(data); err != nil {
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
@@ -81,7 +79,7 @@ func BindAndCheck(ctx *gin.Context, data any) bool {
// BindAndCheckReturnErr bind request and check
func BindAndCheckReturnErr(ctx *gin.Context, data any) (errFields []*validator.FormErrorField) {
- lang := GetLang(ctx)
+ lang := GetLangByCtx(ctx)
if err := ctx.ShouldBind(data); err != nil {
log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
diff --git a/internal/base/handler/lang.go b/internal/base/handler/lang.go
index 4ff1ac7f1..8886f0631 100644
--- a/internal/base/handler/lang.go
+++ b/internal/base/handler/lang.go
@@ -23,19 +23,9 @@ import (
"context"
"github.com/apache/answer/internal/base/constant"
- "github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/i18n"
)
-// GetLang get language from header
-func GetLang(ctx *gin.Context) i18n.Language {
- acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
- if len(acceptLanguage) == 0 {
- return i18n.DefaultLanguage
- }
- return i18n.Language(acceptLanguage)
-}
-
// GetLangByCtx get language from header
func GetLangByCtx(ctx context.Context) i18n.Language {
acceptLanguage, ok := ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
diff --git a/internal/base/middleware/accept_language.go b/internal/base/middleware/accept_language.go
index ca8a1f903..5d1b12b2d 100644
--- a/internal/base/middleware/accept_language.go
+++ b/internal/base/middleware/accept_language.go
@@ -23,7 +23,6 @@ import (
"strings"
"github.com/apache/answer/internal/base/constant"
- "github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/translator"
"github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/i18n"
@@ -33,8 +32,8 @@ import (
// ExtractAndSetAcceptLanguage extract accept language from header and set to context
func ExtractAndSetAcceptLanguage(ctx *gin.Context) {
// The language of our front-end configuration, like en_US
- lang := handler.GetLang(ctx)
- tag, _, err := language.ParseAcceptLanguage(string(lang))
+ acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
+ tag, _, err := language.ParseAcceptLanguage(acceptLanguage)
if err != nil || len(tag) == 0 {
ctx.Set(constant.AcceptLanguageFlag, i18n.LanguageEnglish)
return
diff --git a/internal/controller/answer_controller.go b/internal/controller/answer_controller.go
index 0e43121c5..e76b02ccc 100644
--- a/internal/controller/answer_controller.go
+++ b/internal/controller/answer_controller.go
@@ -89,7 +89,7 @@ func (ac *AnswerController) RemoveAnswer(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -225,7 +225,7 @@ func (ac *AnswerController) AddAnswer(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -325,7 +325,7 @@ func (ac *AnswerController) UpdateAnswer(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go
index 65fbedf04..7289a0e18 100644
--- a/internal/controller/comment_controller.go
+++ b/internal/controller/comment_controller.go
@@ -106,7 +106,7 @@ func (cc *CommentController) AddComment(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -154,7 +154,7 @@ func (cc *CommentController) RemoveComment(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -215,7 +215,7 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/lang_controller.go b/internal/controller/lang_controller.go
index c7c607bdc..1e70fa88c 100644
--- a/internal/controller/lang_controller.go
+++ b/internal/controller/lang_controller.go
@@ -48,7 +48,7 @@ func NewLangController(tr i18n.Translator, siteInfoService siteinfo_common.SiteI
// @Success 200 {object} handler.RespBody{}
// @Router /answer/api/v1/language/config [get]
func (u *LangController) GetLangMapping(ctx *gin.Context) {
- data, _ := u.translator.Dump(handler.GetLang(ctx))
+ data, _ := u.translator.Dump(handler.GetLangByCtx(ctx))
var resp map[string]any
_ = json.Unmarshal(data, &resp)
handler.HandleResponse(ctx, nil, resp)
diff --git a/internal/controller/question_controller.go b/internal/controller/question_controller.go
index 581cf548b..d5164fc86 100644
--- a/internal/controller/question_controller.go
+++ b/internal/controller/question_controller.go
@@ -94,7 +94,7 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -419,7 +419,7 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -445,7 +445,7 @@ func (qc *QuestionController) AddQuestion(ctx *gin.Context) {
return
}
if !req.CanAddTag && hasNewTag {
- lang := handler.GetLang(ctx)
+ lang := handler.GetLangByCtx(ctx)
msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: requireRanks[6]})
handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil)
return
@@ -524,7 +524,7 @@ func (qc *QuestionController) AddQuestionByAnswer(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -646,7 +646,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -681,7 +681,7 @@ func (qc *QuestionController) UpdateQuestion(ctx *gin.Context) {
return
}
if !req.CanAddTag && hasNewTag {
- lang := handler.GetLang(ctx)
+ lang := handler.GetLangByCtx(ctx)
msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: requireRanks[4]})
handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil)
return
@@ -765,7 +765,7 @@ func (qc *QuestionController) UpdateQuestionInviteUser(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/report_controller.go b/internal/controller/report_controller.go
index 28048dd3d..13b4c0953 100644
--- a/internal/controller/report_controller.go
+++ b/internal/controller/report_controller.go
@@ -79,7 +79,7 @@ func (rc *ReportController) AddReport(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/search_controller.go b/internal/controller/search_controller.go
index 64acbe252..a5d3e8d13 100644
--- a/internal/controller/search_controller.go
+++ b/internal/controller/search_controller.go
@@ -78,7 +78,7 @@ func (sc *SearchController) Search(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index f6a442c21..257b02fa4 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -206,7 +206,7 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
- siteInfo.Title = fmt.Sprintf("%s - %s", translator.Tr(handler.GetLang(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
+ siteInfo.Title = fmt.Sprintf("%s - %s", translator.Tr(handler.GetLangByCtx(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
"data": data,
"useTitle": UrlUseTitle,
@@ -461,7 +461,7 @@ func (tc *TemplateController) TagList(ctx *gin.Context) {
if req.Page > 1 {
siteInfo.Canonical = fmt.Sprintf("%s/tags?page=%d", siteInfo.General.SiteUrl, req.Page)
}
- siteInfo.Title = fmt.Sprintf("%s - %s", translator.Tr(handler.GetLang(ctx), constant.TagsListTitleTrKey), siteInfo.General.Name)
+ siteInfo.Title = fmt.Sprintf("%s - %s", translator.Tr(handler.GetLangByCtx(ctx), constant.TagsListTitleTrKey), siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "tags.html", siteInfo, gin.H{
"page": page,
"data": data,
@@ -492,14 +492,14 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
}
siteInfo.Description = htmltext.FetchExcerpt(tagInfo.ParsedText, "...", 240)
if len(tagInfo.ParsedText) == 0 {
- siteInfo.Description = translator.Tr(handler.GetLang(ctx), constant.TagHasNoDescription)
+ siteInfo.Description = translator.Tr(handler.GetLangByCtx(ctx), constant.TagHasNoDescription)
}
siteInfo.Keywords = tagInfo.DisplayName
UrlUseTitle := siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitle ||
siteInfo.SiteSeo.Permalink == constant.PermalinkQuestionIDAndTitleByShortID
- siteInfo.Title = fmt.Sprintf("'%s' %s - %s", tagInfo.DisplayName, translator.Tr(handler.GetLang(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
+ siteInfo.Title = fmt.Sprintf("'%s' %s - %s", tagInfo.DisplayName, translator.Tr(handler.GetLangByCtx(ctx), constant.QuestionsTitleTrKey), siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "tag-detail.html", siteInfo, gin.H{
"tag": tagInfo,
"questionList": questionList,
@@ -597,7 +597,7 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
data["title"] = siteInfo.General.Name
}
data["description"] = siteInfo.Description
- data["language"] = handler.GetLang(ctx)
+ data["language"] = handler.GetLangByCtx(ctx)
data["timezone"] = siteInfo.Interface.TimeZone
language := strings.ReplaceAll(siteInfo.Interface.Language, "_", "-")
data["lang"] = language
diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go
index cc89caf1a..77c806e07 100644
--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -142,7 +142,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -154,7 +154,7 @@ func (uc *UserController) UserEmailLogin(ctx *gin.Context) {
uc.actionService.ActionRecordAdd(ctx, entity.CaptchaActionPassword, ctx.ClientIP())
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "e_mail",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.EmailOrPasswordWrong),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.EmailOrPasswordWrong),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.EmailOrPasswordWrong), errFields)
return
@@ -191,7 +191,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -286,7 +286,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -297,7 +297,7 @@ func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
if len(errFields) > 0 {
for _, field := range errFields {
field.ErrorMsg = translator.
- Tr(handler.GetLang(ctx), field.ErrorMsg)
+ Tr(handler.GetLangByCtx(ctx), field.ErrorMsg)
}
handler.HandleResponse(ctx, err, errFields)
} else {
@@ -364,7 +364,7 @@ func (uc *UserController) UserVerifyEmailSend(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -399,7 +399,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -415,7 +415,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
if !oldPassVerification {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "old_pass",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.OldPasswordVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.OldPasswordVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.OldPasswordVerificationFailed), errFields)
return
@@ -424,7 +424,7 @@ func (uc *UserController) UserModifyPassWord(ctx *gin.Context) {
if req.OldPass == req.Pass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "pass",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.NewPasswordSameAsPreviousSetting),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.NewPasswordSameAsPreviousSetting),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.NewPasswordSameAsPreviousSetting), errFields)
return
@@ -456,7 +456,7 @@ func (uc *UserController) UserUpdateInfo(ctx *gin.Context) {
req.IsAdmin = middleware.GetUserIsAdminModerator(ctx)
errFields, err := uc.userService.UpdateInfo(ctx, req)
for _, field := range errFields {
- field.ErrorMsg = translator.Tr(handler.GetLang(ctx), field.ErrorMsg)
+ field.ErrorMsg = translator.Tr(handler.GetLangByCtx(ctx), field.ErrorMsg)
}
handler.HandleResponse(ctx, err, errFields)
}
@@ -587,7 +587,7 @@ func (uc *UserController) UserChangeEmailSendCode(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller/vote_controller.go b/internal/controller/vote_controller.go
index 2e0ee6121..302796677 100644
--- a/internal/controller/vote_controller.go
+++ b/internal/controller/vote_controller.go
@@ -79,7 +79,7 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
return
}
if !can {
- lang := handler.GetLang(ctx)
+ lang := handler.GetLangByCtx(ctx)
msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: needRank})
handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil)
return
@@ -91,7 +91,7 @@ func (vc *VoteController) VoteUp(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
@@ -134,7 +134,7 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
return
}
if !can {
- lang := handler.GetLang(ctx)
+ lang := handler.GetLangByCtx(ctx)
msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: needRank})
handler.HandleResponse(ctx, errors.Forbidden(reason.NoEnoughRankToOperate).WithMsg(msg), nil)
return
@@ -145,7 +145,7 @@ func (vc *VoteController) VoteDown(ctx *gin.Context) {
if !captchaPass {
errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
ErrorField: "captcha_code",
- ErrorMsg: translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
+ ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.CaptchaVerificationFailed),
})
handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
return
diff --git a/internal/controller_admin/user_backyard_controller.go b/internal/controller_admin/user_backyard_controller.go
index 00dfa2c3d..1356c55a1 100644
--- a/internal/controller_admin/user_backyard_controller.go
+++ b/internal/controller_admin/user_backyard_controller.go
@@ -177,7 +177,7 @@ func (uc *UserAdminController) EditUserProfile(ctx *gin.Context) {
errFields, err := uc.userService.EditUserProfile(ctx, req)
for _, field := range errFields {
- field.ErrorMsg = translator.Tr(handler.GetLang(ctx), field.ErrorMsg)
+ field.ErrorMsg = translator.Tr(handler.GetLangByCtx(ctx), field.ErrorMsg)
}
handler.HandleResponse(ctx, err, errFields)
}
diff --git a/internal/service/importer/importer_service.go b/internal/service/importer/importer_service.go
index c7673ffb5..9d12bf07b 100644
--- a/internal/service/importer/importer_service.go
+++ b/internal/service/importer/importer_service.go
@@ -135,7 +135,7 @@ func (ip *ImporterService) ImportQuestion(ctx context.Context, questionInfo plug
return err
}
if !req.CanAddTag && hasNewTag {
- lang := handler.GetLang(ctx.(*gin.Context))
+ lang := handler.GetLangByCtx(ctx.(*gin.Context))
msg := translator.TrWithData(lang, reason.NoEnoughRankToOperate, &schema.PermissionTrTplData{Rank: requireRanks[6]})
log.Errorf("error: %v", msg)
return errors.BadRequest(msg)
diff --git a/plugin/plugin.go b/plugin/plugin.go
index 266848353..8778b1625 100644
--- a/plugin/plugin.go
+++ b/plugin/plugin.go
@@ -216,7 +216,7 @@ func (m *statusManager) UnmarshalJSON(data []byte) error {
// Translate translates the key to the current language of the context
func Translate(ctx *GinContext, key string) string {
- return translator.Tr(handler.GetLang(ctx), key)
+ return translator.Tr(handler.GetLangByCtx(ctx), key)
}
// TranslateWithData translates the key to the language with data
From 8e395d421e5ded9277414c69cee1452429acc524 Mon Sep 17 00:00:00 2001
From: kinjelom
Date: Sat, 22 Nov 2025 12:36:06 +0100
Subject: [PATCH 17/92] Polish translation
---
i18n/pl_PL.yaml | 544 ++++++++++++++++++++++++++----------------------
1 file changed, 300 insertions(+), 244 deletions(-)
diff --git a/i18n/pl_PL.yaml b/i18n/pl_PL.yaml
index e5972d9b3..c5c97d118 100644
--- a/i18n/pl_PL.yaml
+++ b/i18n/pl_PL.yaml
@@ -486,43 +486,42 @@ backend:
title:
other: "[{{.SiteName}}] Potwierdź swój nowy adres e-mail"
body:
- other: "Confirm your new email address for {{.SiteName}} by clicking on the following link: \n{{.ChangeEmailUrl}} \n\nIf you did not request this change, please ignore this email. \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ other: "Potwierdź swój nowy adres e-mail dla {{.SiteName}}, klikając poniższy link: \n{{.ChangeEmailUrl}} \n\nJeśli nie prosiłeś(-aś) o zmianę adresu e-mail, zignoruj tę wiadomość. \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana."
new_answer:
title:
other: "[{{.SiteName}}] {{.DisplayName}} odpowiedział(-a) na pytanie"
body:
- other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}} napisał(-a): \n{{.AnswerSummary}} \nZobacz na {{.SiteName}} \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana. \n\nZrezygnuj z subskrypcji "
invited_you_to_answer:
title:
- other: "[{{.SiteName}}] {{.DisplayName}} zaprosił(a) Cię do odpowiedzi"
+ other: "[{{.SiteName}}] {{.DisplayName}} zaprosił(-a) Cię do odpowiedzi"
body:
- other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \nI think you may know the answer. \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}} napisał(-a): \nMyślę, że możesz znać odpowiedź. \nZobacz na {{.SiteName}} \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana. \n\nZrezygnuj z subskrypcji "
new_comment:
title:
- other: "[{{.SiteName}}] {{.DisplayName}} skomentował/-a Twój wpis"
+ other: "[{{.SiteName}}] {{.DisplayName}} skomentował(-a) Twój wpis"
body:
- other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.CommentSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}} napisał(-a): \n{{.CommentSummary}} \nZobacz na {{.SiteName}} \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana. \n\nZrezygnuj z subskrypcji "
new_question:
title:
other: "[{{.SiteName}}] Nowe pytanie: {{.QuestionTitle}}"
body:
- other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana. \n\nZrezygnuj z subskrypcji "
pass_reset:
title:
- other: "[{{.SiteName }}] Reset hasła"
+ other: "[{{.SiteName}}] Reset hasła"
body:
- other: "Somebody asked to reset your password on {{.SiteName}}. \n\nIf it was not you, you can safely ignore this email. \n\nClick the following link to choose a new password: \n{{.PassResetUrl}} \n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ other: "Otrzymaliśmy prośbę o zresetowanie Twojego hasła w serwisie {{.SiteName}}. \n\nJeśli to nie Ty wysłałeś(-aś) tę prośbę, możesz bezpiecznie zignorować tę wiadomość. \n\nKliknij poniższy link, aby ustawić nowe hasło: \n{{.PassResetUrl}} \n \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana."
register:
title:
other: "[{{.SiteName}}] Potwierdź swoje nowe konto"
body:
- other: "Welcome to {{.SiteName}}! \n\nClick the following link to confirm and activate your new account: \n{{.RegisterUrl}} \n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ other: "Witamy w {{.SiteName}}! \n\nKliknij poniższy link, aby potwierdzić i aktywować swoje nowe konto: \n{{.RegisterUrl}} \n\nJeśli powyższy link nie jest klikalny, spróbuj skopiować go i wkleić do paska adresu przeglądarki.\n \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana."
test:
title:
other: "[{{.SiteName}}] Wiadomość testowa"
body:
- other: "This is a test email.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
- action_activity_type:
+ other: "To jest testowa wiadomość e-mail.\n \n\n-- \nUwaga: to jest automatyczna wiadomość systemowa, nie odpowiadaj na nią, ponieważ odpowiedź nie będzie odczytana."
upvote:
other: oceń pozytywnie
upvoted:
@@ -573,244 +572,300 @@ backend:
name:
other: Pierwszy pozytywny głos
desc:
- other: First up voted a post.
+ other: Po raz pierwszy oddano pozytywny głos na post.
first_link:
name:
other: Pierwszy odnośnik
desc:
- other: First added a link to another post.
+ other: Po raz pierwszy dodano link do innego wpisu.
+
first_reaction:
name:
- other: Pierwsza Reakcja
+ other: Pierwsza reakcja
desc:
- other: First reacted to the post.
+ other: Po raz pierwszy zareagowano na wpis.
+
first_share:
name:
- other: Pierwsze udostępnianie
+ other: Pierwsze udostępnienie
desc:
- other: First shared a post.
+ other: Po raz pierwszy udostępniono wpis.
+
scholar:
name:
other: Scholar
desc:
- other: Zadane pytania i zaakceptowane odpowiedź.
+ other: Zadano pytanie i zaakceptowano odpowiedź.
+
commentator:
name:
- other: Commentator
+ other: Komentator
desc:
- other: Pozostaw 5 komentarzy.
+ other: Pozostawiono 5 komentarzy.
+
new_user_of_the_month:
name:
other: Nowy użytkownik miesiąca
desc:
- other: Outstanding contributions in their first month.
+ other: Wyjątkowy wkład w pierwszym miesiącu aktywności.
+
read_guidelines:
name:
- other: Read Guidelines
+ other: Przeczytano zasady
desc:
- other: Read the [community guidelines].
+ other: Przeczytano [zasady społeczności].
+
reader:
name:
- other: Reader
+ other: Czytelnik
desc:
- other: Read every answers in a topic with more than 10 answers.
+ other: Przeczytano wszystkie odpowiedzi w wątku mającym ponad 10 odpowiedzi.
+
welcome:
name:
- other: Welcome
+ other: Witamy
desc:
- other: Received a up vote.
+ other: Otrzymano pozytywny głos.
+
nice_share:
name:
- other: Nice Share
+ other: Udane udostępnienie
desc:
- other: Shared a post with 25 unique visitors.
+ other: Udostępniono wpis, który odwiedziło 25 unikalnych użytkowników.
+
good_share:
name:
- other: Good Share
+ other: Dobre udostępnienie
desc:
- other: Shared a post with 300 unique visitors.
+ other: Udostępniono wpis, który odwiedziło 300 unikalnych użytkowników.
+
great_share:
name:
- other: Great Share
+ other: Świetne udostępnienie
desc:
- other: Shared a post with 1000 unique visitors.
+ other: Udostępniono wpis, który odwiedziło 1000 unikalnych użytkowników.
+
out_of_love:
name:
- other: Out of Love
+ other: Z miłości
desc:
- other: Used 50 up votes in a day.
+ other: Wykorzystano 50 pozytywnych głosów w ciągu dnia.
+
higher_love:
name:
- other: Higher Love
+ other: Więcej miłości
desc:
- other: Used 50 up votes in a day 5 times.
+ other: Wykorzystano 50 pozytywnych głosów w ciągu dnia - 5 razy.
+
crazy_in_love:
name:
- other: Crazy in Love
+ other: Szaleństwo miłości
desc:
- other: Used 50 up votes in a day 20 times.
+ other: Wykorzystano 50 pozytywnych głosów w ciągu dnia - 20 razy.
+
promoter:
name:
- other: Promoter
+ other: Promotor
desc:
- other: Invited a user.
+ other: Zaproszono użytkownika.
+
campaigner:
name:
- other: Campaigner
+ other: Kampanier
desc:
- other: Invited 3 basic users.
+ other: Zaproszono 3 podstawowych użytkowników.
+
champion:
name:
- other: Champion
+ other: Mistrz
desc:
- other: Invited 5 members.
+ other: Zaproszono 5 użytkowników.
+
thank_you:
name:
- other: Thank You
+ other: Dziękuję
desc:
- other: Has 20 up voted posts and gave 10 up votes.
+ other: Otrzymano 20 pozytywnych głosów i oddano 10.
+
gives_back:
name:
- other: Gives Back
+ other: Oddający dalej
desc:
- other: Has 100 up voted posts and gave 100 up votes.
+ other: Otrzymano 100 pozytywnych głosów i oddano 100.
+
empathetic:
name:
- other: Empathetic
+ other: Empatyczny
desc:
- other: Has 500 up voted posts and gave 1000 up votes.
+ other: Otrzymano 500 pozytywnych głosów i oddano 1000.
+
enthusiast:
name:
- other: Enthusiast
+ other: Entuzjasta
desc:
- other: Visited 10 consecutive days.
+ other: Odwiedzono serwis 10 dni z rzędu.
+
aficionado:
name:
- other: Aficionado
+ other: Koneser
desc:
- other: Visited 100 consecutive days.
+ other: Odwiedzono serwis 100 dni z rzędu.
+
devotee:
name:
- other: Devotee
+ other: Wytrwały
desc:
- other: Visited 365 consecutive days.
+ other: Odwiedzono serwis 365 dni z rzędu.
+
anniversary:
name:
- other: Anniversary
+ other: Rocznica
desc:
- other: Active member for a year, posted at least once.
+ other: Aktywny użytkownik od roku, co najmniej jeden wpis.
+
appreciated:
name:
- other: Appreciated
+ other: Doceniony
desc:
- other: Received 1 up vote on 20 posts.
+ other: Otrzymano 1 pozytywny głos na 20 wpisach.
+
respected:
name:
- other: Respected
+ other: Szanujący
desc:
- other: Received 2 up votes on 100 posts.
+ other: Otrzymano 2 pozytywne głosy na 100 wpisach.
+
admired:
name:
- other: Admired
+ other: Podziwiany
desc:
- other: Received 5 up votes on 300 posts.
+ other: Otrzymano 5 pozytywnych głosów na 300 wpisach.
+
solved:
name:
- other: Solved
+ other: Rozwiązane
desc:
- other: Have an answer be accepted.
+ other: Udzielono odpowiedzi, która została zaakceptowana.
+
guidance_counsellor:
name:
- other: Guidance Counsellor
+ other: Doradca
desc:
- other: Have 10 answers be accepted.
+ other: 10 udzielonych odpowiedzi zostało zaakceptowanych.
+
know_it_all:
name:
- other: Know-it-All
+ other: Wszystkowiedzący
desc:
- other: Have 50 answers be accepted.
+ other: 50 udzielonych odpowiedzi zostało zaakceptowanych.
+
solution_institution:
name:
- other: Solution Institution
+ other: Instytucja rozwiązań
desc:
- other: Have 150 answers be accepted.
+ other: 150 udzielonych odpowiedzi zostało zaakceptowanych.
+
nice_answer:
name:
- other: Nice Answer
+ other: Dobra odpowiedź
desc:
- other: Answer score of 10 or more.
+ other: Odpowiedź z wynikiem co najmniej 10.
+
good_answer:
name:
- other: Good Answer
+ other: Bardzo dobra odpowiedź
desc:
- other: Answer score of 25 or more.
+ other: Odpowiedź z wynikiem co najmniej 25.
+
great_answer:
name:
- other: Great Answer
+ other: Świetna odpowiedź
desc:
- other: Answer score of 50 or more.
+ other: Odpowiedź z wynikiem co najmniej 50.
+
nice_question:
name:
- other: Nice Question
+ other: Dobre pytanie
desc:
- other: Question score of 10 or more.
+ other: Pytanie z wynikiem co najmniej 10.
+
good_question:
name:
- other: Good Question
+ other: Bardzo dobre pytanie
desc:
- other: Question score of 25 or more.
+ other: Pytanie z wynikiem co najmniej 25.
+
great_question:
name:
- other: Great Question
+ other: Świetne pytanie
desc:
- other: Question score of 50 or more.
+ other: Pytanie z wynikiem co najmniej 50.
+
popular_question:
name:
- other: Popular Question
+ other: Popularne pytanie
desc:
- other: Question with 500 views.
+ other: Pytanie z 500 wyświetleniami.
+
notable_question:
name:
- other: Notable Question
+ other: Zauważalne pytanie
desc:
- other: Question with 1,000 views.
+ other: Pytanie z 1000 wyświetleniami.
+
famous_question:
name:
- other: Famous Question
+ other: Słynne pytanie
desc:
- other: Question with 5,000 views.
+ other: Pytanie z 5000 wyświetleniami.
+
popular_link:
name:
- other: Popular Link
+ other: Popularny link
desc:
- other: Posted an external link with 50 clicks.
+ other: Opublikowano zewnętrzny link z 50 kliknięciami.
+
hot_link:
name:
- other: Hot Link
+ other: Gorący link
desc:
- other: Posted an external link with 300 clicks.
+ other: Opublikowano zewnętrzny link z 300 kliknięciami.
+
famous_link:
name:
- other: Famous Link
+ other: Słynny link
desc:
- other: Posted an external link with 100 clicks.
+ other: Opublikowano zewnętrzny link z 1000 kliknięciami.
default_badge_groups:
getting_started:
name:
- other: Getting Started
+ other: Pierwsze kroki
community:
name:
- other: Community
+ other: Społeczność
posting:
name:
- other: Posting
+ other: Publikowanie
# The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
title: Jak formatować
desc: >-
- mention a post: #post_id
to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
- pagination:
+
+ wspomnij wpis: #post_id
+ tworzenie linków
+ <https://url.com> [Tytuł](https://url.com)
+
+ oddziel akapity pustą linią
+ _kursywa_ lub **pogrubienie **
+ zagnieźdź kod, dodając 4 spacje na początku wiersza
+ cytuj, dodając > na początku wiersza
+ użyj odwrotnego apostrofu (backtick) do zagnieżdżonego kodu `tak _to_ działa`
+ twórz bloki kodu przy pomocy potrójnych odwrotnych apostrofów `
+ ``` kod tutaj ```
+
+
+ pagination:
prev: Poprzedni
next: Następny
page_title:
@@ -970,9 +1025,9 @@ ui:
heading: Nagłówek
cell: Komórka
file:
- text: Attach files
- not_supported: "Don’t support that file type. Try again with {{file_type}}."
- max_size: "Attach files size cannot exceed {{size}} MB."
+ text: Dołącz pliki
+ not_supported: "Ten typ pliku nie jest obsługiwany. Spróbuj ponownie z {{file_type}}."
+ max_size: "Rozmiar dołączanych plików nie może przekraczać {{size}} MB."
close_modal:
title: Zamykam ten post jako...
btn_cancel: Anuluj
@@ -1037,20 +1092,22 @@ ui:
delete:
title: Usuń ten tag
tip_with_posts: >-
- We do not allow deleting tag with posts .
Please remove this tag from the posts first.
+ Nie można usunąć tagu, który jest używany w postach .
+ Najpierw usuń ten tag z powiązanych postów.
tip_with_synonyms: >-
- We do not allow deleting tag with synonyms .
Please remove the synonyms from this tag first.
+ Nie można usunąć tagu, który ma synonimy .
+ Najpierw usuń synonimy przypisane do tego tagu.
tip: Czy na pewno chcesz usunąć?
close: Zamknij
merge:
- title: Merge tag
- source_tag_title: Source tag
- source_tag_description: The source tag and its associated data will be remapped to the target tag.
- target_tag_title: Target tag
- target_tag_description: A synonym between these two tags will be created after merging.
- no_results: No tags matched
- btn_submit: Submit
- btn_close: Close
+ title: Scal tag
+ source_tag_title: Źródłowy tag
+ source_tag_description: Źródłowy tag i wszystkie powiązane dane zostaną przemapowane na tag docelowy.
+ target_tag_title: Docelowy tag
+ target_tag_description: Po scaleniu zostanie utworzony synonim między tymi dwoma tagami.
+ no_results: Brak pasujących tagów
+ btn_submit: Zatwierdź
+ btn_close: Zamknij
edit_tag:
title: Edytuj tag
default_reason: Edytuj tag
@@ -1058,20 +1115,20 @@ ui:
btn_save_edits: Zapisz edycje
btn_cancel: Anuluj
dates:
- long_date: MMM D
- long_date_with_year: "MMM D, YYYY"
- long_date_with_time: "MMM D, YYYY [o] HH:mm"
+ long_date: D MMM
+ long_date_with_year: "YYYY MMM D"
+ long_date_with_time: "YYYY MMM D [o] HH:mm"
now: teraz
- x_seconds_ago: "{{count}} s temu"
- x_minutes_ago: "{{count}} min temu"
- x_hours_ago: "{{count}} h temu"
+ x_seconds_ago: "{{count}} sek. temu"
+ x_minutes_ago: "{{count}} min. temu"
+ x_hours_ago: "{{count}} godz. temu"
hour: godzina
day: dzień
hours: godziny
days: dni
month: month
months: months
- year: year
+ year: rok
reaction:
heart: serce
smile: uśmiech
@@ -1127,7 +1184,7 @@ ui:
more: Więcej
wiki: Wiki
ask:
- title: Create Question
+ title: Utwórz pytanie
edit_title: Edytuj pytanie
default_reason: Edytuj pytanie
default_first_reason: Create question
@@ -1138,7 +1195,7 @@ ui:
label: Rewizja
title:
label: Tytuł
- placeholder: What's your topic? Be specific.
+ placeholder: Jaki jest temat? Bądź konkretny.
msg:
empty: Tytuł nie może być pusty.
range: Tytuł do 150 znaków
@@ -1147,8 +1204,8 @@ ui:
msg:
empty: Treść nie może być pusta.
hint:
- optional_body: Describe what the question is about.
- minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ optional_body: Opisz, czego dotyczy pytanie.
+ minimum_characters: "Opisz, czego dotyczy pytanie — wymagane jest co najmniej {{min_content_length}} znaków."
tags:
label: Tagi
msg:
@@ -1169,9 +1226,9 @@ ui:
add_btn: Dodaj tag
create_btn: Utwórz nowy tag
search_tag: Wyszukaj tag
- hint: Describe what your content is about, at least one tag is required.
- hint_zero_tags: Describe what your content is about.
- hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ hint: Opisz, czego dotyczy Twoja treść — wymagany jest co najmniej jeden tag.
+ hint_zero_tags: Opisz, czego dotyczy Twoja treść.
+ hint_more_than_one_tag: "Opisz, czego dotyczy Twoja treść — wymagane są co najmniej {{min_tags_number}} tagi."
no_result: Nie znaleziono pasujących tagów
tag_required_text: Wymagany tag (co najmniej jeden)
header:
@@ -1179,7 +1236,7 @@ ui:
question: Pytania
tag: Tagi
user: Użytkownicy
- badges: Badges
+ badges: Odznaki
profile: Profil
setting: Ustawienia
logout: Wyloguj
@@ -1296,13 +1353,13 @@ ui:
display_name:
label: Nazwa wyświetlana
msg: Wyświetlana nazwa nie może być pusta.
- msg_range: Display name must be 2-30 characters in length.
+ msg_range: Wyświetlana nazwa musi mieć od 2 do 30 znaków długości.
username:
label: Nazwa użytkownika
caption: Ludzie mogą oznaczać Cię jako "@nazwa_użytkownika".
msg: Nazwa użytkownika nie może być pusta.
- msg_range: Username must be 2-30 characters in length.
- character: 'Must use the character set "a-z", "0-9", "- . _"'
+ msg_range: Nazwa użytkownika musi mieć od 2 do 30 znaków długości.
+ character: 'Muszą być znaki ze zbioru "a-z", "0-9", "- . _"'
avatar:
label: Zdjęcie profilowe
gravatar: Gravatar
@@ -1391,11 +1448,11 @@ ui:
search: Wyszukaj osoby
question_detail:
action: Akcja
- created: Created
+ created: Utworzono
Asked: Zadane
asked: zadał(a)
update: Zmodyfikowane
- Edited: Edited
+ Edited: Wyedytowane
edit: edytowany
commented: skomentowano
Views: Wyświetlone
@@ -1442,7 +1499,7 @@ ui:
list:
confirm_btn: Lista
title: Pokaż ten post
- content: Are you sure you want to list?
+ content: Czy na pewno chcesz wyświetlić tę listę?
unlist:
confirm_btn: Usuń z listy
title: Usuń ten post z listy
@@ -1507,16 +1564,16 @@ ui:
normal: Normalny
closed: Zamknięty
deleted: Usunięty
- deleted_permanently: Deleted permanently
+ deleted_permanently: Usunięto trwale
pending: Oczekujący
more: Więcej
- view: View
- card: Card
- compact: Compact
- display_below: Display below
- always_display: Always display
- or: or
- back_sites: Back to sites
+ view: Podgląd
+ card: Karta
+ compact: Kompakt
+ display_below: Wyświetl poniżej
+ always_display: Wyświetlaj zawsze
+ or: lub
+ back_sites: Powrót do stron
search:
title: Wyniki wyszukiwania
keywords: Słowa kluczowe
@@ -1524,7 +1581,7 @@ ui:
follow: Obserwuj
following: Obserwuje
counts: "Liczba wyników: {{count}}"
- counts_loading: "... Results"
+ counts_loading: "... Wyniki"
more: Więcej
sort_btns:
relevance: Relewantność
@@ -1547,13 +1604,13 @@ ui:
via: Udostępnij post za pośrednictwem...
copied: Skopiowano
facebook: Udostępnij na Facebooku
- twitter: Share to X
+ twitter: Udostępnij na X
cannot_vote_for_self: Nie możesz głosować na własne posty.
modal_confirm:
title: Błąd...
delete_permanently:
- title: Delete permanently
- content: Are you sure you want to delete permanently?
+ title: Usuń trwale
+ content: Czy na pewno chcesz usunąć to trwale?
account_result:
success: Twoje nowe konto zostało potwierdzone; zostaniesz przekierowany na stronę główną.
link: Kontynuuj do strony głównej
@@ -1582,7 +1639,7 @@ ui:
newest: Najnowsze
active: Aktywne
hot: Gorące
- frequent: Frequent
+ frequent: Częste
recommend: Polecane
score: Ocena
unanswered: Bez odpowiedzi
@@ -1628,7 +1685,7 @@ ui:
x_votes: otrzymane głosy
x_answers: odpowiedzi
x_questions: pytania
- recent_badges: Recent Badges
+ recent_badges: Ostatnie odznaki
install:
title: Instalacja
next: Dalej
@@ -1667,14 +1724,14 @@ ui:
ssl_mode:
label: SSL Mode
ssl_root_cert:
- placeholder: sslrootcert file path
- msg: Path to sslrootcert file cannot be empty
+ placeholder: Ścieżka do pliku sslrootcert
+ msg: Ścieżka do pliku sslrootcert nie może być pusta
ssl_cert:
- placeholder: sslcert file path
- msg: Path to sslcert file cannot be empty
+ placeholder: Ścieżka do pliku sslcert
+ msg: Ścieżka do pliku sslcert nie może być pusta
ssl_key:
- placeholder: sslkey file path
- msg: Path to sslkey file cannot be empty
+ placeholder: Ścieżka do pliku sslkey
+ msg: Ścieżka do pliku sslkey nie może być pusta
config_yaml:
title: Utwórz plik config.yaml
label: Plik config.yaml utworzony.
@@ -1717,9 +1774,9 @@ ui:
msg_min_length: Hasło musi mieć co najmniej 8 znaków.
msg_max_length: Hasło musi mieć maksymalnie 32 znaki.
admin_confirm_password:
- label: "Confirm Password"
- text: "Please re-enter your password to confirm."
- msg: "Confirm password does not match."
+ label: "Potwierdź hasło"
+ text: "Wprowadź ponownie swoje hasło, aby potwierdzić."
+ msg: "Potwierdzenie hasła nie jest zgodne."
admin_email:
label: Email
text: Będziesz potrzebować tego adresu e-mail do logowania.
@@ -1777,7 +1834,7 @@ ui:
privileges: Uprawnienia
plugins: Wtyczki
installed_plugins: Zainstalowane wtyczki
- apperance: Appearance
+ apperance: Wygląd
website_welcome: Witamy w serwisie {{site_name}}
user_center:
login: Zaloguj się
@@ -1786,15 +1843,15 @@ ui:
badges:
modal:
title: Gratulacje
- content: You've earned a new badge.
- close: Close
- confirm: View badges
- title: Badges
- awarded: Awarded
- earned_×: Earned ×{{ number }}
- ×_awarded: "{{ number }} awarded"
- can_earn_multiple: You can earn this multiple times.
- earned: Earned
+ content: Zdobyłeś nową odznakę.
+ close: Zamknij
+ confirm: Zobacz odznaki
+ title: Odznaki
+ awarded: Przyznano
+ earned_×: Zdobyto ×{{ number }}
+ ×_awarded: „{{ number }} przyznano”
+ can_earn_multiple: Możesz zdobyć tę odznakę wielokrotnie.
+ earned: Zdobyto
admin:
admin_header:
title: Administrator
@@ -1803,15 +1860,15 @@ ui:
welcome: Witaj Administratorze!
site_statistics: Statystyki witryny
questions: "Pytania:"
- resolved: "Resolved:"
- unanswered: "Unanswered:"
+ resolved: "Rozwiązane:"
+ unanswered: "Bez odpowiedzi:"
answers: "Odpowiedzi:"
comments: "Komentarze:"
votes: "Głosy:"
users: "Użytkownicy:"
- flags: "Flagi:"
- reviews: "Reviews:"
- site_health: Site health
+ flags: "Zgłoszenia:"
+ reviews: "Przeglądy:"
+ site_health: "Stan serwisu"
version: "Wersja:"
https: "HTTPS:"
upload_folder: "Prześlij folder:"
@@ -1876,14 +1933,14 @@ ui:
form:
fields:
display_name:
- label: Display name
- msg_range: Display name must be 2-30 characters in length.
+ label: Wyświetlana nazwa
+ msg_range: Wyświetlana nazwa musi mieć od 2 do 30 znaków.
username:
- label: Nazwa
- msg_range: Username must be 2-30 characters in length.
+ label: Nazwa użytkownika
+ msg_range: Nazwa użytkownika musi mieć od 2 do 30 znaków.
email:
label: Email
- msg_invalid: Błędny adresy email.
+ msg_invalid: Błędny adres e-mail.
edit_success: Edycja zakończona pomyślnie
btn_cancel: Anuluj
btn_submit: Prześlij
@@ -1898,7 +1955,7 @@ ui:
msg: "Podaj adresy e-mail użytkowników, jeden w każdej linii."
display_name:
label: Nazwa wyświetlana
- msg: Display name must be 2-30 characters in length.
+ msg: Wyświetlana nazwa musi mieć od 2 do 30 znaków.
email:
label: E-mail
msg: Email nie jest prawidłowy.
@@ -1912,10 +1969,10 @@ ui:
name: Imię
email: E-mail
reputation: Reputacja
- created_at: Created time
- delete_at: Deleted time
- suspend_at: Suspended time
- suspend_until: Suspend until
+ created_at: Czas utworzenia
+ delete_at: Czas usunięcia
+ suspend_at: Czas zawieszenia
+ suspend_until: Zawieszone do
status: Status
role: Rola
action: Akcja
@@ -1950,8 +2007,8 @@ ui:
suspend_user:
title: Zawieś tego użytkownika
content: Zawieszony użytkownik nie może się logować.
- label: How long will the user be suspended for?
- forever: Forever
+ label: Na jak długo użytkownik zostanie zawieszony?
+ forever: Na zawsze
questions:
page_title: Pytania
unlisted: Unlisted
@@ -2013,11 +2070,11 @@ ui:
msg: Strefa czasowa nie może być pusta.
text: Wybierz miasto w tej samej strefie czasowej, co Ty.
avatar:
- label: Default avatar
- text: For users without a custom avatar of their own.
+ label: Domyślny awatar
+ text: Dla użytkowników, którzy nie ustawili własnego awatara.
gravatar_base_url:
- label: Gravatar base URL
- text: URL of the Gravatar provider's API base. Ignored when empty.
+ label: Bazowy URL Gravatara
+ text: Adres bazowy API dostawcy Gravatara. Ignorowane, jeśli puste.
smtp:
page_title: SMTP
from_email:
@@ -2096,37 +2153,37 @@ ui:
restrict_answer:
title: Answer write
label: Każdy użytkownik może napisać tylko jedną odpowiedź na każde pytanie
- text: "Turn off to allow users to write multiple answers to the same question, which may cause answers to be unfocused."
+ text: "Wyłącz, aby pozwolić użytkownikom pisać wiele odpowiedzi na to samo pytanie, co może powodować, że odpowiedzi będą mniej skupione."
min_tags:
- label: "Minimum tags per question"
- text: "Minimum number of tags required in a question."
+ label: "Minimalna liczba tagów na pytanie"
+ text: "Minimalna liczba tagów wymagana w pytaniu."
recommend_tags:
label: Rekomendowane tagi
- text: "Recommend tags will show in the dropdown list by default."
+ text: "Rekomendowane tagi będą domyślnie wyświetlane na liście wyboru."
msg:
- contain_reserved: "recommended tags cannot contain reserved tags"
+ contain_reserved: "Rekomendowane tagi nie mogą zawierać tagów zarezerwowanych."
required_tag:
- title: Set required tags
- label: Set “Recommend tags” as required tags
+ title: Ustaw wymagane tagi
+ label: Ustaw „Rekomendowane tagi” jako wymagane
text: "Każde nowe pytanie musi mieć przynajmniej jeden rekomendowany tag."
reserved_tags:
label: Zarezerwowane tagi
- text: "Reserved tags can only be used by moderator."
+ text: "Zarezerwowane tagi mogą być używane tylko przez moderatorów."
image_size:
- label: Max image size (MB)
- text: "The maximum image upload size."
+ label: Maksymalny rozmiar obrazu (MB)
+ text: "Maksymalny dopuszczalny rozmiar przesyłanego obrazu."
attachment_size:
- label: Max attachment size (MB)
- text: "The maximum attachment files upload size."
+ label: Maksymalny rozmiar załącznika (MB)
+ text: "Maksymalny dopuszczalny rozmiar przesyłanych plików."
image_megapixels:
- label: Max image megapixels
- text: "Maximum number of megapixels allowed for an image."
+ label: Maksymalna liczba megapikseli
+ text: "Maksymalna liczba megapikseli dopuszczona dla obrazu."
image_extensions:
- label: Authorized image extensions
- text: "A list of file extensions allowed for image display, separate with commas."
+ label: Dozwolone rozszerzenia obrazów
+ text: "Lista rozszerzeń plików dozwolonych dla obrazów; oddziel po przecinkach."
attachment_extensions:
- label: Authorized attachment extensions
- text: "A list of file extensions allowed for upload, separate with commas. WARNING: Allowing uploads may cause security issues."
+ label: Dozwolone rozszerzenia załączników
+ text: "Lista rozszerzeń plików dozwolonych do przesyłania; oddziel po przecinkach. UWAGA: Zezwolenie na przesyłanie plików może powodować ryzyko bezpieczeństwa."
seo:
page_title: SEO
permalink:
@@ -2190,7 +2247,7 @@ ui:
text: "OSTRZEŻENIE: Jeśli wyłączone, już się nie zalogujesz, jeśli wcześniej nie skonfigurowałeś innej metody logowania."
installed_plugins:
title: Zainstalowane wtyczki
- plugin_link: Plugins extend and expand the functionality. You may find plugins in the <1>Plugin Repository1>.
+ plugin_link: Wtyczki rozszerzają i rozbudowują funkcjonalność. Wtyczki znajdziesz w <1>Repozytorium Wtyczek1>.
filter:
all: Wszystkie
active: Aktywne
@@ -2232,25 +2289,25 @@ ui:
title: Uprawnienia
level:
label: Wymagany poziom reputacji
- text: Wybierz reputację wymaganą dla uprawnień
+ text: Wybierz reputację wymaganą dla tego uprawnienia
msg:
- should_be_number: the input should be number
- number_larger_1: number should be equal or larger than 1
+ should_be_number: Wartość musi być liczbą
+ number_larger_1: Liczba musi być równa 1 lub większa
badges:
- action: Action
- active: Active
- activate: Activate
- all: All
- awards: Awards
- deactivate: Deactivate
+ action: Akcja
+ active: Aktywne
+ activate: Aktywuj
+ all: Wszystkie
+ awards: Przyznane
+ deactivate: Dezaktywuj
filter:
- placeholder: Filter by name, badge:id
+ placeholder: Filtruj po nazwie, badge:id
group: Grupa
- inactive: Inactive
- name: Name
+ inactive: Nieaktywne
+ name: Nazwa
show_logs: Wyświetl dzienniki
status: Status
- title: Badges
+ title: Odznaki
form:
optional: (opcjonalne)
empty: nie może być puste
@@ -2273,11 +2330,11 @@ ui:
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
+ suggest_edits: Sugerowane edycje
flag_post: Oznacz wpis
flag_user: Oznacz użytkownika
- queued_post: Queued post
- queued_user: Queued user
+ queued_post: Oczekujący post
+ queued_user: Oczekujący użytkownik
filter_label: Typ
reputation: reputacja
flag_post_type: Oznaczono ten wpis jako {{ type }}.
@@ -2330,7 +2387,7 @@ ui:
discard_confirm: Czy na pewno chcesz odrzucić swoją wersję roboczą?
messages:
post_deleted: Ten post został usunięty.
- post_cancel_deleted: This post has been undeleted.
+ post_cancel_deleted: Usunięcie tego posta zostało anulowane.
post_pin: Ten post został przypięty.
post_unpin: Ten post został odpięty.
post_hide_list: Ten post został ukryty na liście.
@@ -2339,21 +2396,20 @@ ui:
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.
- post_closed: This post has been closed.
- answer_deleted: This answer has been deleted.
- answer_cancel_deleted: This answer has been undeleted.
- change_user_role: This user's role has been changed.
- user_inactive: This user is already inactive.
- user_normal: This user is already normal.
- user_suspended: This user has been suspended.
- user_deleted: This user has been deleted.
- badge_activated: This badge has been activated.
- badge_inactivated: This badge has been inactivated.
- users_deleted: These users have been deleted.
- posts_deleted: These questions have been deleted.
- answers_deleted: These answers have been deleted.
- copy: Copy to clipboard
- copied: Copied
- external_content_warning: External images/media are not displayed.
-
+ post_closed: Ten post został zamknięty.
+ answer_deleted: Ta odpowiedź została usunięta.
+ answer_cancel_deleted: Ta odpowiedź została przywrócona.
+ change_user_role: Rola tego użytkownika została zmieniona.
+ user_inactive: Ten użytkownik jest już nieaktywny.
+ user_normal: Ten użytkownik jest już aktywny.
+ user_suspended: Ten użytkownik został zawieszony.
+ user_deleted: Ten użytkownik został usunięty.
+ badge_activated: Ta odznaka została aktywowana.
+ badge_inactivated: Ta odznaka została dezaktywowana.
+ users_deleted: Ci użytkownicy zostali usunięci.
+ posts_deleted: Te pytania zostały usunięte.
+ answers_deleted: Te odpowiedzi zostały usunięte.
+ copy: Skopiuj do schowka
+ copied: Skopiowano
+ external_content_warning: Zewnętrzne obrazy/media nie są wyświetlane.
From 48b1de831473ed9e79fc4f453696428a43e69474 Mon Sep 17 00:00:00 2001
From: joaoback <156559121+joaoback@users.noreply.github.com>
Date: Fri, 12 Sep 2025 22:21:32 -0300
Subject: [PATCH 18/92] Update pt_BR.yaml
Translations of items that had not yet been translated. Adjustments to translations already made.
---
i18n/pt_BR.yaml | 248 ++++++++++++++++++++++++------------------------
1 file changed, 124 insertions(+), 124 deletions(-)
diff --git a/i18n/pt_BR.yaml b/i18n/pt_BR.yaml
index 42d56aaf7..45e00873c 100644
--- a/i18n/pt_BR.yaml
+++ b/i18n/pt_BR.yaml
@@ -288,7 +288,7 @@ ui:
change_email: Modificar e-mail
install: Instalação do Resposta
upgrade: Atualização do Resposta
- maintenance: Manutençã do Website
+ maintenance: Manutenção do Website
users: Usuários
notifications:
title: Notificações
@@ -327,7 +327,7 @@ ui:
empty: Código não pode ser vazio.
language:
label: Idioma (opcional)
- placeholder: Tetecção automática
+ placeholder: Detecção automática
btn_cancel: Cancelar
btn_confirm: Adicionar
formula:
@@ -351,7 +351,7 @@ ui:
image:
text: Imagem
add_image: Adicionar imagem
- tab_image: Enviar image,
+ tab_image: Enviar imagem
form_image:
fields:
file:
@@ -380,7 +380,7 @@ ui:
outdent:
text: Não identado
italic:
- text: Emphase
+ text: Ênfase
link:
text: Superlink (Hyperlink)
add_link: Adicionar superlink (hyperlink)
@@ -537,7 +537,7 @@ ui:
title: Adicionar Pergunta
edit_title: Editar Pergunta
default_reason: Editar pergunta
- similar_questions: Similar perguntas
+ similar_questions: Perguntas similares
form:
fields:
revision:
@@ -564,10 +564,10 @@ ui:
label: Resumo da edição
placeholder: >-
Explique resumidamente suas alterações (ortografia corrigida, gramática corrigida, formatação aprimorada)
- btn_post_question: Publicação a sua pergunta
+ btn_post_question: Publicar a sua pergunta
btn_save_edits: Salvar edições
answer_question: Responda a sua própria pergunta
- post_question&answer: Publicação a sua pergunta e resposta
+ post_question&answer: Publicar a sua pergunta e resposta
tag_selector:
add_btn: Adicionar marcador
create_btn: Criar novo marcador
@@ -589,7 +589,7 @@ ui:
placeholder: Procurar
footer:
build_on: >-
- Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ Desenvolvido com base no <1> Answer 1> — o software de código aberto que alimenta comunidades de perguntas e respostas. Feito com amor © {{cc}}.
upload_img:
name: Mudar
loading: carregando...
@@ -604,13 +604,13 @@ ui:
info: "Se não chegar, verifique sua pasta de spam."
another: >-
Enviamos outro e-mail de ativação para você em {{mail}} . Pode levar alguns minutos para chegar; certifique-se de verificar sua pasta de spam.
- btn_name: Resend activation email
+ btn_name: Reenviar e-mail de ativação
change_btn_name: Mudar email
msg:
empty: Não pode ser vazio.
login:
page_title: Bem vindo ao {{site_name}}
- login_to_continue: Entre para continue
+ login_to_continue: Entre para continuar
info_sign: Não possui uma conta? <1>Cadastrar-se1>
info_login: Já possui uma conta? <1>Entre1>
agreements: Ao se registrar, você concorda com as <1>políticas de privacidades1> e os <3>termos de serviços3>.
@@ -776,9 +776,9 @@ ui:
delete:
title: Excluir esta postagem
question: >-
- Nós não recomendamos excluindo perguntas com respostas porque isso priva os futuros leitores desse conhecimento.
Repeated deletion of answered questions can result in a sua account being blocked from asking. Você tem certeza que deseja deletar?
+ Nós não recomendamos excluir perguntas com respostas porque isso priva os futuros leitores desse conhecimento.
A exclusão repetida de perguntas respondidas pode resultar no bloqueio de perguntas de sua conta. Você tem certeza que deseja excluir?
answer_accepted: >-
-
Nós não recomendamos deleting accepted answer porque isso priva os futuros leitores desse conhecimento.
Repeated deletion of accepted answers can result in a sua account being blocked from answering. Você tem certeza que deseja deletar?
+ Não recomendamos excluir resposta aceita porque isso priva os futuros leitores desse conhecimento.
A exclusão repetida de respostas aceitas pode resultar no bloqueio de respostas de uma conta sua. Você tem certeza que deseja excluir?
other: Você tem certeza que deseja deletar?
tip_question_deleted: Esta postagem foi deletada
tip_answer_deleted: Esta resposta foi deletada
@@ -834,7 +834,7 @@ ui:
link: Continuar para a página inicial.
invalid: >-
Desculpe, este link de confirmação não é mais válido. Talvez a sua já está ativa.
- confirm_new_email: Your email has been updated.
+ confirm_new_email: Seu e-mail foi atualizado.
confirm_new_email_invalid: >-
Desculpe, este link de confirmação não é mais válido. Talvez o seu e-mail já tenha sido alterado.
unsubscribe:
@@ -846,7 +846,7 @@ ui:
following_tags: Seguindo Marcadores
edit: Editar
save: Salvar
- follow_tag_tip: Seguir tags to curate a sua lista de perguntas.
+ follow_tag_tip: Siga as tags para selecionar sua lista de perguntas.
hot_questions: Perguntas quentes
all_questions: Todas Perguntas
x_questions: "{{ count }} perguntas"
@@ -878,7 +878,7 @@ ui:
score: Pontuação
edit_profile: Editar Perfil
visited_x_days: "Visitado {{ count }} dias"
- viewed: Viewed
+ viewed: Visualizado
joined: Ingressou
last_login: Visto
about_me: Sobre mim
@@ -900,13 +900,13 @@ ui:
x_questions: perguntas
install:
title: Instalação
- next: Proximo
+ next: Próximo
done: Completo
config_yaml_error: Não é possível criar o arquivo config.yaml.
lang:
label: Por favor Escolha um Idioma
db_type:
- label: Database Engine
+ label: Mecanismo de banco de dados
db_username:
label: Nome de usuário
placeholder: root
@@ -916,68 +916,68 @@ ui:
placeholder: root
msg: Senha não pode ser vazio.
db_host:
- label: Database Host
+ label: Host do banco de dados
placeholder: "db:3306"
- msg: Database Host não pode ser vazio.
+ msg: Host de banco de dados não pode ficar vazio.
db_name:
- label: Database Nome
+ label: Nome do banco de dados
placeholder: answer
- msg: Database Nome não pode ser vazio.
+ msg: O nome do banco de dados não pode ficar vazio.
db_file:
- label: Database File
+ label: Arquivo de banco de dados
placeholder: /data/answer.db
- msg: Database File não pode ser vazio.
+ msg: O arquivo de banco de dados não pode ficar vazio.
config_yaml:
- title: Create config.yaml
- label: The config.yaml file created.
+ title: Criar config.yaml
+ label: O arquivo config.yaml foi criado.
desc: >-
- You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
- info: After you've done that, click "Next" button.
- site_information: Site Information
+ Você pode criar o arquivo <1>config.yaml1> manualmente no diretório <1>/var/www/xxx/1> e colar o seguinte texto nele.
+ info: Depois de fazer isso, clique no botão "Avançar".
+ site_information: Informações do site
admin_account: Administrador Conta
site_name:
label: Site Nome
msg: Site Nome não pode ser vazio.
site_url:
label: Site URL
- text: The address of a sua site.
+ text: O endereço do seu site.
msg:
empty: Site URL não pode ser vazio.
- incorrect: Site URL incorrect format.
+ incorrect: Formato incorreto da URL do site.
contact_email:
- label: E-mail par contato
- text: Email address of key contact responsible for this site.
+ label: E-mail para contato
+ text: Endereço de e-mail do contato principal responsável por este site.
msg:
- empty: E-mail par contato não pode ser vazio.
- incorrect: E-mail par contato incorrect format.
+ empty: E-mail para contato não pode ser vazio.
+ incorrect: E-mail para contato em formato incorreto.
admin_name:
label: Nome
msg: Nome não pode ser vazio.
admin_password:
label: Senha
text: >-
- You will need this password to log in. Por favor store it in a secure location.
- msg: Senha não pode ser vazio.
+ Você precisará dessa senha para efetuar login. Por favor, guarde-a em um local seguro.
+ msg: Senha não pode ser vazia.
admin_email:
label: Email
- text: You will need this email to log in.
+ text: Você precisará deste e-mail para fazer login.
msg:
empty: Email não pode ser vazio.
- incorrect: Email incorrect format.
- ready_title: Your Resposta is Ready!
+ incorrect: Formato de e-mail incorreto.
+ ready_title: Sua resposta está pronta!
ready_desc: >-
- If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
- good_luck: "Have fun, and good luck!"
- warn_title: Warning
+ Se você quiser alterar mais configurações, visite a <1>seção de administração1>; encontre-a no menu do site.
+ good_luck: "Divirta-se e boa sorte!"
+ warn_title: Aviso
warn_desc: >-
- The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
- install_now: You may try <1>installing now1>.
- installed: Already installed
+ O arquivo <1>config.yaml1> já existe. Se precisar redefinir algum item de configuração neste arquivo, exclua-o primeiro.
+ install_now: Você pode tentar <1>instalar agora1>.
+ installed: Já instalado
installed_desc: >-
- You appear to have already installed. To reinstall please clear a sua old database tables first.
- db_failed: Database connection failed
+ Parece que você já instalou. Para reinstalar, limpe primeiro as tabelas antigas do seu banco de dados.
+ db_failed: Falha na conexão do banco de dados
db_failed_desc: >-
- This either means that the database information in a sua <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean a sua host's database server is down.
+ Isso significa que as informações do banco de dados em um arquivo <1>config.yaml1> do SUA estão incorretas ou que o contato com o servidor do banco de dados não pôde ser estabelecido. Isso pode significar que o servidor de banco de dados de um host SUA está inativo.
counts:
views: visualizações
Votos: votos
@@ -987,7 +987,7 @@ ui:
desc: "Infelizmente, esta postagem não existe mais."
back_home: Voltar para a página inicial
page_50X:
- desc: O servidor encontrou um erro e não pôde concluir uma solicitação sua.
+ desc: O servidor encontrou um erro e não pôde concluir sua solicitação.
back_home: Voltar para a página inicial
page_maintenance:
desc: "Estamos em manutenção, voltaremos em breve."
@@ -1037,7 +1037,7 @@ ui:
answer_links: Links das Respostas
documents: Documentos
feedback: Opinião
- support: Supporte
+ support: Suporte
review: Revisar
config: Configurações
update_to: Atualizar ao
@@ -1233,7 +1233,7 @@ ui:
smtp_port:
label: SMTP Port
msg: SMTP port must be number 1 ~ 65535.
- text: The port to a sua mail server.
+ text: A porta para seu servidor de e-mail.
smtp_username:
label: SMTP Nome de usuário
msg: SMTP username não pode ser vazio.
@@ -1241,9 +1241,9 @@ ui:
label: SMTP Senha
msg: SMTP password não pode ser vazio.
test_email_recipient:
- label: Test Email Recipients
- text: Provide email address that will receive test sends.
- msg: Test email recipients is invalid
+ label: Destinatários de e-mail de teste
+ text: Forneça o endereço de e-mail que receberá os envios de testes.
+ msg: Os destinatários do e-mail de teste são inválidos
smtp_authentication:
label: Enable authentication
title: SMTP Authentication
@@ -1255,127 +1255,127 @@ ui:
logo:
label: Logo (opcional)
msg: Logo não pode ser vazio.
- text: The logo image at the top left of a sua site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ text: A imagem do logotipo no canto superior esquerdo do seu site. Use uma imagem retangular larga com altura de 56 e proporção maior que 3:1. Se deixada em branco, o texto do título do site será exibido.
mobile_logo:
label: Mobile Logo (opcional)
- text: The logo used on mobile version of a sua site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ text: O logotipo usado na versão mobile do seu site. Use uma imagem retangular larga com altura de 56. Se deixado em branco, a imagem da configuração "logotipo" será usada.
square_icon:
label: Square Icon (opcional)
msg: Square icon não pode ser vazio.
- text: Imagem used as the base for metadata icons. Should ideally be larger than 512x512.
+ text: Imagem usada como base para ícones de metadados. Idealmente, deve ser maior que 512x512.
favicon:
label: Favicon (opcional)
- text: A favicon for a sua site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ text: Um favicon para o seu site. Para funcionar corretamente em uma CDN, ele deve ser um png. Será redimensionado para 32x32. Se deixado em branco, o "ícone quadrado" será usado.
legal:
page_title: Legal
terms_of_service:
- label: Terms of Service
- text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ label: Termos de Serviço
+ text: "Você pode adicionar conteúdo dos termos de serviço aqui. Se você já possui um documento hospedado em outro lugar, informe o URL completo aqui."
privacy_policy:
- label: Privacy Policy
- text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ label: Política de Privacidade
+ text: "Você pode adicionar o conteúdo da política de privacidade aqui. Se você já possui um documento hospedado em outro lugar, informe o URL completo aqui."
write:
- page_title: Write
+ page_title: Escrever
recommend_tags:
- label: Recommend Marcadores
- text: "Por favor input tag slug above, one tag per line."
+ label: Recomendar Marcadores
+ text: "Por favor, insira o slug da tag acima, uma tag por linha."
required_tag:
- title: Required Tag
- label: Set recommend tag as requirido
- text: "Every new question must have ao menos one recommend tag."
+ title: Tag necessária
+ label: Definir tag recomendada como necessária
+ text: "Cada nova pergunta deve ter pelo menos uma tag de recomendação."
reserved_tags:
- label: Reserved Marcadores
- text: "Reserved tags can only be added to a post by moderator."
+ label: Marcadores Reservados
+ text: "Tags reservadas só podem ser adicionadas a uma postagem pelo moderador."
seo:
page_title: SEO
permalink:
label: Permalink
- text: Custom URL structures can improve the usability, and forward-compatibility of a sua links.
+ text: Estruturas de URL personalizadas podem melhorar a usabilidade e a compatibilidade futura de seus links.
robots:
label: robots.txt
- text: This will permanently override any related site settings.
+ text: Isso substituirá permanentemente todas as configurações relacionadas do site.
Temas:
page_title: Temas
Temas:
label: Temas
- text: Select an existing Tema.
+ text: Selecione um tema existente.
navbar_style:
- label: Navbar Style
- text: Select an existing Tema.
+ label: Estilo da barra de navegação
+ text: Selecione um tema existente.
primary_color:
- label: Primary Color
- text: Modify the colors used by a sua Temas
+ label: Cor primária
+ text: Modifique as cores usadas por seus Temas
css_and_html:
- page_title: CSS and HTML
+ page_title: CSS e HTML
custom_css:
label: Custom CSS
- text: This will insert as
+ text: Isto será inserido como
head:
label: Head
- text: This will insert before
+ text: Isto será inserido antes de
header:
label: Header
- text: This will insert after
+ text: Isto será inserido após
footer:
label: Footer
- text: This will insert before .
+ text: Isso será inserido antes de .
login:
page_title: Login
membership:
- title: Membership
- label: Allow new registrations
- text: Turn off to prevent anyone from creating a new account.
+ title: Associação
+ label: Permitir novos registros
+ text: Desative para impedir que alguém crie uma nova conta.
private:
- title: Private
+ title: Privado
label: Login requirido
- text: Only logged in users can access this community.
+ text: Somente usuários logados podem acessar esta comunidade.
form:
empty: não pode ser vazio
- invalid: is invalid
+ invalid: é inválido
btn_submit: Salvar
- not_found_props: "Required property {{ key }} not found."
+ not_found_props: "Propriedade necessária {{ key }} não encontrada."
page_review:
review: Revisar
- proposed: proposed
- question_edit: Pergunta edit
- answer_edit: Resposta edit
- tag_edit: Tag edit
- edit_summary: Editar summary
- edit_question: Editar question
- edit_answer: Editar answer
+ proposed: proposta
+ question_edit: Editar Pergunta
+ answer_edit: Editar Resposta
+ tag_edit: Editar Tag
+ edit_summary: Editar resumo
+ edit_question: Editar pergunta
+ edit_answer: Editar resposta
edit_tag: Editar tag
- empty: No review tasks left.
+ empty: Não há mais tarefas de revisão.
timeline:
- undeleted: undeleted
- deleted: deleted
- downvote: downvote
- upvote: upvote
- accept: accept
- cancelled: cancelled
- commented: commented
+ undeleted: não excluído
+ deleted: apagado
+ downvote: voto negativo
+ upvote: voto positivo
+ accept: aceitar
+ cancelled: cancelado
+ commented: comentado
rollback: rollback
- edited: edited
- answered: answered
- asked: asked
- closed: closed
- reopened: reopened
- created: created
- title: "Histórico for"
- tag_title: "Timeline for"
- show_Votos: "Show Votos"
+ edited: editado
+ answered: respondido
+ asked: perguntado
+ closed: fechado
+ reopened: reaberto
+ created: criado
+ title: "Histórico para"
+ tag_title: "Linha do tempo para"
+ show_Votos: "Mostrar votos"
n_or_a: N/A
- title_for_question: "Timeline for"
- title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
- title_for_tag: "Timeline for tag"
+ title_for_question: "Linha do tempo para"
+ title_for_answer: "Linha do tempo para resposta a {{ title }} por {{ author }}"
+ title_for_tag: "Linha do tempo para tag"
datetime: Datetime
- type: Type
- by: By
- comment: Comment
- no_data: "We couldn't find anything."
+ type: Tipo
+ by: Por
+ comment: Comentário
+ no_data: "Não conseguimos encontrar nada."
users:
title: Usuários
- users_with_the_most_reputation: Usuários with the highest reputation scores
- users_with_the_most_vote: Usuários who voted the most
- staffs: Our community staff
- reputation: reputation
+ users_with_the_most_reputation: Usuários com as maiores pontuações de reputação
+ users_with_the_most_vote: Usuários que mais votaram
+ staffs: Nossa equipe comunitária
+ reputation: reputação
Votos: Votos
From 57ba299543e91b9f00362bfb3b0452bf2ccf0f35 Mon Sep 17 00:00:00 2001
From: Burak Tekin
Date: Thu, 11 Dec 2025 04:42:21 +0300
Subject: [PATCH 19/92] chore: turkish translation improved (#1454)
turkish translations improved
Co-authored-by: dashuai
---
i18n/tr_TR.yaml | 56 ++++++++++++++++++++++++-------------------------
1 file changed, 28 insertions(+), 28 deletions(-)
diff --git a/i18n/tr_TR.yaml b/i18n/tr_TR.yaml
index 92d97ebe2..86a6321c2 100644
--- a/i18n/tr_TR.yaml
+++ b/i18n/tr_TR.yaml
@@ -234,8 +234,8 @@ backend:
other: Güncelleme izni yok.
content_cannot_empty:
other: İçerik boş olamaz.
- content_less_than_minimum:
- other: Not enough content entered.
+ content_less_than_minumum:
+ other: Yeterli içerik girilmedi.
rank:
fail_to_meet_the_condition:
other: İtibar seviyesi koşulu karşılamıyor.
@@ -266,7 +266,7 @@ backend:
cannot_set_synonym_as_itself:
other: Bir etiketin eş anlamlısını kendisi olarak ayarlayamazsınız.
minimum_count:
- other: Not enough tags were entered.
+ other: Yeterli sayıda etiket girilmedi.
smtp:
config_from_name_cannot_be_email:
other: Gönderen adı bir e-posta adresi olamaz.
@@ -311,13 +311,13 @@ backend:
add_bulk_users_amount_error:
other: "Bir kerede eklediğiniz kullanıcı sayısı 1-{{.MaxAmount}} aralığında olmalıdır."
status_suspended_forever:
- other: "This user was suspended forever. This user doesn't meet a community guideline."
+ other: "Bu kullanıcı süresiz olarak askıya alındı. Bu kullanıcı topluluk kurallarını karşılamıyor."
status_suspended_until:
- other: "This user was suspended until {{.SuspendedUntil}}. This user doesn't meet a community guideline."
+ other: "Bu kullanıcı {{.SuspendedUntil}} tarihine kadar askıya alındı. Bu kullanıcı topluluk kurallarını karşılamıyor."
status_deleted:
- other: "This user was deleted."
+ other: "Bu kullanıcı silindi."
status_inactive:
- other: "This user is inactive."
+ other: "Bu kullanıcı aktif değil."
config:
read_config_failed:
other: Yapılandırma okunamadı.
@@ -821,7 +821,7 @@ ui:
tag_wiki: etiket wikisi
create_tag: Etiket Oluştur
edit_tag: Etiketi Düzenle
- ask_a_question: Create Question
+ ask_a_question: Soru Oluştur
edit_question: Soruyu Düzenle
edit_answer: Cevabı Düzenle
search: Ara
@@ -1069,9 +1069,9 @@ ui:
day: gün
hours: saatler
days: günler
- month: month
- months: months
- year: year
+ month: ay
+ months: aylar
+ year: yıl
reaction:
heart: kalp
smile: gülümseme
@@ -1127,10 +1127,10 @@ ui:
more: Daha Fazla
wiki: Wiki
ask:
- title: Create Question
+ title: Soru Oluştur
edit_title: Soruyu Düzenle
default_reason: Soruyu düzenle
- default_first_reason: Create question
+ default_first_reason: Soru oluştur
similar_questions: Benzer sorular
form:
fields:
@@ -1138,7 +1138,7 @@ ui:
label: Revizyon
title:
label: Başlık
- placeholder: What's your topic? Be specific.
+ placeholder: Konunuz nedir? Ayrıntılı belirtin.
msg:
empty: Başlık boş olamaz.
range: Başlık en fazla 150 karakter olabilir
@@ -1147,8 +1147,8 @@ ui:
msg:
empty: İçerik boş olamaz.
hint:
- optional_body: Describe what the question is about.
- minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ optional_body: Sorunun ne hakkında olduğunu açıklayın.
+ minimum_characters: "Sorunun ne hakkında olduğunu açıklayın, en az {{min_content_length}} karakter gereklidir."
tags:
label: Etiketler
msg:
@@ -1169,9 +1169,9 @@ ui:
add_btn: Etiket ekle
create_btn: Yeni etiket oluştur
search_tag: Etiket ara
- hint: Describe what your content is about, at least one tag is required.
- hint_zero_tags: Describe what your content is about.
- hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ hint: İçeriğinizin ne hakkında olduğunu açıklayın, en az bir etiket gereklidir.
+ hint_zero_tags: İçeriğinizin ne hakkında olduğunu açıklayın.
+ hint_more_than_one_tag: "İçeriğinizin ne hakkında olduğunu açıklayın, en az {{min_tags_number}} etiket gereklidir."
no_result: Eşleşen etiket bulunamadı
tag_required_text: Gerekli etiket (en az bir tane)
header:
@@ -1377,12 +1377,12 @@ ui:
review: Revizyonunuz incelendikten sonra görünecek.
sent_success: Başarıyla gönderildi
related_question:
- title: Related
+ title: İlgili
answers: cevap
linked_question:
- title: Linked
- description: Posts linked to
- no_linked_question: No contents linked from this content.
+ title: Bağlantılı
+ description: Bağlantılı gönderiler
+ no_linked_question: Bu içerikten bağlantı verilen içerik yok.
invite_to_answer:
title: İnsanları Davet Et
desc: Cevap verebileceğini düşündüğünüz kişileri davet edin.
@@ -1391,11 +1391,11 @@ ui:
search: Kişi ara
question_detail:
action: Eylem
- created: Created
+ created: Oluşturuldu
Asked: Soruldu
asked: sordu
update: Değiştirildi
- Edited: Edited
+ Edited: Düzenlendi
edit: düzenledi
commented: yorum yaptı
Views: Görüntülendi
@@ -1403,7 +1403,7 @@ ui:
Following: Takip Ediliyor
follow_tip: Bildirim almak için bu soruyu takip edin
answered: cevapladı
- closed_in: Şurada kapatıldı
+ closed_in: Burada kapatıldı
show_exist: Var olan soruyu göster.
useful: Faydalı
question_useful: Faydalı ve açık
@@ -2098,8 +2098,8 @@ ui:
label: Her kullanıcı aynı soru için sadece bir cevap yazabilir
text: "Kullanıcıların aynı soruya birden fazla cevap yazmasına izin vermek için kapatın, bu cevapların odaktan uzaklaşmasına neden olabilir."
min_tags:
- label: "Minimum tags per question"
- text: "Minimum number of tags required in a question."
+ label: "Soru başına minimum etiket"
+ text: "Bir soruda bulunması gereken minimum etiket sayısı."
recommend_tags:
label: Önerilen etiketler
text: "Önerilen etiketler varsayılan olarak açılır listede gösterilecektir."
From d468e2ba8dee2577ff0af8e7cbcfb2b111f6abdb Mon Sep 17 00:00:00 2001
From: liruohrh <2372221537@qq.com>
Date: Sun, 7 Dec 2025 00:51:15 +0800
Subject: [PATCH 20/92] feat: add env for glob load template files by gin debug
render
---
internal/base/server/http.go | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/internal/base/server/http.go b/internal/base/server/http.go
index 4fbb04cb2..512f3da21 100644
--- a/internal/base/server/http.go
+++ b/internal/base/server/http.go
@@ -22,6 +22,7 @@ package server
import (
"html/template"
"io/fs"
+ "os"
brotli "github.com/anargu/gin-brotli"
"github.com/apache/answer/internal/base/middleware"
@@ -53,9 +54,14 @@ func NewHTTPServer(debug bool,
r.Use(brotli.Brotli(brotli.DefaultCompression), middleware.ExtractAndSetAcceptLanguage, shortIDMiddleware.SetShortIDFlag())
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
- html, _ := fs.Sub(ui.Template, "template")
- htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
- r.SetHTMLTemplate(htmlTemplate)
+ templatePath := os.Getenv("ANSWER_TEMPLATE_PATH")
+ if templatePath != "" {
+ r.LoadHTMLGlob(templatePath)
+ } else {
+ html, _ := fs.Sub(ui.Template, "template")
+ htmlTemplate := template.Must(template.New("").Funcs(funcMap).ParseFS(html, "*"))
+ r.SetHTMLTemplate(htmlTemplate)
+ }
r.Use(middleware.HeadersByRequestURI())
viewRouter.Register(r, uiConf.BaseURL)
From fbb877ab11760b69ee17cb0d00822fead64e60c9 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Thu, 11 Dec 2025 14:23:17 +0800
Subject: [PATCH 21/92] fix(translator): enhance error reporting for invalid
translator YAML files
---
internal/base/translator/provider.go | 165 +++++++++++++++++++++++++++
1 file changed, 165 insertions(+)
diff --git a/internal/base/translator/provider.go b/internal/base/translator/provider.go
index 47212e84f..1a465b1e8 100644
--- a/internal/base/translator/provider.go
+++ b/internal/base/translator/provider.go
@@ -23,6 +23,8 @@ import (
"fmt"
"os"
"path/filepath"
+ "sort"
+ "strings"
"github.com/google/wire"
myTran "github.com/segmentfault/pacman/contrib/i18n"
@@ -100,6 +102,7 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
// add translator use backend translation
if err = myTran.AddTranslator(content, file.Name()); err != nil {
log.Debugf("add translator failed: %s %s", file.Name(), err)
+ reportTranslatorFormatError(file.Name(), buf)
continue
}
}
@@ -160,3 +163,165 @@ func TrWithData(lang i18n.Language, key string, templateData any) string {
}
return translation
}
+
+// reportTranslatorFormatError re-parses the YAML file to locate the invalid entry
+// when go-i18n fails to add the translator.
+func reportTranslatorFormatError(fileName string, content []byte) {
+ var raw any
+ if err := yaml.Unmarshal(content, &raw); err != nil {
+ log.Errorf("parse translator file %s failed when diagnosing format error: %s", fileName, err)
+ return
+ }
+ if err := inspectTranslatorNode(raw, nil, true); err != nil {
+ log.Errorf("translator file %s invalid: %s", fileName, err)
+ }
+}
+
+func inspectTranslatorNode(node any, path []string, isRoot bool) error {
+ switch data := node.(type) {
+ case nil:
+ if isRoot {
+ return fmt.Errorf("root value is empty")
+ }
+ return fmt.Errorf("%s contains an empty value", formatTranslationPath(path))
+ case string:
+ if isRoot {
+ return fmt.Errorf("root value must be an object but found string")
+ }
+ return nil
+ case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
+ if isRoot {
+ return fmt.Errorf("root value must be an object but found %T", data)
+ }
+ return fmt.Errorf("%s expects a string translation but found %T", formatTranslationPath(path), data)
+ case map[string]any:
+ if isMessageMap(data) {
+ return nil
+ }
+ keys := make([]string, 0, len(data))
+ for key := range data {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ if err := inspectTranslatorNode(data[key], append(path, key), false); err != nil {
+ return err
+ }
+ }
+ return nil
+ case map[string]string:
+ mapped := make(map[string]any, len(data))
+ for k, v := range data {
+ mapped[k] = v
+ }
+ return inspectTranslatorNode(mapped, path, isRoot)
+ case map[any]any:
+ if isMessageMap(data) {
+ return nil
+ }
+ type kv struct {
+ key string
+ val any
+ }
+ items := make([]kv, 0, len(data))
+ for key, val := range data {
+ strKey, ok := key.(string)
+ if !ok {
+ return fmt.Errorf("%s uses a non-string key %#v", formatTranslationPath(path), key)
+ }
+ items = append(items, kv{key: strKey, val: val})
+ }
+ sort.Slice(items, func(i, j int) bool {
+ return items[i].key < items[j].key
+ })
+ for _, item := range items {
+ if err := inspectTranslatorNode(item.val, append(path, item.key), false); err != nil {
+ return err
+ }
+ }
+ return nil
+ case []any:
+ for idx, child := range data {
+ nextPath := append(path, fmt.Sprintf("[%d]", idx))
+ if err := inspectTranslatorNode(child, nextPath, false); err != nil {
+ return err
+ }
+ }
+ return nil
+ case []map[string]any:
+ for idx, child := range data {
+ nextPath := append(path, fmt.Sprintf("[%d]", idx))
+ if err := inspectTranslatorNode(child, nextPath, false); err != nil {
+ return err
+ }
+ }
+ return nil
+ default:
+ if isRoot {
+ return fmt.Errorf("root value must be an object but found %T", data)
+ }
+ return fmt.Errorf("%s contains unsupported value type %T", formatTranslationPath(path), data)
+ }
+}
+
+var translatorReservedKeys = []string{
+ "id", "description", "hash", "leftdelim", "rightdelim",
+ "zero", "one", "two", "few", "many", "other",
+}
+
+func isMessageMap(data any) bool {
+ switch v := data.(type) {
+ case map[string]any:
+ for _, key := range translatorReservedKeys {
+ val, ok := v[key]
+ if !ok {
+ continue
+ }
+ if _, ok := val.(string); ok {
+ return true
+ }
+ }
+ case map[string]string:
+ for _, key := range translatorReservedKeys {
+ val, ok := v[key]
+ if !ok {
+ continue
+ }
+ if val != "" {
+ return true
+ }
+ }
+ case map[any]any:
+ for _, key := range translatorReservedKeys {
+ val, ok := v[key]
+ if !ok {
+ continue
+ }
+ if _, ok := val.(string); ok {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func formatTranslationPath(path []string) string {
+ if len(path) == 0 {
+ return "root"
+ }
+ var b strings.Builder
+ for _, part := range path {
+ if part == "" {
+ continue
+ }
+ if part[0] == '[' {
+ b.WriteString(part)
+ continue
+ }
+ if b.Len() > 0 {
+ b.WriteByte('.')
+ }
+ b.WriteString(part)
+ }
+ return b.String()
+}
From e35b9551597aa5979b624b342e7e923cee814988 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Mon, 15 Dec 2025 12:34:42 +0800
Subject: [PATCH 22/92] fix(lang): enhance language retrieval from gin context
---
internal/base/handler/lang.go | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/internal/base/handler/lang.go b/internal/base/handler/lang.go
index 8886f0631..202449e9d 100644
--- a/internal/base/handler/lang.go
+++ b/internal/base/handler/lang.go
@@ -23,11 +23,22 @@ import (
"context"
"github.com/apache/answer/internal/base/constant"
+ "github.com/gin-gonic/gin"
"github.com/segmentfault/pacman/i18n"
)
// GetLangByCtx get language from header
func GetLangByCtx(ctx context.Context) i18n.Language {
+ if ginCtx, ok := ctx.(*gin.Context); ok {
+ acceptLanguage, ok := ginCtx.Get(constant.AcceptLanguageFlag)
+ if ok {
+ if acceptLanguage, ok := acceptLanguage.(i18n.Language); ok {
+ return acceptLanguage
+ }
+ return i18n.DefaultLanguage
+ }
+ }
+
acceptLanguage, ok := ctx.Value(constant.AcceptLanguageContextKey).(i18n.Language)
if ok {
return acceptLanguage
From 59408774fba059d039846a88cf2aebd2b943c042 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Mon, 15 Dec 2025 15:08:51 +0800
Subject: [PATCH 23/92] fix(lang): correct translations in Polish and Turkish
language files
---
i18n/pl_PL.yaml | 3 +--
i18n/tr_TR.yaml | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/i18n/pl_PL.yaml b/i18n/pl_PL.yaml
index a8d7b064a..7cf88431b 100644
--- a/i18n/pl_PL.yaml
+++ b/i18n/pl_PL.yaml
@@ -864,8 +864,7 @@ ui:
twórz bloki kodu przy pomocy potrójnych odwrotnych apostrofów `
``` kod tutaj ```
-
-
+
prev: Poprzedni
next: Następny
page_title:
diff --git a/i18n/tr_TR.yaml b/i18n/tr_TR.yaml
index 970ae8cb2..802a81f69 100644
--- a/i18n/tr_TR.yaml
+++ b/i18n/tr_TR.yaml
@@ -235,7 +235,7 @@ backend:
content_cannot_empty:
other: İçerik boş olamaz.
content_less_than_minimum:
- other: Yeterli içerik girilmedi.
+ other: Not enough content entered.
rank:
fail_to_meet_the_condition:
other: İtibar seviyesi koşulu karşılamıyor.
From 9ef55ca07c66bbdd8251c7be14f583e8879afa99 Mon Sep 17 00:00:00 2001
From: robin
Date: Mon, 8 Dec 2025 09:37:48 +0800
Subject: [PATCH 24/92] feat(editor): integrate TipTap WYSIWYG editor with
Markdown support and enhance editor functionalities
- Added TipTap extensions for image, placeholder, table, and markdown support.
- Implemented WYSIWYG and Markdown editor components.
- Refactored editor context and tool items to utilize the new editor interface.
- Updated styles for the WYSIWYG editor.
- Removed deprecated utility functions and integrated new command methods for better editor interaction.
---
ui/package.json | 8 +-
ui/pnpm-lock.yaml | 2165 +++++++++++++++--
ui/src/components/Editor/EditorContext.ts | 4 +-
ui/src/components/Editor/MarkdownEditor.tsx | 112 +
.../components/Editor/ToolBars/blockquote.tsx | 21 +-
ui/src/components/Editor/ToolBars/bold.tsx | 10 +-
ui/src/components/Editor/ToolBars/code.tsx | 32 +-
ui/src/components/Editor/ToolBars/file.tsx | 15 +-
ui/src/components/Editor/ToolBars/heading.tsx | 24 +-
ui/src/components/Editor/ToolBars/hr.tsx | 11 +-
ui/src/components/Editor/ToolBars/image.tsx | 99 +-
ui/src/components/Editor/ToolBars/indent.tsx | 15 +-
ui/src/components/Editor/ToolBars/italic.tsx | 11 +-
ui/src/components/Editor/ToolBars/link.tsx | 27 +-
ui/src/components/Editor/ToolBars/ol.tsx | 19 +-
ui/src/components/Editor/ToolBars/outdent.tsx | 16 +-
ui/src/components/Editor/ToolBars/table.tsx | 67 +-
ui/src/components/Editor/ToolBars/ul.tsx | 19 +-
ui/src/components/Editor/WysiwygEditor.tsx | 162 ++
ui/src/components/Editor/index.scss | 113 +
ui/src/components/Editor/index.tsx | 153 +-
ui/src/components/Editor/toolItem.tsx | 42 +-
ui/src/components/Editor/types.ts | 54 +-
.../Editor/utils/codemirror/adapter.ts | 59 +
.../Editor/utils/codemirror/base.ts | 110 +
.../Editor/utils/codemirror/commands.ts | 264 ++
.../Editor/utils/codemirror/events.ts | 98 +
ui/src/components/Editor/utils/extension.ts | 255 --
ui/src/components/Editor/utils/index.ts | 4 +-
.../components/Editor/utils/tiptap/adapter.ts | 59 +
ui/src/components/Editor/utils/tiptap/base.ts | 358 +++
.../Editor/utils/tiptap/commands.ts | 718 ++++++
.../Editor/utils/tiptap/constants.ts | 48 +
.../Editor/utils/tiptap/errorHandler.ts | 155 ++
.../components/Editor/utils/tiptap/events.ts | 98 +
.../Editor/utils/tiptap/position.ts | 147 ++
ui/src/pages/Questions/Detail/index.scss | 9 +-
37 files changed, 4819 insertions(+), 762 deletions(-)
create mode 100644 ui/src/components/Editor/MarkdownEditor.tsx
create mode 100644 ui/src/components/Editor/WysiwygEditor.tsx
create mode 100644 ui/src/components/Editor/utils/codemirror/adapter.ts
create mode 100644 ui/src/components/Editor/utils/codemirror/base.ts
create mode 100644 ui/src/components/Editor/utils/codemirror/commands.ts
create mode 100644 ui/src/components/Editor/utils/codemirror/events.ts
delete mode 100644 ui/src/components/Editor/utils/extension.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/adapter.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/base.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/commands.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/constants.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/errorHandler.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/events.ts
create mode 100644 ui/src/components/Editor/utils/tiptap/position.ts
diff --git a/ui/package.json b/ui/package.json
index b88fc12da..5a6704ceb 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -21,6 +21,12 @@
"@codemirror/language-data": "^6.5.0",
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.26.1",
+ "@tiptap/extension-image": "^3.11.1",
+ "@tiptap/extension-placeholder": "^3.11.1",
+ "@tiptap/extension-table": "^3.11.1",
+ "@tiptap/markdown": "^3.11.1",
+ "@tiptap/react": "^3.11.1",
+ "@tiptap/starter-kit": "^3.11.1",
"axios": "^1.7.7",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.10.5",
@@ -100,4 +106,4 @@
"pnpm": ">=9"
},
"license": "MIT"
-}
+}
\ No newline at end of file
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 842009a70..901c503de 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -20,6 +20,36 @@ importers:
'@codemirror/view':
specifier: ^6.26.1
version: 6.35.3
+ '@tiptap/extension-image':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-list':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-placeholder':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-table':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-table-cell':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-table-header':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-table-row':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/markdown':
+ specifier: ^3.11.1
+ version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/react':
+ specifier: ^3.11.1
+ version: 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tiptap/starter-kit':
+ specifier: ^3.11.1
+ version: 3.11.1
axios:
specifier: ^1.7.7
version: 1.7.9
@@ -97,11 +127,11 @@ importers:
version: 1.3.0(react@18.3.1)
zustand:
specifier: ^5.0.2
- version: 5.0.2(@types/react@18.3.16)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1))
+ version: 5.0.2(@types/react@18.3.16)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
devDependencies:
'@commitlint/cli':
specifier: ^17.0.3
- version: 17.8.1
+ version: 17.8.1(@swc/core@1.15.3)
'@commitlint/config-conventional':
specifier: ^17.2.0
version: 17.8.1
@@ -212,13 +242,13 @@ importers:
version: 3.4.2
purgecss-webpack-plugin:
specifier: ^4.1.3
- version: 4.1.3(webpack@5.97.1)
+ version: 4.1.3(webpack@5.97.1(@swc/core@1.15.3))
react-app-rewired:
specifier: ^2.2.1
- version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5))
+ version: 2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@swc/core@1.15.3)(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)(vue-template-compiler@2.7.16))
react-scripts:
specifier: 5.0.1
- version: 5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)
+ version: 5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@swc/core@1.15.3)(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)(vue-template-compiler@2.7.16)
sass:
specifier: 1.54.4
version: 1.54.4
@@ -232,6 +262,190 @@ importers:
specifier: ^0.8.0
version: 0.8.1
+ src/plugins/demo-captcha:
+ dependencies:
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-bootstrap:
+ specifier: ^2.10.0
+ version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-i18next:
+ specifier: ^11.18.3
+ version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ devDependencies:
+ '@modyfi/vite-plugin-yaml':
+ specifier: ^1.1.0
+ version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.0.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.0.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.3.2
+ version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ eslint:
+ specifier: ^8.45.0
+ version: 8.57.1
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.2(eslint@8.57.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.3
+ version: 0.4.24(eslint@8.57.1)
+ typescript:
+ specifier: ^5.0.2
+ version: 5.9.3
+ vite:
+ specifier: ^4.4.5
+ version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ vite-plugin-dts:
+ specifier: ^3.9.1
+ version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+
+ src/plugins/demo-editor:
+ dependencies:
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-bootstrap:
+ specifier: ^2.10.0
+ version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-i18next:
+ specifier: ^11.18.3
+ version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ devDependencies:
+ '@modyfi/vite-plugin-yaml':
+ specifier: ^1.1.0
+ version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.0.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.0.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.3.2
+ version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ eslint:
+ specifier: ^8.45.0
+ version: 8.57.1
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.2(eslint@8.57.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.3
+ version: 0.4.24(eslint@8.57.1)
+ typescript:
+ specifier: ^5.0.2
+ version: 5.9.3
+ vite:
+ specifier: ^4.4.5
+ version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ vite-plugin-dts:
+ specifier: ^3.9.1
+ version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+
+ src/plugins/demo-render:
+ dependencies:
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-bootstrap:
+ specifier: ^2.10.0
+ version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-i18next:
+ specifier: ^11.18.3
+ version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ devDependencies:
+ '@modyfi/vite-plugin-yaml':
+ specifier: ^1.1.0
+ version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.0.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.0.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.3.2
+ version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ eslint:
+ specifier: ^8.45.0
+ version: 8.57.1
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.2(eslint@8.57.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.3
+ version: 0.4.24(eslint@8.57.1)
+ typescript:
+ specifier: ^5.0.2
+ version: 5.9.3
+ vite:
+ specifier: ^4.4.5
+ version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ vite-plugin-dts:
+ specifier: ^3.9.1
+ version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+
+ src/plugins/demo-route:
+ dependencies:
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-bootstrap:
+ specifier: ^2.10.0
+ version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-i18next:
+ specifier: ^11.18.3
+ version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ devDependencies:
+ '@modyfi/vite-plugin-yaml':
+ specifier: ^1.1.0
+ version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.0.0
+ version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.0.0
+ version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.3.2
+ version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+ eslint:
+ specifier: ^8.45.0
+ version: 8.57.1
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.2(eslint@8.57.1)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.3
+ version: 0.4.24(eslint@8.57.1)
+ typescript:
+ specifier: ^5.0.2
+ version: 5.9.3
+ vite:
+ specifier: ^4.4.5
+ version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ vite-plugin-dts:
+ specifier: ^3.9.1
+ version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
+
packages:
'@alloc/quick-lru@5.2.0':
@@ -338,10 +552,18 @@ packages:
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-identifier@7.25.9':
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.25.9':
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
@@ -359,6 +581,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9':
resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==}
engines: {node: '>=6.9.0'}
@@ -945,6 +1172,10 @@ packages:
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -1206,6 +1437,138 @@ packages:
peerDependencies:
postcss-selector-parser: ^6.0.10
+ '@esbuild/android-arm64@0.18.20':
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.18.20':
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.18.20':
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.18.20':
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.18.20':
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.18.20':
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.18.20':
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.18.20':
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.18.20':
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.18.20':
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.18.20':
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.18.20':
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.18.20':
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.18.20':
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.18.20':
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.18.20':
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.18.20':
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-x64@0.18.20':
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.18.20':
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.18.20':
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.18.20':
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.18.20':
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
'@eslint-community/eslint-utils@4.4.1':
resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1224,6 +1587,15 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
+ '@floating-ui/utils@0.2.10':
+ resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
+
'@fullhuman/postcss-purgecss@4.1.3':
resolution: {integrity: sha512-jqcsyfvq09VOsMXxJMPLRF6Fhg/NNltzWKnC9qtzva+QKTxerCO4esG6je7hbnmkpZtaDyPTwMBj9bzfWorsrw==}
peerDependencies:
@@ -1346,6 +1718,9 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -1409,6 +1784,24 @@ packages:
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
+ '@microsoft/api-extractor-model@7.28.13':
+ resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==}
+
+ '@microsoft/api-extractor@7.43.0':
+ resolution: {integrity: sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==}
+ hasBin: true
+
+ '@microsoft/tsdoc-config@0.16.2':
+ resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==}
+
+ '@microsoft/tsdoc@0.14.2':
+ resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
+
+ '@modyfi/vite-plugin-yaml@1.1.1':
+ resolution: {integrity: sha512-rEbfFNlMGLKpAYs2RsfLAhxCHFa6M4QKHHk0A4EYcCJAUwFtFO6qiEdLjUGUTtnRUxAC7GxxCa+ZbeUILSDvqQ==}
+ peerDependencies:
+ vite: '>=3.2.7'
+
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
@@ -1467,6 +1860,9 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ '@remirror/core-constants@3.0.0':
+ resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
+
'@restart/hooks@0.4.16':
resolution: {integrity: sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==}
peerDependencies:
@@ -1483,6 +1879,9 @@ packages:
react: '>=16.14.0'
react-dom: '>=16.14.0'
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
'@rollup/plugin-babel@5.3.1':
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
@@ -1511,12 +1910,52 @@ packages:
peerDependencies:
rollup: ^1.20.0||^2.0.0
+ '@rollup/pluginutils@5.1.0':
+ resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/pluginutils@5.3.0':
+ resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
+ '@rushstack/node-core-library@4.0.2':
+ resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rushstack/rig-package@0.5.2':
+ resolution: {integrity: sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==}
+
+ '@rushstack/terminal@0.10.0':
+ resolution: {integrity: sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rushstack/ts-command-line@4.19.1':
+ resolution: {integrity: sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==}
+
'@sinclair/typebox@0.24.51':
resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
@@ -1585,9 +2024,84 @@ packages:
resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==}
engines: {node: '>=10'}
+ '@swc/core-darwin-arm64@1.15.3':
+ resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@swc/core-darwin-x64@1.15.3':
+ resolution: {integrity: sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@swc/core-linux-arm-gnueabihf@1.15.3':
+ resolution: {integrity: sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==}
+ engines: {node: '>=10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@swc/core-linux-arm64-gnu@1.15.3':
+ resolution: {integrity: sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-arm64-musl@1.15.3':
+ resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@swc/core-linux-x64-gnu@1.15.3':
+ resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-linux-x64-musl@1.15.3':
+ resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@swc/core-win32-arm64-msvc@1.15.3':
+ resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==}
+ engines: {node: '>=10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@swc/core-win32-ia32-msvc@1.15.3':
+ resolution: {integrity: sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==}
+ engines: {node: '>=10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@swc/core-win32-x64-msvc@1.15.3':
+ resolution: {integrity: sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==}
+ engines: {node: '>=10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/core@1.15.3':
+ resolution: {integrity: sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@swc/helpers': '>=0.5.17'
+ peerDependenciesMeta:
+ '@swc/helpers':
+ optional: true
+
+ '@swc/counter@0.1.3':
+ resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
+
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@swc/types@0.1.25':
+ resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+
'@testing-library/dom@8.20.1':
resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==}
engines: {node: '>=12'}
@@ -1609,6 +2123,192 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
+ '@tiptap/core@3.11.1':
+ resolution: {integrity: sha512-q7uzYrCq40JOIi6lceWe2HuA8tSr97iPwP/xtJd0bZjyL1rWhUyqxMb7y+aq4RcELrx/aNRa2JIvLtRRdy02Dg==}
+ peerDependencies:
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-blockquote@3.11.1':
+ resolution: {integrity: sha512-c3DN5c/9kl8w1wCcylH9XqW0OyCegqE3EL4rDlVYkyBD0GwCnUS30pN+jdxCUq/tl94lkkRk7XMyEUwzQmG+5g==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-bold@3.11.1':
+ resolution: {integrity: sha512-ee/OoAPViUAgJb8dxF7D2YSSYUWcw8RXqhNSDx15w58rxpYbJbvOv3WDMrGNvl4M9nuwXYfXc3iPl/eYtwHx2w==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-bubble-menu@3.11.1':
+ resolution: {integrity: sha512-rLgU2drvoSTpdXEmoo61ZSmtRR44vMeS36OoDpUA1dNzo/vWAiOzQeLnm8gC9cD2TmvJ+WIe7tOkpAEfw4kmiQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-bullet-list@3.11.1':
+ resolution: {integrity: sha512-6fj0b0Ynam8FMsP3NiCZ4a2uP7lCBHDFBXfcRwFDOqAgBIPvIK+r6CuHEGothGaF7EeQ9MTyj9fwlGjyHsPQcg==}
+ peerDependencies:
+ '@tiptap/extension-list': ^3.11.1
+
+ '@tiptap/extension-code-block@3.11.1':
+ resolution: {integrity: sha512-Bk7mmA+m510zzLG5AMFmywrL50NlBA5p7bR0cKfdp4ckXr8FohxH3QS0Woy1MRnFUGRtIzJkSYQTJ3O/G1lBqQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-code@3.11.1':
+ resolution: {integrity: sha512-R3HtNPAuaKqbDwK1uWO/6QFHXbbKcxbV27XVCVtTQ4gCAzIZbJElp9REEqAOp/zI6bqt774UrAekeV+5i8NQYw==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-document@3.11.1':
+ resolution: {integrity: sha512-Px8T7Kv8EEiFpM/h13Rro8HoynrlK8zA3u3ekHq/FBSTXnPtqPAUYNx/DUhIrLs3eWWJ8+P0Onm+sVLZmaLMug==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-dropcursor@3.11.1':
+ resolution: {integrity: sha512-+tmWD/4tg7Mt1TArrvc1Gna1FiSyru2rE6sapEerXCH3RFfaqGBeMqeRaOeZrCiqB+vIsXfthHDC/7xz5rFp/g==}
+ peerDependencies:
+ '@tiptap/extensions': ^3.11.1
+
+ '@tiptap/extension-floating-menu@3.11.1':
+ resolution: {integrity: sha512-HGF04KhDn1oAD+2/zStSWeGIgR41l/raf64h/Rwkvde5Sf2g3BPRW4M1ESS6e2Rjw74Kwa4/xNO6CzZNid6yRg==}
+ peerDependencies:
+ '@floating-ui/dom': ^1.0.0
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-gapcursor@3.11.1':
+ resolution: {integrity: sha512-rZ4eIFOPrLPM0bAMW560v/i9WeAz6D6PPtmFJ/Rwh7F5QFbg+jSXAyGvg7V9ZwzA5OaXqsToyJBR7qtGXBXAhQ==}
+ peerDependencies:
+ '@tiptap/extensions': ^3.11.1
+
+ '@tiptap/extension-hard-break@3.11.1':
+ resolution: {integrity: sha512-JMp6CizdB7LoY2jmaZub2D+Aj6RJTkSu0EhIcN/bmBrm4MjYa/ir6nRoo4/gYGIHzHwgwGR/1KmlqTJZW/xl4g==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-heading@3.11.1':
+ resolution: {integrity: sha512-b9ShCSQhWXNzdbdn9a3j33cq646nS0EpVyNBQr0BMOpIcMI4Ot8LGEvPo0BNqPPvpjMJaP2N6xp+EIdk6tunfQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-horizontal-rule@3.11.1':
+ resolution: {integrity: sha512-9zr6dItcJvzZtFlC+dyFb5VfWGzKzldPAOuln1d/GwKrBZds53O2vBmu4Jxfy22N9LuwiGB+2PYerq0UkLnxnA==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-image@3.11.1':
+ resolution: {integrity: sha512-lI+FyyHavUXHmDKxvSAdqGAvaYtVesAxHckeA60ZjZu9fBkUnVWHD8uR0TStX7EdOIRBWpzYrG7dDT4EkFVjTA==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-italic@3.11.1':
+ resolution: {integrity: sha512-SrjsU+gvhjoPKelc4YSeC2AQ0lhwLiDWMO7fW83CVitCF8iWXpBSeVCI5SxtPeZNKTZ1ZCc3lIQCeEHOC/gP0g==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-link@3.11.1':
+ resolution: {integrity: sha512-ds5auQnWGcHwC2/c1iEvvybdLPcSDlxsii7FPaZg4LaSGdNojRB0qDRZw5dzYQZbfIf5vgYGcIVCVjNPZs1UwQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-list-item@3.11.1':
+ resolution: {integrity: sha512-KFw3TAwN6hQ+oDeE3lRqwzCRKhxU1NWf9q5SAwiUxlp/LcEjuhXcYJYX8SHPOLOlTo0P42v1i0KBeLUPKnO58g==}
+ peerDependencies:
+ '@tiptap/extension-list': ^3.11.1
+
+ '@tiptap/extension-list-keymap@3.11.1':
+ resolution: {integrity: sha512-MpeuVi+bOHulbN65bOjaeoNJstNuAAEPdLwNjW25c9y2a8b5iZFY8vdVNENDiqq+dI+F5EaFGaEq0FN0uslfiA==}
+ peerDependencies:
+ '@tiptap/extension-list': ^3.11.1
+
+ '@tiptap/extension-list@3.11.1':
+ resolution: {integrity: sha512-XJRN9pOPMi3SsaKv4qM8WBEi3YDrjXYtYlAlZutQe1JpdKykSjLwwYq7k3V8UHqR3YKxyOV8HTYOYoOaZ9TMTQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-ordered-list@3.11.1':
+ resolution: {integrity: sha512-Aphq0kfk6J/hNQennJ+bntvDzqRPT7RVpnow1s4U4dLBsR6PP7X4zEBg96uAv2OW0RjDHFK9NFqpJPbZtQTmFw==}
+ peerDependencies:
+ '@tiptap/extension-list': ^3.11.1
+
+ '@tiptap/extension-paragraph@3.11.1':
+ resolution: {integrity: sha512-a3lm1WvYewAP2IESq+qnbOtLSJ9yULY2Bj/6DvBq9fzWpb2gSlUdElYh6JLunxB1HEPECTuuRsNPdTrMsSpV4g==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-placeholder@3.11.1':
+ resolution: {integrity: sha512-RCPLeZ12+20YeqjwENK8rviaCnN0mL+Rm7dS+NT8Dz1WHjv9ZWwWS7WzAHfY2w3CNlMfOKwctnaobKmJ/2++CQ==}
+ peerDependencies:
+ '@tiptap/extensions': ^3.11.1
+
+ '@tiptap/extension-strike@3.11.1':
+ resolution: {integrity: sha512-1LfkHNkrGR509cPRgcMr95+nWcAHE0JDm9LkuzdexunhCfJ2tl/h1rA14x3sic8GxQFqEnMefvBUpUbQwPydYw==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-table-cell@3.11.1':
+ resolution: {integrity: sha512-loFq2aZ+nDyrgxPjOCY/73YelRHqQWMw4oIiB4D0sYiDka+bBN/cV7LGWqfP5CldF8oSBJ8u+5ViDWCilTfDeg==}
+ peerDependencies:
+ '@tiptap/extension-table': ^3.11.1
+
+ '@tiptap/extension-table-header@3.11.1':
+ resolution: {integrity: sha512-oCn71L7FYxnpzHO7hn8xbaF6jxRhPinNsqgde+5OY23BEvVBpX8QFbOJkphQDyMwvSHAw/3S+ZF+vxHZU51aRg==}
+ peerDependencies:
+ '@tiptap/extension-table': ^3.11.1
+
+ '@tiptap/extension-table-row@3.11.1':
+ resolution: {integrity: sha512-KfB9axBtfE59/xC++4SS2iYkfEv93k5EWDbjIjC6T62FpfTYlBDOQutj79Nnmn8XkS5srT4nmxrohqRc1wUSQg==}
+ peerDependencies:
+ '@tiptap/extension-table': ^3.11.1
+
+ '@tiptap/extension-table@3.11.1':
+ resolution: {integrity: sha512-ae81cJgJ3cAA1Ry0JtLKtoPpA23YvMy0WnAw1gqnOX9yTYRYgT5W6MzP0O2rIBOI6b5Xkt6xtZzChjwMzioGbQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/extension-text@3.11.1':
+ resolution: {integrity: sha512-5E94ggkFAZ7OSFSwnofAsmxqmSStRoeCB8AnRuWrR+nnXi43Rq7yptdejQaLi13Z9fSVdnF6h+pB3ua2Exg6WQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extension-underline@3.11.1':
+ resolution: {integrity: sha512-Y3EJxfE1g4XSGbUZN+74o38mp3O+BQXtlqxAQvedzXiGGrdK2kWhp2b4nj3IkxHdRdoSijf+oZzgyBrRDdgC/w==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+
+ '@tiptap/extensions@3.11.1':
+ resolution: {integrity: sha512-/xXJdV+EVvSQv2slvAUChb5iGVv5K0EqBqxPGAAuBHdIc4Y7Id1aaKKSiyDmqon+kjSnnQIIda9oUt+o/Z66uA==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/markdown@3.11.1':
+ resolution: {integrity: sha512-cC3mKV+bBoHJ0Bsd4zstVYFBm8KGsbeAhIayVzY59G4QpvIQC78BgajqXukfUKDLcehiycRYYW/HcOAwvAysWg==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+
+ '@tiptap/pm@3.11.1':
+ resolution: {integrity: sha512-8RIUhlEoCFGsbdNb+EUdQctG1Wnd7rl4wlMLS6giO7UcZT5dVfg625eMZVrl0/kA7JBJdKLIuqNmzzQ0MxsJEw==}
+
+ '@tiptap/react@3.11.1':
+ resolution: {integrity: sha512-aPInZbpSWYzJvCFXaY6EhxD+H5ITURElUmUXBoRvlAB6QrR6NIWBt68hNe8i+aDGmuvLS18g60HWK5S6K2RjWQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.1
+ '@tiptap/pm': ^3.11.1
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tiptap/starter-kit@3.11.1':
+ resolution: {integrity: sha512-weRrhp0p5J6cMNcybYobhbOVrgym7KYIwBblJ/1M1snykg+avZawVk2M5Y7j9gM1p2zo112MCw8z8nOa9Yrwow==}
+
'@tootallnate/once@1.1.2':
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
engines: {node: '>= 6'}
@@ -1629,6 +2329,9 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ '@types/argparse@1.0.38':
+ resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -1728,12 +2431,21 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
'@types/lodash@4.17.13':
resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==}
+ '@types/markdown-it@14.1.2':
+ resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
'@types/marked@4.3.2':
resolution: {integrity: sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==}
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
@@ -1808,6 +2520,9 @@ packages:
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+ '@types/use-sync-external-store@0.0.6':
+ resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+
'@types/warning@3.0.3':
resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==}
@@ -1951,6 +2666,37 @@ packages:
'@ungap/structured-clone@1.2.1':
resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==}
+ '@vitejs/plugin-react-swc@3.11.0':
+ resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==}
+ peerDependencies:
+ vite: ^4 || ^5 || ^6 || ^7
+
+ '@volar/language-core@1.11.1':
+ resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==}
+
+ '@volar/source-map@1.11.1':
+ resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==}
+
+ '@volar/typescript@1.11.1':
+ resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==}
+
+ '@vue/compiler-core@3.5.25':
+ resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==}
+
+ '@vue/compiler-dom@3.5.25':
+ resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==}
+
+ '@vue/language-core@1.8.27':
+ resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@vue/shared@3.5.25':
+ resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==}
+
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -2554,6 +3300,10 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
+ commander@9.5.0:
+ resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
+ engines: {node: ^12.20.0 || >=14}
+
common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
engines: {node: '>=4.0.0'}
@@ -2572,6 +3322,9 @@ packages:
resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==}
engines: {node: '>= 0.8.0'}
+ computeds@0.0.1:
+ resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -2836,6 +3589,9 @@ packages:
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+ de-indent@1.0.2:
+ resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
+
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -3086,6 +3842,10 @@ packages:
entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -3136,6 +3896,11 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
+ esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -3331,6 +4096,11 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ eslint-plugin-react-refresh@0.4.24:
+ resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
+ peerDependencies:
+ eslint: '>=8.40'
+
eslint-plugin-react@7.37.2:
resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==}
engines: {node: '>=4'}
@@ -3405,6 +4175,9 @@ packages:
estree-walker@1.0.1:
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -3580,6 +4353,10 @@ packages:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
+ fs-extra@7.0.1:
+ resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
+ engines: {node: '>=6 <7 || >=8'}
+
fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@@ -3889,6 +4666,10 @@ packages:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
+ import-lazy@4.0.0:
+ resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
+ engines: {node: '>=8'}
+
import-local@3.2.0:
resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
engines: {node: '>=8'}
@@ -4349,6 +5130,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
+ jju@1.4.0:
+ resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
+
js-sha256@0.11.0:
resolution: {integrity: sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==}
@@ -4404,6 +5188,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
+ jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -4440,6 +5227,9 @@ packages:
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
engines: {node: '>= 8'}
+ kolorist@1.8.0:
+ resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
+
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -4473,6 +5263,12 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
+ linkifyjs@4.3.2:
+ resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
+
lint-staged@15.5.0:
resolution: {integrity: sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==}
engines: {node: '>=18.12.0'}
@@ -4515,6 +5311,14 @@ packages:
lodash.flow@3.5.0:
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
+ lodash.get@4.4.2:
+ resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
+ deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
+
+ lodash.isequal@4.5.0:
+ resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
+ deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
+
lodash.isfunction@3.0.9:
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
@@ -4579,6 +5383,9 @@ packages:
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@@ -4601,6 +5408,15 @@ packages:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
+ markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+
+ marked@15.0.12:
+ resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
marked@4.3.0:
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
engines: {node: '>= 12'}
@@ -4612,6 +5428,9 @@ packages:
mdn-data@2.0.4:
resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==}
+ mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -4684,6 +5503,9 @@ packages:
minimalistic-assert@1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
+ minimatch@3.0.8:
+ resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -4720,6 +5542,9 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ muggle-string@0.3.1:
+ resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
+
multicast-dns@7.2.5:
resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
hasBin: true
@@ -4890,6 +5715,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
+ orderedmap@2.1.1:
+ resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
+
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
@@ -4942,6 +5770,9 @@ packages:
pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
@@ -4989,6 +5820,10 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
pidtree@0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
@@ -5502,6 +6337,64 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ prosemirror-changeset@2.3.1:
+ resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
+
+ prosemirror-collab@1.3.1:
+ resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
+
+ prosemirror-commands@1.7.1:
+ resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
+
+ prosemirror-dropcursor@1.8.2:
+ resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
+
+ prosemirror-gapcursor@1.4.0:
+ resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==}
+
+ prosemirror-history@1.5.0:
+ resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
+
+ prosemirror-inputrules@1.5.1:
+ resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
+
+ prosemirror-keymap@1.2.3:
+ resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
+
+ prosemirror-markdown@1.13.2:
+ resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
+
+ prosemirror-menu@1.2.5:
+ resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
+
+ prosemirror-model@1.25.4:
+ resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
+
+ prosemirror-schema-basic@1.2.4:
+ resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
+
+ prosemirror-schema-list@1.5.1:
+ resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
+
+ prosemirror-state@1.4.4:
+ resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
+
+ prosemirror-tables@1.8.1:
+ resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==}
+
+ prosemirror-trailing-node@3.0.0:
+ resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
+ peerDependencies:
+ prosemirror-model: ^1.22.1
+ prosemirror-state: ^1.4.2
+ prosemirror-view: ^1.33.8
+
+ prosemirror-transform@1.10.5:
+ resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==}
+
+ prosemirror-view@1.41.3:
+ resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==}
+
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -5512,6 +6405,10 @@ packages:
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -5813,6 +6710,9 @@ packages:
resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==}
engines: {node: '>=10'}
+ resolve@1.19.0:
+ resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==}
+
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
@@ -5857,6 +6757,14 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
+ rollup@3.29.5:
+ resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==}
+ engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rope-sequence@1.3.4:
+ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6084,6 +6992,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
+ deprecated: The work that was done in this beta branch won't be included in future versions
sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
@@ -6395,6 +7304,10 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
+ tosource@2.0.0-alpha.3:
+ resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==}
+ engines: {node: '>=10'}
+
tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
@@ -6522,6 +7435,19 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
+ typescript@5.4.2:
+ resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@@ -6558,6 +7484,10 @@ packages:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
+ universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@@ -6593,10 +7523,10 @@ packages:
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
- use-sync-external-store@1.2.2:
- resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==}
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -6625,14 +7555,65 @@ packages:
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
+ validator@13.15.23:
+ resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
+ engines: {node: '>= 0.10'}
+
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ vite-plugin-dts@3.9.1:
+ resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ typescript: '*'
+ vite: '*'
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ vite@4.5.14:
+ resolution: {integrity: sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==}
+ 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
+
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
+ vue-template-compiler@2.7.16:
+ resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
+
+ vue-tsc@1.8.27:
+ resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==}
+ hasBin: true
+ peerDependencies:
+ typescript: '*'
+
w3c-hr-time@1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
deprecated: Use your platform's native performance.now() and performance.timeOrigin.
@@ -6942,6 +7923,11 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ z-schema@5.0.5:
+ resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==}
+ engines: {node: '>=8.0.0'}
+ hasBin: true
+
zustand@5.0.2:
resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==}
engines: {node: '>=12.20.0'}
@@ -7119,8 +8105,12 @@ snapshots:
'@babel/helper-string-parser@7.25.9': {}
+ '@babel/helper-string-parser@7.27.1': {}
+
'@babel/helper-validator-identifier@7.25.9': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
+
'@babel/helper-validator-option@7.25.9': {}
'@babel/helper-wrap-function@7.25.9':
@@ -7140,6 +8130,10 @@ snapshots:
dependencies:
'@babel/types': 7.26.3
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
@@ -7855,6 +8849,11 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
'@bcoe/v8-coverage@0.2.3': {}
'@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)':
@@ -8111,11 +9110,11 @@ snapshots:
style-mod: 4.1.2
w3c-keyname: 2.2.8
- '@commitlint/cli@17.8.1':
+ '@commitlint/cli@17.8.1(@swc/core@1.15.3)':
dependencies:
'@commitlint/format': 17.8.1
'@commitlint/lint': 17.8.1
- '@commitlint/load': 17.8.1
+ '@commitlint/load': 17.8.1(@swc/core@1.15.3)
'@commitlint/read': 17.8.1
'@commitlint/types': 17.8.1
execa: 5.1.1
@@ -8164,7 +9163,7 @@ snapshots:
'@commitlint/rules': 17.8.1
'@commitlint/types': 17.8.1
- '@commitlint/load@17.8.1':
+ '@commitlint/load@17.8.1(@swc/core@1.15.3)':
dependencies:
'@commitlint/config-validator': 17.8.1
'@commitlint/execute-rule': 17.8.1
@@ -8173,12 +9172,12 @@ snapshots:
'@types/node': 20.5.1
chalk: 4.1.2
cosmiconfig: 8.3.6(typescript@4.9.5)
- cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5)
+ cosmiconfig-typescript-loader: 4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
- ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
+ ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5)
typescript: 4.9.5
transitivePeerDependencies:
- '@swc/core'
@@ -8311,6 +9310,72 @@ snapshots:
dependencies:
postcss-selector-parser: 6.1.2
+ '@esbuild/android-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/android-arm@0.18.20':
+ optional: true
+
+ '@esbuild/android-x64@0.18.20':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/darwin-x64@0.18.20':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-arm@0.18.20':
+ optional: true
+
+ '@esbuild/linux-ia32@0.18.20':
+ optional: true
+
+ '@esbuild/linux-loong64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.18.20':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.18.20':
+ optional: true
+
+ '@esbuild/linux-s390x@0.18.20':
+ optional: true
+
+ '@esbuild/linux-x64@0.18.20':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.18.20':
+ optional: true
+
+ '@esbuild/sunos-x64@0.18.20':
+ optional: true
+
+ '@esbuild/win32-arm64@0.18.20':
+ optional: true
+
+ '@esbuild/win32-ia32@0.18.20':
+ optional: true
+
+ '@esbuild/win32-x64@0.18.20':
+ optional: true
+
'@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
@@ -8334,6 +9399,20 @@ snapshots:
'@eslint/js@8.57.1': {}
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+ optional: true
+
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+ optional: true
+
+ '@floating-ui/utils@0.2.10':
+ optional: true
+
'@fullhuman/postcss-purgecss@4.1.3(postcss@8.4.49)':
dependencies:
postcss: 8.4.49
@@ -8388,7 +9467,7 @@ snapshots:
jest-util: 28.1.3
slash: 3.0.0
- '@jest/core@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))':
+ '@jest/core@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))':
dependencies:
'@jest/console': 27.5.1
'@jest/reporters': 27.5.1
@@ -8402,7 +9481,7 @@ snapshots:
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 27.5.1
- jest-config: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
jest-haste-map: 27.5.1
jest-message-util: 27.5.1
jest-regex-util: 27.5.1
@@ -8570,6 +9649,8 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.0': {}
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
@@ -8677,6 +9758,50 @@ snapshots:
'@marijn/find-cluster-break@1.0.2': {}
+ '@microsoft/api-extractor-model@7.28.13(@types/node@20.5.1)':
+ dependencies:
+ '@microsoft/tsdoc': 0.14.2
+ '@microsoft/tsdoc-config': 0.16.2
+ '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@microsoft/api-extractor@7.43.0(@types/node@20.5.1)':
+ dependencies:
+ '@microsoft/api-extractor-model': 7.28.13(@types/node@20.5.1)
+ '@microsoft/tsdoc': 0.14.2
+ '@microsoft/tsdoc-config': 0.16.2
+ '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
+ '@rushstack/rig-package': 0.5.2
+ '@rushstack/terminal': 0.10.0(@types/node@20.5.1)
+ '@rushstack/ts-command-line': 4.19.1(@types/node@20.5.1)
+ lodash: 4.17.21
+ minimatch: 3.0.8
+ resolve: 1.22.8
+ semver: 7.5.4
+ source-map: 0.6.1
+ typescript: 5.4.2
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@microsoft/tsdoc-config@0.16.2':
+ dependencies:
+ '@microsoft/tsdoc': 0.14.2
+ ajv: 6.12.6
+ jju: 1.4.0
+ resolve: 1.19.0
+
+ '@microsoft/tsdoc@0.14.2': {}
+
+ '@modyfi/vite-plugin-yaml@1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))':
+ dependencies:
+ '@rollup/pluginutils': 5.1.0(rollup@3.29.5)
+ js-yaml: 4.1.0
+ tosource: 2.0.0-alpha.3
+ vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ transitivePeerDependencies:
+ - rollup
+
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
dependencies:
eslint-scope: 5.1.1
@@ -8698,7 +9823,7 @@ snapshots:
'@pkgr/core@0.1.1': {}
- '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.11.0)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.97.1))(webpack@5.97.1)':
+ '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.11.0)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.97.1(@swc/core@1.15.3)))(webpack@5.97.1(@swc/core@1.15.3))':
dependencies:
ansi-html: 0.0.9
core-js-pure: 3.39.0
@@ -8708,10 +9833,10 @@ snapshots:
react-refresh: 0.11.0
schema-utils: 4.2.0
source-map: 0.7.4
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
optionalDependencies:
type-fest: 1.4.0
- webpack-dev-server: 4.15.2(webpack@5.97.1)
+ webpack-dev-server: 4.15.2(webpack@5.97.1(@swc/core@1.15.3))
'@popperjs/core@2.11.8': {}
@@ -8720,6 +9845,8 @@ snapshots:
'@swc/helpers': 0.5.15
react: 18.3.1
+ '@remirror/core-constants@3.0.0': {}
+
'@restart/hooks@0.4.16(react@18.3.1)':
dependencies:
dequal: 2.0.3
@@ -8744,6 +9871,8 @@ snapshots:
uncontrollable: 8.0.4(react@18.3.1)
warning: 4.0.3
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
'@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@2.79.2)':
dependencies:
'@babel/core': 7.26.0
@@ -8778,10 +9907,58 @@ snapshots:
picomatch: 2.3.1
rollup: 2.79.2
+ '@rollup/pluginutils@5.1.0(rollup@3.29.5)':
+ dependencies:
+ '@types/estree': 1.0.6
+ estree-walker: 2.0.2
+ picomatch: 2.3.1
+ optionalDependencies:
+ rollup: 3.29.5
+
+ '@rollup/pluginutils@5.3.0(rollup@3.29.5)':
+ dependencies:
+ '@types/estree': 1.0.6
+ estree-walker: 2.0.2
+ picomatch: 4.0.3
+ optionalDependencies:
+ rollup: 3.29.5
+
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.10.4': {}
+ '@rushstack/node-core-library@4.0.2(@types/node@20.5.1)':
+ dependencies:
+ fs-extra: 7.0.1
+ import-lazy: 4.0.0
+ jju: 1.4.0
+ resolve: 1.22.8
+ semver: 7.5.4
+ z-schema: 5.0.5
+ optionalDependencies:
+ '@types/node': 20.5.1
+
+ '@rushstack/rig-package@0.5.2':
+ dependencies:
+ resolve: 1.22.8
+ strip-json-comments: 3.1.1
+
+ '@rushstack/terminal@0.10.0(@types/node@20.5.1)':
+ dependencies:
+ '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
+ supports-color: 8.1.1
+ optionalDependencies:
+ '@types/node': 20.5.1
+
+ '@rushstack/ts-command-line@4.19.1(@types/node@20.5.1)':
+ dependencies:
+ '@rushstack/terminal': 0.10.0(@types/node@20.5.1)
+ '@types/argparse': 1.0.38
+ argparse: 1.0.10
+ string-argv: 0.3.2
+ transitivePeerDependencies:
+ - '@types/node'
+
'@sinclair/typebox@0.24.51': {}
'@sinonjs/commons@1.8.6':
@@ -8847,66 +10024,326 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@svgr/plugin-svgo@5.5.0':
+ '@svgr/plugin-svgo@5.5.0':
+ dependencies:
+ cosmiconfig: 7.1.0
+ deepmerge: 4.3.1
+ svgo: 1.3.2
+
+ '@svgr/webpack@5.5.0':
+ dependencies:
+ '@babel/core': 7.26.0
+ '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0)
+ '@babel/preset-env': 7.26.0(@babel/core@7.26.0)
+ '@babel/preset-react': 7.26.3(@babel/core@7.26.0)
+ '@svgr/core': 5.5.0
+ '@svgr/plugin-jsx': 5.5.0
+ '@svgr/plugin-svgo': 5.5.0
+ loader-utils: 2.0.4
+ transitivePeerDependencies:
+ - supports-color
+
+ '@swc/core-darwin-arm64@1.15.3':
+ optional: true
+
+ '@swc/core-darwin-x64@1.15.3':
+ optional: true
+
+ '@swc/core-linux-arm-gnueabihf@1.15.3':
+ optional: true
+
+ '@swc/core-linux-arm64-gnu@1.15.3':
+ optional: true
+
+ '@swc/core-linux-arm64-musl@1.15.3':
+ optional: true
+
+ '@swc/core-linux-x64-gnu@1.15.3':
+ optional: true
+
+ '@swc/core-linux-x64-musl@1.15.3':
+ optional: true
+
+ '@swc/core-win32-arm64-msvc@1.15.3':
+ optional: true
+
+ '@swc/core-win32-ia32-msvc@1.15.3':
+ optional: true
+
+ '@swc/core-win32-x64-msvc@1.15.3':
+ optional: true
+
+ '@swc/core@1.15.3':
+ dependencies:
+ '@swc/counter': 0.1.3
+ '@swc/types': 0.1.25
+ optionalDependencies:
+ '@swc/core-darwin-arm64': 1.15.3
+ '@swc/core-darwin-x64': 1.15.3
+ '@swc/core-linux-arm-gnueabihf': 1.15.3
+ '@swc/core-linux-arm64-gnu': 1.15.3
+ '@swc/core-linux-arm64-musl': 1.15.3
+ '@swc/core-linux-x64-gnu': 1.15.3
+ '@swc/core-linux-x64-musl': 1.15.3
+ '@swc/core-win32-arm64-msvc': 1.15.3
+ '@swc/core-win32-ia32-msvc': 1.15.3
+ '@swc/core-win32-x64-msvc': 1.15.3
+
+ '@swc/counter@0.1.3': {}
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@swc/types@0.1.25':
+ dependencies:
+ '@swc/counter': 0.1.3
+
+ '@testing-library/dom@8.20.1':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/runtime': 7.26.0
+ '@types/aria-query': 5.0.4
+ aria-query: 5.1.3
+ chalk: 4.1.2
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@4.2.4':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ chalk: 2.4.2
+ css: 2.2.4
+ css.escape: 1.5.1
+ jest-diff: 24.9.0
+ jest-matcher-utils: 24.9.0
+ lodash: 4.17.21
+ pretty-format: 24.9.0
+ redent: 3.0.0
+
+ '@testing-library/react@13.4.0(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ '@testing-library/dom': 8.20.1
+ '@types/react-dom': 18.3.5(@types/react@18.3.16)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@testing-library/user-event@13.5.0(@testing-library/dom@8.20.1)':
+ dependencies:
+ '@babel/runtime': 7.26.0
+ '@testing-library/dom': 8.20.1
+
+ '@tiptap/core@3.11.1(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@tiptap/pm': 3.11.1
+
+ '@tiptap/extension-blockquote@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-bold@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-bubble-menu@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+ optional: true
+
+ '@tiptap/extension-bullet-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-code-block@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+
+ '@tiptap/extension-code@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-document@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-dropcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-floating-menu@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+ optional: true
+
+ '@tiptap/extension-gapcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-hard-break@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-heading@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-horizontal-rule@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+
+ '@tiptap/extension-image@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-italic@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-link@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+ linkifyjs: 4.3.2
+
+ '@tiptap/extension-list-item@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-list-keymap@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+
+ '@tiptap/extension-ordered-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-paragraph@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-placeholder@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-strike@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-table-cell@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-table-header@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ dependencies:
+ '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+
+ '@tiptap/extension-table-row@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- cosmiconfig: 7.1.0
- deepmerge: 4.3.1
- svgo: 1.3.2
+ '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@svgr/webpack@5.5.0':
+ '@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@babel/core': 7.26.0
- '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0)
- '@babel/preset-env': 7.26.0(@babel/core@7.26.0)
- '@babel/preset-react': 7.26.3(@babel/core@7.26.0)
- '@svgr/core': 5.5.0
- '@svgr/plugin-jsx': 5.5.0
- '@svgr/plugin-svgo': 5.5.0
- loader-utils: 2.0.4
- transitivePeerDependencies:
- - supports-color
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
- '@swc/helpers@0.5.15':
+ '@tiptap/extension-text@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
dependencies:
- tslib: 2.8.1
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
- '@testing-library/dom@8.20.1':
+ '@tiptap/extension-underline@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
dependencies:
- '@babel/code-frame': 7.26.2
- '@babel/runtime': 7.26.0
- '@types/aria-query': 5.0.4
- aria-query: 5.1.3
- chalk: 4.1.2
- dom-accessibility-api: 0.5.16
- lz-string: 1.5.0
- pretty-format: 27.5.1
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
- '@testing-library/jest-dom@4.2.4':
+ '@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@babel/runtime': 7.26.0
- chalk: 2.4.2
- css: 2.2.4
- css.escape: 1.5.1
- jest-diff: 24.9.0
- jest-matcher-utils: 24.9.0
- lodash: 4.17.21
- pretty-format: 24.9.0
- redent: 3.0.0
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
- '@testing-library/react@13.4.0(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tiptap/markdown@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@babel/runtime': 7.26.0
- '@testing-library/dom': 8.20.1
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+ marked: 15.0.12
+
+ '@tiptap/pm@3.11.1':
+ dependencies:
+ prosemirror-changeset: 2.3.1
+ prosemirror-collab: 1.3.1
+ prosemirror-commands: 1.7.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-gapcursor: 1.4.0
+ prosemirror-history: 1.5.0
+ prosemirror-inputrules: 1.5.1
+ prosemirror-keymap: 1.2.3
+ prosemirror-markdown: 1.13.2
+ prosemirror-menu: 1.2.5
+ prosemirror-model: 1.25.4
+ prosemirror-schema-basic: 1.2.4
+ prosemirror-schema-list: 1.5.1
+ prosemirror-state: 1.4.4
+ prosemirror-tables: 1.8.1
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ '@tiptap/react@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
+ '@types/react': 18.3.16
'@types/react-dom': 18.3.5(@types/react@18.3.16)
+ '@types/use-sync-external-store': 0.0.6
+ fast-deep-equal: 3.1.3
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ optionalDependencies:
+ '@tiptap/extension-bubble-menu': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-floating-menu': 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
transitivePeerDependencies:
- - '@types/react'
-
- '@testing-library/user-event@13.5.0(@testing-library/dom@8.20.1)':
- dependencies:
- '@babel/runtime': 7.26.0
- '@testing-library/dom': 8.20.1
+ - '@floating-ui/dom'
+
+ '@tiptap/starter-kit@3.11.1':
+ dependencies:
+ '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/extension-blockquote': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-bold': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-bullet-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-code': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-code-block': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-document': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-dropcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-gapcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-hard-break': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-heading': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-horizontal-rule': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-italic': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-link': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list-item': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-list-keymap': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-ordered-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-paragraph': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-strike': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-text': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extension-underline': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/pm': 3.11.1
'@tootallnate/once@1.1.2': {}
@@ -8920,6 +10357,8 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
+ '@types/argparse@1.0.38': {}
+
'@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
@@ -9053,10 +10492,19 @@ snapshots:
'@types/json5@0.0.29': {}
+ '@types/linkify-it@5.0.0': {}
+
'@types/lodash@4.17.13': {}
+ '@types/markdown-it@14.1.2':
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+
'@types/marked@4.3.2': {}
+ '@types/mdurl@2.0.0': {}
+
'@types/mime@1.3.5': {}
'@types/minimist@1.2.5': {}
@@ -9127,6 +10575,8 @@ snapshots:
'@types/trusted-types@2.0.7': {}
+ '@types/use-sync-external-store@0.0.6': {}
+
'@types/warning@3.0.3': {}
'@types/ws@8.5.13':
@@ -9186,6 +10636,26 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 6.21.0
+ '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 6.21.0
+ debug: 4.4.0
+ eslint: 8.57.1
+ graphemer: 1.4.0
+ ignore: 5.3.2
+ natural-compare: 1.4.0
+ semver: 7.6.3
+ ts-api-utils: 1.4.3(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
dependencies:
'@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
@@ -9219,6 +10689,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 6.21.0
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 6.21.0
+ debug: 4.4.0
+ eslint: 8.57.1
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/scope-manager@5.62.0':
dependencies:
'@typescript-eslint/types': 5.62.0
@@ -9253,6 +10736,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
+ debug: 4.4.0
+ eslint: 8.57.1
+ ts-api-utils: 1.4.3(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/types@5.62.0': {}
'@typescript-eslint/types@6.21.0': {}
@@ -9286,6 +10781,21 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/visitor-keys': 6.21.0
+ debug: 4.4.0
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.6.3
+ ts-api-utils: 1.4.3(typescript@5.9.3)
+ optionalDependencies:
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
'@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
@@ -9315,6 +10825,20 @@ snapshots:
- supports-color
- typescript
+ '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.8
+ '@typescript-eslint/scope-manager': 6.21.0
+ '@typescript-eslint/types': 6.21.0
+ '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
+ eslint: 8.57.1
+ semver: 7.6.3
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
'@typescript-eslint/visitor-keys@5.62.0':
dependencies:
'@typescript-eslint/types': 5.62.0
@@ -9327,6 +10851,56 @@ snapshots:
'@ungap/structured-clone@1.2.1': {}
+ '@vitejs/plugin-react-swc@3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@swc/core': 1.15.3
+ vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
+ '@volar/language-core@1.11.1':
+ dependencies:
+ '@volar/source-map': 1.11.1
+
+ '@volar/source-map@1.11.1':
+ dependencies:
+ muggle-string: 0.3.1
+
+ '@volar/typescript@1.11.1':
+ dependencies:
+ '@volar/language-core': 1.11.1
+ path-browserify: 1.0.1
+
+ '@vue/compiler-core@3.5.25':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@vue/shared': 3.5.25
+ entities: 4.5.0
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.25':
+ dependencies:
+ '@vue/compiler-core': 3.5.25
+ '@vue/shared': 3.5.25
+
+ '@vue/language-core@1.8.27(typescript@5.9.3)':
+ dependencies:
+ '@volar/language-core': 1.11.1
+ '@volar/source-map': 1.11.1
+ '@vue/compiler-dom': 3.5.25
+ '@vue/shared': 3.5.25
+ computeds: 0.0.1
+ minimatch: 9.0.5
+ muggle-string: 0.3.1
+ path-browserify: 1.0.1
+ vue-template-compiler: 2.7.16
+ optionalDependencies:
+ typescript: 5.9.3
+
+ '@vue/shared@3.5.25': {}
+
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@@ -9666,14 +11240,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- babel-loader@8.4.1(@babel/core@7.26.0)(webpack@5.97.1):
+ babel-loader@8.4.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@babel/core': 7.26.0
find-cache-dir: 3.3.2
loader-utils: 2.0.4
make-dir: 3.1.0
schema-utils: 2.7.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
babel-plugin-istanbul@6.1.1:
dependencies:
@@ -10042,6 +11616,9 @@ snapshots:
commander@8.3.0: {}
+ commander@9.5.0:
+ optional: true
+
common-tags@1.8.2: {}
commondir@1.0.1: {}
@@ -10067,6 +11644,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ computeds@0.0.1: {}
+
concat-map@0.0.1: {}
confusing-browser-globals@1.0.11: {}
@@ -10118,11 +11697,11 @@ snapshots:
core-util-is@1.0.3: {}
- cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5):
+ cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6(typescript@4.9.5))(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(typescript@4.9.5):
dependencies:
'@types/node': 20.5.1
cosmiconfig: 8.3.6(typescript@4.9.5)
- ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
+ ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5)
typescript: 4.9.5
cosmiconfig@6.0.0:
@@ -10176,7 +11755,7 @@ snapshots:
postcss: 8.4.49
postcss-selector-parser: 6.1.2
- css-loader@6.11.0(webpack@5.97.1):
+ css-loader@6.11.0(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
icss-utils: 5.1.0(postcss@8.4.49)
postcss: 8.4.49
@@ -10187,9 +11766,9 @@ snapshots:
postcss-value-parser: 4.2.0
semver: 7.6.3
optionalDependencies:
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
- css-minimizer-webpack-plugin@3.4.1(webpack@5.97.1):
+ css-minimizer-webpack-plugin@3.4.1(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
cssnano: 5.1.15(postcss@8.4.49)
jest-worker: 27.5.1
@@ -10197,7 +11776,7 @@ snapshots:
schema-utils: 4.2.0
serialize-javascript: 6.0.2
source-map: 0.6.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
css-prefers-color-scheme@6.0.3(postcss@8.4.49):
dependencies:
@@ -10339,6 +11918,8 @@ snapshots:
dayjs@1.11.13: {}
+ de-indent@1.0.2: {}
+
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -10557,6 +12138,8 @@ snapshots:
entities@2.2.0: {}
+ entities@4.5.0: {}
+
environment@1.1.0: {}
error-ex@1.3.2:
@@ -10674,6 +12257,31 @@ snapshots:
is-date-object: 1.0.5
is-symbol: 1.1.0
+ esbuild@0.18.20:
+ 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
+
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -10738,7 +12346,7 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)))(typescript@4.9.5):
+ eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)))(typescript@4.9.5):
dependencies:
'@babel/core': 7.26.0
'@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@8.57.1)
@@ -10750,7 +12358,7 @@ snapshots:
eslint: 8.57.1
eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)
- eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)))(typescript@4.9.5)
+ eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)))(typescript@4.9.5)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.2(eslint@8.57.1)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
@@ -10886,13 +12494,13 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)))(typescript@4.9.5):
+ eslint-plugin-jest@25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)))(typescript@4.9.5):
dependencies:
'@typescript-eslint/experimental-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
eslint: 8.57.1
optionalDependencies:
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
- jest: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
transitivePeerDependencies:
- supports-color
- typescript
@@ -10949,6 +12557,10 @@ snapshots:
dependencies:
eslint: 8.57.1
+ eslint-plugin-react-refresh@0.4.24(eslint@8.57.1):
+ dependencies:
+ eslint: 8.57.1
+
eslint-plugin-react@7.37.2(eslint@8.57.1):
dependencies:
array-includes: 3.1.8
@@ -10993,7 +12605,7 @@ snapshots:
eslint-visitor-keys@3.4.3: {}
- eslint-webpack-plugin@3.2.0(eslint@8.57.1)(webpack@5.97.1):
+ eslint-webpack-plugin@3.2.0(eslint@8.57.1)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@types/eslint': 8.56.12
eslint: 8.57.1
@@ -11001,7 +12613,7 @@ snapshots:
micromatch: 4.0.8
normalize-path: 3.0.0
schema-utils: 4.2.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
eslint@8.57.1:
dependencies:
@@ -11070,6 +12682,8 @@ snapshots:
estree-walker@1.0.1: {}
+ estree-walker@2.0.2: {}
+
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -11183,11 +12797,11 @@ snapshots:
dependencies:
flat-cache: 3.2.0
- file-loader@6.2.0(webpack@5.97.1):
+ file-loader@6.2.0(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
loader-utils: 2.0.4
schema-utils: 3.3.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
filelist@1.0.4:
dependencies:
@@ -11250,7 +12864,7 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- fork-ts-checker-webpack-plugin@6.5.3(eslint@8.57.1)(typescript@4.9.5)(webpack@5.97.1):
+ fork-ts-checker-webpack-plugin@6.5.3(eslint@8.57.1)(typescript@4.9.5)(vue-template-compiler@2.7.16)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@babel/code-frame': 7.26.2
'@types/json-schema': 7.0.15
@@ -11266,9 +12880,10 @@ snapshots:
semver: 7.6.3
tapable: 1.1.3
typescript: 4.9.5
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
optionalDependencies:
eslint: 8.57.1
+ vue-template-compiler: 2.7.16
form-data@3.0.2:
dependencies:
@@ -11304,6 +12919,12 @@ snapshots:
jsonfile: 6.1.0
universalify: 2.0.1
+ fs-extra@7.0.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+
fs-extra@9.1.0:
dependencies:
at-least-node: 1.0.0
@@ -11513,7 +13134,7 @@ snapshots:
dependencies:
void-elements: 3.1.0
- html-webpack-plugin@5.6.3(webpack@5.97.1):
+ html-webpack-plugin@5.6.3(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@types/html-minifier-terser': 6.1.0
html-minifier-terser: 6.1.0
@@ -11521,7 +13142,7 @@ snapshots:
pretty-error: 4.0.0
tapable: 2.2.1
optionalDependencies:
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
htmlparser2@6.1.0:
dependencies:
@@ -11625,6 +13246,8 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
+ import-lazy@4.0.0: {}
+
import-local@3.2.0:
dependencies:
pkg-dir: 4.2.0
@@ -11906,16 +13529,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- jest-cli@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)):
+ jest-cli@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)):
dependencies:
- '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
'@jest/test-result': 27.5.1
'@jest/types': 27.5.1
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
import-local: 3.2.0
- jest-config: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest-config: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
jest-util: 27.5.1
jest-validate: 27.5.1
prompts: 2.4.2
@@ -11927,7 +13550,7 @@ snapshots:
- ts-node
- utf-8-validate
- jest-config@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)):
+ jest-config@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)):
dependencies:
'@babel/core': 7.26.0
'@jest/test-sequencer': 27.5.1
@@ -11954,7 +13577,7 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
- ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
+ ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5)
transitivePeerDependencies:
- bufferutil
- canvas
@@ -12246,11 +13869,11 @@ snapshots:
leven: 3.1.0
pretty-format: 27.5.1
- jest-watch-typeahead@1.1.0(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))):
+ jest-watch-typeahead@1.1.0(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))):
dependencies:
ansi-escapes: 4.3.2
chalk: 4.1.2
- jest: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
jest-regex-util: 28.0.2
jest-watcher: 28.1.3
slash: 4.0.0
@@ -12296,11 +13919,11 @@ snapshots:
merge-stream: 2.0.0
supports-color: 8.1.1
- jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)):
+ jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)):
dependencies:
- '@jest/core': 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ '@jest/core': 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
import-local: 3.2.0
- jest-cli: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest-cli: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
transitivePeerDependencies:
- bufferutil
- canvas
@@ -12310,6 +13933,8 @@ snapshots:
jiti@1.21.6: {}
+ jju@1.4.0: {}
+
js-sha256@0.11.0: {}
js-tokens@4.0.0: {}
@@ -12377,6 +14002,10 @@ snapshots:
json5@2.2.3: {}
+ jsonfile@4.0.0:
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
@@ -12416,6 +14045,8 @@ snapshots:
klona@2.0.6: {}
+ kolorist@1.8.0: {}
+
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
@@ -12445,6 +14076,12 @@ snapshots:
lines-and-columns@1.2.4: {}
+ linkify-it@5.0.0:
+ dependencies:
+ uc.micro: 2.1.0
+
+ linkifyjs@4.3.2: {}
+
lint-staged@15.5.0:
dependencies:
chalk: 5.4.1
@@ -12498,6 +14135,10 @@ snapshots:
lodash.flow@3.5.0: {}
+ lodash.get@4.4.2: {}
+
+ lodash.isequal@4.5.0: {}
+
lodash.isfunction@3.0.9: {}
lodash.isplainobject@4.0.6: {}
@@ -12554,6 +14195,10 @@ snapshots:
dependencies:
sourcemap-codec: 1.4.8
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
make-dir@3.1.0:
dependencies:
semver: 6.3.1
@@ -12572,12 +14217,25 @@ snapshots:
map-obj@4.3.0: {}
+ markdown-it@14.1.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
+ marked@15.0.12: {}
+
marked@4.3.0: {}
mdn-data@2.0.14: {}
mdn-data@2.0.4: {}
+ mdurl@2.0.0: {}
+
media-typer@0.3.0: {}
memfs@3.5.3:
@@ -12629,14 +14287,18 @@ snapshots:
min-indent@1.0.1: {}
- mini-css-extract-plugin@2.9.2(webpack@5.97.1):
+ mini-css-extract-plugin@2.9.2(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
schema-utils: 4.2.0
tapable: 2.2.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
minimalistic-assert@1.0.1: {}
+ minimatch@3.0.8:
+ dependencies:
+ brace-expansion: 1.1.11
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -12671,6 +14333,8 @@ snapshots:
ms@2.1.3: {}
+ muggle-string@0.3.1: {}
+
multicast-dns@7.2.5:
dependencies:
dns-packet: 5.6.1
@@ -12858,6 +14522,8 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
+ orderedmap@2.1.1: {}
+
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
@@ -12912,6 +14578,8 @@ snapshots:
no-case: 3.0.4
tslib: 2.8.1
+ path-browserify@1.0.1: {}
+
path-exists@3.0.0: {}
path-exists@4.0.0: {}
@@ -12941,6 +14609,8 @@ snapshots:
picomatch@2.3.1: {}
+ picomatch@4.0.3: {}
+
pidtree@0.6.0: {}
pify@2.3.0: {}
@@ -13105,21 +14775,21 @@ snapshots:
postcss: 8.4.49
postcss-value-parser: 4.2.0
- postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)):
+ postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)):
dependencies:
lilconfig: 3.1.3
yaml: 2.7.0
optionalDependencies:
postcss: 8.4.49
- ts-node: 10.9.2(@types/node@20.5.1)(typescript@4.9.5)
+ ts-node: 10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5)
- postcss-loader@6.2.1(postcss@8.4.49)(webpack@5.97.1):
+ postcss-loader@6.2.1(postcss@8.4.49)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
cosmiconfig: 7.1.0
klona: 2.0.6
postcss: 8.4.49
semver: 7.6.3
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
postcss-logical@5.0.4(postcss@8.4.49):
dependencies:
@@ -13449,6 +15119,109 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ prosemirror-changeset@2.3.1:
+ dependencies:
+ prosemirror-transform: 1.10.5
+
+ prosemirror-collab@1.3.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-gapcursor@1.4.0:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.3
+
+ prosemirror-history@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.4
+ w3c-keyname: 2.2.8
+
+ prosemirror-markdown@1.13.2:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ prosemirror-model: 1.25.4
+
+ prosemirror-menu@1.2.5:
+ dependencies:
+ crelt: 1.0.6
+ prosemirror-commands: 1.7.1
+ prosemirror-history: 1.5.0
+ prosemirror-state: 1.4.4
+
+ prosemirror-model@1.25.4:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-basic@1.2.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-state@1.4.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-tables@1.8.1:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3):
+ dependencies:
+ '@remirror/core-constants': 3.0.0
+ escape-string-regexp: 4.0.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.3
+
+ prosemirror-transform@1.10.5:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-view@1.41.3:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -13460,12 +15233,14 @@ snapshots:
dependencies:
punycode: 2.3.1
+ punycode.js@2.3.1: {}
+
punycode@2.3.1: {}
- purgecss-webpack-plugin@4.1.3(webpack@5.97.1):
+ purgecss-webpack-plugin@4.1.3(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
purgecss: 4.1.3
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
webpack-sources: 3.2.3
purgecss@4.1.3:
@@ -13523,9 +15298,9 @@ snapshots:
regenerator-runtime: 0.13.11
whatwg-fetch: 3.6.20
- react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)):
+ react-app-rewired@2.2.1(react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@swc/core@1.15.3)(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)(vue-template-compiler@2.7.16)):
dependencies:
- react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)
+ react-scripts: 5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@swc/core@1.15.3)(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)(vue-template-compiler@2.7.16)
semver: 5.7.2
react-bootstrap@2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
@@ -13547,7 +15322,7 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.16
- react-dev-utils@12.0.1(eslint@8.57.1)(typescript@4.9.5)(webpack@5.97.1):
+ react-dev-utils@12.0.1(eslint@8.57.1)(typescript@4.9.5)(vue-template-compiler@2.7.16)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@babel/code-frame': 7.26.2
address: 1.2.2
@@ -13558,7 +15333,7 @@ snapshots:
escape-string-regexp: 4.0.0
filesize: 8.0.7
find-up: 5.0.0
- fork-ts-checker-webpack-plugin: 6.5.3(eslint@8.57.1)(typescript@4.9.5)(webpack@5.97.1)
+ fork-ts-checker-webpack-plugin: 6.5.3(eslint@8.57.1)(typescript@4.9.5)(vue-template-compiler@2.7.16)(webpack@5.97.1(@swc/core@1.15.3))
global-modules: 2.0.0
globby: 11.1.0
gzip-size: 6.0.0
@@ -13573,7 +15348,7 @@ snapshots:
shell-quote: 1.8.2
strip-ansi: 6.0.1
text-table: 0.2.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
optionalDependencies:
typescript: 4.9.5
transitivePeerDependencies:
@@ -13636,56 +15411,56 @@ snapshots:
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
- react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5):
+ react-scripts@5.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(@swc/core@1.15.3)(@types/babel__core@7.20.5)(eslint@8.57.1)(react@18.3.1)(sass@1.54.4)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))(type-fest@1.4.0)(typescript@4.9.5)(vue-template-compiler@2.7.16):
dependencies:
'@babel/core': 7.26.0
- '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.11.0)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.97.1))(webpack@5.97.1)
+ '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.11.0)(type-fest@1.4.0)(webpack-dev-server@4.15.2(webpack@5.97.1(@swc/core@1.15.3)))(webpack@5.97.1(@swc/core@1.15.3))
'@svgr/webpack': 5.5.0
babel-jest: 27.5.1(@babel/core@7.26.0)
- babel-loader: 8.4.1(@babel/core@7.26.0)(webpack@5.97.1)
+ babel-loader: 8.4.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.15.3))
babel-plugin-named-asset-import: 0.3.8(@babel/core@7.26.0)
babel-preset-react-app: 10.0.1
bfj: 7.1.0
browserslist: 4.24.2
camelcase: 6.3.0
case-sensitive-paths-webpack-plugin: 2.4.0
- css-loader: 6.11.0(webpack@5.97.1)
- css-minimizer-webpack-plugin: 3.4.1(webpack@5.97.1)
+ css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.15.3))
+ css-minimizer-webpack-plugin: 3.4.1(webpack@5.97.1(@swc/core@1.15.3))
dotenv: 10.0.0
dotenv-expand: 5.1.0
eslint: 8.57.1
- eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)))(typescript@4.9.5)
- eslint-webpack-plugin: 3.2.0(eslint@8.57.1)(webpack@5.97.1)
- file-loader: 6.2.0(webpack@5.97.1)
+ eslint-config-react-app: 7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0))(eslint@8.57.1)(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)))(typescript@4.9.5)
+ eslint-webpack-plugin: 3.2.0(eslint@8.57.1)(webpack@5.97.1(@swc/core@1.15.3))
+ file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.15.3))
fs-extra: 10.1.0
- html-webpack-plugin: 5.6.3(webpack@5.97.1)
+ html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.15.3))
identity-obj-proxy: 3.0.0
- jest: 27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ jest: 27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
jest-resolve: 27.5.1
- jest-watch-typeahead: 1.1.0(jest@27.5.1(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)))
- mini-css-extract-plugin: 2.9.2(webpack@5.97.1)
+ jest-watch-typeahead: 1.1.0(jest@27.5.1(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)))
+ mini-css-extract-plugin: 2.9.2(webpack@5.97.1(@swc/core@1.15.3))
postcss: 8.4.49
postcss-flexbugs-fixes: 5.0.2(postcss@8.4.49)
- postcss-loader: 6.2.1(postcss@8.4.49)(webpack@5.97.1)
+ postcss-loader: 6.2.1(postcss@8.4.49)(webpack@5.97.1(@swc/core@1.15.3))
postcss-normalize: 10.0.1(browserslist@4.24.2)(postcss@8.4.49)
postcss-preset-env: 7.8.3(postcss@8.4.49)
prompts: 2.4.2
react: 18.3.1
react-app-polyfill: 3.0.0
- react-dev-utils: 12.0.1(eslint@8.57.1)(typescript@4.9.5)(webpack@5.97.1)
+ react-dev-utils: 12.0.1(eslint@8.57.1)(typescript@4.9.5)(vue-template-compiler@2.7.16)(webpack@5.97.1(@swc/core@1.15.3))
react-refresh: 0.11.0
resolve: 1.22.8
resolve-url-loader: 4.0.0
- sass-loader: 12.6.0(sass@1.54.4)(webpack@5.97.1)
+ sass-loader: 12.6.0(sass@1.54.4)(webpack@5.97.1(@swc/core@1.15.3))
semver: 7.6.3
- source-map-loader: 3.0.2(webpack@5.97.1)
- style-loader: 3.3.4(webpack@5.97.1)
- tailwindcss: 3.4.16(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
- terser-webpack-plugin: 5.3.10(webpack@5.97.1)
- webpack: 5.97.1
- webpack-dev-server: 4.15.2(webpack@5.97.1)
- webpack-manifest-plugin: 4.1.1(webpack@5.97.1)
- workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.97.1)
+ source-map-loader: 3.0.2(webpack@5.97.1(@swc/core@1.15.3))
+ style-loader: 3.3.4(webpack@5.97.1(@swc/core@1.15.3))
+ tailwindcss: 3.4.16(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
+ terser-webpack-plugin: 5.3.10(@swc/core@1.15.3)(webpack@5.97.1(@swc/core@1.15.3))
+ webpack: 5.97.1(@swc/core@1.15.3)
+ webpack-dev-server: 4.15.2(webpack@5.97.1(@swc/core@1.15.3))
+ webpack-manifest-plugin: 4.1.1(webpack@5.97.1(@swc/core@1.15.3))
+ workbox-webpack-plugin: 6.6.0(@types/babel__core@7.20.5)(webpack@5.97.1(@swc/core@1.15.3))
optionalDependencies:
fsevents: 2.3.3
typescript: 4.9.5
@@ -13875,6 +15650,11 @@ snapshots:
resolve.exports@1.1.1: {}
+ resolve@1.19.0:
+ dependencies:
+ is-core-module: 2.15.1
+ path-parse: 1.0.7
+
resolve@1.22.8:
dependencies:
is-core-module: 2.15.1
@@ -13918,6 +15698,12 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ rollup@3.29.5:
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ rope-sequence@1.3.4: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -13943,11 +15729,11 @@ snapshots:
sanitize.css@13.0.0: {}
- sass-loader@12.6.0(sass@1.54.4)(webpack@5.97.1):
+ sass-loader@12.6.0(sass@1.54.4)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
klona: 2.0.6
neo-async: 2.6.2
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
optionalDependencies:
sass: 1.54.4
@@ -14146,12 +15932,12 @@ snapshots:
source-map-js@1.2.1: {}
- source-map-loader@3.0.2(webpack@5.97.1):
+ source-map-loader@3.0.2(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
abab: 2.0.6
iconv-lite: 0.6.3
source-map-js: 1.2.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
source-map-resolve@0.5.3:
dependencies:
@@ -14354,9 +16140,9 @@ snapshots:
strip-json-comments@3.1.1: {}
- style-loader@3.3.4(webpack@5.97.1):
+ style-loader@3.3.4(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
style-mod@4.1.2: {}
@@ -14434,7 +16220,7 @@ snapshots:
'@pkgr/core': 0.1.1
tslib: 2.8.1
- tailwindcss@3.4.16(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5)):
+ tailwindcss@3.4.16(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -14453,7 +16239,7 @@ snapshots:
postcss: 8.4.49
postcss-import: 15.1.0(postcss@8.4.49)
postcss-js: 4.0.1(postcss@8.4.49)
- postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@16.18.121)(typescript@4.9.5))
+ postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@swc/core@1.15.3)(@types/node@20.5.1)(typescript@4.9.5))
postcss-nested: 6.2.0(postcss@8.4.49)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -14484,14 +16270,16 @@ snapshots:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0
- terser-webpack-plugin@5.3.10(webpack@5.97.1):
+ terser-webpack-plugin@5.3.10(@swc/core@1.15.3)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.37.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
+ optionalDependencies:
+ '@swc/core': 1.15.3
terser@5.37.0:
dependencies:
@@ -14538,6 +16326,8 @@ snapshots:
toidentifier@1.0.1: {}
+ tosource@2.0.0-alpha.3: {}
+
tough-cookie@4.1.4:
dependencies:
psl: 1.15.0
@@ -14561,16 +16351,20 @@ snapshots:
dependencies:
typescript: 4.9.5
+ ts-api-utils@1.4.3(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
ts-interface-checker@0.1.13: {}
- ts-node@10.9.2(@types/node@20.5.1)(typescript@4.9.5):
+ ts-node@10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
- '@types/node': 20.5.1
+ '@types/node': 16.18.121
acorn: 8.14.0
acorn-walk: 8.3.4
arg: 4.1.3
@@ -14580,6 +16374,8 @@ snapshots:
typescript: 4.9.5
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
+ optionalDependencies:
+ '@swc/core': 1.15.3
tsconfig-paths@3.15.0:
dependencies:
@@ -14668,6 +16464,12 @@ snapshots:
typescript@4.9.5: {}
+ typescript@5.4.2: {}
+
+ typescript@5.9.3: {}
+
+ uc.micro@2.1.0: {}
+
unbox-primitive@1.0.2:
dependencies:
call-bind: 1.0.8
@@ -14704,6 +16506,8 @@ snapshots:
dependencies:
crypto-random-string: 2.0.0
+ universalify@0.1.2: {}
+
universalify@0.2.0: {}
universalify@2.0.1: {}
@@ -14731,10 +16535,9 @@ snapshots:
querystringify: 2.2.0
requires-port: 1.0.0
- use-sync-external-store@1.2.2(react@18.3.1):
+ use-sync-external-store@1.6.0(react@18.3.1):
dependencies:
react: 18.3.1
- optional: true
util-deprecate@1.0.2: {}
@@ -14764,10 +16567,52 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
+ validator@13.15.23: {}
+
vary@1.1.2: {}
+ vite-plugin-dts@3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)):
+ dependencies:
+ '@microsoft/api-extractor': 7.43.0(@types/node@20.5.1)
+ '@rollup/pluginutils': 5.3.0(rollup@3.29.5)
+ '@vue/language-core': 1.8.27(typescript@5.9.3)
+ debug: 4.4.0
+ kolorist: 1.8.0
+ magic-string: 0.30.21
+ typescript: 5.9.3
+ vue-tsc: 1.8.27(typescript@5.9.3)
+ optionalDependencies:
+ vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
+ transitivePeerDependencies:
+ - '@types/node'
+ - rollup
+ - supports-color
+
+ vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0):
+ dependencies:
+ esbuild: 0.18.20
+ postcss: 8.4.49
+ rollup: 3.29.5
+ optionalDependencies:
+ '@types/node': 20.5.1
+ fsevents: 2.3.3
+ sass: 1.54.4
+ terser: 5.37.0
+
void-elements@3.1.0: {}
+ vue-template-compiler@2.7.16:
+ dependencies:
+ de-indent: 1.0.2
+ he: 1.2.0
+
+ vue-tsc@1.8.27(typescript@5.9.3):
+ dependencies:
+ '@volar/typescript': 1.11.1
+ '@vue/language-core': 1.8.27(typescript@5.9.3)
+ semver: 7.6.3
+ typescript: 5.9.3
+
w3c-hr-time@1.0.2:
dependencies:
browser-process-hrtime: 1.0.0
@@ -14801,16 +16646,16 @@ snapshots:
webidl-conversions@6.1.0: {}
- webpack-dev-middleware@5.3.4(webpack@5.97.1):
+ webpack-dev-middleware@5.3.4(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
colorette: 2.0.20
memfs: 3.5.3
mime-types: 2.1.35
range-parser: 1.2.1
schema-utils: 4.2.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
- webpack-dev-server@4.15.2(webpack@5.97.1):
+ webpack-dev-server@4.15.2(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
'@types/bonjour': 3.5.13
'@types/connect-history-api-fallback': 1.5.4
@@ -14840,20 +16685,20 @@ snapshots:
serve-index: 1.9.1
sockjs: 0.3.24
spdy: 4.0.2
- webpack-dev-middleware: 5.3.4(webpack@5.97.1)
+ webpack-dev-middleware: 5.3.4(webpack@5.97.1(@swc/core@1.15.3))
ws: 8.18.0
optionalDependencies:
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
transitivePeerDependencies:
- bufferutil
- debug
- supports-color
- utf-8-validate
- webpack-manifest-plugin@4.1.1(webpack@5.97.1):
+ webpack-manifest-plugin@4.1.1(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
tapable: 2.2.1
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
webpack-sources: 2.3.1
webpack-sources@1.4.3:
@@ -14868,7 +16713,7 @@ snapshots:
webpack-sources@3.2.3: {}
- webpack@5.97.1:
+ webpack@5.97.1(@swc/core@1.15.3):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.6
@@ -14890,7 +16735,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(webpack@5.97.1)
+ terser-webpack-plugin: 5.3.10(@swc/core@1.15.3)(webpack@5.97.1(@swc/core@1.15.3))
watchpack: 2.4.2
webpack-sources: 3.2.3
transitivePeerDependencies:
@@ -15085,12 +16930,12 @@ snapshots:
workbox-sw@6.6.0: {}
- workbox-webpack-plugin@6.6.0(@types/babel__core@7.20.5)(webpack@5.97.1):
+ workbox-webpack-plugin@6.6.0(@types/babel__core@7.20.5)(webpack@5.97.1(@swc/core@1.15.3)):
dependencies:
fast-json-stable-stringify: 2.1.0
pretty-bytes: 5.6.0
upath: 1.2.0
- webpack: 5.97.1
+ webpack: 5.97.1(@swc/core@1.15.3)
webpack-sources: 1.4.3
workbox-build: 6.6.0(@types/babel__core@7.20.5)
transitivePeerDependencies:
@@ -15210,9 +17055,17 @@ snapshots:
yocto-queue@0.1.0: {}
- zustand@5.0.2(@types/react@18.3.16)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.2.2(react@18.3.1)):
+ z-schema@5.0.5:
+ dependencies:
+ lodash.get: 4.4.2
+ lodash.isequal: 4.5.0
+ validator: 13.15.23
+ optionalDependencies:
+ commander: 9.5.0
+
+ zustand@5.0.2(@types/react@18.3.16)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
optionalDependencies:
'@types/react': 18.3.16
immer: 9.0.21
react: 18.3.1
- use-sync-external-store: 1.2.2(react@18.3.1)
+ use-sync-external-store: 1.6.0(react@18.3.1)
diff --git a/ui/src/components/Editor/EditorContext.ts b/ui/src/components/Editor/EditorContext.ts
index 9cfaffe2d..c5c584579 100644
--- a/ui/src/components/Editor/EditorContext.ts
+++ b/ui/src/components/Editor/EditorContext.ts
@@ -19,6 +19,6 @@
import React from 'react';
-import { IEditorContext } from './types';
+import { Editor } from './types';
-export const EditorContext = React.createContext({});
+export const EditorContext = React.createContext(null);
diff --git a/ui/src/components/Editor/MarkdownEditor.tsx b/ui/src/components/Editor/MarkdownEditor.tsx
new file mode 100644
index 000000000..10145a228
--- /dev/null
+++ b/ui/src/components/Editor/MarkdownEditor.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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 { useEffect, useRef } from 'react';
+
+import { EditorView } from '@codemirror/view';
+
+import { Editor } from './types';
+import { useEditor } from './utils';
+
+interface MarkdownEditorProps {
+ value: string;
+ onChange?: (value: string) => void;
+ onFocus?: () => void;
+ onBlur?: () => void;
+ placeholder?: string;
+ autoFocus?: boolean;
+ onEditorReady?: (editor: Editor) => void;
+}
+
+const MarkdownEditor: React.FC = ({
+ value,
+ onChange,
+ onFocus,
+ onBlur,
+ placeholder,
+ autoFocus,
+ onEditorReady,
+}) => {
+ const editorRef = useRef(null);
+ const lastSyncedValueRef = useRef(value);
+
+ const editor = useEditor({
+ editorRef,
+ onChange,
+ onFocus,
+ onBlur,
+ placeholder,
+ autoFocus,
+ });
+
+ // 初始化内容(只在编辑器创建时执行)
+ useEffect(() => {
+ if (!editor) {
+ return;
+ }
+
+ // 初始化编辑器内容
+ editor.setValue(value || '');
+ lastSyncedValueRef.current = value || '';
+ onEditorReady?.(editor);
+ }, [editor]); // 只在编辑器创建时执行
+
+ // 当外部 value 变化时更新(但不是用户输入导致的)
+ useEffect(() => {
+ if (!editor) {
+ return;
+ }
+
+ // 如果 value 和 lastSyncedValueRef 相同,说明是用户输入导致的更新,跳过
+ if (value === lastSyncedValueRef.current) {
+ return;
+ }
+
+ // 外部 value 真正变化,更新编辑器
+ const currentValue = editor.getValue();
+ if (currentValue !== value) {
+ editor.setValue(value || '');
+ lastSyncedValueRef.current = value || '';
+ }
+ }, [editor, value]);
+
+ // 清理:组件卸载时销毁编辑器
+ useEffect(() => {
+ return () => {
+ if (editor) {
+ // CodeMirror EditorView 有 destroy 方法
+ const view = editor as unknown as EditorView;
+ if (view.destroy) {
+ view.destroy();
+ }
+ }
+ };
+ }, [editor]);
+
+ return (
+
+ );
+};
+
+export default MarkdownEditor;
diff --git a/ui/src/components/Editor/ToolBars/blockquote.tsx b/ui/src/components/Editor/ToolBars/blockquote.tsx
index fac2fc5a7..27147ed29 100644
--- a/ui/src/components/Editor/ToolBars/blockquote.tsx
+++ b/ui/src/components/Editor/ToolBars/blockquote.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const BlockQuote = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
@@ -33,21 +32,9 @@ const BlockQuote = () => {
tip: `${t('blockquote.text')} (Ctrl+Q)`,
};
- const handleClick = (ctx) => {
- context = ctx;
- context.replaceLines((line) => {
- const FIND_BLOCKQUOTE_RX = /^>\s+?/g;
-
- if (line === `> ${t('blockquote.text')}`) {
- line = '';
- } else if (line.match(FIND_BLOCKQUOTE_RX)) {
- line = line.replace(FIND_BLOCKQUOTE_RX, '');
- } else {
- line = `> ${line || t('blockquote.text')}`;
- }
- return line;
- }, 2);
- context.editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.insertBlockquote(t('blockquote.text'));
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/bold.tsx b/ui/src/components/Editor/ToolBars/bold.tsx
index 8efe69c5b..2a9a292ef 100644
--- a/ui/src/components/Editor/ToolBars/bold.tsx
+++ b/ui/src/components/Editor/ToolBars/bold.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Bold = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -33,10 +32,9 @@ const Bold = () => {
};
const DEFAULTTEXT = t('bold.text');
- const handleClick = (ctx) => {
- context = ctx;
- context.wrapText('**', '**', DEFAULTTEXT);
- context.editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.insertBold(DEFAULTTEXT);
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/code.tsx b/ui/src/components/Editor/ToolBars/code.tsx
index afbd7dde9..599ffaea5 100644
--- a/ui/src/components/Editor/ToolBars/code.tsx
+++ b/ui/src/components/Editor/ToolBars/code.tsx
@@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next';
import Select from '../Select';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
const codeLanguageType = [
'bash',
@@ -150,7 +150,6 @@ const codeLanguageType = [
'yml',
];
-let context: IEditorContext;
const Code = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
@@ -170,22 +169,20 @@ const Code = () => {
const inputRef = useRef(null);
const SINGLELINEMAXLENGTH = 40;
- const addCode = (ctx) => {
- context = ctx;
+ const [currentEditor, setCurrentEditor] = useState(null);
- const { wrapText, editor } = context;
-
- const text = context.editor.getSelection();
+ const addCode = (editor: Editor) => {
+ setCurrentEditor(editor);
+ const text = editor.getSelection();
if (!text) {
setVisible(true);
-
return;
}
if (text.length > SINGLELINEMAXLENGTH) {
- context.wrapText('```\n', '\n```');
+ editor.insertCodeBlock('', text);
} else {
- wrapText('`', '`');
+ editor.insertCode(text);
}
editor.focus();
};
@@ -197,6 +194,10 @@ const Code = () => {
}, [visible]);
const handleClick = () => {
+ if (!currentEditor) {
+ return;
+ }
+
if (!code.value.trim()) {
setCode({
...code,
@@ -206,17 +207,15 @@ const Code = () => {
return;
}
- let value;
-
if (
code.value.split('\n').length > 1 ||
code.value.length >= SINGLELINEMAXLENGTH
) {
- value = `\n\`\`\`${lang}\n${code.value}\n\`\`\`\n`;
+ currentEditor.insertCodeBlock(lang || undefined, code.value);
} else {
- value = `\`${code.value}\``;
+ currentEditor.insertCode(code.value);
}
- context.editor.replaceSelection(value);
+
setCode({
value: '',
isInvalid: false,
@@ -224,9 +223,10 @@ const Code = () => {
});
setLang('');
setVisible(false);
+ currentEditor.focus();
};
const onHide = () => setVisible(false);
- const onExited = () => context.editor?.focus();
+ const onExited = () => currentEditor?.focus();
return (
diff --git a/ui/src/components/Editor/ToolBars/file.tsx b/ui/src/components/Editor/ToolBars/file.tsx
index 9e6e7399c..d48c0e6ac 100644
--- a/ui/src/components/Editor/ToolBars/file.tsx
+++ b/ui/src/components/Editor/ToolBars/file.tsx
@@ -17,31 +17,28 @@
* under the License.
*/
-import { useState, memo, useRef } from 'react';
+import { memo, useRef, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal as AnswerModal } from '@/components';
import ToolItem from '../toolItem';
-import { IEditorContext, Editor } from '../types';
+import { EditorContext } from '../EditorContext';
import { uploadImage } from '@/services';
import { writeSettingStore } from '@/stores';
-let context: IEditorContext;
-const Image = ({ editorInstance }) => {
+const File = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const { max_attachment_size = 8, authorized_attachment_extensions = [] } =
writeSettingStore((state) => state.write);
const fileInputRef = useRef(null);
- const [editor, setEditor] = useState(editorInstance);
+ const editor = useContext(EditorContext);
const item = {
label: 'paperclip',
tip: `${t('file.text')}`,
};
- const addLink = (ctx) => {
- context = ctx;
- setEditor(context.editor);
+ const addLink = () => {
fileInputRef.current?.click?.();
};
@@ -132,4 +129,4 @@ const Image = ({ editorInstance }) => {
);
};
-export default memo(Image);
+export default memo(File);
diff --git a/ui/src/components/Editor/ToolBars/heading.tsx b/ui/src/components/Editor/ToolBars/heading.tsx
index 8b0cb6043..4b49212cb 100644
--- a/ui/src/components/Editor/ToolBars/heading.tsx
+++ b/ui/src/components/Editor/ToolBars/heading.tsx
@@ -22,9 +22,8 @@ import { Dropdown } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor, Level } from '../types';
-let context: IEditorContext;
const Heading = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const headerList = [
@@ -61,19 +60,18 @@ const Heading = () => {
};
const [isShow, setShowState] = useState(false);
const [isLocked, setLockState] = useState(false);
+ const [currentEditor, setCurrentEditor] = useState(null);
- const handleClick = (level = 2, label = '大标题') => {
- const { replaceLines } = context;
-
- replaceLines((line) => {
- line = line.trim().replace(/^#*/, '').trim();
- line = `${'#'.repeat(level)} ${line || label}`;
- return line;
- }, level + 1);
+ const handleClick = (level: Level = 2, label = '大标题') => {
+ if (!currentEditor) {
+ return;
+ }
+ currentEditor.insertHeading(level, label);
+ currentEditor.focus();
setShowState(false);
};
- const onAddHeader = (ctx) => {
- context = ctx;
+ const onAddHeader = (editor: Editor) => {
+ setCurrentEditor(editor);
if (isLocked) {
return;
}
@@ -104,7 +102,7 @@ const Heading = () => {
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
- handleClick(header.level, header.label);
+ handleClick(header.level as Level, header.label);
}}
dangerouslySetInnerHTML={{ __html: header.text }}
/>
diff --git a/ui/src/components/Editor/ToolBars/hr.tsx b/ui/src/components/Editor/ToolBars/hr.tsx
index ac988a877..6919eb3bd 100644
--- a/ui/src/components/Editor/ToolBars/hr.tsx
+++ b/ui/src/components/Editor/ToolBars/hr.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Hr = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -31,11 +30,9 @@ const Hr = () => {
keyMap: ['Ctrl-r'],
tip: `${t('hr.text')} (Ctrl+r)`,
};
- const handleClick = (ctx) => {
- context = ctx;
- const { appendBlock, editor } = context;
- appendBlock('----------\n');
- editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.insertHorizontalRule();
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx
index c9950b069..67a79b3ca 100644
--- a/ui/src/components/Editor/ToolBars/image.tsx
+++ b/ui/src/components/Editor/ToolBars/image.tsx
@@ -17,19 +17,28 @@
* under the License.
*/
-import { useEffect, useState, memo } from 'react';
+import { useEffect, useState, memo, useContext } from 'react';
import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Modal as AnswerModal } from '@/components';
import ToolItem from '../toolItem';
-import { IEditorContext, Editor } from '../types';
+import { EditorContext } from '../EditorContext';
+import { Editor } from '../types';
import { uploadImage } from '@/services';
import { writeSettingStore } from '@/stores';
-let context: IEditorContext;
-const Image = ({ editorInstance }) => {
- const [editor, setEditor] = useState(editorInstance);
+const Image = () => {
+ const editor = useContext(EditorContext);
+ const [editorState, setEditorState] = useState(editor);
+
+ // 当 editor 改变时,更新 editor state
+ // 这样切换编辑器模式时,事件监听器会重新绑定
+ useEffect(() => {
+ if (editor) {
+ setEditorState(editor);
+ }
+ }, [editor]);
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const {
max_image_size = 4,
@@ -160,19 +169,22 @@ const Image = ({ editorInstance }) => {
return;
}
- const startPos = editor.getCursor();
+ if (!editorState) {
+ return;
+ }
+ const startPos = editorState.getCursor();
const endPos = { ...startPos, ch: startPos.ch + loadingText.length };
- editor.replaceSelection(loadingText);
- editor.setReadOnly(true);
+ editorState.replaceSelection(loadingText);
+ editorState.setReadOnly(true);
const urls = await upload(fileList)
.catch(() => {
- editor.replaceRange('', startPos, endPos);
+ editorState.replaceRange('', startPos, endPos);
})
.finally(() => {
- editor.setReadOnly(false);
- editor.focus();
+ editorState?.setReadOnly(false);
+ editorState?.focus();
});
const text: string[] = [];
@@ -184,9 +196,9 @@ const Image = ({ editorInstance }) => {
});
}
if (text.length) {
- editor.replaceRange(text.join('\n'), startPos, endPos);
+ editorState.replaceRange(text.join('\n'), startPos, endPos);
} else {
- editor.replaceRange('', startPos, endPos);
+ editorState?.replaceRange('', startPos, endPos);
}
};
@@ -197,25 +209,28 @@ const Image = ({ editorInstance }) => {
if (bool) {
event.preventDefault();
- const startPos = editor.getCursor();
+ if (!editorState) {
+ return;
+ }
+ const startPos = editorState.getCursor();
const endPos = { ...startPos, ch: startPos.ch + loadingText.length };
- editor.replaceSelection(loadingText);
- editor.setReadOnly(true);
+ editorState?.replaceSelection(loadingText);
+ editorState?.setReadOnly(true);
upload(clipboard.files)
.then((urls) => {
const text = urls.map(({ name, url, type }) => {
return `${type === 'post' ? '!' : ''}[${name}](${url})`;
});
- editor.replaceRange(text.join('\n'), startPos, endPos);
+ editorState.replaceRange(text.join('\n'), startPos, endPos);
})
.catch(() => {
- editor.replaceRange('', startPos, endPos);
+ editorState.replaceRange('', startPos, endPos);
})
.finally(() => {
- editor.setReadOnly(false);
- editor.focus();
+ editorState?.setReadOnly(false);
+ editorState?.focus();
});
return;
@@ -289,7 +304,9 @@ const Image = ({ editorInstance }) => {
return match.length > 1 ? '\n\n' : match;
});
- editor.replaceSelection(markdownText);
+ if (editorState) {
+ editorState.replaceSelection(markdownText);
+ }
};
const handleClick = () => {
if (!link.value) {
@@ -298,28 +315,35 @@ const Image = ({ editorInstance }) => {
}
setLink({ ...link, type: '' });
- const text = ``;
-
- editor.replaceSelection(text);
+ if (editorState) {
+ editorState.insertImage(link.value, imageName.value || undefined);
+ }
setVisible(false);
- editor.focus();
+ editorState?.focus();
setLink({ ...link, value: '' });
setImageName({ ...imageName, value: '' });
};
useEffect(() => {
- editor?.on('dragenter', dragenter);
- editor?.on('dragover', dragover);
- editor?.on('drop', drop);
- editor?.on('paste', paste);
+ if (!editorState) {
+ return undefined;
+ }
+ // 绑定事件监听器
+ editorState.on('dragenter', dragenter);
+ editorState.on('dragover', dragover);
+ editorState.on('drop', drop);
+ editorState.on('paste', paste);
return () => {
- editor?.off('dragenter', dragenter);
- editor?.off('dragover', dragover);
- editor?.off('drop', drop);
- editor?.off('paste', paste);
+ // 清理事件监听器
+ editorState.off('dragenter', dragenter);
+ editorState.off('dragover', dragover);
+ editorState.off('drop', drop);
+ editorState.off('paste', paste);
};
- }, [editor]);
+ // 注意:dragenter, dragover, drop, paste 函数在组件生命周期内是稳定的
+ // 它们不依赖任何会变化的值,所以不需要加入依赖项
+ }, [editorState]);
useEffect(() => {
if (link.value && link.type === 'drop') {
@@ -327,10 +351,9 @@ const Image = ({ editorInstance }) => {
}
}, [link.value]);
- const addLink = (ctx) => {
- context = ctx;
- setEditor(context.editor);
- const text = context.editor?.getSelection();
+ const addLink = (editorInstance: Editor) => {
+ setEditorState(editorInstance);
+ const text = editorInstance?.getSelection();
setImageName({ ...imageName, value: text });
diff --git a/ui/src/components/Editor/ToolBars/indent.tsx b/ui/src/components/Editor/ToolBars/indent.tsx
index 6ec01dcea..7099fbcb1 100644
--- a/ui/src/components/Editor/ToolBars/indent.tsx
+++ b/ui/src/components/Editor/ToolBars/indent.tsx
@@ -21,24 +21,17 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Indent = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'text-indent-left',
tip: t('indent.text'),
};
- const handleClick = (ctx) => {
- context = ctx;
- const { editor, replaceLines } = context;
-
- replaceLines((line) => {
- line = ` ${line}`;
- return line;
- });
- editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.indent();
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/italic.tsx b/ui/src/components/Editor/ToolBars/italic.tsx
index cb585893f..4b359d2aa 100644
--- a/ui/src/components/Editor/ToolBars/italic.tsx
+++ b/ui/src/components/Editor/ToolBars/italic.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Italic = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -33,11 +32,9 @@ const Italic = () => {
};
const DEFAULTTEXT = t('italic.text');
- const handleClick = (ctx) => {
- context = ctx;
- const { editor, wrapText } = context;
- wrapText('*', '*', DEFAULTTEXT);
- editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.insertItalic(DEFAULTTEXT);
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/link.tsx b/ui/src/components/Editor/ToolBars/link.tsx
index e761ef3d1..be84c1b37 100644
--- a/ui/src/components/Editor/ToolBars/link.tsx
+++ b/ui/src/components/Editor/ToolBars/link.tsx
@@ -22,9 +22,8 @@ import { Button, Form, Modal } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Link = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -33,6 +32,7 @@ const Link = () => {
tip: `${t('link.text')} (Ctrl+l)`,
};
const [visible, setVisible] = useState(false);
+ const [currentEditor, setCurrentEditor] = useState(null);
const [link, setLink] = useState({
value: 'https://',
isInvalid: false,
@@ -52,39 +52,32 @@ const Link = () => {
}
}, [visible]);
- const addLink = (ctx) => {
- context = ctx;
- const { editor } = context;
-
+ const addLink = (editor: Editor) => {
+ setCurrentEditor(editor);
const text = editor.getSelection();
-
setName({ ...name, value: text });
-
setVisible(true);
};
const handleClick = () => {
- const { editor } = context;
+ if (!currentEditor) {
+ return;
+ }
if (!link.value) {
setLink({ ...link, isInvalid: true });
return;
}
- const newStr = name.value
- ? `[${name.value}](${link.value})`
- : `<${link.value}>`;
- editor.replaceSelection(newStr);
+ currentEditor.insertLink(link.value, name.value || undefined);
setVisible(false);
-
- editor.focus();
+ currentEditor.focus();
setLink({ ...link, value: '' });
setName({ ...name, value: '' });
};
const onHide = () => setVisible(false);
const onExited = () => {
- const { editor } = context;
- editor.focus();
+ currentEditor?.focus();
};
return (
diff --git a/ui/src/components/Editor/ToolBars/ol.tsx b/ui/src/components/Editor/ToolBars/ol.tsx
index 011c35a3c..a42523487 100644
--- a/ui/src/components/Editor/ToolBars/ol.tsx
+++ b/ui/src/components/Editor/ToolBars/ol.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const OL = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -32,20 +31,8 @@ const OL = () => {
tip: `${t('ordered_list.text')} (Ctrl+o)`,
};
- const handleClick = (ctx) => {
- context = ctx;
- const { editor, replaceLines } = context;
-
- replaceLines((line, i) => {
- const FIND_OL_RX = /^(\s{0,})(\d+)\.\s/;
-
- if (line.match(FIND_OL_RX)) {
- line = line.replace(FIND_OL_RX, '');
- } else {
- line = `${i + 1}. ${line}`;
- }
- return line;
- });
+ const handleClick = (editor: Editor) => {
+ editor.insertOrderedList();
editor.focus();
};
diff --git a/ui/src/components/Editor/ToolBars/outdent.tsx b/ui/src/components/Editor/ToolBars/outdent.tsx
index b80670371..484e434cf 100644
--- a/ui/src/components/Editor/ToolBars/outdent.tsx
+++ b/ui/src/components/Editor/ToolBars/outdent.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Outdent = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -31,16 +30,9 @@ const Outdent = () => {
keyMap: ['Shift-Tab'],
tip: t('outdent.text'),
};
- const handleClick = (ctx) => {
- context = ctx;
- const { editor, replaceLines } = context;
- replaceLines((line) => {
- line = line.replace(/^(\s{0,})/, (_1, $1) => {
- return $1.length > 4 ? $1.substring(4) : '';
- });
- return line;
- });
- editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.outdent();
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/table.tsx b/ui/src/components/Editor/ToolBars/table.tsx
index 30ca101d1..6381f4ccd 100644
--- a/ui/src/components/Editor/ToolBars/table.tsx
+++ b/ui/src/components/Editor/ToolBars/table.tsx
@@ -21,77 +21,18 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const Table = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
label: 'table',
tip: t('table.text'),
};
- const tableData = [
- [`${t('table.heading')} A`],
- [`${t('table.heading')} B`],
- [`${t('table.cell')} 1`],
- [`${t('table.cell')} 2`],
- [`${t('table.cell')} 3`],
- [`${t('table.cell')} 4`],
- ];
- const makeHeader = (col, data) => {
- let header = '|';
- let border = '|';
- let index = 0;
-
- while (col) {
- if (data) {
- header += ` ${data[index]} |`;
- index += 1;
- } else {
- header += ' |';
- }
-
- border += ' ----- |';
-
- col -= 1;
- }
-
- return `${header}\n${border}\n`;
- };
-
- const makeBody = (col, row, data) => {
- let body = '';
- let index = col;
-
- for (let irow = 0; irow < row; irow += 1) {
- body += '|';
-
- for (let icol = 0; icol < col; icol += 1) {
- if (data) {
- body += ` ${data[index]} |`;
- index += 1;
- } else {
- body += ' |';
- }
- }
-
- body += '\n';
- }
-
- body = body.replace(/\n$/g, '');
-
- return body;
- };
- const handleClick = (ctx) => {
- context = ctx;
- const { editor } = context;
- let table = '\n';
-
- table += makeHeader(2, tableData);
- table += makeBody(2, 2, tableData);
- editor?.replaceSelection(table);
- editor?.focus();
+ const handleClick = (editor: Editor) => {
+ editor.insertTable(3, 3);
+ editor.focus();
};
return ;
diff --git a/ui/src/components/Editor/ToolBars/ul.tsx b/ui/src/components/Editor/ToolBars/ul.tsx
index b906a346b..9651f5a42 100644
--- a/ui/src/components/Editor/ToolBars/ul.tsx
+++ b/ui/src/components/Editor/ToolBars/ul.tsx
@@ -21,9 +21,8 @@ import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
+import { Editor } from '../types';
-let context: IEditorContext;
const UL = () => {
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
const item = {
@@ -32,20 +31,8 @@ const UL = () => {
tip: `${t('unordered_list.text')} (Ctrl+u)`,
};
- const handleClick = (ctx) => {
- context = ctx;
- const { editor, replaceLines } = context;
-
- replaceLines((line) => {
- const FIND_UL_RX = /^(\s{0,})(-|\*)\s/;
-
- if (line.match(FIND_UL_RX)) {
- line = line.replace(FIND_UL_RX, '');
- } else {
- line = `* ${line}`;
- }
- return line;
- });
+ const handleClick = (editor: Editor) => {
+ editor.insertUnorderedList();
editor.focus();
};
diff --git a/ui/src/components/Editor/WysiwygEditor.tsx b/ui/src/components/Editor/WysiwygEditor.tsx
new file mode 100644
index 000000000..7ddb28368
--- /dev/null
+++ b/ui/src/components/Editor/WysiwygEditor.tsx
@@ -0,0 +1,162 @@
+/*
+ * 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 { useEffect, useRef, useCallback } from 'react';
+
+import {
+ useEditor,
+ EditorContent,
+ Editor as TipTapEditor,
+} from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import Placeholder from '@tiptap/extension-placeholder';
+import { Markdown } from '@tiptap/markdown';
+import Image from '@tiptap/extension-image';
+import { TableKit } from '@tiptap/extension-table';
+
+import { Editor } from './types';
+import { createTipTapAdapter } from './utils/tiptap/adapter';
+
+interface WysiwygEditorProps {
+ value: string;
+ onChange?: (value: string) => void;
+ onFocus?: () => void;
+ onBlur?: () => void;
+ placeholder?: string;
+ autoFocus?: boolean;
+ onEditorReady?: (editor: Editor) => void;
+}
+
+const WysiwygEditor: React.FC = ({
+ value,
+ onChange,
+ onFocus,
+ onBlur,
+ placeholder = '',
+ autoFocus = false,
+ onEditorReady,
+}) => {
+ const lastSyncedValueRef = useRef(value);
+
+ const adaptedEditorRef = useRef(null);
+
+ const handleUpdate = useCallback(
+ ({ editor: editorInstance }: { editor: TipTapEditor }) => {
+ if (onChange) {
+ const markdown = editorInstance.getMarkdown();
+ onChange(markdown);
+ }
+ },
+ [onChange],
+ );
+
+ const handleFocus = useCallback(() => {
+ onFocus?.();
+ }, [onFocus]);
+
+ const handleBlur = useCallback(() => {
+ onBlur?.();
+ }, [onBlur]);
+
+ const editor = useEditor({
+ extensions: [
+ StarterKit,
+ Markdown,
+ Image,
+ TableKit,
+ Placeholder.configure({
+ placeholder,
+ }),
+ ],
+ content: value || '',
+ onUpdate: handleUpdate,
+ onFocus: handleFocus,
+ onBlur: handleBlur,
+ editorProps: {
+ attributes: {
+ class: 'tiptap-editor',
+ },
+ },
+ });
+
+ useEffect(() => {
+ if (!editor) {
+ return;
+ }
+
+ const checkEditorReady = () => {
+ if (editor.view && editor.view.dom) {
+ if (value && value.trim() !== '') {
+ editor.commands.setContent(value, { contentType: 'markdown' });
+ } else {
+ editor.commands.clearContent();
+ }
+ lastSyncedValueRef.current = value || '';
+ if (!adaptedEditorRef.current) {
+ adaptedEditorRef.current = createTipTapAdapter(editor);
+ }
+ onEditorReady?.(adaptedEditorRef.current);
+ } else {
+ setTimeout(checkEditorReady, 10);
+ }
+ };
+
+ checkEditorReady();
+ }, [editor]);
+
+ useEffect(() => {
+ if (!editor) {
+ return;
+ }
+
+ if (value === lastSyncedValueRef.current) {
+ return;
+ }
+
+ const currentMarkdown = editor.getMarkdown();
+ if (currentMarkdown !== value) {
+ if (value && value.trim() !== '') {
+ editor.commands.setContent(value, { contentType: 'markdown' });
+ } else {
+ editor.commands.clearContent();
+ }
+ lastSyncedValueRef.current = value || '';
+ }
+ }, [editor, value]);
+
+ useEffect(() => {
+ if (editor && autoFocus) {
+ setTimeout(() => {
+ editor.commands.focus();
+ }, 100);
+ }
+ }, [editor, autoFocus]);
+
+ if (!editor) {
+ return Loading editor...
;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default WysiwygEditor;
diff --git a/ui/src/components/Editor/index.scss b/ui/src/components/Editor/index.scss
index afd158715..20ab5bab4 100644
--- a/ui/src/components/Editor/index.scss
+++ b/ui/src/components/Editor/index.scss
@@ -114,6 +114,119 @@
height: 264px;
}
+ // WYSIWYG 编辑器样式
+ .wysiwyg-editor-wrap {
+ height: 264px;
+ overflow-y: auto;
+ padding: 0.375rem 0.75rem;
+
+ .tiptap-editor {
+ outline: none;
+ min-height: 100%;
+
+ &:focus {
+ outline: none;
+ }
+
+ p {
+ margin: 0.5rem 0;
+ line-height: 1.6;
+
+ &.is-editor-empty:first-child::before {
+ content: attr(data-placeholder);
+ float: left;
+ color: var(--an-editor-placeholder-color);
+ pointer-events: none;
+ height: 0;
+ }
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ margin: 1rem 0 0.5rem;
+ font-weight: 600;
+ line-height: 1.4;
+ }
+
+ ul,
+ ol {
+ padding-left: 1.5rem;
+ margin: 0.5rem 0;
+ }
+
+ li {
+ margin: 0.25rem 0;
+ }
+
+ code {
+ background-color: var(--an-code-bg, #f4f4f4);
+ color: var(--an-code-color, #e83e8c);
+ padding: 0.2em 0.4em;
+ border-radius: 3px;
+ font-size: 0.9em;
+ }
+
+ pre {
+ background-color: var(--an-code-block-bg, #f8f9fa);
+ border: 1px solid var(--an-ced4da);
+ border-radius: 4px;
+ padding: 1rem;
+ margin: 0.5rem 0;
+ overflow-x: auto;
+
+ code {
+ background-color: transparent;
+ color: inherit;
+ padding: 0;
+ }
+ }
+
+ blockquote {
+ border-left: 4px solid var(--an-primary, #0d6efd);
+ padding-left: 1rem;
+ margin: 0.5rem 0;
+ color: var(--an-text-secondary, #6c757d);
+ }
+
+ a {
+ color: var(--an-link-color, #0d6efd);
+ text-decoration: underline;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 4px;
+ }
+
+ hr {
+ border: none;
+ border-top: 1px solid var(--an-ced4da);
+ margin: 1rem 0;
+ }
+
+ ::selection {
+ background-color: var(--an-selection-bg, rgba(0, 123, 255, 0.2));
+ }
+ }
+
+ .editor-loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: var(--an-text-secondary, #6c757d);
+ }
+ }
+
.CodeMirror {
height: auto;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index 45919b2c9..a914d912c 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -18,11 +18,12 @@
*/
import {
- useEffect,
useRef,
+ useState,
ForwardRefRenderFunction,
forwardRef,
useImperativeHandle,
+ useCallback,
} from 'react';
import classNames from 'classnames';
@@ -47,9 +48,12 @@ import {
UL,
File,
} from './ToolBars';
-import { htmlRender, useEditor } from './utils';
+import { htmlRender } from './utils';
import Viewer from './Viewer';
import { EditorContext } from './EditorContext';
+import WysiwygEditor from './WysiwygEditor';
+import MarkdownEditor from './MarkdownEditor';
+import { Editor } from './types';
import './index.scss';
@@ -82,75 +86,106 @@ const MDEditor: ForwardRefRenderFunction = (
},
ref,
) => {
- const editorRef = useRef(null);
+ const [mode, setMode] = useState<'markdown' | 'wysiwyg'>('markdown');
+ const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
useRenderPlugin(previewRef.current?.element);
- const editor = useEditor({
- editorRef,
- onChange,
- onFocus,
- onBlur,
- placeholder: editorPlaceholder,
- autoFocus,
- });
+ const handleModeChange = useCallback(
+ (newMode: 'markdown' | 'wysiwyg') => {
+ if (newMode === mode) {
+ return;
+ }
- const getHtml = () => {
- return previewRef.current?.getHtml();
- };
+ setMode(newMode);
+ setCurrentEditor(null);
+ },
+ [mode, currentEditor],
+ );
- useImperativeHandle(ref, () => ({
- getHtml,
- }));
+ const getHtml = useCallback(() => {
+ return previewRef.current?.getHtml();
+ }, []);
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ getHtml,
+ }),
+ [getHtml],
+ );
- useEffect(() => {
- if (!editor) {
- return;
- }
- if (editor.getValue() !== value) {
- editor.setValue(value || '');
- }
- }, [editor, value]);
+ const EditorComponent = mode === 'markdown' ? MarkdownEditor : WysiwygEditor;
return (
<>
-
- {editor && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
-
-
-
+
+
+ {currentEditor && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ handleModeChange('markdown')}>
+
+
+ handleModeChange('wysiwyg')}>
+
+
+
+
+
{
+ onChange?.(markdown);
+ }}
+ onFocus={onFocus}
+ onBlur={onBlur}
+ placeholder={editorPlaceholder}
+ autoFocus={autoFocus}
+ onEditorReady={(editor) => {
+ setCurrentEditor(editor);
+ }}
+ />
>
diff --git a/ui/src/components/Editor/toolItem.tsx b/ui/src/components/Editor/toolItem.tsx
index 0c4ca2f10..c0fdf1757 100644
--- a/ui/src/components/Editor/toolItem.tsx
+++ b/ui/src/components/Editor/toolItem.tsx
@@ -21,16 +21,11 @@ import { FC, useContext, useEffect } from 'react';
import { Dropdown, Button } from 'react-bootstrap';
import { EditorContext } from './EditorContext';
-import { IEditorContext } from './types';
+import { Editor } from './types';
interface IProps {
keyMap?: string[];
- onClick?: ({
- editor,
- wrapText,
- replaceLines,
- appendBlock,
- }: IEditorContext) => void;
+ onClick?: (editor: Editor) => void;
tip?: string;
className?: string;
as?: any;
@@ -38,12 +33,7 @@ interface IProps {
label?: string;
disable?: boolean;
isShow?: boolean;
- onBlur?: ({
- editor,
- wrapText,
- replaceLines,
- appendBlock,
- }: IEditorContext) => void;
+ onBlur?: (editor: Editor) => void;
}
const ToolItem: FC
= (props) => {
const editor = useContext(EditorContext);
@@ -72,12 +62,8 @@ const ToolItem: FC = (props) => {
keyMap.forEach((key) => {
editor?.addKeyMap({
[key]: () => {
- onClick?.({
- editor,
- wrapText: editor?.wrapText,
- replaceLines: editor?.replaceLines,
- appendBlock: editor?.appendBlock,
- });
+ onClick?.(editor);
+ return true; // Command 类型要求返回 boolean
},
});
});
@@ -94,21 +80,15 @@ const ToolItem: FC = (props) => {
tabIndex={-1}
onClick={(e) => {
e.preventDefault();
- onClick?.({
- editor,
- wrapText: editor?.wrapText,
- replaceLines: editor?.replaceLines,
- appendBlock: editor?.appendBlock,
- });
+ if (editor) {
+ onClick?.(editor);
+ }
}}
onBlur={(e) => {
e.preventDefault();
- onBlur?.({
- editor,
- wrapText: editor?.wrapText,
- replaceLines: editor?.replaceLines,
- appendBlock: editor?.appendBlock,
- });
+ if (editor) {
+ onBlur?.(editor);
+ }
}}>
diff --git a/ui/src/components/Editor/types.ts b/ui/src/components/Editor/types.ts
index ce33833fe..926d3c203 100644
--- a/ui/src/components/Editor/types.ts
+++ b/ui/src/components/Editor/types.ts
@@ -24,6 +24,9 @@ export interface Position {
line: number;
sticky?: string | undefined;
}
+
+export type Level = 1 | 2 | 3 | 4 | 5 | 6;
+
export interface ExtendEditor {
addKeyMap: (keyMap: Record) => void;
on: (
@@ -53,16 +56,57 @@ export interface ExtendEditor {
getSelection: () => string;
replaceSelection: (value: string) => void;
focus: () => void;
+ getCursor: () => Position;
+ replaceRange: (value: string, from: Position, to: Position) => void;
+ setSelection: (anchor: Position, head?: Position) => void;
+ setReadOnly: (readOnly: boolean) => void;
+
+ // 底层方法(供编辑器内部使用,不推荐工具栏直接使用)
wrapText: (before: string, after?: string, defaultText?: string) => void;
replaceLines: (
replace: Parameters['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;
- setReadOnly: (readOnly: boolean) => void;
+
+ // 语义化高级方法(工具栏推荐使用)
+ // 文本格式
+ insertBold: (text?: string) => void;
+ insertItalic: (text?: string) => void;
+ insertCode: (text?: string) => void;
+ insertStrikethrough: (text?: string) => void;
+
+ // 块级元素
+ insertHeading: (level: Level, text?: string) => void;
+ insertBlockquote: (text?: string) => void;
+ insertCodeBlock: (language?: string, code?: string) => void;
+ insertHorizontalRule: () => void;
+
+ // 列表
+ insertOrderedList: () => void;
+ insertUnorderedList: () => void;
+ toggleOrderedList: () => void;
+ toggleUnorderedList: () => void;
+
+ // 链接和媒体
+ insertLink: (url: string, text?: string) => void;
+ insertImage: (url: string, alt?: string) => void;
+
+ // 表格
+ insertTable: (rows?: number, cols?: number) => void;
+
+ // 缩进
+ indent: () => void;
+ outdent: () => void;
+
+ // 状态查询
+ isBold: () => boolean;
+ isItalic: () => boolean;
+ isHeading: (level?: number) => boolean;
+ isBlockquote: () => boolean;
+ isCodeBlock: () => boolean;
+ isOrderedList: () => boolean;
+ isUnorderedList: () => boolean;
}
export type Editor = EditorView & ExtendEditor;
@@ -72,6 +116,8 @@ export interface CodeMirrorEditor extends Editor {
moduleType;
}
+// @deprecated 已废弃,请直接使用 Editor 接口
+// 保留此接口仅用于向后兼容,新代码不应使用
export interface IEditorContext {
editor: Editor;
wrapText?;
diff --git a/ui/src/components/Editor/utils/codemirror/adapter.ts b/ui/src/components/Editor/utils/codemirror/adapter.ts
new file mode 100644
index 000000000..7e3f681ba
--- /dev/null
+++ b/ui/src/components/Editor/utils/codemirror/adapter.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { Editor, ExtendEditor } from '../../types';
+
+import { createBaseMethods } from './base';
+import { createEventMethods } from './events';
+import { createCommandMethods } from './commands';
+
+/**
+ * Adapts CodeMirror editor to unified editor interface
+ *
+ * This adapter function extends CodeMirror editor with additional methods,
+ * enabling toolbar components to work properly in Markdown mode. The adapter
+ * implements the complete `ExtendEditor` interface, including base methods,
+ * event handling, and command methods.
+ *
+ * @param editor - CodeMirror editor instance
+ * @returns Extended editor instance that implements the unified Editor interface
+ *
+ * @example
+ * ```typescript
+ * const cmEditor = new EditorView({ ... });
+ * const adaptedEditor = createCodeMirrorAdapter(cmEditor as Editor);
+ * // Now you can use the unified API
+ * adaptedEditor.insertBold('text');
+ * adaptedEditor.insertHeading(1, 'Title');
+ * ```
+ */
+export function createCodeMirrorAdapter(editor: Editor): Editor {
+ const baseMethods = createBaseMethods(editor);
+ const eventMethods = createEventMethods(editor);
+ const commandMethods = createCommandMethods(editor);
+
+ const editorAdapter: ExtendEditor = {
+ ...editor,
+ ...baseMethods,
+ ...eventMethods,
+ ...commandMethods,
+ };
+
+ return editorAdapter as unknown as Editor;
+}
diff --git a/ui/src/components/Editor/utils/codemirror/base.ts b/ui/src/components/Editor/utils/codemirror/base.ts
new file mode 100644
index 000000000..4a257f043
--- /dev/null
+++ b/ui/src/components/Editor/utils/codemirror/base.ts
@@ -0,0 +1,110 @@
+/*
+ * 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 { keymap, KeyBinding, Command } from '@codemirror/view';
+
+import { Editor, Position } from '../../types';
+
+/**
+ * Creates base methods module
+ *
+ * Provides core base methods for the editor, including:
+ * - Content getter and setter (getValue, setValue)
+ * - Selection operations (getSelection, replaceSelection)
+ * - Cursor and selection position (getCursor, setSelection)
+ * - Focus and keyboard mapping (focus, addKeyMap)
+ *
+ * @param editor - CodeMirror editor instance
+ * @returns Object containing base methods
+ */
+export function createBaseMethods(editor: Editor) {
+ return {
+ focus: () => {
+ editor.contentDOM.focus();
+ },
+
+ 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 };
+ },
+
+ addKeyMap: (keyMap: Record) => {
+ 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)),
+ });
+ },
+
+ getSelection: () => {
+ return editor.state.sliceDoc(
+ editor.state.selection.main.from,
+ editor.state.selection.main.to,
+ );
+ },
+
+ 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,
+ ),
+ });
+ },
+
+ 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,
+ ),
+ ]),
+ });
+ },
+
+ getValue: () => {
+ return editor.state.doc.toString();
+ },
+
+ setValue: (value: string) => {
+ editor.dispatch({
+ changes: { from: 0, to: editor.state.doc.length, insert: value },
+ });
+ },
+ };
+}
diff --git a/ui/src/components/Editor/utils/codemirror/commands.ts b/ui/src/components/Editor/utils/codemirror/commands.ts
new file mode 100644
index 000000000..59ab064ec
--- /dev/null
+++ b/ui/src/components/Editor/utils/codemirror/commands.ts
@@ -0,0 +1,264 @@
+/*
+ * 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 } from '@codemirror/state';
+
+import { Editor, Level } from '../../types';
+
+/**
+ * Creates command methods module
+ *
+ * Provides semantic command methods and low-level text manipulation methods:
+ * - Semantic methods: insertBold, insertHeading, insertImage, etc. (for toolbar use)
+ * - Low-level methods: wrapText, replaceLines, appendBlock (for internal use)
+ * - State query methods: isBold, isHeading, etc.
+ *
+ * @param editor - CodeMirror editor instance
+ * @returns Object containing all command methods
+ */
+export function createCommandMethods(editor: Editor) {
+ return {
+ wrapText: (before: string, after = before, defaultText) => {
+ const range = editor.state.selection.ranges[0];
+ const selectedText = editor.state.sliceDoc(range.from, range.to);
+ const text = selectedText || defaultText || '';
+ const wrappedText = before + text + after;
+ const insertFrom = range.from;
+ const insertTo = range.to;
+
+ editor.dispatch({
+ changes: [
+ {
+ from: insertFrom,
+ to: insertTo,
+ insert: wrappedText,
+ },
+ ],
+ selection: selectedText
+ ? EditorSelection.cursor(insertFrom + before.length + text.length)
+ : EditorSelection.range(
+ insertFrom + before.length,
+ insertFrom + before.length + text.length,
+ ),
+ });
+ },
+
+ replaceLines: (replace: Parameters['map']>[0]) => {
+ const { doc } = editor.state;
+ const lines: string[] = [];
+ for (let i = 1; i <= doc.lines; i += 1) {
+ lines.push(doc.line(i).text);
+ }
+
+ const newLines = lines.map(replace) as string[];
+ const newText = newLines.join('\n');
+ editor.setValue(newText);
+ },
+
+ appendBlock: (content: string) => {
+ const { doc } = editor.state;
+ const currentText = doc.toString();
+ const newText = currentText ? `${currentText}\n\n${content}` : content;
+ editor.setValue(newText);
+ },
+
+ insertBold: (text?: string) => {
+ editor.wrapText('**', '**', text || 'bold text');
+ },
+
+ insertItalic: (text?: string) => {
+ editor.wrapText('*', '*', text || 'italic text');
+ },
+
+ insertCode: (text?: string) => {
+ editor.wrapText('`', '`', text || 'code');
+ },
+
+ insertStrikethrough: (text?: string) => {
+ editor.wrapText('~~', '~~', text || 'strikethrough text');
+ },
+
+ insertHeading: (level: Level, text?: string) => {
+ const headingText = '#'.repeat(level);
+ editor.wrapText(`${headingText} `, '', text || 'heading');
+ },
+
+ insertBlockquote: (text?: string) => {
+ editor.wrapText('> ', '', text || 'quote');
+ },
+
+ insertCodeBlock: (language?: string, code?: string) => {
+ const lang = language || '';
+ const codeText = code || '';
+ const block = `\`\`\`${lang}\n${codeText}\n\`\`\``;
+ editor.appendBlock(block);
+ },
+
+ insertHorizontalRule: () => {
+ editor.appendBlock('---');
+ },
+
+ insertOrderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ if (/^\d+\.\s/.test(lineText)) {
+ return;
+ }
+ editor.replaceLines((lineItem) => {
+ if (lineItem.trim() === '') {
+ return lineItem;
+ }
+ return `1. ${lineItem}`;
+ });
+ },
+
+ insertUnorderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ if (/^[-*+]\s/.test(lineText)) {
+ return;
+ }
+ editor.replaceLines((lineItem) => {
+ if (lineItem.trim() === '') {
+ return lineItem;
+ }
+ return `- ${lineItem}`;
+ });
+ },
+
+ toggleOrderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ if (/^\d+\.\s/.test(lineText)) {
+ editor.replaceLines((lineItem) => {
+ return lineItem.replace(/^\d+\.\s/, '');
+ });
+ } else {
+ editor.insertOrderedList();
+ }
+ },
+
+ toggleUnorderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ if (/^[-*+]\s/.test(lineText)) {
+ editor.replaceLines((lineItem) => {
+ return lineItem.replace(/^[-*+]\s/, '');
+ });
+ } else {
+ editor.insertUnorderedList();
+ }
+ },
+
+ insertLink: (url: string, text?: string) => {
+ const linkText = text || url;
+ editor.wrapText('[', `](${url})`, linkText);
+ },
+
+ insertImage: (url: string, alt?: string) => {
+ const altText = alt || '';
+ editor.wrapText('`, altText);
+ },
+
+ insertTable: (rows = 3, cols = 3) => {
+ const table: string[] = [];
+ for (let i = 0; i < rows; i += 1) {
+ const row: string[] = [];
+ for (let j = 0; j < cols; j += 1) {
+ row.push(i === 0 ? 'Header' : 'Cell');
+ }
+ table.push(`| ${row.join(' | ')} |`);
+ if (i === 0) {
+ table.push(`| ${'---'.repeat(cols).split('').join(' | ')} |`);
+ }
+ }
+ editor.appendBlock(table.join('\n'));
+ },
+
+ indent: () => {
+ editor.replaceLines((line) => {
+ if (line.trim() === '') {
+ return line;
+ }
+ return ` ${line}`;
+ });
+ },
+
+ outdent: () => {
+ editor.replaceLines((line) => {
+ if (line.trim() === '') {
+ return line;
+ }
+ return line.replace(/^ {2}/, '');
+ });
+ },
+
+ isBold: () => {
+ const selection = editor.getSelection();
+ return /^\*\*.*\*\*$/.test(selection) || /^__.*__$/.test(selection);
+ },
+
+ isItalic: () => {
+ const selection = editor.getSelection();
+ return /^\*.*\*$/.test(selection) || /^_.*_$/.test(selection);
+ },
+
+ isHeading: (level?: number) => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ if (level) {
+ return new RegExp(`^#{${level}}\\s`).test(lineText);
+ }
+ return /^#{1,6}\s/.test(lineText);
+ },
+
+ isBlockquote: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ return /^>\s/.test(lineText);
+ },
+
+ isCodeBlock: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ return /^```/.test(lineText);
+ },
+
+ isOrderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ return /^\d+\.\s/.test(lineText);
+ },
+
+ isUnorderedList: () => {
+ const cursor = editor.getCursor();
+ const line = editor.state.doc.line(cursor.line);
+ const lineText = line.text.trim();
+ return /^[-*+]\s/.test(lineText);
+ },
+ };
+}
diff --git a/ui/src/components/Editor/utils/codemirror/events.ts b/ui/src/components/Editor/utils/codemirror/events.ts
new file mode 100644
index 000000000..dea832c64
--- /dev/null
+++ b/ui/src/components/Editor/utils/codemirror/events.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { StateEffect } from '@codemirror/state';
+import { EditorView } from '@codemirror/view';
+
+import { Editor } from '../../types';
+
+/**
+ * Creates event methods module
+ *
+ * Provides event listener registration and removal for the editor.
+ * Handles various DOM events including focus, blur, drag, drop, and paste.
+ *
+ * @param editor - CodeMirror editor instance
+ * @returns Object containing event methods (on, off)
+ */
+export function createEventMethods(editor: Editor) {
+ return {
+ 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);
+ }
+ },
+
+ 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);
+ }
+ },
+ };
+}
diff --git a/ui/src/components/Editor/utils/extension.ts b/ui/src/components/Editor/utils/extension.ts
deleted file mode 100644
index 4118fed46..000000000
--- a/ui/src/components/Editor/utils/extension.ts
+++ /dev/null
@@ -1,255 +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 { 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['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 61e95fbb8..938b37613 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -30,7 +30,7 @@ import Tooltip from 'bootstrap/js/dist/tooltip';
import { Editor } from '../types';
import { isDarkTheme } from '@/utils/common';
-import createEditorUtils from './extension';
+import { createCodeMirrorAdapter } from './codemirror/adapter';
const editableCompartment = new Compartment();
interface htmlRenderConfig {
@@ -190,7 +190,7 @@ export const useEditor = ({
state: startState,
});
- const cm = createEditorUtils(view as Editor);
+ const cm = createCodeMirrorAdapter(view as Editor);
cm.setReadOnly = (readOnly: boolean) => {
cm.dispatch({
diff --git a/ui/src/components/Editor/utils/tiptap/adapter.ts b/ui/src/components/Editor/utils/tiptap/adapter.ts
new file mode 100644
index 000000000..862cbf6c2
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/adapter.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { Editor as TipTapEditor } from '@tiptap/react';
+
+import { Editor, ExtendEditor } from '../../types';
+
+import { createBaseMethods } from './base';
+import { createEventMethods } from './events';
+import { createCommandMethods } from './commands';
+
+/**
+ * Adapts TipTap editor to CodeMirror editor interface
+ *
+ * This adapter function converts TipTap editor's API to a CodeMirror-compatible interface,
+ * enabling toolbar components to work properly in WYSIWYG mode. The adapter implements
+ * the complete `ExtendEditor` interface, including base methods, event handling, and command methods.
+ *
+ * @param editor - TipTap editor instance
+ * @returns Adapted editor instance that implements the unified Editor interface
+ *
+ * @example
+ * ```typescript
+ * const tipTapEditor = useEditor({ ... });
+ * const adaptedEditor = createTipTapAdapter(tipTapEditor);
+ * // Now you can use the unified API
+ * adaptedEditor.insertBold('text');
+ * adaptedEditor.insertHeading(1, 'Title');
+ * ```
+ */
+export function createTipTapAdapter(editor: TipTapEditor): Editor {
+ const baseMethods = createBaseMethods(editor);
+ const eventMethods = createEventMethods(editor);
+ const commandMethods = createCommandMethods(editor);
+
+ const editorAdapter: ExtendEditor = {
+ ...baseMethods,
+ ...eventMethods,
+ ...commandMethods,
+ };
+
+ return editorAdapter as unknown as Editor;
+}
diff --git a/ui/src/components/Editor/utils/tiptap/base.ts b/ui/src/components/Editor/utils/tiptap/base.ts
new file mode 100644
index 000000000..e088a9f5d
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/base.ts
@@ -0,0 +1,358 @@
+/*
+ * 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 { Editor as TipTapEditor } from '@tiptap/react';
+
+import { Position } from '../../types';
+
+import {
+ safeExecuteCommand,
+ EditorErrorType,
+ handleEditorError,
+} from './errorHandler';
+import {
+ convertTipTapPositionToCodeMirror,
+ convertCodeMirrorPositionToTipTap,
+} from './position';
+import { MARKDOWN_PATTERNS } from './constants';
+
+/**
+ * Creates base methods module
+ *
+ * Provides core base methods for the editor, including:
+ * - Content getter and setter (getValue, setValue)
+ * - Selection operations (getSelection, replaceSelection)
+ * - Cursor and selection position (getCursor, setSelection)
+ * - Read-only state control (setReadOnly)
+ *
+ * @param editor - TipTap editor instance
+ * @returns Object containing base methods
+ */
+export function createBaseMethods(editor: TipTapEditor) {
+ return {
+ getValue: () => {
+ return (
+ safeExecuteCommand(
+ () => editor.getMarkdown(),
+ () => '',
+ EditorErrorType.COMMAND_EXECUTION_FAILED,
+ { function: 'getValue' },
+ ) || ''
+ );
+ },
+
+ setValue: (value: string) => {
+ safeExecuteCommand(
+ () => {
+ editor.commands.setContent(value, { contentType: 'markdown' });
+ },
+ undefined,
+ EditorErrorType.COMMAND_EXECUTION_FAILED,
+ { function: 'setValue', valueLength: value.length },
+ );
+ },
+
+ getSelection: () => {
+ const { from, to } = editor.state.selection;
+ return editor.state.doc.textBetween(from, to);
+ },
+
+ replaceSelection: (value: string) => {
+ const inlineCodeMatch = value.match(MARKDOWN_PATTERNS.INLINE_CODE);
+ if (inlineCodeMatch && value.length > 2) {
+ const codeText = inlineCodeMatch[1];
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'text',
+ text: codeText,
+ marks: [{ type: 'code' }],
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ const codeBlockMatch = value.match(MARKDOWN_PATTERNS.CODE_BLOCK);
+ if (codeBlockMatch) {
+ const [, , lang, codeText] = codeBlockMatch;
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'codeBlock',
+ attrs: lang ? { language: lang } : {},
+ content: [
+ {
+ type: 'text',
+ text: codeText,
+ },
+ ],
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
+ if (imageMatch) {
+ const [, alt, url] = imageMatch;
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'image',
+ attrs: {
+ src: url,
+ alt: alt || '',
+ },
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ const linkMatch = value.match(MARKDOWN_PATTERNS.LINK);
+ if (linkMatch) {
+ const [, text, url] = linkMatch;
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'text',
+ text: text || url,
+ marks: [
+ {
+ type: 'link',
+ attrs: {
+ href: url,
+ },
+ },
+ ],
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ const autoLinkMatch = value.match(MARKDOWN_PATTERNS.AUTO_LINK);
+ if (autoLinkMatch && value.length > 2) {
+ const url = autoLinkMatch[1];
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'text',
+ text: url,
+ marks: [
+ {
+ type: 'link',
+ attrs: {
+ href: url,
+ },
+ },
+ ],
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ if (MARKDOWN_PATTERNS.HORIZONTAL_RULE.test(value.trim())) {
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'horizontalRule',
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ },
+ );
+ return;
+ }
+
+ safeExecuteCommand(() => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ });
+ },
+
+ focus: () => {
+ editor.commands.focus();
+ },
+
+ getCursor: () => {
+ try {
+ const { from } = editor.state.selection;
+ return convertTipTapPositionToCodeMirror(editor, from);
+ } catch (error) {
+ handleEditorError(
+ error as Error,
+ EditorErrorType.POSITION_CONVERSION_FAILED,
+ {
+ function: 'getCursor',
+ },
+ );
+ return { line: 0, ch: 0 };
+ }
+ },
+
+ setSelection: (anchor?: unknown, head?: unknown) => {
+ try {
+ if (
+ anchor &&
+ typeof anchor === 'object' &&
+ 'line' in anchor &&
+ 'ch' in anchor
+ ) {
+ const anchorPos = convertCodeMirrorPositionToTipTap(
+ editor,
+ anchor as Position,
+ );
+ let headPos = anchorPos;
+
+ if (
+ head &&
+ typeof head === 'object' &&
+ 'line' in head &&
+ 'ch' in head
+ ) {
+ headPos = convertCodeMirrorPositionToTipTap(
+ editor,
+ head as Position,
+ );
+ }
+
+ safeExecuteCommand(
+ () => {
+ editor.commands.setTextSelection({
+ from: anchorPos,
+ to: headPos,
+ });
+ },
+ undefined,
+ EditorErrorType.COMMAND_EXECUTION_FAILED,
+ { function: 'setSelection', anchorPos, headPos },
+ );
+ } else {
+ editor.commands.focus();
+ }
+ } catch (error) {
+ handleEditorError(
+ error as Error,
+ EditorErrorType.COMMAND_EXECUTION_FAILED,
+ {
+ function: 'setSelection',
+ anchor,
+ head,
+ },
+ );
+ safeExecuteCommand(
+ () => {
+ editor.commands.focus();
+ },
+ undefined,
+ EditorErrorType.COMMAND_EXECUTION_FAILED,
+ { function: 'setSelection', isFallback: true },
+ );
+ }
+ },
+
+ setReadOnly: (readOnly: boolean) => {
+ editor.setEditable(!readOnly);
+ },
+
+ replaceRange: (value: string, from?: unknown, to?: unknown) => {
+ if (from && to && typeof from === 'object' && typeof to === 'object') {
+ const { from: currentFrom, to: currentTo } = editor.state.selection;
+ const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
+ if (imageMatch) {
+ const [, alt, url] = imageMatch;
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContentAt(
+ { from: currentFrom, to: currentTo },
+ {
+ type: 'image',
+ attrs: {
+ src: url,
+ alt: alt || '',
+ },
+ },
+ );
+ },
+ () => {
+ editor.commands.insertContentAt(
+ { from: currentFrom, to: currentTo },
+ value,
+ { contentType: 'markdown' },
+ );
+ },
+ );
+ return;
+ }
+
+ safeExecuteCommand(() => {
+ editor.commands.insertContentAt(
+ { from: currentFrom, to: currentTo },
+ value,
+ { contentType: 'markdown' },
+ );
+ });
+ } else {
+ const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
+ if (imageMatch) {
+ const [, alt, url] = imageMatch;
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'image',
+ attrs: {
+ src: url,
+ alt: alt || '',
+ },
+ });
+ },
+ () => {
+ editor.commands.insertContent(value, {
+ contentType: 'markdown',
+ });
+ },
+ );
+ return;
+ }
+
+ safeExecuteCommand(() => {
+ editor.commands.insertContent(value, { contentType: 'markdown' });
+ });
+ }
+ },
+ };
+}
diff --git a/ui/src/components/Editor/utils/tiptap/commands.ts b/ui/src/components/Editor/utils/tiptap/commands.ts
new file mode 100644
index 000000000..ee32a4a3b
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/commands.ts
@@ -0,0 +1,718 @@
+/*
+ * 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 { Editor as TipTapEditor } from '@tiptap/react';
+import { Command } from '@codemirror/view';
+
+import { Level } from '../../types';
+
+import { safeExecuteCommand, logWarning } from './errorHandler';
+import { MARKDOWN_PATTERNS } from './constants';
+
+/**
+ * Creates command methods module
+ *
+ * Provides command methods for the editor, including:
+ * - Low-level methods: wrapText, replaceLines, appendBlock (for internal editor use)
+ * - Semantic methods: insertBold, insertHeading, insertImage, etc. (for toolbar use)
+ * - State query methods: isBold, isHeading, etc.
+ *
+ * @param editor - TipTap editor instance
+ * @returns Object containing all command methods
+ */
+export function createCommandMethods(editor: TipTapEditor) {
+ return {
+ wrapText: (before: string, after?: string, defaultText?: string) => {
+ const { from, to } = editor.state.selection;
+ const actualAfter = after || before;
+
+ if (before === '**' && actualAfter === '**') {
+ if (from === to) {
+ if (defaultText) {
+ const insertPos = from;
+ editor.commands.insertContent(defaultText);
+ editor.commands.setTextSelection({
+ from: insertPos,
+ to: insertPos + defaultText.length,
+ });
+ editor.commands.toggleBold();
+ } else {
+ editor.commands.toggleBold();
+ }
+ } else {
+ editor.commands.toggleBold();
+ }
+ return;
+ }
+
+ if (before === '*' && actualAfter === '*') {
+ if (from === to) {
+ if (defaultText) {
+ const insertPos = from;
+ editor.commands.insertContent(defaultText);
+ editor.commands.setTextSelection({
+ from: insertPos,
+ to: insertPos + defaultText.length,
+ });
+ editor.commands.toggleItalic();
+ } else {
+ editor.commands.toggleItalic();
+ }
+ } else {
+ editor.commands.toggleItalic();
+ }
+ return;
+ }
+
+ if (before === '`' && actualAfter === '`') {
+ if (from === to) {
+ if (defaultText) {
+ const insertPos = from;
+ editor.commands.insertContent(defaultText);
+ editor.commands.setTextSelection({
+ from: insertPos,
+ to: insertPos + defaultText.length,
+ });
+ editor.commands.toggleCode();
+ } else {
+ editor.commands.toggleCode();
+ }
+ } else {
+ editor.commands.toggleCode();
+ }
+ return;
+ }
+
+ if (before === '```\n' && actualAfter === '\n```') {
+ if (from === to) {
+ const codeBlockText = defaultText
+ ? `\`\`\`\n${defaultText}\n\`\`\``
+ : '```\n\n```';
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent(codeBlockText, {
+ contentType: 'markdown',
+ });
+ },
+ () => {
+ editor.commands.insertContent({
+ type: 'codeBlock',
+ content: defaultText
+ ? [
+ {
+ type: 'text',
+ text: defaultText,
+ },
+ ]
+ : [],
+ });
+ },
+ );
+ } else {
+ const selectedText = editor.state.doc.textBetween(from, to);
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContentAt(
+ { from, to },
+ {
+ type: 'codeBlock',
+ content: [
+ {
+ type: 'text',
+ text: selectedText,
+ },
+ ],
+ },
+ );
+ },
+ () => {
+ const codeBlockText = `\`\`\`\n${selectedText}\n\`\`\``;
+ editor.commands.insertContentAt({ from, to }, codeBlockText, {
+ contentType: 'markdown',
+ });
+ },
+ );
+ }
+ return;
+ }
+
+ if (from === to) {
+ const text = before + (defaultText || '') + actualAfter;
+ editor.commands.insertContent(text, { contentType: 'markdown' });
+ } else {
+ const selectedText = editor.state.doc.textBetween(from, to);
+ const wrappedText = before + selectedText + actualAfter;
+ editor.commands.insertContentAt({ from, to }, wrappedText, {
+ contentType: 'markdown',
+ });
+ }
+ },
+
+ replaceLines: (replace: Parameters['map']>[0]) => {
+ const { from } = editor.state.selection;
+ const $pos = editor.state.doc.resolve(from);
+ const block = $pos.parent;
+ const lineText = block.textContent;
+ const newText = replace(lineText, 0, [lineText]) as string;
+
+ const finalText = newText || ' ';
+ const headingMatch = finalText.match(MARKDOWN_PATTERNS.HEADING);
+ if (headingMatch) {
+ const [, hashes, text] = headingMatch;
+ const level = hashes.length;
+ const start = $pos.start($pos.depth);
+ const end = $pos.end($pos.depth);
+
+ if (start < 0 || end < 0 || start > end) {
+ logWarning('Invalid position range for heading', { start, end });
+ return;
+ }
+
+ const headingText = text.trim() || 'Heading';
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'heading',
+ attrs: { level },
+ content: [
+ {
+ type: 'text',
+ text: headingText,
+ },
+ ],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'heading',
+ attrs: { level },
+ content: [
+ {
+ type: 'text',
+ text: headingText,
+ },
+ ],
+ },
+ );
+ }
+ },
+ () => {
+ const markdownText = finalText.trim() || `# Heading`;
+ if (start === end) {
+ editor.commands.insertContent(markdownText, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ markdownText,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ );
+ return;
+ }
+
+ if (finalText.startsWith('> ')) {
+ const quoteText = finalText.slice(2).trim();
+ const start = $pos.start($pos.depth);
+ const end = $pos.end($pos.depth);
+
+ if (start < 0 || end < 0 || start > end) {
+ logWarning('Invalid position range for heading', { start, end });
+ return;
+ }
+
+ const quoteMarkdown = quoteText ? `> ${quoteText}` : '> ';
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(quoteMarkdown, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ quoteMarkdown,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: quoteText ? [{ type: 'text', text: quoteText }] : [],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: quoteText ? [{ type: 'text', text: quoteText }] : [],
+ },
+ );
+ }
+ },
+ );
+ return;
+ }
+
+ const olMatchOriginal = lineText.match(
+ MARKDOWN_PATTERNS.ORDERED_LIST_ORIGINAL,
+ );
+ const olMatchNew = finalText.match(MARKDOWN_PATTERNS.ORDERED_LIST_NEW);
+
+ if (olMatchOriginal || olMatchNew) {
+ const isInOrderedList = editor.isActive('orderedList');
+ const start = $pos.start($pos.depth);
+ const end = $pos.end($pos.depth);
+
+ if (start < 0 || end < 0 || start > end) {
+ logWarning('Invalid position range for ordered list', {
+ start,
+ end,
+ });
+ return;
+ }
+
+ if (olMatchOriginal && !olMatchNew) {
+ const textContent = finalText.trim();
+ const contentToInsert = textContent || 'Paragraph';
+
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(contentToInsert, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ contentToInsert,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ },
+ );
+ }
+ },
+ );
+ if (isInOrderedList) {
+ safeExecuteCommand(() => {
+ editor.chain().focus().toggleOrderedList().run();
+ });
+ }
+ } else if (!olMatchOriginal && olMatchNew) {
+ const [, , text] = olMatchNew;
+ const textContent = text.trim();
+ const contentToInsert = textContent || 'List item';
+
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(contentToInsert, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ contentToInsert,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ },
+ );
+ }
+ },
+ );
+ if (!isInOrderedList) {
+ safeExecuteCommand(() => {
+ editor.chain().focus().toggleOrderedList().run();
+ });
+ }
+ }
+ return;
+ }
+ const ulMatchOriginal = lineText.match(
+ MARKDOWN_PATTERNS.UNORDERED_LIST_ORIGINAL,
+ );
+ const ulMatchNew = finalText.match(MARKDOWN_PATTERNS.UNORDERED_LIST_NEW);
+
+ if (ulMatchOriginal || ulMatchNew) {
+ const isInBulletList = editor.isActive('bulletList');
+ const start = $pos.start($pos.depth);
+ const end = $pos.end($pos.depth);
+
+ if (start < 0 || end < 0 || start > end) {
+ logWarning('Invalid position range for unordered list', {
+ start,
+ end,
+ });
+ return;
+ }
+
+ if (ulMatchOriginal && !ulMatchNew) {
+ const textContent = finalText.trim();
+ const contentToInsert = textContent || 'Paragraph';
+
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(contentToInsert, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ contentToInsert,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ },
+ );
+ }
+ },
+ );
+ if (isInBulletList) {
+ safeExecuteCommand(() => {
+ editor.chain().focus().toggleBulletList().run();
+ });
+ }
+ } else if (!ulMatchOriginal && ulMatchNew) {
+ const [, text] = ulMatchNew;
+ const textContent = text.trim();
+ const contentToInsert = textContent || 'List item';
+
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(contentToInsert, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ contentToInsert,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ },
+ );
+ }
+ },
+ );
+ if (!isInBulletList) {
+ safeExecuteCommand(() => {
+ editor.chain().focus().toggleBulletList().run();
+ });
+ }
+ }
+ return;
+ }
+
+ const start = $pos.start($pos.depth);
+ const end = $pos.end($pos.depth);
+
+ if (start < 0 || end < 0 || start > end) {
+ logWarning('Invalid position range', {
+ start,
+ end,
+ function: 'replaceLines',
+ });
+ return;
+ }
+
+ const contentToInsert = finalText.trim() || ' ';
+
+ safeExecuteCommand(
+ () => {
+ if (start === end) {
+ editor.commands.insertContent(contentToInsert, {
+ contentType: 'markdown',
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ contentToInsert,
+ { contentType: 'markdown' },
+ );
+ }
+ },
+ () => {
+ if (start === end) {
+ editor.commands.insertContent({
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ });
+ } else {
+ editor.commands.insertContentAt(
+ { from: start, to: end },
+ {
+ type: 'paragraph',
+ content: [{ type: 'text', text: contentToInsert }],
+ },
+ );
+ }
+ },
+ );
+ },
+
+ appendBlock: (content: string) => {
+ if (MARKDOWN_PATTERNS.HORIZONTAL_RULE.test(content.trim())) {
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'horizontalRule',
+ });
+ },
+ () => {
+ editor.commands.insertContent(content, {
+ contentType: 'markdown',
+ });
+ },
+ );
+ return;
+ }
+
+ safeExecuteCommand(() => {
+ editor.commands.insertContent(`\n\n${content}`, {
+ contentType: 'markdown',
+ });
+ });
+ },
+
+ addKeyMap: (keyMap: Record) => {
+ Object.keys(keyMap).forEach(() => {});
+ },
+ insertBold: (text?: string) => {
+ if (text) {
+ const { from } = editor.state.selection;
+ editor.commands.insertContent(text);
+ editor.commands.setTextSelection({ from, to: from + text.length });
+ }
+ editor.commands.toggleBold();
+ },
+
+ insertItalic: (text?: string) => {
+ if (text) {
+ const { from } = editor.state.selection;
+ editor.commands.insertContent(text);
+ editor.commands.setTextSelection({ from, to: from + text.length });
+ }
+ editor.commands.toggleItalic();
+ },
+
+ insertCode: (text?: string) => {
+ if (text) {
+ const { from } = editor.state.selection;
+ editor.commands.insertContent(text);
+ editor.commands.setTextSelection({ from, to: from + text.length });
+ }
+ editor.commands.toggleCode();
+ },
+
+ insertStrikethrough: (text?: string) => {
+ if (text) {
+ const { from } = editor.state.selection;
+ editor.commands.insertContent(text);
+ editor.commands.setTextSelection({ from, to: from + text.length });
+ }
+ editor.commands.toggleStrike();
+ },
+
+ insertHeading: (level: Level, text?: string) => {
+ if (text) {
+ // Insert heading using TipTap's native API to ensure proper structure
+ safeExecuteCommand(
+ () => {
+ editor.commands.insertContent({
+ type: 'heading',
+ attrs: { level },
+ content: [
+ {
+ type: 'text',
+ text,
+ },
+ ],
+ });
+ // Select only the text part (excluding the heading node structure)
+ // After insertion, the cursor is at the end of the heading
+ // We need to select backwards from the current position
+ const { to } = editor.state.selection;
+ editor.commands.setTextSelection({
+ from: to - text.length,
+ to,
+ });
+ },
+ () => {
+ // Fallback: use markdown format
+ const headingText = `${'#'.repeat(level)} ${text}`;
+ editor.commands.insertContent(headingText, {
+ contentType: 'markdown',
+ });
+ },
+ );
+ } else {
+ editor.commands.toggleHeading({ level });
+ }
+ },
+
+ insertBlockquote: (text?: string) => {
+ if (text) {
+ const { from } = editor.state.selection;
+ const blockquoteText = `> ${text}`;
+ editor.commands.insertContent(blockquoteText, {
+ contentType: 'markdown',
+ });
+ // Select the text part (excluding the '> ' marker)
+ const textStart = from + 2; // 2 for '> '
+ editor.commands.setTextSelection({
+ from: textStart,
+ to: textStart + text.length,
+ });
+ } else {
+ editor.commands.toggleBlockquote();
+ }
+ },
+
+ insertCodeBlock: (language?: string, code?: string) => {
+ const lang = language || '';
+ const codeText = code || 'code here';
+ editor.commands.insertContent(`\`\`\`${lang}\n${codeText}\n\`\`\``, {
+ contentType: 'markdown',
+ });
+ },
+
+ insertHorizontalRule: () => {
+ editor.commands.setHorizontalRule();
+ },
+
+ insertOrderedList: () => {
+ editor.commands.toggleOrderedList();
+ },
+
+ insertUnorderedList: () => {
+ editor.commands.toggleBulletList();
+ },
+
+ toggleOrderedList: () => {
+ editor.commands.toggleOrderedList();
+ },
+
+ toggleUnorderedList: () => {
+ editor.commands.toggleBulletList();
+ },
+
+ insertLink: (url: string, text?: string) => {
+ const linkText = text || url;
+ editor.commands.insertContent(`[${linkText}](${url})`, {
+ contentType: 'markdown',
+ });
+ },
+
+ insertImage: (url: string, alt?: string) => {
+ editor.commands.setImage({ src: url, alt: alt || 'image' });
+ },
+
+ insertTable: (rows = 3, cols = 3) => {
+ editor.commands.insertTable({
+ rows,
+ cols,
+ withHeaderRow: true,
+ });
+ },
+
+ indent: () => {
+ editor.commands.sinkListItem('listItem');
+ },
+
+ outdent: () => {
+ editor.commands.liftListItem('listItem');
+ },
+
+ isBold: () => editor.isActive('bold'),
+ isItalic: () => editor.isActive('italic'),
+ isHeading: (level?: number) => {
+ if (level) {
+ return editor.isActive('heading', { level });
+ }
+ return editor.isActive('heading');
+ },
+ isBlockquote: () => editor.isActive('blockquote'),
+ isCodeBlock: () => editor.isActive('codeBlock'),
+ isOrderedList: () => editor.isActive('orderedList'),
+ isUnorderedList: () => editor.isActive('bulletList'),
+ };
+}
diff --git a/ui/src/components/Editor/utils/tiptap/constants.ts b/ui/src/components/Editor/utils/tiptap/constants.ts
new file mode 100644
index 000000000..a4ff7de42
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/constants.ts
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/**
+ * Markdown pattern matching regular expression constants
+ *
+ * Defines regular expression patterns for parsing and matching Markdown syntax.
+ * These patterns are used to convert Markdown syntax to TipTap nodes, or from TipTap nodes
+ * to Markdown format.
+ *
+ * @example
+ * ```typescript
+ * const headingMatch = text.match(MARKDOWN_PATTERNS.HEADING);
+ * if (headingMatch) {
+ * const level = headingMatch[1].length; // Number of #
+ * const text = headingMatch[2]; // Heading text
+ * }
+ * ```
+ */
+export const MARKDOWN_PATTERNS = {
+ HEADING: /^(#{1,6})\s+(.+)$/,
+ ORDERED_LIST_ORIGINAL: /^(\s{0,})(\d+)\.\s/,
+ ORDERED_LIST_NEW: /^(\d+)\.\s*(.*)$/,
+ UNORDERED_LIST_ORIGINAL: /^(\s{0,})(-|\*)\s/,
+ UNORDERED_LIST_NEW: /^[-*]\s*(.*)$/,
+ INLINE_CODE: /^`(.+?)`$/,
+ CODE_BLOCK: /^(\n)?```(\w+)?\n([\s\S]*?)\n```(\n)?$/,
+ IMAGE: /^!\[([^\]]*)\]\(([^)]+)\)$/,
+ LINK: /^\[([^\]]*)\]\(([^)]+)\)$/,
+ AUTO_LINK: /^<(.+?)>$/,
+ HORIZONTAL_RULE: /^-{3,}$/,
+} as const;
diff --git a/ui/src/components/Editor/utils/tiptap/errorHandler.ts b/ui/src/components/Editor/utils/tiptap/errorHandler.ts
new file mode 100644
index 000000000..c8f4e7a3b
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/errorHandler.ts
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+/**
+ * Editor error type enumeration
+ *
+ * Defines various error types that may occur in the editor, used for error classification and handling.
+ */
+export enum EditorErrorType {
+ COMMAND_EXECUTION_FAILED = 'COMMAND_EXECUTION_FAILED',
+ POSITION_CONVERSION_FAILED = 'POSITION_CONVERSION_FAILED',
+ CONTENT_PARSING_FAILED = 'CONTENT_PARSING_FAILED',
+ EVENT_LISTENER_FAILED = 'EVENT_LISTENER_FAILED',
+}
+
+/**
+ * Editor error interface
+ */
+export interface EditorError {
+ type: EditorErrorType;
+ message: string;
+ originalError?: Error;
+ context?: Record;
+ timestamp: number;
+}
+
+/**
+ * Handles editor errors with unified log format
+ *
+ * Unified error handling for the editor, recording error information, context, and stack traces
+ * for easier problem identification and debugging.
+ *
+ * @param error - Original error object
+ * @param type - Error type
+ * @param context - Optional context information (function name, parameters, etc.)
+ * @returns Processed error object
+ *
+ * @example
+ * ```typescript
+ * try {
+ * editor.commands.insertContent(content);
+ * } catch (error) {
+ * handleEditorError(error, EditorErrorType.COMMAND_EXECUTION_FAILED, {
+ * function: 'insertContent',
+ * content: content.substring(0, 50),
+ * });
+ * }
+ * ```
+ */
+export function handleEditorError(
+ error: Error,
+ type: EditorErrorType,
+ context?: Record,
+): EditorError {
+ const editorError: EditorError = {
+ type,
+ message: error.message,
+ originalError: error,
+ context,
+ timestamp: Date.now(),
+ };
+
+ console.error(`[Editor Error] ${type}:`, {
+ message: editorError.message,
+ context: editorError.context,
+ stack: error.stack,
+ });
+
+ return editorError;
+}
+
+/**
+ * Safely executes TipTap command with error handling and fallback strategy
+ *
+ * Automatically catches errors when executing TipTap commands. If a fallback function is provided,
+ * it attempts to execute the fallback operation when the main command fails. All errors are uniformly recorded.
+ *
+ * @param command - Main command function to execute
+ * @param fallback - Optional fallback function to execute when main command fails
+ * @param errorType - Error type, defaults to COMMAND_EXECUTION_FAILED
+ * @param context - Optional context information
+ * @returns Command execution result, returns undefined if failed and no fallback
+ *
+ * @example
+ * ```typescript
+ * const result = safeExecuteCommand(
+ * () => editor.commands.insertContent(content),
+ * () => editor.commands.insertContent(content, { contentType: 'markdown' }),
+ * EditorErrorType.COMMAND_EXECUTION_FAILED,
+ * { function: 'insertContent', contentLength: content.length }
+ * );
+ * ```
+ */
+export function safeExecuteCommand(
+ command: () => T,
+ fallback?: () => T,
+ errorType: EditorErrorType = EditorErrorType.COMMAND_EXECUTION_FAILED,
+ context?: Record,
+): T | undefined {
+ try {
+ return command();
+ } catch (error) {
+ handleEditorError(error as Error, errorType, context);
+ if (fallback) {
+ try {
+ return fallback();
+ } catch (fallbackError) {
+ handleEditorError(fallbackError as Error, errorType, {
+ ...context,
+ isFallback: true,
+ });
+ }
+ }
+ return undefined;
+ }
+}
+
+/**
+ * Logs warning information (for non-fatal errors)
+ *
+ * Records non-fatal warning information to alert potential issues without affecting functionality.
+ *
+ * @param message - Warning message
+ * @param context - Optional context information
+ *
+ * @example
+ * ```typescript
+ * if (start < 0 || end < 0) {
+ * logWarning('Invalid position range', { start, end, function: 'setSelection' });
+ * return;
+ * }
+ * ```
+ */
+export function logWarning(
+ message: string,
+ context?: Record,
+): void {
+ console.warn(`[Editor Warning] ${message}`, context || {});
+}
diff --git a/ui/src/components/Editor/utils/tiptap/events.ts b/ui/src/components/Editor/utils/tiptap/events.ts
new file mode 100644
index 000000000..495ee6e57
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/events.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { Editor as TipTapEditor } from '@tiptap/react';
+
+import { logWarning } from './errorHandler';
+
+/**
+ * Checks if editor view is available
+ */
+function isViewAvailable(editor: TipTapEditor): boolean {
+ return !!(editor.view && editor.view.dom);
+}
+
+/**
+ * Creates event handling methods module
+ *
+ * Provides event handling methods for the editor, including:
+ * - on: Register event listeners (change, focus, blur, dragenter, dragover, drop, paste)
+ * - off: Remove event listeners
+ *
+ * Note: For DOM events (dragenter, dragover, drop, paste),
+ * the editor view must be mounted before binding events.
+ *
+ * @param editor - TipTap editor instance
+ * @returns Object containing event handling methods
+ */
+export function createEventMethods(editor: TipTapEditor) {
+ return {
+ on: (event: string, callback: (e?: unknown) => void) => {
+ if (event === 'change') {
+ editor.on('update', callback);
+ } else if (event === 'focus') {
+ editor.on('focus', callback);
+ } else if (event === 'blur') {
+ editor.on('blur', callback);
+ } else if (
+ event === 'dragenter' ||
+ event === 'dragover' ||
+ event === 'drop' ||
+ event === 'paste'
+ ) {
+ if (!isViewAvailable(editor)) {
+ logWarning(
+ 'TipTap editor view is not available yet. Event listener not attached.',
+ {
+ event,
+ },
+ );
+ return;
+ }
+ editor.view.dom.addEventListener(event, callback as EventListener);
+ }
+ },
+
+ off: (event: string, callback: (e?: unknown) => void) => {
+ if (
+ (event === 'dragenter' ||
+ event === 'dragover' ||
+ event === 'drop' ||
+ event === 'paste') &&
+ !isViewAvailable(editor)
+ ) {
+ return;
+ }
+ if (event === 'change') {
+ editor.off('update', callback);
+ } else if (event === 'focus') {
+ editor.off('focus', callback);
+ } else if (event === 'blur') {
+ editor.off('blur', callback);
+ } else if (
+ event === 'dragenter' ||
+ event === 'dragover' ||
+ event === 'drop' ||
+ event === 'paste'
+ ) {
+ editor.view.dom.removeEventListener(event, callback as EventListener);
+ }
+ },
+ };
+}
diff --git a/ui/src/components/Editor/utils/tiptap/position.ts b/ui/src/components/Editor/utils/tiptap/position.ts
new file mode 100644
index 000000000..378908b88
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/position.ts
@@ -0,0 +1,147 @@
+/*
+ * 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 { Editor as TipTapEditor } from '@tiptap/react';
+
+import { Position } from '../../types';
+
+import { handleEditorError, EditorErrorType } from './errorHandler';
+
+/**
+ * Converts TipTap position to CodeMirror Position
+ *
+ * TipTap uses document tree-based node positions (character index), while CodeMirror uses
+ * line-based positions (line number and column number). This function converts between them.
+ *
+ * @param editor - TipTap editor instance
+ * @param pos - TipTap position index (character position)
+ * @returns CodeMirror position object with line and ch properties
+ *
+ * @example
+ * ```typescript
+ * const tipTapPos = 100; // Character position
+ * const codeMirrorPos = convertTipTapPositionToCodeMirror(editor, tipTapPos);
+ * // { line: 5, ch: 10 }
+ * ```
+ */
+export function convertTipTapPositionToCodeMirror(
+ editor: TipTapEditor,
+ pos: number,
+): Position {
+ try {
+ const { doc } = editor.state;
+ let line = 0;
+ let ch = 0;
+ let currentPos = 0;
+
+ for (let i = 0; i < doc.content.childCount; i += 1) {
+ const child = doc.content.child(i);
+ const childSize = child.nodeSize;
+
+ if (currentPos + childSize > pos) {
+ const text = child.textContent;
+ const relativePos = pos - currentPos;
+
+ const textBeforePos = text.substring(0, relativePos);
+ const newlineMatches = textBeforePos.match(/\n/g);
+ line += newlineMatches ? newlineMatches.length : 0;
+ ch = relativePos - textBeforePos.lastIndexOf('\n') - 1;
+ break;
+ }
+
+ const text = child.textContent;
+ const newlineMatches = text.match(/\n/g);
+ line += newlineMatches ? newlineMatches.length : 0;
+ currentPos += childSize;
+ }
+
+ return { line, ch };
+ } catch (error) {
+ handleEditorError(
+ error as Error,
+ EditorErrorType.POSITION_CONVERSION_FAILED,
+ {
+ function: 'convertTipTapPositionToCodeMirror',
+ position: pos,
+ },
+ );
+ return { line: 0, ch: 0 };
+ }
+}
+
+/**
+ * Converts CodeMirror Position to TipTap position index
+ *
+ * Converts CodeMirror's line-based position to TipTap's character index position.
+ *
+ * @param editor - TipTap editor instance
+ * @param position - CodeMirror position object with line and ch properties
+ * @returns TipTap position index (character position)
+ *
+ * @example
+ * ```typescript
+ * const codeMirrorPos = { line: 5, ch: 10 };
+ * const tipTapPos = convertCodeMirrorPositionToTipTap(editor, codeMirrorPos);
+ * // 100 (character position)
+ * ```
+ */
+export function convertCodeMirrorPositionToTipTap(
+ editor: TipTapEditor,
+ position: Position,
+): number {
+ try {
+ const { doc } = editor.state;
+ let currentLine = 0;
+ let currentPos = 0;
+
+ for (let i = 0; i < doc.content.childCount; i += 1) {
+ const child = doc.content.child(i);
+ const text = child.textContent;
+ const lines = text.split('\n');
+
+ if (currentLine + lines.length - 1 >= position.line) {
+ const lineInNode = position.line - currentLine;
+ const { ch: posInLine } = position;
+
+ let pos = 0;
+ for (let j = 0; j < lineInNode; j += 1) {
+ pos += lines[j].length + 1; // +1 for newline
+ }
+ pos += posInLine;
+
+ return currentPos + pos;
+ }
+
+ currentLine += lines.length - 1;
+ currentPos += child.nodeSize;
+ }
+
+ return doc.content.size;
+ } catch (error) {
+ handleEditorError(
+ error as Error,
+ EditorErrorType.POSITION_CONVERSION_FAILED,
+ {
+ function: 'convertCodeMirrorPositionToTipTap',
+ position,
+ },
+ );
+ return editor.state.doc.content.size;
+ }
+}
diff --git a/ui/src/pages/Questions/Detail/index.scss b/ui/src/pages/Questions/Detail/index.scss
index a5726b6a5..da379ba91 100644
--- a/ui/src/pages/Questions/Detail/index.scss
+++ b/ui/src/pages/Questions/Detail/index.scss
@@ -47,11 +47,12 @@
[data-bs-theme='dark'] & {
color: var(--bs-gray-400) !important;
background-color: var(--bs-gray-800);
- &:active, &.active {
- background-color: #626E79 !important;
+ &:active,
+ &.active {
+ background-color: #626e79 !important;
}
&:hover {
- background-color: #57616B !important;
+ background-color: #57616b !important;
}
}
}
@@ -78,4 +79,4 @@
font-size: calc(1.275rem + 0.3vw) !important;
}
}
-}
\ No newline at end of file
+}
From c2a62804b0dcf31048eeb145e569ce0605d007ea Mon Sep 17 00:00:00 2001
From: Gregorius Bima Kharisma Wicaksana
<51526537+bimakw@users.noreply.github.com>
Date: Tue, 16 Dec 2025 13:43:21 +0700
Subject: [PATCH 25/92] fix: add feedback after successfully adding a user in
admin panel (#1462)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- Show success toast notification after user is added
- Refresh user list and navigate to 'normal' filter on page 1 to display
the newly added user
## Problem
When adding a user in Admin -> Users, the page had no feedback after
submission (as reported in #1457).
**Root cause:** The code only refreshed the user list if the current
filter was "all" or "staff", but the default filter is "normal".
Additionally, there was no success toast notification.
## Solution
1. Added toast notification to confirm successful user creation
2. After adding user, navigate to "normal" filter page 1 and refresh the
list so the new user is visible
## Test plan
1. Go to Admin -> Users
2. Click "Add User"
3. Submit user information
4. ✅ Success toast should appear
5. ✅ Page should navigate to "normal" filter
6. ✅ Newly added user should be visible in the list
Fixes #1457
---------
Co-authored-by: LinkinStars
---
i18n/en_US.yaml | 1 +
ui/src/pages/Admin/Users/index.tsx | 11 ++++++++---
ui/src/pages/SideNavLayout/index.tsx | 2 +-
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index eac322dac..705db9c25 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -2383,6 +2383,7 @@ ui:
user_normal: This user is already normal.
user_suspended: This user has been suspended.
user_deleted: This user has been deleted.
+ user_added: User has been added successfully.
badge_activated: This badge has been activated.
badge_inactivated: This badge has been inactivated.
users_deleted: These users have been deleted.
diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx
index 7e8e4fd61..220429007 100644
--- a/ui/src/pages/Admin/Users/index.tsx
+++ b/ui/src/pages/Admin/Users/index.tsx
@@ -109,9 +109,14 @@ const Users: FC = () => {
return new Promise((resolve, reject) => {
addUsers(userModel)
.then(() => {
- if (/all|staff/.test(curFilter) && curPage === 1) {
- refreshUsers();
- }
+ toastStore.getState().show({
+ msg: t('user_added', { keyPrefix: 'messages' }),
+ variant: 'success',
+ });
+ urlSearchParams.set('filter', 'normal');
+ urlSearchParams.delete('page');
+ setUrlSearchParams(urlSearchParams);
+ refreshUsers();
resolve(true);
})
.catch((e) => {
diff --git a/ui/src/pages/SideNavLayout/index.tsx b/ui/src/pages/SideNavLayout/index.tsx
index 907b9b281..b9bc38b9c 100644
--- a/ui/src/pages/SideNavLayout/index.tsx
+++ b/ui/src/pages/SideNavLayout/index.tsx
@@ -38,7 +38,7 @@ const Index: FC = () => {
-
+
From 762773cedbcc44c91ce41e5375e40d63f9600b4f Mon Sep 17 00:00:00 2001
From: robin
Date: Tue, 16 Dec 2025 14:50:19 +0800
Subject: [PATCH 26/92] chore: clean up pnpm-lock.yaml by removing unused
dependencies and updating existing ones
---
ui/pnpm-lock.yaml | 1058 +--------------------------------------------
1 file changed, 8 insertions(+), 1050 deletions(-)
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 901c503de..83c706312 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -23,24 +23,12 @@ importers:
'@tiptap/extension-image':
specifier: ^3.11.1
version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-list':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
'@tiptap/extension-placeholder':
specifier: ^3.11.1
version: 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
'@tiptap/extension-table':
specifier: ^3.11.1
version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-table-cell':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-table-header':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-table-row':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
'@tiptap/markdown':
specifier: ^3.11.1
version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
@@ -262,190 +250,6 @@ importers:
specifier: ^0.8.0
version: 0.8.1
- src/plugins/demo-captcha:
- dependencies:
- react:
- specifier: ^18.2.0
- version: 18.3.1
- react-bootstrap:
- specifier: ^2.10.0
- version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react-dom:
- specifier: ^18.2.0
- version: 18.3.1(react@18.3.1)
- react-i18next:
- specifier: ^11.18.3
- version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- devDependencies:
- '@modyfi/vite-plugin-yaml':
- specifier: ^1.1.0
- version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- '@typescript-eslint/eslint-plugin':
- specifier: ^6.0.0
- version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/parser':
- specifier: ^6.0.0
- version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@vitejs/plugin-react-swc':
- specifier: ^3.3.2
- version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- eslint:
- specifier: ^8.45.0
- version: 8.57.1
- eslint-plugin-react-hooks:
- specifier: ^4.6.0
- version: 4.6.2(eslint@8.57.1)
- eslint-plugin-react-refresh:
- specifier: ^0.4.3
- version: 0.4.24(eslint@8.57.1)
- typescript:
- specifier: ^5.0.2
- version: 5.9.3
- vite:
- specifier: ^4.4.5
- version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- vite-plugin-dts:
- specifier: ^3.9.1
- version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
-
- src/plugins/demo-editor:
- dependencies:
- react:
- specifier: ^18.2.0
- version: 18.3.1
- react-bootstrap:
- specifier: ^2.10.0
- version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react-dom:
- specifier: ^18.2.0
- version: 18.3.1(react@18.3.1)
- react-i18next:
- specifier: ^11.18.3
- version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- devDependencies:
- '@modyfi/vite-plugin-yaml':
- specifier: ^1.1.0
- version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- '@typescript-eslint/eslint-plugin':
- specifier: ^6.0.0
- version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/parser':
- specifier: ^6.0.0
- version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@vitejs/plugin-react-swc':
- specifier: ^3.3.2
- version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- eslint:
- specifier: ^8.45.0
- version: 8.57.1
- eslint-plugin-react-hooks:
- specifier: ^4.6.0
- version: 4.6.2(eslint@8.57.1)
- eslint-plugin-react-refresh:
- specifier: ^0.4.3
- version: 0.4.24(eslint@8.57.1)
- typescript:
- specifier: ^5.0.2
- version: 5.9.3
- vite:
- specifier: ^4.4.5
- version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- vite-plugin-dts:
- specifier: ^3.9.1
- version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
-
- src/plugins/demo-render:
- dependencies:
- react:
- specifier: ^18.2.0
- version: 18.3.1
- react-bootstrap:
- specifier: ^2.10.0
- version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react-dom:
- specifier: ^18.2.0
- version: 18.3.1(react@18.3.1)
- react-i18next:
- specifier: ^11.18.3
- version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- devDependencies:
- '@modyfi/vite-plugin-yaml':
- specifier: ^1.1.0
- version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- '@typescript-eslint/eslint-plugin':
- specifier: ^6.0.0
- version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/parser':
- specifier: ^6.0.0
- version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@vitejs/plugin-react-swc':
- specifier: ^3.3.2
- version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- eslint:
- specifier: ^8.45.0
- version: 8.57.1
- eslint-plugin-react-hooks:
- specifier: ^4.6.0
- version: 4.6.2(eslint@8.57.1)
- eslint-plugin-react-refresh:
- specifier: ^0.4.3
- version: 0.4.24(eslint@8.57.1)
- typescript:
- specifier: ^5.0.2
- version: 5.9.3
- vite:
- specifier: ^4.4.5
- version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- vite-plugin-dts:
- specifier: ^3.9.1
- version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
-
- src/plugins/demo-route:
- dependencies:
- react:
- specifier: ^18.2.0
- version: 18.3.1
- react-bootstrap:
- specifier: ^2.10.0
- version: 2.10.6(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react-dom:
- specifier: ^18.2.0
- version: 18.3.1(react@18.3.1)
- react-i18next:
- specifier: ^11.18.3
- version: 11.18.6(i18next@21.10.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- devDependencies:
- '@modyfi/vite-plugin-yaml':
- specifier: ^1.1.0
- version: 1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- '@typescript-eslint/eslint-plugin':
- specifier: ^6.0.0
- version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/parser':
- specifier: ^6.0.0
- version: 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@vitejs/plugin-react-swc':
- specifier: ^3.3.2
- version: 3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
- eslint:
- specifier: ^8.45.0
- version: 8.57.1
- eslint-plugin-react-hooks:
- specifier: ^4.6.0
- version: 4.6.2(eslint@8.57.1)
- eslint-plugin-react-refresh:
- specifier: ^0.4.3
- version: 0.4.24(eslint@8.57.1)
- typescript:
- specifier: ^5.0.2
- version: 5.9.3
- vite:
- specifier: ^4.4.5
- version: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- vite-plugin-dts:
- specifier: ^3.9.1
- version: 3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))
-
packages:
'@alloc/quick-lru@5.2.0':
@@ -552,18 +356,10 @@ packages:
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
- '@babel/helper-string-parser@7.27.1':
- resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
- engines: {node: '>=6.9.0'}
-
'@babel/helper-validator-identifier@7.25.9':
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
- '@babel/helper-validator-identifier@7.28.5':
- resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
- engines: {node: '>=6.9.0'}
-
'@babel/helper-validator-option@7.25.9':
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
@@ -581,11 +377,6 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@7.28.5':
- resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
- engines: {node: '>=6.0.0'}
- hasBin: true
-
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9':
resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==}
engines: {node: '>=6.9.0'}
@@ -1172,10 +963,6 @@ packages:
resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.28.5':
- resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
- engines: {node: '>=6.9.0'}
-
'@bcoe/v8-coverage@0.2.3':
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
@@ -1437,138 +1224,6 @@ packages:
peerDependencies:
postcss-selector-parser: ^6.0.10
- '@esbuild/android-arm64@0.18.20':
- resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm@0.18.20':
- resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-x64@0.18.20':
- resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/darwin-arm64@0.18.20':
- resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.18.20':
- resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/freebsd-arm64@0.18.20':
- resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.18.20':
- resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/linux-arm64@0.18.20':
- resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm@0.18.20':
- resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
- engines: {node: '>=12'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-ia32@0.18.20':
- resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-loong64@0.18.20':
- resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
- engines: {node: '>=12'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.18.20':
- resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
- engines: {node: '>=12'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.18.20':
- resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
- engines: {node: '>=12'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.18.20':
- resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
- engines: {node: '>=12'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-s390x@0.18.20':
- resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
- engines: {node: '>=12'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-x64@0.18.20':
- resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/netbsd-x64@0.18.20':
- resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/openbsd-x64@0.18.20':
- resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/sunos-x64@0.18.20':
- resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/win32-arm64@0.18.20':
- resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
- engines: {node: '>=12'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-ia32@0.18.20':
- resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
- engines: {node: '>=12'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-x64@0.18.20':
- resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
- engines: {node: '>=12'}
- cpu: [x64]
- os: [win32]
-
'@eslint-community/eslint-utils@4.4.1':
resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1718,9 +1373,6 @@ packages:
'@jridgewell/sourcemap-codec@1.5.0':
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
- '@jridgewell/sourcemap-codec@1.5.5':
- resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
-
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -1784,24 +1436,6 @@ packages:
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
- '@microsoft/api-extractor-model@7.28.13':
- resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==}
-
- '@microsoft/api-extractor@7.43.0':
- resolution: {integrity: sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==}
- hasBin: true
-
- '@microsoft/tsdoc-config@0.16.2':
- resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==}
-
- '@microsoft/tsdoc@0.14.2':
- resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
-
- '@modyfi/vite-plugin-yaml@1.1.1':
- resolution: {integrity: sha512-rEbfFNlMGLKpAYs2RsfLAhxCHFa6M4QKHHk0A4EYcCJAUwFtFO6qiEdLjUGUTtnRUxAC7GxxCa+ZbeUILSDvqQ==}
- peerDependencies:
- vite: '>=3.2.7'
-
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
@@ -1879,9 +1513,6 @@ packages:
react: '>=16.14.0'
react-dom: '>=16.14.0'
- '@rolldown/pluginutils@1.0.0-beta.27':
- resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
-
'@rollup/plugin-babel@5.3.1':
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
engines: {node: '>= 10.0.0'}
@@ -1910,52 +1541,12 @@ packages:
peerDependencies:
rollup: ^1.20.0||^2.0.0
- '@rollup/pluginutils@5.1.0':
- resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
- peerDependenciesMeta:
- rollup:
- optional: true
-
- '@rollup/pluginutils@5.3.0':
- resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
- peerDependenciesMeta:
- rollup:
- optional: true
-
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@rushstack/eslint-patch@1.10.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
- '@rushstack/node-core-library@4.0.2':
- resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==}
- peerDependencies:
- '@types/node': '*'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@rushstack/rig-package@0.5.2':
- resolution: {integrity: sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==}
-
- '@rushstack/terminal@0.10.0':
- resolution: {integrity: sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==}
- peerDependencies:
- '@types/node': '*'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@rushstack/ts-command-line@4.19.1':
- resolution: {integrity: sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==}
-
'@sinclair/typebox@0.24.51':
resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
@@ -2250,21 +1841,6 @@ packages:
peerDependencies:
'@tiptap/core': ^3.11.1
- '@tiptap/extension-table-cell@3.11.1':
- resolution: {integrity: sha512-loFq2aZ+nDyrgxPjOCY/73YelRHqQWMw4oIiB4D0sYiDka+bBN/cV7LGWqfP5CldF8oSBJ8u+5ViDWCilTfDeg==}
- peerDependencies:
- '@tiptap/extension-table': ^3.11.1
-
- '@tiptap/extension-table-header@3.11.1':
- resolution: {integrity: sha512-oCn71L7FYxnpzHO7hn8xbaF6jxRhPinNsqgde+5OY23BEvVBpX8QFbOJkphQDyMwvSHAw/3S+ZF+vxHZU51aRg==}
- peerDependencies:
- '@tiptap/extension-table': ^3.11.1
-
- '@tiptap/extension-table-row@3.11.1':
- resolution: {integrity: sha512-KfB9axBtfE59/xC++4SS2iYkfEv93k5EWDbjIjC6T62FpfTYlBDOQutj79Nnmn8XkS5srT4nmxrohqRc1wUSQg==}
- peerDependencies:
- '@tiptap/extension-table': ^3.11.1
-
'@tiptap/extension-table@3.11.1':
resolution: {integrity: sha512-ae81cJgJ3cAA1Ry0JtLKtoPpA23YvMy0WnAw1gqnOX9yTYRYgT5W6MzP0O2rIBOI6b5Xkt6xtZzChjwMzioGbQ==}
peerDependencies:
@@ -2329,9 +1905,6 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
- '@types/argparse@1.0.38':
- resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
-
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -2666,37 +2239,6 @@ packages:
'@ungap/structured-clone@1.2.1':
resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==}
- '@vitejs/plugin-react-swc@3.11.0':
- resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==}
- peerDependencies:
- vite: ^4 || ^5 || ^6 || ^7
-
- '@volar/language-core@1.11.1':
- resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==}
-
- '@volar/source-map@1.11.1':
- resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==}
-
- '@volar/typescript@1.11.1':
- resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==}
-
- '@vue/compiler-core@3.5.25':
- resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==}
-
- '@vue/compiler-dom@3.5.25':
- resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==}
-
- '@vue/language-core@1.8.27':
- resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
- peerDependencies:
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
-
- '@vue/shared@3.5.25':
- resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==}
-
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
@@ -3300,10 +2842,6 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
- commander@9.5.0:
- resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
- engines: {node: ^12.20.0 || >=14}
-
common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
engines: {node: '>=4.0.0'}
@@ -3322,9 +2860,6 @@ packages:
resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==}
engines: {node: '>= 0.8.0'}
- computeds@0.0.1:
- resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
-
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@@ -3896,11 +3431,6 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
- esbuild@0.18.20:
- resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
- engines: {node: '>=12'}
- hasBin: true
-
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -4096,11 +3626,6 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
- eslint-plugin-react-refresh@0.4.24:
- resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==}
- peerDependencies:
- eslint: '>=8.40'
-
eslint-plugin-react@7.37.2:
resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==}
engines: {node: '>=4'}
@@ -4175,9 +3700,6 @@ packages:
estree-walker@1.0.1:
resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==}
- estree-walker@2.0.2:
- resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
-
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -4353,10 +3875,6 @@ packages:
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
engines: {node: '>=14.14'}
- fs-extra@7.0.1:
- resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
- engines: {node: '>=6 <7 || >=8'}
-
fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@@ -4666,10 +4184,6 @@ packages:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
- import-lazy@4.0.0:
- resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
- engines: {node: '>=8'}
-
import-local@3.2.0:
resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
engines: {node: '>=8'}
@@ -5130,9 +4644,6 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
- jju@1.4.0:
- resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
-
js-sha256@0.11.0:
resolution: {integrity: sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==}
@@ -5188,9 +4699,6 @@ packages:
engines: {node: '>=6'}
hasBin: true
- jsonfile@4.0.0:
- resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
-
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -5227,9 +4735,6 @@ packages:
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
engines: {node: '>= 8'}
- kolorist@1.8.0:
- resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
-
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -5311,14 +4816,6 @@ packages:
lodash.flow@3.5.0:
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
- lodash.get@4.4.2:
- resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
- deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
-
- lodash.isequal@4.5.0:
- resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
- deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
-
lodash.isfunction@3.0.9:
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
@@ -5383,9 +4880,6 @@ packages:
magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
- magic-string@0.30.21:
- resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
-
make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
@@ -5503,9 +4997,6 @@ packages:
minimalistic-assert@1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
- minimatch@3.0.8:
- resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==}
-
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -5542,9 +5033,6 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- muggle-string@0.3.1:
- resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
-
multicast-dns@7.2.5:
resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==}
hasBin: true
@@ -5770,9 +5258,6 @@ packages:
pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
- path-browserify@1.0.1:
- resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
-
path-exists@3.0.0:
resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
engines: {node: '>=4'}
@@ -5820,10 +5305,6 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
- picomatch@4.0.3:
- resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
- engines: {node: '>=12'}
-
pidtree@0.6.0:
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
engines: {node: '>=0.10'}
@@ -6710,9 +6191,6 @@ packages:
resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==}
engines: {node: '>=10'}
- resolve@1.19.0:
- resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==}
-
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
@@ -6757,11 +6235,6 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
- rollup@3.29.5:
- resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==}
- engines: {node: '>=14.18.0', npm: '>=8.0.0'}
- hasBin: true
-
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
@@ -7304,10 +6777,6 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
- tosource@2.0.0-alpha.3:
- resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==}
- engines: {node: '>=10'}
-
tough-cookie@4.1.4:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
@@ -7435,16 +6904,6 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
- typescript@5.4.2:
- resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==}
- engines: {node: '>=14.17'}
- hasBin: true
-
- typescript@5.9.3:
- resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
- engines: {node: '>=14.17'}
- hasBin: true
-
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
@@ -7484,10 +6943,6 @@ packages:
resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
engines: {node: '>=8'}
- universalify@0.1.2:
- resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
- engines: {node: '>= 4.0.0'}
-
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@@ -7555,64 +7010,16 @@ packages:
validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
- validator@13.15.23:
- resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
- engines: {node: '>= 0.10'}
-
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
- vite-plugin-dts@3.9.1:
- resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==}
- engines: {node: ^14.18.0 || >=16.0.0}
- peerDependencies:
- typescript: '*'
- vite: '*'
- peerDependenciesMeta:
- vite:
- optional: true
-
- vite@4.5.14:
- resolution: {integrity: sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==}
- 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
-
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
vue-template-compiler@2.7.16:
- resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
-
- vue-tsc@1.8.27:
- resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==}
- hasBin: true
- peerDependencies:
- typescript: '*'
+ resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
w3c-hr-time@1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
@@ -7923,11 +7330,6 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- z-schema@5.0.5:
- resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==}
- engines: {node: '>=8.0.0'}
- hasBin: true
-
zustand@5.0.2:
resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==}
engines: {node: '>=12.20.0'}
@@ -8105,12 +7507,8 @@ snapshots:
'@babel/helper-string-parser@7.25.9': {}
- '@babel/helper-string-parser@7.27.1': {}
-
'@babel/helper-validator-identifier@7.25.9': {}
- '@babel/helper-validator-identifier@7.28.5': {}
-
'@babel/helper-validator-option@7.25.9': {}
'@babel/helper-wrap-function@7.25.9':
@@ -8130,10 +7528,6 @@ snapshots:
dependencies:
'@babel/types': 7.26.3
- '@babel/parser@7.28.5':
- dependencies:
- '@babel/types': 7.28.5
-
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
@@ -8849,11 +8243,6 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
- '@babel/types@7.28.5':
- dependencies:
- '@babel/helper-string-parser': 7.27.1
- '@babel/helper-validator-identifier': 7.28.5
-
'@bcoe/v8-coverage@0.2.3': {}
'@codemirror/autocomplete@6.18.3(@codemirror/language@6.10.6)(@codemirror/state@6.5.0)(@codemirror/view@6.35.3)(@lezer/common@1.2.3)':
@@ -9310,72 +8699,6 @@ snapshots:
dependencies:
postcss-selector-parser: 6.1.2
- '@esbuild/android-arm64@0.18.20':
- optional: true
-
- '@esbuild/android-arm@0.18.20':
- optional: true
-
- '@esbuild/android-x64@0.18.20':
- optional: true
-
- '@esbuild/darwin-arm64@0.18.20':
- optional: true
-
- '@esbuild/darwin-x64@0.18.20':
- optional: true
-
- '@esbuild/freebsd-arm64@0.18.20':
- optional: true
-
- '@esbuild/freebsd-x64@0.18.20':
- optional: true
-
- '@esbuild/linux-arm64@0.18.20':
- optional: true
-
- '@esbuild/linux-arm@0.18.20':
- optional: true
-
- '@esbuild/linux-ia32@0.18.20':
- optional: true
-
- '@esbuild/linux-loong64@0.18.20':
- optional: true
-
- '@esbuild/linux-mips64el@0.18.20':
- optional: true
-
- '@esbuild/linux-ppc64@0.18.20':
- optional: true
-
- '@esbuild/linux-riscv64@0.18.20':
- optional: true
-
- '@esbuild/linux-s390x@0.18.20':
- optional: true
-
- '@esbuild/linux-x64@0.18.20':
- optional: true
-
- '@esbuild/netbsd-x64@0.18.20':
- optional: true
-
- '@esbuild/openbsd-x64@0.18.20':
- optional: true
-
- '@esbuild/sunos-x64@0.18.20':
- optional: true
-
- '@esbuild/win32-arm64@0.18.20':
- optional: true
-
- '@esbuild/win32-ia32@0.18.20':
- optional: true
-
- '@esbuild/win32-x64@0.18.20':
- optional: true
-
'@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
@@ -9649,8 +8972,6 @@ snapshots:
'@jridgewell/sourcemap-codec@1.5.0': {}
- '@jridgewell/sourcemap-codec@1.5.5': {}
-
'@jridgewell/trace-mapping@0.3.25':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
@@ -9758,50 +9079,6 @@ snapshots:
'@marijn/find-cluster-break@1.0.2': {}
- '@microsoft/api-extractor-model@7.28.13(@types/node@20.5.1)':
- dependencies:
- '@microsoft/tsdoc': 0.14.2
- '@microsoft/tsdoc-config': 0.16.2
- '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
- transitivePeerDependencies:
- - '@types/node'
-
- '@microsoft/api-extractor@7.43.0(@types/node@20.5.1)':
- dependencies:
- '@microsoft/api-extractor-model': 7.28.13(@types/node@20.5.1)
- '@microsoft/tsdoc': 0.14.2
- '@microsoft/tsdoc-config': 0.16.2
- '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
- '@rushstack/rig-package': 0.5.2
- '@rushstack/terminal': 0.10.0(@types/node@20.5.1)
- '@rushstack/ts-command-line': 4.19.1(@types/node@20.5.1)
- lodash: 4.17.21
- minimatch: 3.0.8
- resolve: 1.22.8
- semver: 7.5.4
- source-map: 0.6.1
- typescript: 5.4.2
- transitivePeerDependencies:
- - '@types/node'
-
- '@microsoft/tsdoc-config@0.16.2':
- dependencies:
- '@microsoft/tsdoc': 0.14.2
- ajv: 6.12.6
- jju: 1.4.0
- resolve: 1.19.0
-
- '@microsoft/tsdoc@0.14.2': {}
-
- '@modyfi/vite-plugin-yaml@1.1.1(rollup@3.29.5)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))':
- dependencies:
- '@rollup/pluginutils': 5.1.0(rollup@3.29.5)
- js-yaml: 4.1.0
- tosource: 2.0.0-alpha.3
- vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- transitivePeerDependencies:
- - rollup
-
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
dependencies:
eslint-scope: 5.1.1
@@ -9871,8 +9148,6 @@ snapshots:
uncontrollable: 8.0.4(react@18.3.1)
warning: 4.0.3
- '@rolldown/pluginutils@1.0.0-beta.27': {}
-
'@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@2.79.2)':
dependencies:
'@babel/core': 7.26.0
@@ -9907,58 +9182,10 @@ snapshots:
picomatch: 2.3.1
rollup: 2.79.2
- '@rollup/pluginutils@5.1.0(rollup@3.29.5)':
- dependencies:
- '@types/estree': 1.0.6
- estree-walker: 2.0.2
- picomatch: 2.3.1
- optionalDependencies:
- rollup: 3.29.5
-
- '@rollup/pluginutils@5.3.0(rollup@3.29.5)':
- dependencies:
- '@types/estree': 1.0.6
- estree-walker: 2.0.2
- picomatch: 4.0.3
- optionalDependencies:
- rollup: 3.29.5
-
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.10.4': {}
- '@rushstack/node-core-library@4.0.2(@types/node@20.5.1)':
- dependencies:
- fs-extra: 7.0.1
- import-lazy: 4.0.0
- jju: 1.4.0
- resolve: 1.22.8
- semver: 7.5.4
- z-schema: 5.0.5
- optionalDependencies:
- '@types/node': 20.5.1
-
- '@rushstack/rig-package@0.5.2':
- dependencies:
- resolve: 1.22.8
- strip-json-comments: 3.1.1
-
- '@rushstack/terminal@0.10.0(@types/node@20.5.1)':
- dependencies:
- '@rushstack/node-core-library': 4.0.2(@types/node@20.5.1)
- supports-color: 8.1.1
- optionalDependencies:
- '@types/node': 20.5.1
-
- '@rushstack/ts-command-line@4.19.1(@types/node@20.5.1)':
- dependencies:
- '@rushstack/terminal': 0.10.0(@types/node@20.5.1)
- '@types/argparse': 1.0.38
- argparse: 1.0.10
- string-argv: 0.3.2
- transitivePeerDependencies:
- - '@types/node'
-
'@sinclair/typebox@0.24.51': {}
'@sinonjs/commons@1.8.6':
@@ -10088,8 +9315,10 @@ snapshots:
'@swc/core-win32-arm64-msvc': 1.15.3
'@swc/core-win32-ia32-msvc': 1.15.3
'@swc/core-win32-x64-msvc': 1.15.3
+ optional: true
- '@swc/counter@0.1.3': {}
+ '@swc/counter@0.1.3':
+ optional: true
'@swc/helpers@0.5.15':
dependencies:
@@ -10098,6 +9327,7 @@ snapshots:
'@swc/types@0.1.25':
dependencies:
'@swc/counter': 0.1.3
+ optional: true
'@testing-library/dom@8.20.1':
dependencies:
@@ -10244,18 +9474,6 @@ snapshots:
dependencies:
'@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
- '@tiptap/extension-table-cell@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
- dependencies:
- '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
-
- '@tiptap/extension-table-header@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
- dependencies:
- '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
-
- '@tiptap/extension-table-row@3.11.1(@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
- dependencies:
- '@tiptap/extension-table': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
-
'@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
'@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
@@ -10357,8 +9575,6 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
- '@types/argparse@1.0.38': {}
-
'@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
@@ -10636,26 +9852,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)':
- dependencies:
- '@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.4.0
- eslint: 8.57.1
- graphemer: 1.4.0
- ignore: 5.3.2
- natural-compare: 1.4.0
- semver: 7.6.3
- ts-api-utils: 1.4.3(typescript@5.9.3)
- optionalDependencies:
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/experimental-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
dependencies:
'@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
@@ -10689,19 +9885,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/types': 6.21.0
- '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.4.0
- eslint: 8.57.1
- optionalDependencies:
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/scope-manager@5.62.0':
dependencies:
'@typescript-eslint/types': 5.62.0
@@ -10736,18 +9919,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
- '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3)
- debug: 4.4.0
- eslint: 8.57.1
- ts-api-utils: 1.4.3(typescript@5.9.3)
- optionalDependencies:
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/types@5.62.0': {}
'@typescript-eslint/types@6.21.0': {}
@@ -10781,21 +9952,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)':
- dependencies:
- '@typescript-eslint/types': 6.21.0
- '@typescript-eslint/visitor-keys': 6.21.0
- debug: 4.4.0
- globby: 11.1.0
- is-glob: 4.0.3
- minimatch: 9.0.3
- semver: 7.6.3
- ts-api-utils: 1.4.3(typescript@5.9.3)
- optionalDependencies:
- typescript: 5.9.3
- transitivePeerDependencies:
- - supports-color
-
'@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
@@ -10825,20 +9981,6 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)':
- dependencies:
- '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
- '@types/json-schema': 7.0.15
- '@types/semver': 7.5.8
- '@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/types': 6.21.0
- '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
- eslint: 8.57.1
- semver: 7.6.3
- transitivePeerDependencies:
- - supports-color
- - typescript
-
'@typescript-eslint/visitor-keys@5.62.0':
dependencies:
'@typescript-eslint/types': 5.62.0
@@ -10851,56 +9993,6 @@ snapshots:
'@ungap/structured-clone@1.2.1': {}
- '@vitejs/plugin-react-swc@3.11.0(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0))':
- dependencies:
- '@rolldown/pluginutils': 1.0.0-beta.27
- '@swc/core': 1.15.3
- vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- transitivePeerDependencies:
- - '@swc/helpers'
-
- '@volar/language-core@1.11.1':
- dependencies:
- '@volar/source-map': 1.11.1
-
- '@volar/source-map@1.11.1':
- dependencies:
- muggle-string: 0.3.1
-
- '@volar/typescript@1.11.1':
- dependencies:
- '@volar/language-core': 1.11.1
- path-browserify: 1.0.1
-
- '@vue/compiler-core@3.5.25':
- dependencies:
- '@babel/parser': 7.28.5
- '@vue/shared': 3.5.25
- entities: 4.5.0
- estree-walker: 2.0.2
- source-map-js: 1.2.1
-
- '@vue/compiler-dom@3.5.25':
- dependencies:
- '@vue/compiler-core': 3.5.25
- '@vue/shared': 3.5.25
-
- '@vue/language-core@1.8.27(typescript@5.9.3)':
- dependencies:
- '@volar/language-core': 1.11.1
- '@volar/source-map': 1.11.1
- '@vue/compiler-dom': 3.5.25
- '@vue/shared': 3.5.25
- computeds: 0.0.1
- minimatch: 9.0.5
- muggle-string: 0.3.1
- path-browserify: 1.0.1
- vue-template-compiler: 2.7.16
- optionalDependencies:
- typescript: 5.9.3
-
- '@vue/shared@3.5.25': {}
-
'@webassemblyjs/ast@1.14.1':
dependencies:
'@webassemblyjs/helper-numbers': 1.13.2
@@ -11616,9 +10708,6 @@ snapshots:
commander@8.3.0: {}
- commander@9.5.0:
- optional: true
-
common-tags@1.8.2: {}
commondir@1.0.1: {}
@@ -11644,8 +10733,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- computeds@0.0.1: {}
-
concat-map@0.0.1: {}
confusing-browser-globals@1.0.11: {}
@@ -11918,7 +11005,8 @@ snapshots:
dayjs@1.11.13: {}
- de-indent@1.0.2: {}
+ de-indent@1.0.2:
+ optional: true
debug@2.6.9:
dependencies:
@@ -12257,31 +11345,6 @@ snapshots:
is-date-object: 1.0.5
is-symbol: 1.1.0
- esbuild@0.18.20:
- 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
-
escalade@3.2.0: {}
escape-html@1.0.3: {}
@@ -12557,10 +11620,6 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-plugin-react-refresh@0.4.24(eslint@8.57.1):
- dependencies:
- eslint: 8.57.1
-
eslint-plugin-react@7.37.2(eslint@8.57.1):
dependencies:
array-includes: 3.1.8
@@ -12682,8 +11741,6 @@ snapshots:
estree-walker@1.0.1: {}
- estree-walker@2.0.2: {}
-
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -12919,12 +11976,6 @@ snapshots:
jsonfile: 6.1.0
universalify: 2.0.1
- fs-extra@7.0.1:
- dependencies:
- graceful-fs: 4.2.11
- jsonfile: 4.0.0
- universalify: 0.1.2
-
fs-extra@9.1.0:
dependencies:
at-least-node: 1.0.0
@@ -13246,8 +12297,6 @@ snapshots:
parent-module: 1.0.1
resolve-from: 4.0.0
- import-lazy@4.0.0: {}
-
import-local@3.2.0:
dependencies:
pkg-dir: 4.2.0
@@ -13933,8 +12982,6 @@ snapshots:
jiti@1.21.6: {}
- jju@1.4.0: {}
-
js-sha256@0.11.0: {}
js-tokens@4.0.0: {}
@@ -14002,10 +13049,6 @@ snapshots:
json5@2.2.3: {}
- jsonfile@4.0.0:
- optionalDependencies:
- graceful-fs: 4.2.11
-
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
@@ -14045,8 +13088,6 @@ snapshots:
klona@2.0.6: {}
- kolorist@1.8.0: {}
-
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
@@ -14135,10 +13176,6 @@ snapshots:
lodash.flow@3.5.0: {}
- lodash.get@4.4.2: {}
-
- lodash.isequal@4.5.0: {}
-
lodash.isfunction@3.0.9: {}
lodash.isplainobject@4.0.6: {}
@@ -14195,10 +13232,6 @@ snapshots:
dependencies:
sourcemap-codec: 1.4.8
- magic-string@0.30.21:
- dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
-
make-dir@3.1.0:
dependencies:
semver: 6.3.1
@@ -14295,10 +13328,6 @@ snapshots:
minimalistic-assert@1.0.1: {}
- minimatch@3.0.8:
- dependencies:
- brace-expansion: 1.1.11
-
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
@@ -14333,8 +13362,6 @@ snapshots:
ms@2.1.3: {}
- muggle-string@0.3.1: {}
-
multicast-dns@7.2.5:
dependencies:
dns-packet: 5.6.1
@@ -14578,8 +13605,6 @@ snapshots:
no-case: 3.0.4
tslib: 2.8.1
- path-browserify@1.0.1: {}
-
path-exists@3.0.0: {}
path-exists@4.0.0: {}
@@ -14609,8 +13634,6 @@ snapshots:
picomatch@2.3.1: {}
- picomatch@4.0.3: {}
-
pidtree@0.6.0: {}
pify@2.3.0: {}
@@ -15650,11 +14673,6 @@ snapshots:
resolve.exports@1.1.1: {}
- resolve@1.19.0:
- dependencies:
- is-core-module: 2.15.1
- path-parse: 1.0.7
-
resolve@1.22.8:
dependencies:
is-core-module: 2.15.1
@@ -15698,10 +14716,6 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
- rollup@3.29.5:
- optionalDependencies:
- fsevents: 2.3.3
-
rope-sequence@1.3.4: {}
run-parallel@1.2.0:
@@ -16326,8 +15340,6 @@ snapshots:
toidentifier@1.0.1: {}
- tosource@2.0.0-alpha.3: {}
-
tough-cookie@4.1.4:
dependencies:
psl: 1.15.0
@@ -16351,10 +15363,6 @@ snapshots:
dependencies:
typescript: 4.9.5
- ts-api-utils@1.4.3(typescript@5.9.3):
- dependencies:
- typescript: 5.9.3
-
ts-interface-checker@0.1.13: {}
ts-node@10.9.2(@swc/core@1.15.3)(@types/node@16.18.121)(typescript@4.9.5):
@@ -16464,10 +15472,6 @@ snapshots:
typescript@4.9.5: {}
- typescript@5.4.2: {}
-
- typescript@5.9.3: {}
-
uc.micro@2.1.0: {}
unbox-primitive@1.0.2:
@@ -16506,8 +15510,6 @@ snapshots:
dependencies:
crypto-random-string: 2.0.0
- universalify@0.1.2: {}
-
universalify@0.2.0: {}
universalify@2.0.1: {}
@@ -16567,51 +15569,15 @@ snapshots:
spdx-correct: 3.2.0
spdx-expression-parse: 3.0.1
- validator@13.15.23: {}
-
vary@1.1.2: {}
- vite-plugin-dts@3.9.1(@types/node@20.5.1)(rollup@3.29.5)(typescript@5.9.3)(vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)):
- dependencies:
- '@microsoft/api-extractor': 7.43.0(@types/node@20.5.1)
- '@rollup/pluginutils': 5.3.0(rollup@3.29.5)
- '@vue/language-core': 1.8.27(typescript@5.9.3)
- debug: 4.4.0
- kolorist: 1.8.0
- magic-string: 0.30.21
- typescript: 5.9.3
- vue-tsc: 1.8.27(typescript@5.9.3)
- optionalDependencies:
- vite: 4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0)
- transitivePeerDependencies:
- - '@types/node'
- - rollup
- - supports-color
-
- vite@4.5.14(@types/node@20.5.1)(sass@1.54.4)(terser@5.37.0):
- dependencies:
- esbuild: 0.18.20
- postcss: 8.4.49
- rollup: 3.29.5
- optionalDependencies:
- '@types/node': 20.5.1
- fsevents: 2.3.3
- sass: 1.54.4
- terser: 5.37.0
-
void-elements@3.1.0: {}
vue-template-compiler@2.7.16:
dependencies:
de-indent: 1.0.2
he: 1.2.0
-
- vue-tsc@1.8.27(typescript@5.9.3):
- dependencies:
- '@volar/typescript': 1.11.1
- '@vue/language-core': 1.8.27(typescript@5.9.3)
- semver: 7.6.3
- typescript: 5.9.3
+ optional: true
w3c-hr-time@1.0.2:
dependencies:
@@ -17055,14 +16021,6 @@ snapshots:
yocto-queue@0.1.0: {}
- z-schema@5.0.5:
- dependencies:
- lodash.get: 4.4.2
- lodash.isequal: 4.5.0
- validator: 13.15.23
- optionalDependencies:
- commander: 9.5.0
-
zustand@5.0.2(@types/react@18.3.16)(immer@9.0.21)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
optionalDependencies:
'@types/react': 18.3.16
From 3d43700556306965f7816e6cdb031327c16193a6 Mon Sep 17 00:00:00 2001
From: robin
Date: Tue, 16 Dec 2025 14:50:29 +0800
Subject: [PATCH 27/92] refactor(editor): streamline editor component structure
and enhance command methods
- Removed unnecessary conditional rendering in the MDEditor component for the PluginRender.
- Simplified the MarkdownEditor component's useEffect hooks for better clarity.
- Refactored command methods to utilize self-referencing for improved maintainability.
---
ui/src/components/Editor/MarkdownEditor.tsx | 9 +---
ui/src/components/Editor/index.tsx | 50 +++++++++----------
.../Editor/utils/codemirror/commands.ts | 43 ++++++++--------
3 files changed, 48 insertions(+), 54 deletions(-)
diff --git a/ui/src/components/Editor/MarkdownEditor.tsx b/ui/src/components/Editor/MarkdownEditor.tsx
index 10145a228..fe5d8bb44 100644
--- a/ui/src/components/Editor/MarkdownEditor.tsx
+++ b/ui/src/components/Editor/MarkdownEditor.tsx
@@ -55,30 +55,25 @@ const MarkdownEditor: React.FC = ({
autoFocus,
});
- // 初始化内容(只在编辑器创建时执行)
useEffect(() => {
if (!editor) {
return;
}
- // 初始化编辑器内容
editor.setValue(value || '');
lastSyncedValueRef.current = value || '';
onEditorReady?.(editor);
- }, [editor]); // 只在编辑器创建时执行
+ }, [editor]);
- // 当外部 value 变化时更新(但不是用户输入导致的)
useEffect(() => {
if (!editor) {
return;
}
- // 如果 value 和 lastSyncedValueRef 相同,说明是用户输入导致的更新,跳过
if (value === lastSyncedValueRef.current) {
return;
}
- // 外部 value 真正变化,更新编辑器
const currentValue = editor.getValue();
if (currentValue !== value) {
editor.setValue(value || '');
@@ -86,11 +81,9 @@ const MarkdownEditor: React.FC = ({
}
}, [editor, value]);
- // 清理:组件卸载时销毁编辑器
useEffect(() => {
return () => {
if (editor) {
- // CodeMirror EditorView 有 destroy 方法
const view = editor as unknown as EditorView;
if (view.destroy) {
view.destroy();
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index a914d912c..94bd5836e 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -123,32 +123,30 @@ const MDEditor: ForwardRefRenderFunction = (
- {currentEditor && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/components/Editor/utils/codemirror/commands.ts b/ui/src/components/Editor/utils/codemirror/commands.ts
index 59ab064ec..152bc632d 100644
--- a/ui/src/components/Editor/utils/codemirror/commands.ts
+++ b/ui/src/components/Editor/utils/codemirror/commands.ts
@@ -33,7 +33,8 @@ import { Editor, Level } from '../../types';
* @returns Object containing all command methods
*/
export function createCommandMethods(editor: Editor) {
- return {
+ // Create methods object that allows self-reference
+ const methods = {
wrapText: (before: string, after = before, defaultText) => {
const range = editor.state.selection.ranges[0];
const selectedText = editor.state.sliceDoc(range.from, range.to);
@@ -79,39 +80,39 @@ export function createCommandMethods(editor: Editor) {
},
insertBold: (text?: string) => {
- editor.wrapText('**', '**', text || 'bold text');
+ methods.wrapText('**', '**', text || 'bold text');
},
insertItalic: (text?: string) => {
- editor.wrapText('*', '*', text || 'italic text');
+ methods.wrapText('*', '*', text || 'italic text');
},
insertCode: (text?: string) => {
- editor.wrapText('`', '`', text || 'code');
+ methods.wrapText('`', '`', text || 'code');
},
insertStrikethrough: (text?: string) => {
- editor.wrapText('~~', '~~', text || 'strikethrough text');
+ methods.wrapText('~~', '~~', text || 'strikethrough text');
},
insertHeading: (level: Level, text?: string) => {
const headingText = '#'.repeat(level);
- editor.wrapText(`${headingText} `, '', text || 'heading');
+ methods.wrapText(`${headingText} `, '', text || 'heading');
},
insertBlockquote: (text?: string) => {
- editor.wrapText('> ', '', text || 'quote');
+ methods.wrapText('> ', '', text || 'quote');
},
insertCodeBlock: (language?: string, code?: string) => {
const lang = language || '';
const codeText = code || '';
const block = `\`\`\`${lang}\n${codeText}\n\`\`\``;
- editor.appendBlock(block);
+ methods.appendBlock(block);
},
insertHorizontalRule: () => {
- editor.appendBlock('---');
+ methods.appendBlock('---');
},
insertOrderedList: () => {
@@ -121,7 +122,7 @@ export function createCommandMethods(editor: Editor) {
if (/^\d+\.\s/.test(lineText)) {
return;
}
- editor.replaceLines((lineItem) => {
+ methods.replaceLines((lineItem) => {
if (lineItem.trim() === '') {
return lineItem;
}
@@ -136,7 +137,7 @@ export function createCommandMethods(editor: Editor) {
if (/^[-*+]\s/.test(lineText)) {
return;
}
- editor.replaceLines((lineItem) => {
+ methods.replaceLines((lineItem) => {
if (lineItem.trim() === '') {
return lineItem;
}
@@ -149,11 +150,11 @@ export function createCommandMethods(editor: Editor) {
const line = editor.state.doc.line(cursor.line);
const lineText = line.text.trim();
if (/^\d+\.\s/.test(lineText)) {
- editor.replaceLines((lineItem) => {
+ methods.replaceLines((lineItem) => {
return lineItem.replace(/^\d+\.\s/, '');
});
} else {
- editor.insertOrderedList();
+ methods.insertOrderedList();
}
},
@@ -162,22 +163,22 @@ export function createCommandMethods(editor: Editor) {
const line = editor.state.doc.line(cursor.line);
const lineText = line.text.trim();
if (/^[-*+]\s/.test(lineText)) {
- editor.replaceLines((lineItem) => {
+ methods.replaceLines((lineItem) => {
return lineItem.replace(/^[-*+]\s/, '');
});
} else {
- editor.insertUnorderedList();
+ methods.insertUnorderedList();
}
},
insertLink: (url: string, text?: string) => {
const linkText = text || url;
- editor.wrapText('[', `](${url})`, linkText);
+ methods.wrapText('[', `](${url})`, linkText);
},
insertImage: (url: string, alt?: string) => {
const altText = alt || '';
- editor.wrapText('`, altText);
+ methods.wrapText('`, altText);
},
insertTable: (rows = 3, cols = 3) => {
@@ -192,11 +193,11 @@ export function createCommandMethods(editor: Editor) {
table.push(`| ${'---'.repeat(cols).split('').join(' | ')} |`);
}
}
- editor.appendBlock(table.join('\n'));
+ methods.appendBlock(table.join('\n'));
},
indent: () => {
- editor.replaceLines((line) => {
+ methods.replaceLines((line) => {
if (line.trim() === '') {
return line;
}
@@ -205,7 +206,7 @@ export function createCommandMethods(editor: Editor) {
},
outdent: () => {
- editor.replaceLines((line) => {
+ methods.replaceLines((line) => {
if (line.trim() === '') {
return line;
}
@@ -261,4 +262,6 @@ export function createCommandMethods(editor: Editor) {
return /^[-*+]\s/.test(lineText);
},
};
+
+ return methods;
}
From 498c1421983feface8b1bee2981eb9521bcc56fb Mon Sep 17 00:00:00 2001
From: robin
Date: Tue, 16 Dec 2025 15:07:29 +0800
Subject: [PATCH 28/92] feat(editor): rename WYSIWYG editor to Rich editor and
implement new RichEditor component
- Updated the editor mode from WYSIWYG to Rich in the MDEditor component.
- Introduced a new RichEditor component utilizing TipTap for enhanced editing capabilities.
- Adjusted styles and references in the editor components to reflect the new naming convention.
---
.../Editor/{WysiwygEditor.tsx => RichEditor.tsx} | 8 ++++----
ui/src/components/Editor/index.scss | 3 +--
ui/src/components/Editor/index.tsx | 16 ++++++++--------
ui/src/components/Editor/utils/tiptap/adapter.ts | 2 +-
4 files changed, 14 insertions(+), 15 deletions(-)
rename ui/src/components/Editor/{WysiwygEditor.tsx => RichEditor.tsx} (96%)
diff --git a/ui/src/components/Editor/WysiwygEditor.tsx b/ui/src/components/Editor/RichEditor.tsx
similarity index 96%
rename from ui/src/components/Editor/WysiwygEditor.tsx
rename to ui/src/components/Editor/RichEditor.tsx
index 7ddb28368..c574551c6 100644
--- a/ui/src/components/Editor/WysiwygEditor.tsx
+++ b/ui/src/components/Editor/RichEditor.tsx
@@ -33,7 +33,7 @@ import { TableKit } from '@tiptap/extension-table';
import { Editor } from './types';
import { createTipTapAdapter } from './utils/tiptap/adapter';
-interface WysiwygEditorProps {
+interface RichEditorProps {
value: string;
onChange?: (value: string) => void;
onFocus?: () => void;
@@ -43,7 +43,7 @@ interface WysiwygEditorProps {
onEditorReady?: (editor: Editor) => void;
}
-const WysiwygEditor: React.FC = ({
+const RichEditor: React.FC = ({
value,
onChange,
onFocus,
@@ -153,10 +153,10 @@ const WysiwygEditor: React.FC = ({
}
return (
-
+
);
};
-export default WysiwygEditor;
+export default RichEditor;
diff --git a/ui/src/components/Editor/index.scss b/ui/src/components/Editor/index.scss
index 20ab5bab4..4775e84a5 100644
--- a/ui/src/components/Editor/index.scss
+++ b/ui/src/components/Editor/index.scss
@@ -114,8 +114,7 @@
height: 264px;
}
- // WYSIWYG 编辑器样式
- .wysiwyg-editor-wrap {
+ .rich-editor-wrap {
height: 264px;
overflow-y: auto;
padding: 0.375rem 0.75rem;
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index 94bd5836e..c293fa653 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -51,7 +51,7 @@ import {
import { htmlRender } from './utils';
import Viewer from './Viewer';
import { EditorContext } from './EditorContext';
-import WysiwygEditor from './WysiwygEditor';
+import RichEditor from './RichEditor';
import MarkdownEditor from './MarkdownEditor';
import { Editor } from './types';
@@ -86,14 +86,14 @@ const MDEditor: ForwardRefRenderFunction
= (
},
ref,
) => {
- const [mode, setMode] = useState<'markdown' | 'wysiwyg'>('markdown');
+ const [mode, setMode] = useState<'markdown' | 'rich'>('markdown');
const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
useRenderPlugin(previewRef.current?.element);
const handleModeChange = useCallback(
- (newMode: 'markdown' | 'wysiwyg') => {
+ (newMode: 'markdown' | 'rich') => {
if (newMode === mode) {
return;
}
@@ -116,7 +116,7 @@ const MDEditor: ForwardRefRenderFunction = (
[getHtml],
);
- const EditorComponent = mode === 'markdown' ? MarkdownEditor : WysiwygEditor;
+ const EditorComponent = mode === 'markdown' ? MarkdownEditor : RichEditor;
return (
<>
@@ -155,17 +155,17 @@ const MDEditor: ForwardRefRenderFunction = (
className={`btn btn-sm ${
mode === 'markdown' ? 'btn-primary' : 'btn-outline-secondary'
}`}
- title="Markdown 模式"
+ title="Markdown Mode"
onClick={() => handleModeChange('markdown')}>
handleModeChange('wysiwyg')}>
+ title="Rich Mode"
+ onClick={() => handleModeChange('rich')}>
diff --git a/ui/src/components/Editor/utils/tiptap/adapter.ts b/ui/src/components/Editor/utils/tiptap/adapter.ts
index 862cbf6c2..415edb175 100644
--- a/ui/src/components/Editor/utils/tiptap/adapter.ts
+++ b/ui/src/components/Editor/utils/tiptap/adapter.ts
@@ -29,7 +29,7 @@ import { createCommandMethods } from './commands';
* Adapts TipTap editor to CodeMirror editor interface
*
* This adapter function converts TipTap editor's API to a CodeMirror-compatible interface,
- * enabling toolbar components to work properly in WYSIWYG mode. The adapter implements
+ * enabling toolbar components to work properly in Rich mode. The adapter implements
* the complete `ExtendEditor` interface, including base methods, event handling, and command methods.
*
* @param editor - TipTap editor instance
From d87726bd6e54e4fc88f8338f6f6499f878b00c4f Mon Sep 17 00:00:00 2001
From: robin
Date: Wed, 17 Dec 2025 10:38:31 +0800
Subject: [PATCH 29/92] refactor(editor): enhance editor components with base
props and initialization logic
- Introduced BaseEditorProps interface to standardize props across MarkdownEditor and RichEditor components.
- Improved initialization logic in RichEditor and MarkdownEditor to handle editor state more effectively.
- Updated useEditor hook to support initial values and prevent unnecessary updates during prop changes.
- Refactored command methods to utilize dispatch for state changes in CodeMirror editor.
---
ui/src/components/Editor/MarkdownEditor.tsx | 33 ++--
ui/src/components/Editor/RichEditor.tsx | 137 +++++++------
ui/src/components/Editor/ToolBars/chart.tsx | 181 ------------------
ui/src/components/Editor/ToolBars/index.ts | 2 -
ui/src/components/Editor/index.tsx | 5 +-
ui/src/components/Editor/types.ts | 15 +-
.../Editor/utils/codemirror/commands.ts | 16 +-
ui/src/components/Editor/utils/index.ts | 26 ++-
.../components/Editor/utils/tiptap/events.ts | 8 +-
9 files changed, 145 insertions(+), 278 deletions(-)
delete mode 100644 ui/src/components/Editor/ToolBars/chart.tsx
diff --git a/ui/src/components/Editor/MarkdownEditor.tsx b/ui/src/components/Editor/MarkdownEditor.tsx
index fe5d8bb44..068a408f7 100644
--- a/ui/src/components/Editor/MarkdownEditor.tsx
+++ b/ui/src/components/Editor/MarkdownEditor.tsx
@@ -21,18 +21,10 @@ import { useEffect, useRef } from 'react';
import { EditorView } from '@codemirror/view';
-import { Editor } from './types';
+import { BaseEditorProps } from './types';
import { useEditor } from './utils';
-interface MarkdownEditorProps {
- value: string;
- onChange?: (value: string) => void;
- onFocus?: () => void;
- onBlur?: () => void;
- placeholder?: string;
- autoFocus?: boolean;
- onEditorReady?: (editor: Editor) => void;
-}
+interface MarkdownEditorProps extends BaseEditorProps {}
const MarkdownEditor: React.FC = ({
value,
@@ -45,6 +37,7 @@ const MarkdownEditor: React.FC = ({
}) => {
const editorRef = useRef(null);
const lastSyncedValueRef = useRef(value);
+ const isInitializedRef = useRef(false);
const editor = useEditor({
editorRef,
@@ -53,24 +46,20 @@ const MarkdownEditor: React.FC = ({
onBlur,
placeholder,
autoFocus,
+ initialValue: value,
});
useEffect(() => {
- if (!editor) {
+ if (!editor || isInitializedRef.current) {
return;
}
- editor.setValue(value || '');
- lastSyncedValueRef.current = value || '';
+ isInitializedRef.current = true;
onEditorReady?.(editor);
- }, [editor]);
+ }, [editor, onEditorReady]);
useEffect(() => {
- if (!editor) {
- return;
- }
-
- if (value === lastSyncedValueRef.current) {
+ if (!editor || value === lastSyncedValueRef.current) {
return;
}
@@ -82,6 +71,9 @@ const MarkdownEditor: React.FC = ({
}, [editor, value]);
useEffect(() => {
+ lastSyncedValueRef.current = value;
+ isInitializedRef.current = false;
+
return () => {
if (editor) {
const view = editor as unknown as EditorView;
@@ -89,8 +81,9 @@ const MarkdownEditor: React.FC = ({
view.destroy();
}
}
+ isInitializedRef.current = false;
};
- }, [editor]);
+ }, []);
return (
diff --git a/ui/src/components/Editor/RichEditor.tsx b/ui/src/components/Editor/RichEditor.tsx
index c574551c6..916c82492 100644
--- a/ui/src/components/Editor/RichEditor.tsx
+++ b/ui/src/components/Editor/RichEditor.tsx
@@ -30,18 +30,10 @@ import { Markdown } from '@tiptap/markdown';
import Image from '@tiptap/extension-image';
import { TableKit } from '@tiptap/extension-table';
-import { Editor } from './types';
+import { Editor, BaseEditorProps } from './types';
import { createTipTapAdapter } from './utils/tiptap/adapter';
-interface RichEditorProps {
- value: string;
- onChange?: (value: string) => void;
- onFocus?: () => void;
- onBlur?: () => void;
- placeholder?: string;
- autoFocus?: boolean;
- onEditorReady?: (editor: Editor) => void;
-}
+interface RichEditorProps extends BaseEditorProps {}
const RichEditor: React.FC
= ({
value,
@@ -53,14 +45,60 @@ const RichEditor: React.FC = ({
onEditorReady,
}) => {
const lastSyncedValueRef = useRef(value);
-
const adaptedEditorRef = useRef(null);
+ const isInitializedRef = useRef(false);
+ const isUpdatingFromPropsRef = useRef(false);
+ const onEditorReadyRef = useRef(onEditorReady);
+ const autoFocusRef = useRef(autoFocus);
+ const initialValueRef = useRef(value);
+
+ useEffect(() => {
+ onEditorReadyRef.current = onEditorReady;
+ autoFocusRef.current = autoFocus;
+ }, [onEditorReady, autoFocus]);
+
+ const isViewAvailable = (editorInstance: TipTapEditor | null): boolean => {
+ if (!editorInstance) {
+ return false;
+ }
+ if (editorInstance.isDestroyed) {
+ return false;
+ }
+ return !!(editorInstance.view && editorInstance.state);
+ };
+
+ const handleCreate = useCallback(
+ ({ editor: editorInstance }: { editor: TipTapEditor }) => {
+ if (isInitializedRef.current || !isViewAvailable(editorInstance)) {
+ return;
+ }
+
+ isInitializedRef.current = true;
+
+ const initialValue = initialValueRef.current;
+ if (initialValue && initialValue.trim() !== '') {
+ editorInstance.commands.setContent(initialValue, {
+ contentType: 'markdown',
+ });
+ lastSyncedValueRef.current = initialValue;
+ }
+
+ adaptedEditorRef.current = createTipTapAdapter(editorInstance);
+ onEditorReadyRef.current?.(adaptedEditorRef.current);
+
+ if (autoFocusRef.current) {
+ editorInstance.commands.focus();
+ }
+ },
+ [],
+ );
const handleUpdate = useCallback(
({ editor: editorInstance }: { editor: TipTapEditor }) => {
- if (onChange) {
+ if (onChange && !isUpdatingFromPropsRef.current) {
const markdown = editorInstance.getMarkdown();
onChange(markdown);
+ lastSyncedValueRef.current = markdown;
}
},
[onChange],
@@ -84,7 +122,7 @@ const RichEditor: React.FC = ({
placeholder,
}),
],
- content: value || '',
+ onCreate: handleCreate,
onUpdate: handleUpdate,
onFocus: handleFocus,
onBlur: handleBlur,
@@ -96,67 +134,56 @@ const RichEditor: React.FC = ({
});
useEffect(() => {
- if (!editor) {
+ if (
+ !editor ||
+ !isInitializedRef.current ||
+ !isViewAvailable(editor) ||
+ value === lastSyncedValueRef.current
+ ) {
return;
}
- const checkEditorReady = () => {
- if (editor.view && editor.view.dom) {
+ try {
+ const currentMarkdown = editor.getMarkdown();
+ if (currentMarkdown !== value) {
+ isUpdatingFromPropsRef.current = true;
if (value && value.trim() !== '') {
editor.commands.setContent(value, { contentType: 'markdown' });
} else {
editor.commands.clearContent();
}
lastSyncedValueRef.current = value || '';
- if (!adaptedEditorRef.current) {
- adaptedEditorRef.current = createTipTapAdapter(editor);
- }
- onEditorReady?.(adaptedEditorRef.current);
- } else {
- setTimeout(checkEditorReady, 10);
+ setTimeout(() => {
+ isUpdatingFromPropsRef.current = false;
+ }, 0);
}
- };
-
- checkEditorReady();
- }, [editor]);
-
- useEffect(() => {
- if (!editor) {
- return;
- }
-
- if (value === lastSyncedValueRef.current) {
- return;
- }
-
- const currentMarkdown = editor.getMarkdown();
- if (currentMarkdown !== value) {
- if (value && value.trim() !== '') {
- editor.commands.setContent(value, { contentType: 'markdown' });
- } else {
- editor.commands.clearContent();
- }
- lastSyncedValueRef.current = value || '';
+ } catch (error) {
+ console.warn('Editor view not available when syncing value:', error);
}
}, [editor, value]);
useEffect(() => {
- if (editor && autoFocus) {
- setTimeout(() => {
- editor.commands.focus();
- }, 100);
- }
- }, [editor, autoFocus]);
+ initialValueRef.current = value;
+ lastSyncedValueRef.current = value;
+ isInitializedRef.current = false;
+ adaptedEditorRef.current = null;
+ isUpdatingFromPropsRef.current = false;
+
+ return () => {
+ if (editor) {
+ editor.destroy();
+ }
+ isInitializedRef.current = false;
+ adaptedEditorRef.current = null;
+ isUpdatingFromPropsRef.current = false;
+ };
+ }, [editor]);
if (!editor) {
return Loading editor...
;
}
- return (
-
-
-
- );
+ return ;
};
export default RichEditor;
diff --git a/ui/src/components/Editor/ToolBars/chart.tsx b/ui/src/components/Editor/ToolBars/chart.tsx
deleted file mode 100644
index a26a56388..000000000
--- a/ui/src/components/Editor/ToolBars/chart.tsx
+++ /dev/null
@@ -1,181 +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, useEffect, useState, memo } from 'react';
-import { Dropdown } from 'react-bootstrap';
-import { useTranslation } from 'react-i18next';
-
-import ToolItem from '../toolItem';
-import { IEditorContext } from '../types';
-
-const Chart: FC = ({ editor }) => {
- const { t } = useTranslation('translation', { keyPrefix: 'editor' });
-
- const headerList = [
- {
- label: t('chart.flow_chart'),
- tpl: `graph TD
- A[Christmas] -->|Get money| B(Go shopping)
- B --> C{Let me think}
- C -->|One| D[Laptop]
- C -->|Two| E[iPhone]
- C -->|Three| F[fa:fa-car Car]`,
- },
- {
- label: t('chart.sequence_diagram'),
- tpl: `sequenceDiagram
- Alice->>+John: Hello John, how are you?
- Alice->>+John: John, can you hear me?
- John-->>-Alice: Hi Alice, I can hear you!
- John-->>-Alice: I feel great!
- `,
- },
- {
- label: t('chart.state_diagram'),
- tpl: `stateDiagram-v2
- [*] --> Still
- Still --> [*]
- Still --> Moving
- Moving --> Still
- Moving --> Crash
- Crash --> [*]
- `,
- },
- {
- label: t('chart.class_diagram'),
- tpl: `classDiagram
- Animal <|-- Duck
- Animal <|-- Fish
- Animal <|-- Zebra
- Animal : +int age
- Animal : +String gender
- Animal: +isMammal()
- Animal: +mate()
- class Duck{
- +String beakColor
- +swim()
- +quack()
- }
- class Fish{
- -int sizeInFeet
- -canEat()
- }
- class Zebra{
- +bool is_wild
- +run()
- }
- `,
- },
- {
- label: t('chart.pie_chart'),
- tpl: `pie title Pets adopted by volunteers
- "Dogs" : 386
- "Cats" : 85
- "Rats" : 15
- `,
- },
- {
- label: t('chart.gantt_chart'),
- tpl: `gantt
- title A Gantt Diagram
- dateFormat YYYY-MM-DD
- section Section
- A task :a1, 2014-01-01, 30d
- Another task :after a1 , 20d
- section Another
- Task in sec :2014-01-12 , 12d
- another task : 24d
- `,
- },
- {
- label: t('chart.entity_relationship_diagram'),
- tpl: `erDiagram
- CUSTOMER }|..|{ DELIVERY-ADDRESS : has
- CUSTOMER ||--o{ ORDER : places
- CUSTOMER ||--o{ INVOICE : "liable for"
- DELIVERY-ADDRESS ||--o{ ORDER : receives
- INVOICE ||--|{ ORDER : covers
- ORDER ||--|{ ORDER-ITEM : includes
- PRODUCT-CATEGORY ||--|{ PRODUCT : contains
- PRODUCT ||--o{ ORDER-ITEM : "ordered in"
- `,
- },
- ];
- const item = {
- label: 'chart',
- tip: `${t('chart.text')}`,
- };
- const [isShow, setShowState] = useState(false);
- const [isLocked, setLockState] = useState(false);
-
- useEffect(() => {
- if (!editor) {
- return;
- }
- editor.on('focus', () => {
- setShowState(false);
- });
- }, []);
-
- const click = (tpl) => {
- const { ch } = editor.getCursor();
-
- editor.replaceSelection(`${ch ? '\n' : ''}\`\`\`mermaid\n${tpl}\n\`\`\`\n`);
- };
-
- const onAddHeader = () => {
- setShowState(!isShow);
- };
- const handleMouseEnter = () => {
- if (isLocked) {
- return;
- }
- setLockState(true);
- };
-
- const handleMouseLeave = () => {
- setLockState(false);
- };
- return (
-
-
- {headerList.map((header) => {
- return (
- {
- e.preventDefault();
- click(header.tpl);
- }}>
- {header.label}
-
- );
- })}
-
-
- );
-};
-
-export default memo(Chart);
diff --git a/ui/src/components/Editor/ToolBars/index.ts b/ui/src/components/Editor/ToolBars/index.ts
index 05912bc6e..c3fa24be4 100644
--- a/ui/src/components/Editor/ToolBars/index.ts
+++ b/ui/src/components/Editor/ToolBars/index.ts
@@ -31,7 +31,6 @@ import Link from './link';
import BlockQuote from './blockquote';
import Image from './image';
import Help from './help';
-import Chart from './chart';
import File from './file';
export {
@@ -49,6 +48,5 @@ export {
BlockQuote,
Image,
Help,
- Chart,
File,
};
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index c293fa653..eb798c9df 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -98,10 +98,10 @@ const MDEditor: ForwardRefRenderFunction = (
return;
}
- setMode(newMode);
setCurrentEditor(null);
+ setMode(newMode);
},
- [mode, currentEditor],
+ [mode],
);
const getHtml = useCallback(() => {
@@ -172,6 +172,7 @@ const MDEditor: ForwardRefRenderFunction = (
{
onChange?.(markdown);
diff --git a/ui/src/components/Editor/types.ts b/ui/src/components/Editor/types.ts
index 926d3c203..e7d230819 100644
--- a/ui/src/components/Editor/types.ts
+++ b/ui/src/components/Editor/types.ts
@@ -116,11 +116,12 @@ export interface CodeMirrorEditor extends Editor {
moduleType;
}
-// @deprecated 已废弃,请直接使用 Editor 接口
-// 保留此接口仅用于向后兼容,新代码不应使用
-export interface IEditorContext {
- editor: Editor;
- wrapText?;
- replaceLines?;
- appendBlock?;
+export interface BaseEditorProps {
+ value: string;
+ onChange?: (value: string) => void;
+ onFocus?: () => void;
+ onBlur?: () => void;
+ placeholder?: string;
+ autoFocus?: boolean;
+ onEditorReady?: (editor: Editor) => void;
}
diff --git a/ui/src/components/Editor/utils/codemirror/commands.ts b/ui/src/components/Editor/utils/codemirror/commands.ts
index 152bc632d..546382d46 100644
--- a/ui/src/components/Editor/utils/codemirror/commands.ts
+++ b/ui/src/components/Editor/utils/codemirror/commands.ts
@@ -69,14 +69,26 @@ export function createCommandMethods(editor: Editor) {
const newLines = lines.map(replace) as string[];
const newText = newLines.join('\n');
- editor.setValue(newText);
+ editor.dispatch({
+ changes: {
+ from: 0,
+ to: editor.state.doc.length,
+ insert: newText,
+ },
+ });
},
appendBlock: (content: string) => {
const { doc } = editor.state;
const currentText = doc.toString();
const newText = currentText ? `${currentText}\n\n${content}` : content;
- editor.setValue(newText);
+ editor.dispatch({
+ changes: {
+ from: 0,
+ to: editor.state.doc.length,
+ insert: newText,
+ },
+ });
},
insertBold: (text?: string) => {
diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts
index 938b37613..5ba7192ca 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useRef } from 'react';
import { minimalSetup } from 'codemirror';
import { EditorState, Compartment } from '@codemirror/state';
@@ -129,12 +129,14 @@ export const useEditor = ({
editorRef,
placeholder: placeholderText,
autoFocus,
+ initialValue,
onChange,
onFocus,
onBlur,
}) => {
const [editor, setEditor] = useState(null);
- const [value, setValue] = useState('');
+ const isInternalUpdateRef = useRef(false);
+
const init = async () => {
const isDark = isDarkTheme();
@@ -162,6 +164,7 @@ export const useEditor = ({
});
const startState = EditorState.create({
+ doc: initialValue || '',
extensions: [
minimalSetup,
markdown({
@@ -206,9 +209,20 @@ export const useEditor = ({
}, 10);
}
+ const originalSetValue = cm.setValue;
+ cm.setValue = (newValue: string) => {
+ isInternalUpdateRef.current = true;
+ originalSetValue.call(cm, newValue);
+ setTimeout(() => {
+ isInternalUpdateRef.current = false;
+ }, 0);
+ };
+
cm.on('change', () => {
- const newValue = cm.getValue();
- setValue(newValue);
+ if (!isInternalUpdateRef.current && onChange) {
+ const newValue = cm.getValue();
+ onChange(newValue);
+ }
});
cm.on('focus', () => {
@@ -224,10 +238,6 @@ export const useEditor = ({
return cm;
};
- useEffect(() => {
- onChange?.(value);
- }, [value]);
-
useEffect(() => {
if (!editorRef.current) {
return;
diff --git a/ui/src/components/Editor/utils/tiptap/events.ts b/ui/src/components/Editor/utils/tiptap/events.ts
index 495ee6e57..2a86440de 100644
--- a/ui/src/components/Editor/utils/tiptap/events.ts
+++ b/ui/src/components/Editor/utils/tiptap/events.ts
@@ -25,7 +25,13 @@ import { logWarning } from './errorHandler';
* Checks if editor view is available
*/
function isViewAvailable(editor: TipTapEditor): boolean {
- return !!(editor.view && editor.view.dom);
+ if (!editor) {
+ return false;
+ }
+ if (editor.isDestroyed) {
+ return false;
+ }
+ return !!(editor.view && editor.state);
}
/**
From 7a4b57d1ffdb0b2980e8dc9b46e74b12b9e22cba Mon Sep 17 00:00:00 2001
From: robin
Date: Wed, 17 Dec 2025 10:50:36 +0800
Subject: [PATCH 30/92] refactor(editor): improve editor component
functionality and code clarity
- Updated ToolItem component to ensure consistent return type for command functions.
- Modified heading toolbar to allow optional label parameter in handleClick function.
- Enhanced image component comments for clarity on editor state updates and event listener management.
- Cleaned up utility functions in htmlRender to remove unnecessary comments and improve readability.
---
ui/src/components/Editor/ToolBars/heading.tsx | 2 +-
ui/src/components/Editor/ToolBars/image.tsx | 10 ++++------
ui/src/components/Editor/toolItem.tsx | 2 +-
ui/src/components/Editor/types.ts | 9 ---------
ui/src/components/Editor/utils/index.ts | 2 --
5 files changed, 6 insertions(+), 19 deletions(-)
diff --git a/ui/src/components/Editor/ToolBars/heading.tsx b/ui/src/components/Editor/ToolBars/heading.tsx
index 4b49212cb..bcce69aef 100644
--- a/ui/src/components/Editor/ToolBars/heading.tsx
+++ b/ui/src/components/Editor/ToolBars/heading.tsx
@@ -62,7 +62,7 @@ const Heading = () => {
const [isLocked, setLockState] = useState(false);
const [currentEditor, setCurrentEditor] = useState(null);
- const handleClick = (level: Level = 2, label = '大标题') => {
+ const handleClick = (level: Level = 2, label?: string) => {
if (!currentEditor) {
return;
}
diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx
index 67a79b3ca..52b2e5461 100644
--- a/ui/src/components/Editor/ToolBars/image.tsx
+++ b/ui/src/components/Editor/ToolBars/image.tsx
@@ -32,8 +32,8 @@ const Image = () => {
const editor = useContext(EditorContext);
const [editorState, setEditorState] = useState(editor);
- // 当 editor 改变时,更新 editor state
- // 这样切换编辑器模式时,事件监听器会重新绑定
+ // Update editor state when editor context changes
+ // This ensures event listeners are re-bound when switching editor modes
useEffect(() => {
if (editor) {
setEditorState(editor);
@@ -329,20 +329,18 @@ const Image = () => {
if (!editorState) {
return undefined;
}
- // 绑定事件监听器
+
editorState.on('dragenter', dragenter);
editorState.on('dragover', dragover);
editorState.on('drop', drop);
editorState.on('paste', paste);
+
return () => {
- // 清理事件监听器
editorState.off('dragenter', dragenter);
editorState.off('dragover', dragover);
editorState.off('drop', drop);
editorState.off('paste', paste);
};
- // 注意:dragenter, dragover, drop, paste 函数在组件生命周期内是稳定的
- // 它们不依赖任何会变化的值,所以不需要加入依赖项
}, [editorState]);
useEffect(() => {
diff --git a/ui/src/components/Editor/toolItem.tsx b/ui/src/components/Editor/toolItem.tsx
index c0fdf1757..39f82d700 100644
--- a/ui/src/components/Editor/toolItem.tsx
+++ b/ui/src/components/Editor/toolItem.tsx
@@ -63,7 +63,7 @@ const ToolItem: FC = (props) => {
editor?.addKeyMap({
[key]: () => {
onClick?.(editor);
- return true; // Command 类型要求返回 boolean
+ return true;
},
});
});
diff --git a/ui/src/components/Editor/types.ts b/ui/src/components/Editor/types.ts
index e7d230819..48f69322c 100644
--- a/ui/src/components/Editor/types.ts
+++ b/ui/src/components/Editor/types.ts
@@ -61,7 +61,6 @@ export interface ExtendEditor {
setSelection: (anchor: Position, head?: Position) => void;
setReadOnly: (readOnly: boolean) => void;
- // 底层方法(供编辑器内部使用,不推荐工具栏直接使用)
wrapText: (before: string, after?: string, defaultText?: string) => void;
replaceLines: (
replace: Parameters['map']>[0],
@@ -69,37 +68,29 @@ export interface ExtendEditor {
) => void;
appendBlock: (content: string) => void;
- // 语义化高级方法(工具栏推荐使用)
- // 文本格式
insertBold: (text?: string) => void;
insertItalic: (text?: string) => void;
insertCode: (text?: string) => void;
insertStrikethrough: (text?: string) => void;
- // 块级元素
insertHeading: (level: Level, text?: string) => void;
insertBlockquote: (text?: string) => void;
insertCodeBlock: (language?: string, code?: string) => void;
insertHorizontalRule: () => void;
- // 列表
insertOrderedList: () => void;
insertUnorderedList: () => void;
toggleOrderedList: () => void;
toggleUnorderedList: () => void;
- // 链接和媒体
insertLink: (url: string, text?: string) => void;
insertImage: (url: string, alt?: string) => void;
- // 表格
insertTable: (rows?: number, cols?: number) => void;
- // 缩进
indent: () => void;
outdent: () => void;
- // 状态查询
isBold: () => boolean;
isItalic: () => boolean;
isHeading: (level?: number) => boolean;
diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts
index 5ba7192ca..eb4ad16e1 100644
--- a/ui/src/components/Editor/utils/index.ts
+++ b/ui/src/components/Editor/utils/index.ts
@@ -95,10 +95,8 @@ export function htmlRender(el: HTMLElement | null, config?: htmlRenderConfig) {
`;
codeTool.innerHTML = str;
- // Add copy button to pre tag
pre.style.position = 'relative';
- // 将 codeTool 和 pre 插入到 codeWrap 中, 并且使用 codeWrap 替换 pre
codeWrap.appendChild(codeTool);
pre.parentNode?.replaceChild(codeWrap, pre);
codeWrap.appendChild(pre);
From ffa8dc2bb85956f07f4d1c20616c1524832f1571 Mon Sep 17 00:00:00 2001
From: robin
Date: Wed, 17 Dec 2025 14:20:09 +0800
Subject: [PATCH 31/92] feat(editor): update TipTap dependencies and enhance
table functionality
- Added @tiptap/core version 3.13.0 to package.json and updated related dependencies in pnpm-lock.yaml.
- Refactored RichEditor to utilize new table extension with responsive wrapper and improved styling.
- Adjusted editor mode initialization in MDEditor to default to 'rich' for enhanced user experience.
- Removed redundant styles from index.scss to streamline the editor's appearance.
---
ui/package.json | 1 +
ui/pnpm-lock.yaml | 193 +++++++++---------
ui/src/components/Editor/RichEditor.tsx | 23 ++-
ui/src/components/Editor/index.scss | 76 -------
ui/src/components/Editor/index.tsx | 4 +-
.../Editor/utils/tiptap/tableExtension.ts | 81 ++++++++
6 files changed, 202 insertions(+), 176 deletions(-)
create mode 100644 ui/src/components/Editor/utils/tiptap/tableExtension.ts
diff --git a/ui/package.json b/ui/package.json
index 5a6704ceb..a771c39e3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -21,6 +21,7 @@
"@codemirror/language-data": "^6.5.0",
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.26.1",
+ "@tiptap/core": "^3.13.0",
"@tiptap/extension-image": "^3.11.1",
"@tiptap/extension-placeholder": "^3.11.1",
"@tiptap/extension-table": "^3.11.1",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 83c706312..cbbed7e8f 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -20,21 +20,24 @@ importers:
'@codemirror/view':
specifier: ^6.26.1
version: 6.35.3
+ '@tiptap/core':
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/extension-image':
specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
+ version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
'@tiptap/extension-placeholder':
specifier: ^3.11.1
- version: 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ version: 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
'@tiptap/extension-table':
specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
'@tiptap/markdown':
specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
'@tiptap/react':
specifier: ^3.11.1
- version: 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tiptap/starter-kit':
specifier: ^3.11.1
version: 3.11.1
@@ -1714,10 +1717,10 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
- '@tiptap/core@3.11.1':
- resolution: {integrity: sha512-q7uzYrCq40JOIi6lceWe2HuA8tSr97iPwP/xtJd0bZjyL1rWhUyqxMb7y+aq4RcELrx/aNRa2JIvLtRRdy02Dg==}
+ '@tiptap/core@3.13.0':
+ resolution: {integrity: sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==}
peerDependencies:
- '@tiptap/pm': ^3.11.1
+ '@tiptap/pm': ^3.13.0
'@tiptap/extension-blockquote@3.11.1':
resolution: {integrity: sha512-c3DN5c/9kl8w1wCcylH9XqW0OyCegqE3EL4rDlVYkyBD0GwCnUS30pN+jdxCUq/tl94lkkRk7XMyEUwzQmG+5g==}
@@ -1805,10 +1808,10 @@ packages:
'@tiptap/core': ^3.11.1
'@tiptap/pm': ^3.11.1
- '@tiptap/extension-list-item@3.11.1':
- resolution: {integrity: sha512-KFw3TAwN6hQ+oDeE3lRqwzCRKhxU1NWf9q5SAwiUxlp/LcEjuhXcYJYX8SHPOLOlTo0P42v1i0KBeLUPKnO58g==}
+ '@tiptap/extension-list-item@3.13.0':
+ resolution: {integrity: sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==}
peerDependencies:
- '@tiptap/extension-list': ^3.11.1
+ '@tiptap/extension-list': ^3.13.0
'@tiptap/extension-list-keymap@3.11.1':
resolution: {integrity: sha512-MpeuVi+bOHulbN65bOjaeoNJstNuAAEPdLwNjW25c9y2a8b5iZFY8vdVNENDiqq+dI+F5EaFGaEq0FN0uslfiA==}
@@ -9367,134 +9370,134 @@ snapshots:
'@babel/runtime': 7.26.0
'@testing-library/dom': 8.20.1
- '@tiptap/core@3.11.1(@tiptap/pm@3.11.1)':
+ '@tiptap/core@3.13.0(@tiptap/pm@3.11.1)':
dependencies:
'@tiptap/pm': 3.11.1
- '@tiptap/extension-blockquote@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-blockquote@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-bold@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-bold@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-bubble-menu@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-bubble-menu@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
optional: true
- '@tiptap/extension-bullet-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-bullet-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-code-block@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-code-block@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
- '@tiptap/extension-code@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-code@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-document@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-document@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-dropcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-dropcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-floating-menu@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-floating-menu@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
optional: true
- '@tiptap/extension-gapcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-gapcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-hard-break@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-hard-break@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-heading@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-heading@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-horizontal-rule@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-horizontal-rule@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
- '@tiptap/extension-image@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-image@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-italic@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-italic@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-link@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-link@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
linkifyjs: 4.3.2
- '@tiptap/extension-list-item@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list-keymap@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-list-keymap@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
- '@tiptap/extension-ordered-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-ordered-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-paragraph@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-paragraph@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-placeholder@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-placeholder@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-strike@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-strike@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-table@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-table@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
- '@tiptap/extension-text@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-text@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-underline@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-underline@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
- '@tiptap/markdown@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/markdown@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
marked: 15.0.12
@@ -9519,9 +9522,9 @@ snapshots:
prosemirror-transform: 1.10.5
prosemirror-view: 1.41.3
- '@tiptap/react@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tiptap/react@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
'@types/react': 18.3.16
'@types/react-dom': 18.3.5(@types/react@18.3.16)
@@ -9531,36 +9534,36 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
use-sync-external-store: 1.6.0(react@18.3.1)
optionalDependencies:
- '@tiptap/extension-bubble-menu': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-floating-menu': 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-bubble-menu': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-floating-menu': 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
transitivePeerDependencies:
- '@floating-ui/dom'
'@tiptap/starter-kit@3.11.1':
dependencies:
- '@tiptap/core': 3.11.1(@tiptap/pm@3.11.1)
- '@tiptap/extension-blockquote': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-bold': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-bullet-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-code': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-code-block': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-document': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-dropcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-gapcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-hard-break': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-heading': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-horizontal-rule': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-italic': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-link': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list-item': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-list-keymap': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-ordered-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-paragraph': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-strike': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-text': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extension-underline': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.11.1(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/extension-blockquote': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-bold': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-bullet-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-code': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-code-block': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-document': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-dropcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-gapcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-hard-break': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-heading': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-horizontal-rule': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-italic': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-link': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-list-keymap': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-ordered-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ '@tiptap/extension-paragraph': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-strike': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-text': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extension-underline': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
'@tiptap/pm': 3.11.1
'@tootallnate/once@1.1.2': {}
diff --git a/ui/src/components/Editor/RichEditor.tsx b/ui/src/components/Editor/RichEditor.tsx
index 916c82492..55fca5a99 100644
--- a/ui/src/components/Editor/RichEditor.tsx
+++ b/ui/src/components/Editor/RichEditor.tsx
@@ -28,10 +28,11 @@ import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
import { Markdown } from '@tiptap/markdown';
import Image from '@tiptap/extension-image';
-import { TableKit } from '@tiptap/extension-table';
+import { TableRow, TableCell, TableHeader } from '@tiptap/extension-table';
import { Editor, BaseEditorProps } from './types';
import { createTipTapAdapter } from './utils/tiptap/adapter';
+import { TableWithWrapper } from './utils/tiptap/tableExtension';
interface RichEditorProps extends BaseEditorProps {}
@@ -117,7 +118,23 @@ const RichEditor: React.FC = ({
StarterKit,
Markdown,
Image,
- TableKit,
+ TableWithWrapper.configure({
+ HTMLAttributes: {
+ class: 'table table-bordered',
+ style: {
+ width: '100%',
+ },
+ },
+ resizable: true,
+ wrapperClass: 'table-responsive',
+ }),
+ TableRow.configure({
+ HTMLAttributes: {
+ class: 'table-row',
+ },
+ }),
+ TableCell,
+ TableHeader,
Placeholder.configure({
placeholder,
}),
@@ -128,7 +145,7 @@ const RichEditor: React.FC = ({
onBlur: handleBlur,
editorProps: {
attributes: {
- class: 'tiptap-editor',
+ class: 'tiptap-editor fmt',
},
},
});
diff --git a/ui/src/components/Editor/index.scss b/ui/src/components/Editor/index.scss
index 4775e84a5..60a549aaf 100644
--- a/ui/src/components/Editor/index.scss
+++ b/ui/src/components/Editor/index.scss
@@ -139,82 +139,6 @@
height: 0;
}
}
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- margin: 1rem 0 0.5rem;
- font-weight: 600;
- line-height: 1.4;
- }
-
- ul,
- ol {
- padding-left: 1.5rem;
- margin: 0.5rem 0;
- }
-
- li {
- margin: 0.25rem 0;
- }
-
- code {
- background-color: var(--an-code-bg, #f4f4f4);
- color: var(--an-code-color, #e83e8c);
- padding: 0.2em 0.4em;
- border-radius: 3px;
- font-size: 0.9em;
- }
-
- pre {
- background-color: var(--an-code-block-bg, #f8f9fa);
- border: 1px solid var(--an-ced4da);
- border-radius: 4px;
- padding: 1rem;
- margin: 0.5rem 0;
- overflow-x: auto;
-
- code {
- background-color: transparent;
- color: inherit;
- padding: 0;
- }
- }
-
- blockquote {
- border-left: 4px solid var(--an-primary, #0d6efd);
- padding-left: 1rem;
- margin: 0.5rem 0;
- color: var(--an-text-secondary, #6c757d);
- }
-
- a {
- color: var(--an-link-color, #0d6efd);
- text-decoration: underline;
-
- &:hover {
- text-decoration: none;
- }
- }
-
- img {
- max-width: 100%;
- height: auto;
- border-radius: 4px;
- }
-
- hr {
- border: none;
- border-top: 1px solid var(--an-ced4da);
- margin: 1rem 0;
- }
-
- ::selection {
- background-color: var(--an-selection-bg, rgba(0, 123, 255, 0.2));
- }
}
.editor-loading {
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index eb798c9df..91214ac3b 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -86,7 +86,7 @@ const MDEditor: ForwardRefRenderFunction = (
},
ref,
) => {
- const [mode, setMode] = useState<'markdown' | 'rich'>('markdown');
+ const [mode, setMode] = useState<'markdown' | 'rich'>('rich');
const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
@@ -186,7 +186,7 @@ const MDEditor: ForwardRefRenderFunction = (
}}
/>
-
+ {mode === 'markdown' &&
}
>
);
};
diff --git a/ui/src/components/Editor/utils/tiptap/tableExtension.ts b/ui/src/components/Editor/utils/tiptap/tableExtension.ts
new file mode 100644
index 000000000..70e68f77a
--- /dev/null
+++ b/ui/src/components/Editor/utils/tiptap/tableExtension.ts
@@ -0,0 +1,81 @@
+/*
+ * 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 { Table, TableOptions } from '@tiptap/extension-table';
+import type { NodeViewRendererProps } from '@tiptap/core';
+
+interface TableWrapperOptions extends TableOptions {
+ wrapperClass?: string;
+}
+
+export const TableWithWrapper = Table.extend
({
+ addOptions() {
+ const parentOptions = (this.parent?.() || {}) as Partial;
+ return {
+ ...parentOptions,
+ HTMLAttributes: parentOptions.HTMLAttributes || {},
+ wrapperClass: 'table-responsive',
+ } as TableWrapperOptions;
+ },
+
+ addNodeView() {
+ return (props: NodeViewRendererProps) => {
+ const { node } = props;
+ const wrapperClass = this.options.wrapperClass || 'table-responsive';
+
+ const dom = document.createElement('div');
+ dom.className = wrapperClass;
+
+ const table = document.createElement('table');
+
+ const htmlAttrs = this.options.HTMLAttributes || {};
+ if (htmlAttrs.class) {
+ table.className = htmlAttrs.class as string;
+ }
+ if (htmlAttrs.style && typeof htmlAttrs.style === 'object') {
+ Object.assign(table.style, htmlAttrs.style);
+ }
+
+ const colgroup = document.createElement('colgroup');
+ if (node.firstChild) {
+ const { childCount } = node.firstChild;
+ for (let i = 0; i < childCount; i += 1) {
+ const col = document.createElement('col');
+ colgroup.appendChild(col);
+ }
+ }
+ table.appendChild(colgroup);
+
+ const tbody = document.createElement('tbody');
+ table.appendChild(tbody);
+ dom.appendChild(table);
+
+ return {
+ dom,
+ contentDOM: tbody,
+ update: (updatedNode) => {
+ if (updatedNode.type !== node.type) {
+ return false;
+ }
+ return true;
+ },
+ };
+ };
+ },
+});
From 1c2c733190376e62e8734918d542f61ebc3d9fa9 Mon Sep 17 00:00:00 2001
From: robin
Date: Thu, 18 Dec 2025 10:43:59 +0800
Subject: [PATCH 32/92] feat(editor): enhance plugin system and improve command
methods
- Introduced PluginSlot component for better plugin insertion in the editor.
- Updated MDEditor to default to 'markdown' mode for improved user experience.
- Refactored command methods in TipTap to use chaining for better selection handling.
- Enhanced PluginRender to load plugins asynchronously and prevent duplicate registrations.
---
ui/src/components/Editor/index.tsx | 6 +-
.../Editor/utils/tiptap/commands.ts | 20 ++--
ui/src/components/PluginRender/index.tsx | 105 +++++++++++++-----
ui/src/utils/pluginKit/index.ts | 104 ++++++++++++++---
4 files changed, 178 insertions(+), 57 deletions(-)
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index 91214ac3b..ebe04876c 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -29,7 +29,7 @@ import {
import classNames from 'classnames';
import { PluginType, useRenderPlugin } from '@/utils/pluginKit';
-import PluginRender from '../PluginRender';
+import PluginRender, { PluginSlot } from '../PluginRender';
import {
BlockQuote,
@@ -86,7 +86,7 @@ const MDEditor: ForwardRefRenderFunction = (
},
ref,
) => {
- const [mode, setMode] = useState<'markdown' | 'rich'>('rich');
+ const [mode, setMode] = useState<'markdown' | 'rich'>('markdown');
const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
@@ -145,10 +145,10 @@ const MDEditor: ForwardRefRenderFunction = (
+
-
${text}`;
- editor.commands.insertContent(blockquoteText, {
- contentType: 'markdown',
- });
- // Select the text part (excluding the '> ' marker)
- const textStart = from + 2; // 2 for '> '
- editor.commands.setTextSelection({
- from: textStart,
- to: textStart + text.length,
- });
+
+ // Use chain to ensure selection happens after insertion
+ editor
+ .chain()
+ .focus()
+ .insertContent(blockquoteText, { contentType: 'markdown' })
+ .setTextSelection({
+ from: from + 1,
+ to: from + 1 + text.length,
+ })
+ .run();
} else {
editor.commands.toggleBlockquote();
}
diff --git a/ui/src/components/PluginRender/index.tsx b/ui/src/components/PluginRender/index.tsx
index 057f49521..b241bf05c 100644
--- a/ui/src/components/PluginRender/index.tsx
+++ b/ui/src/components/PluginRender/index.tsx
@@ -17,9 +17,12 @@
* under the License.
*/
-import React, { FC, ReactNode } from 'react';
+import React, { FC, ReactNode, useEffect, useState } from 'react';
import PluginKit, { Plugin, PluginType } from '@/utils/pluginKit';
+
+// Marker component for plugin insertion point
+export const PluginSlot: FC = () => null;
/**
* Note:Please set at least either of the `slug_name` and `type` attributes, otherwise no plugins will be rendered.
*
@@ -29,13 +32,16 @@ import PluginKit, { Plugin, PluginType } from '@/utils/pluginKit';
* @field type: Used to formulate the rendering of all plugins of this type.
* (if the `slug_name` attribute is set, it will be ignored)
* @field prop: Any attribute you want to configure, e.g. `className`
+ *
+ * For editor type plugins, use component as a marker to indicate where plugins should be inserted.
*/
interface Props {
slug_name?: string;
type: PluginType;
children?: ReactNode;
- [prop: string]: any;
+ className?: string;
+ [key: string]: unknown;
}
const Index: FC = ({
@@ -45,29 +51,70 @@ const Index: FC = ({
className,
...props
}) => {
- const pluginSlice: Plugin[] = [];
- const plugins = PluginKit.getPlugins().filter((plugin) => plugin.activated);
+ const [pluginSlice, setPluginSlice] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
- plugins.forEach((plugin) => {
- if (type && slug_name) {
- if (plugin.info.slug_name === slug_name && plugin.info.type === type) {
- pluginSlice.push(plugin);
- }
- } else if (type) {
- if (plugin.info.type === type) {
- pluginSlice.push(plugin);
- }
- } else if (slug_name) {
- if (plugin.info.slug_name === slug_name) {
- pluginSlice.push(plugin);
+ useEffect(() => {
+ let mounted = true;
+
+ const loadPlugins = async () => {
+ await PluginKit.initialization;
+
+ if (!mounted) return;
+
+ const plugins = PluginKit.getPlugins().filter(
+ (plugin) => plugin.activated,
+ );
+ console.log(
+ '[PluginRender] Loaded plugins:',
+ plugins.map((p) => p.info.slug_name),
+ );
+ const filtered: Plugin[] = [];
+
+ plugins.forEach((plugin) => {
+ if (type && slug_name) {
+ if (
+ plugin.info.slug_name === slug_name &&
+ plugin.info.type === type
+ ) {
+ filtered.push(plugin);
+ }
+ } else if (type) {
+ if (plugin.info.type === type) {
+ filtered.push(plugin);
+ }
+ } else if (slug_name) {
+ if (plugin.info.slug_name === slug_name) {
+ filtered.push(plugin);
+ }
+ }
+ });
+
+ if (mounted) {
+ setPluginSlice(filtered);
+ setIsLoading(false);
}
- }
- });
+ };
+
+ loadPlugins();
+
+ return () => {
+ mounted = false;
+ };
+ }, [slug_name, type]);
/**
* TODO: Rendering control for non-builtin plug-ins
* ps: Logic such as version compatibility determination can be placed here
*/
+ if (isLoading) {
+ // Don't render anything while loading to avoid flashing
+ if (type === 'editor') {
+ return {children}
;
+ }
+ return null;
+ }
+
if (pluginSlice.length === 0) {
if (type === 'editor') {
return {children}
;
@@ -76,20 +123,17 @@ const Index: FC = ({
}
if (type === 'editor') {
- // index 16 is the position of the toolbar in the editor for plugins
- const nodes = React.Children.map(children, (child, index) => {
- if (index === 16) {
+ // Use PluginSlot marker to insert plugins at the correct position
+ const nodes = React.Children.map(children, (child) => {
+ // Check if this is the PluginSlot marker
+ if (React.isValidElement(child) && child.type === PluginSlot) {
return (
<>
- {child}
{pluginSlice.map((ps) => {
- const PluginFC = ps.component;
- return (
- // @ts-ignore
-
- );
+ const PluginFC = ps.component as FC;
+ return ;
})}
-
+ {pluginSlice.length > 0 &&
}
>
);
}
@@ -102,9 +146,10 @@ const Index: FC = ({
return (
<>
{pluginSlice.map((ps) => {
- const PluginFC = ps.component;
+ const PluginFC = ps.component as FC<
+ { className?: string } & typeof props
+ >;
return (
- // @ts-ignore
);
})}
diff --git a/ui/src/utils/pluginKit/index.ts b/ui/src/utils/pluginKit/index.ts
index 0219da3f7..a217d8936 100644
--- a/ui/src/utils/pluginKit/index.ts
+++ b/ui/src/utils/pluginKit/index.ts
@@ -49,22 +49,45 @@ class Plugins {
initialization: Promise;
+ private isInitialized = false;
+
+ private initializationError: Error | null = null;
+
constructor() {
this.initialization = this.init();
}
async init() {
- this.registerBuiltin();
+ if (this.isInitialized) {
+ return;
+ }
- // Note: The /install stage does not allow access to the getPluginsStatus api, so an initial value needs to be given
- const plugins = (await getPluginsStatus().catch(() => [])) || [];
- this.registeredPlugins = plugins.filter((p) => p.enabled);
- await this.registerPlugins();
+ try {
+ this.registerBuiltin();
+
+ // Note: The /install stage does not allow access to the getPluginsStatus api, so an initial value needs to be given
+ const plugins =
+ (await getPluginsStatus().catch((error) => {
+ console.warn('Failed to get plugins status:', error);
+ return [];
+ })) || [];
+ this.registeredPlugins = plugins.filter((p) => p.enabled);
+ await this.registerPlugins();
+ this.isInitialized = true;
+ this.initializationError = null;
+ } catch (error) {
+ this.initializationError = error as Error;
+ console.error('Plugin initialization failed:', error);
+ throw error;
+ }
}
- refresh() {
+ async refresh() {
this.plugins = [];
- this.init();
+ this.isInitialized = false;
+ this.initializationError = null;
+ this.initialization = this.init();
+ await this.initialization;
}
validate(plugin: Plugin) {
@@ -95,17 +118,46 @@ class Plugins {
});
}
- registerPlugins() {
- const plugins = this.registeredPlugins
+ async registerPlugins() {
+ console.log(
+ '[PluginKit] Registered plugins from API:',
+ this.registeredPlugins.map((p) => p.slug_name),
+ );
+
+ const pluginLoaders = this.registeredPlugins
.map((p) => {
const func = allPlugins[p.slug_name];
-
- return func;
+ if (!func) {
+ console.warn(
+ `[PluginKit] Plugin loader not found for: ${p.slug_name}`,
+ );
+ }
+ return { slug_name: p.slug_name, loader: func };
})
- .filter((p) => p);
- return Promise.all(plugins.map((p) => p())).then((resolvedPlugins) => {
- resolvedPlugins.forEach((plugin) => this.register(plugin));
- return true;
+ .filter((p) => p.loader);
+
+ console.log(
+ '[PluginKit] Found plugin loaders:',
+ pluginLoaders.map((p) => p.slug_name),
+ );
+
+ // Use Promise.allSettled to prevent one plugin failure from breaking all plugins
+ const results = await Promise.allSettled(
+ pluginLoaders.map((p) => p.loader()),
+ );
+
+ results.forEach((result, index) => {
+ if (result.status === 'fulfilled') {
+ console.log(
+ `[PluginKit] Successfully loaded plugin: ${pluginLoaders[index].slug_name}`,
+ );
+ this.register(result.value);
+ } else {
+ console.error(
+ `[PluginKit] Failed to load plugin ${pluginLoaders[index].slug_name}:`,
+ result.reason,
+ );
+ }
});
}
@@ -114,6 +166,16 @@ class Plugins {
if (!bool) {
return;
}
+
+ // Prevent duplicate registration
+ const exists = this.plugins.some(
+ (p) => p.info.slug_name === plugin.info.slug_name,
+ );
+ if (exists) {
+ console.warn(`Plugin ${plugin.info.slug_name} is already registered`);
+ return;
+ }
+
if (plugin.i18nConfig) {
initI18nResource(plugin.i18nConfig);
}
@@ -133,6 +195,18 @@ class Plugins {
getPlugins() {
return this.plugins;
}
+
+ async getPluginsAsync() {
+ await this.initialization;
+ return this.plugins;
+ }
+
+ getInitializationStatus() {
+ return {
+ isInitialized: this.isInitialized,
+ error: this.initializationError,
+ };
+ }
}
const plugins = new Plugins();
From 0bad2c7249299ca7b516e106d6c7fdf68d379c7b Mon Sep 17 00:00:00 2001
From: robin
Date: Thu, 18 Dec 2025 10:46:36 +0800
Subject: [PATCH 33/92] chore(dependencies): update TipTap packages to version
3.13.0
- Upgraded all TipTap related dependencies in package.json and pnpm-lock.yaml to version 3.13.0 for improved functionality and compatibility.
- Ensured consistent versioning across all TipTap extensions and core packages.
---
ui/package.json | 12 +-
ui/pnpm-lock.yaml | 428 +++++++++++++++++++++++-----------------------
2 files changed, 223 insertions(+), 217 deletions(-)
diff --git a/ui/package.json b/ui/package.json
index a771c39e3..005a6abac 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -22,12 +22,12 @@
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.26.1",
"@tiptap/core": "^3.13.0",
- "@tiptap/extension-image": "^3.11.1",
- "@tiptap/extension-placeholder": "^3.11.1",
- "@tiptap/extension-table": "^3.11.1",
- "@tiptap/markdown": "^3.11.1",
- "@tiptap/react": "^3.11.1",
- "@tiptap/starter-kit": "^3.11.1",
+ "@tiptap/extension-image": "^3.13.0",
+ "@tiptap/extension-placeholder": "^3.13.0",
+ "@tiptap/extension-table": "^3.13.0",
+ "@tiptap/markdown": "^3.13.0",
+ "@tiptap/react": "^3.13.0",
+ "@tiptap/starter-kit": "^3.13.0",
"axios": "^1.7.7",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.10.5",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index cbbed7e8f..3fa75e7b4 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -22,25 +22,25 @@ importers:
version: 6.35.3
'@tiptap/core':
specifier: ^3.13.0
- version: 3.13.0(@tiptap/pm@3.11.1)
+ version: 3.13.0(@tiptap/pm@3.13.0)
'@tiptap/extension-image':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
'@tiptap/extension-placeholder':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
'@tiptap/extension-table':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
'@tiptap/markdown':
- specifier: ^3.11.1
- version: 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ specifier: ^3.13.0
+ version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
'@tiptap/react':
- specifier: ^3.11.1
- version: 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: ^3.13.0
+ version: 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tiptap/starter-kit':
- specifier: ^3.11.1
- version: 3.11.1
+ specifier: ^3.13.0
+ version: 3.13.0
axios:
specifier: ^1.7.7
version: 1.7.9
@@ -1722,171 +1722,171 @@ packages:
peerDependencies:
'@tiptap/pm': ^3.13.0
- '@tiptap/extension-blockquote@3.11.1':
- resolution: {integrity: sha512-c3DN5c/9kl8w1wCcylH9XqW0OyCegqE3EL4rDlVYkyBD0GwCnUS30pN+jdxCUq/tl94lkkRk7XMyEUwzQmG+5g==}
+ '@tiptap/extension-blockquote@3.13.0':
+ resolution: {integrity: sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-bold@3.11.1':
- resolution: {integrity: sha512-ee/OoAPViUAgJb8dxF7D2YSSYUWcw8RXqhNSDx15w58rxpYbJbvOv3WDMrGNvl4M9nuwXYfXc3iPl/eYtwHx2w==}
+ '@tiptap/extension-bold@3.13.0':
+ resolution: {integrity: sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-bubble-menu@3.11.1':
- resolution: {integrity: sha512-rLgU2drvoSTpdXEmoo61ZSmtRR44vMeS36OoDpUA1dNzo/vWAiOzQeLnm8gC9cD2TmvJ+WIe7tOkpAEfw4kmiQ==}
+ '@tiptap/extension-bubble-menu@3.13.0':
+ resolution: {integrity: sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-bullet-list@3.11.1':
- resolution: {integrity: sha512-6fj0b0Ynam8FMsP3NiCZ4a2uP7lCBHDFBXfcRwFDOqAgBIPvIK+r6CuHEGothGaF7EeQ9MTyj9fwlGjyHsPQcg==}
+ '@tiptap/extension-bullet-list@3.13.0':
+ resolution: {integrity: sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA==}
peerDependencies:
- '@tiptap/extension-list': ^3.11.1
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-code-block@3.11.1':
- resolution: {integrity: sha512-Bk7mmA+m510zzLG5AMFmywrL50NlBA5p7bR0cKfdp4ckXr8FohxH3QS0Woy1MRnFUGRtIzJkSYQTJ3O/G1lBqQ==}
+ '@tiptap/extension-code-block@3.13.0':
+ resolution: {integrity: sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-code@3.11.1':
- resolution: {integrity: sha512-R3HtNPAuaKqbDwK1uWO/6QFHXbbKcxbV27XVCVtTQ4gCAzIZbJElp9REEqAOp/zI6bqt774UrAekeV+5i8NQYw==}
+ '@tiptap/extension-code@3.13.0':
+ resolution: {integrity: sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-document@3.11.1':
- resolution: {integrity: sha512-Px8T7Kv8EEiFpM/h13Rro8HoynrlK8zA3u3ekHq/FBSTXnPtqPAUYNx/DUhIrLs3eWWJ8+P0Onm+sVLZmaLMug==}
+ '@tiptap/extension-document@3.13.0':
+ resolution: {integrity: sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-dropcursor@3.11.1':
- resolution: {integrity: sha512-+tmWD/4tg7Mt1TArrvc1Gna1FiSyru2rE6sapEerXCH3RFfaqGBeMqeRaOeZrCiqB+vIsXfthHDC/7xz5rFp/g==}
+ '@tiptap/extension-dropcursor@3.13.0':
+ resolution: {integrity: sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ==}
peerDependencies:
- '@tiptap/extensions': ^3.11.1
+ '@tiptap/extensions': ^3.13.0
- '@tiptap/extension-floating-menu@3.11.1':
- resolution: {integrity: sha512-HGF04KhDn1oAD+2/zStSWeGIgR41l/raf64h/Rwkvde5Sf2g3BPRW4M1ESS6e2Rjw74Kwa4/xNO6CzZNid6yRg==}
+ '@tiptap/extension-floating-menu@3.13.0':
+ resolution: {integrity: sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA==}
peerDependencies:
'@floating-ui/dom': ^1.0.0
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-gapcursor@3.11.1':
- resolution: {integrity: sha512-rZ4eIFOPrLPM0bAMW560v/i9WeAz6D6PPtmFJ/Rwh7F5QFbg+jSXAyGvg7V9ZwzA5OaXqsToyJBR7qtGXBXAhQ==}
+ '@tiptap/extension-gapcursor@3.13.0':
+ resolution: {integrity: sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA==}
peerDependencies:
- '@tiptap/extensions': ^3.11.1
+ '@tiptap/extensions': ^3.13.0
- '@tiptap/extension-hard-break@3.11.1':
- resolution: {integrity: sha512-JMp6CizdB7LoY2jmaZub2D+Aj6RJTkSu0EhIcN/bmBrm4MjYa/ir6nRoo4/gYGIHzHwgwGR/1KmlqTJZW/xl4g==}
+ '@tiptap/extension-hard-break@3.13.0':
+ resolution: {integrity: sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-heading@3.11.1':
- resolution: {integrity: sha512-b9ShCSQhWXNzdbdn9a3j33cq646nS0EpVyNBQr0BMOpIcMI4Ot8LGEvPo0BNqPPvpjMJaP2N6xp+EIdk6tunfQ==}
+ '@tiptap/extension-heading@3.13.0':
+ resolution: {integrity: sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-horizontal-rule@3.11.1':
- resolution: {integrity: sha512-9zr6dItcJvzZtFlC+dyFb5VfWGzKzldPAOuln1d/GwKrBZds53O2vBmu4Jxfy22N9LuwiGB+2PYerq0UkLnxnA==}
+ '@tiptap/extension-horizontal-rule@3.13.0':
+ resolution: {integrity: sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-image@3.11.1':
- resolution: {integrity: sha512-lI+FyyHavUXHmDKxvSAdqGAvaYtVesAxHckeA60ZjZu9fBkUnVWHD8uR0TStX7EdOIRBWpzYrG7dDT4EkFVjTA==}
+ '@tiptap/extension-image@3.13.0':
+ resolution: {integrity: sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-italic@3.11.1':
- resolution: {integrity: sha512-SrjsU+gvhjoPKelc4YSeC2AQ0lhwLiDWMO7fW83CVitCF8iWXpBSeVCI5SxtPeZNKTZ1ZCc3lIQCeEHOC/gP0g==}
+ '@tiptap/extension-italic@3.13.0':
+ resolution: {integrity: sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-link@3.11.1':
- resolution: {integrity: sha512-ds5auQnWGcHwC2/c1iEvvybdLPcSDlxsii7FPaZg4LaSGdNojRB0qDRZw5dzYQZbfIf5vgYGcIVCVjNPZs1UwQ==}
+ '@tiptap/extension-link@3.13.0':
+ resolution: {integrity: sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
'@tiptap/extension-list-item@3.13.0':
resolution: {integrity: sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==}
peerDependencies:
'@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-list-keymap@3.11.1':
- resolution: {integrity: sha512-MpeuVi+bOHulbN65bOjaeoNJstNuAAEPdLwNjW25c9y2a8b5iZFY8vdVNENDiqq+dI+F5EaFGaEq0FN0uslfiA==}
+ '@tiptap/extension-list-keymap@3.13.0':
+ resolution: {integrity: sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g==}
peerDependencies:
- '@tiptap/extension-list': ^3.11.1
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-list@3.11.1':
- resolution: {integrity: sha512-XJRN9pOPMi3SsaKv4qM8WBEi3YDrjXYtYlAlZutQe1JpdKykSjLwwYq7k3V8UHqR3YKxyOV8HTYOYoOaZ9TMTQ==}
+ '@tiptap/extension-list@3.13.0':
+ resolution: {integrity: sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-ordered-list@3.11.1':
- resolution: {integrity: sha512-Aphq0kfk6J/hNQennJ+bntvDzqRPT7RVpnow1s4U4dLBsR6PP7X4zEBg96uAv2OW0RjDHFK9NFqpJPbZtQTmFw==}
+ '@tiptap/extension-ordered-list@3.13.0':
+ resolution: {integrity: sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g==}
peerDependencies:
- '@tiptap/extension-list': ^3.11.1
+ '@tiptap/extension-list': ^3.13.0
- '@tiptap/extension-paragraph@3.11.1':
- resolution: {integrity: sha512-a3lm1WvYewAP2IESq+qnbOtLSJ9yULY2Bj/6DvBq9fzWpb2gSlUdElYh6JLunxB1HEPECTuuRsNPdTrMsSpV4g==}
+ '@tiptap/extension-paragraph@3.13.0':
+ resolution: {integrity: sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-placeholder@3.11.1':
- resolution: {integrity: sha512-RCPLeZ12+20YeqjwENK8rviaCnN0mL+Rm7dS+NT8Dz1WHjv9ZWwWS7WzAHfY2w3CNlMfOKwctnaobKmJ/2++CQ==}
+ '@tiptap/extension-placeholder@3.13.0':
+ resolution: {integrity: sha512-Au4ktRBraQktX9gjSzGWyJV6kPof7+kOhzE8ej+rOMjIrHbx3DCHy1CJWftSO9BbqIyonjsFmm4nE+vjzZ3Z5Q==}
peerDependencies:
- '@tiptap/extensions': ^3.11.1
+ '@tiptap/extensions': ^3.13.0
- '@tiptap/extension-strike@3.11.1':
- resolution: {integrity: sha512-1LfkHNkrGR509cPRgcMr95+nWcAHE0JDm9LkuzdexunhCfJ2tl/h1rA14x3sic8GxQFqEnMefvBUpUbQwPydYw==}
+ '@tiptap/extension-strike@3.13.0':
+ resolution: {integrity: sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-table@3.11.1':
- resolution: {integrity: sha512-ae81cJgJ3cAA1Ry0JtLKtoPpA23YvMy0WnAw1gqnOX9yTYRYgT5W6MzP0O2rIBOI6b5Xkt6xtZzChjwMzioGbQ==}
+ '@tiptap/extension-table@3.13.0':
+ resolution: {integrity: sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/extension-text@3.11.1':
- resolution: {integrity: sha512-5E94ggkFAZ7OSFSwnofAsmxqmSStRoeCB8AnRuWrR+nnXi43Rq7yptdejQaLi13Z9fSVdnF6h+pB3ua2Exg6WQ==}
+ '@tiptap/extension-text@3.13.0':
+ resolution: {integrity: sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extension-underline@3.11.1':
- resolution: {integrity: sha512-Y3EJxfE1g4XSGbUZN+74o38mp3O+BQXtlqxAQvedzXiGGrdK2kWhp2b4nj3IkxHdRdoSijf+oZzgyBrRDdgC/w==}
+ '@tiptap/extension-underline@3.13.0':
+ resolution: {integrity: sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg==}
peerDependencies:
- '@tiptap/core': ^3.11.1
+ '@tiptap/core': ^3.13.0
- '@tiptap/extensions@3.11.1':
- resolution: {integrity: sha512-/xXJdV+EVvSQv2slvAUChb5iGVv5K0EqBqxPGAAuBHdIc4Y7Id1aaKKSiyDmqon+kjSnnQIIda9oUt+o/Z66uA==}
+ '@tiptap/extensions@3.13.0':
+ resolution: {integrity: sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/markdown@3.11.1':
- resolution: {integrity: sha512-cC3mKV+bBoHJ0Bsd4zstVYFBm8KGsbeAhIayVzY59G4QpvIQC78BgajqXukfUKDLcehiycRYYW/HcOAwvAysWg==}
+ '@tiptap/markdown@3.13.0':
+ resolution: {integrity: sha512-BI1GZxDFBrEeYbngbKh/si48tRSXO6HVGg7KzlfOwdncSD982/loG2KUnFIjoVGjmMzXNDWbI6O/eqfLVQPB5Q==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
- '@tiptap/pm@3.11.1':
- resolution: {integrity: sha512-8RIUhlEoCFGsbdNb+EUdQctG1Wnd7rl4wlMLS6giO7UcZT5dVfg625eMZVrl0/kA7JBJdKLIuqNmzzQ0MxsJEw==}
+ '@tiptap/pm@3.13.0':
+ resolution: {integrity: sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==}
- '@tiptap/react@3.11.1':
- resolution: {integrity: sha512-aPInZbpSWYzJvCFXaY6EhxD+H5ITURElUmUXBoRvlAB6QrR6NIWBt68hNe8i+aDGmuvLS18g60HWK5S6K2RjWQ==}
+ '@tiptap/react@3.13.0':
+ resolution: {integrity: sha512-VqpqNZ9qtPr3pWK4NsZYxXgLSEiAnzl6oS7tEGmkkvJbcGSC+F7R13Xc9twv/zT5QCLxaHdEbmxHbuAIkrMgJQ==}
peerDependencies:
- '@tiptap/core': ^3.11.1
- '@tiptap/pm': ^3.11.1
+ '@tiptap/core': ^3.13.0
+ '@tiptap/pm': ^3.13.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
'@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
- '@tiptap/starter-kit@3.11.1':
- resolution: {integrity: sha512-weRrhp0p5J6cMNcybYobhbOVrgym7KYIwBblJ/1M1snykg+avZawVk2M5Y7j9gM1p2zo112MCw8z8nOa9Yrwow==}
+ '@tiptap/starter-kit@3.13.0':
+ resolution: {integrity: sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg==}
'@tootallnate/once@1.1.2':
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
@@ -3747,6 +3747,10 @@ packages:
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+ fast-equals@5.4.0:
+ resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==}
+ engines: {node: '>=6.0.0'}
+
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
@@ -9370,138 +9374,138 @@ snapshots:
'@babel/runtime': 7.26.0
'@testing-library/dom': 8.20.1
- '@tiptap/core@3.13.0(@tiptap/pm@3.11.1)':
+ '@tiptap/core@3.13.0(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/pm': 3.11.1
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-blockquote@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-blockquote@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-bold@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-bold@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-bubble-menu@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-bubble-menu@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
optional: true
- '@tiptap/extension-bullet-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-bullet-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-code-block@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-code@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-code@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-document@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-document@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-dropcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-dropcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-floating-menu@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-floating-menu@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
'@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
optional: true
- '@tiptap/extension-gapcursor@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-gapcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-hard-break@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-hard-break@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-heading@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-heading@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-horizontal-rule@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-horizontal-rule@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-image@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-image@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-italic@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-italic@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-link@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
linkifyjs: 4.3.2
- '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-list-keymap@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-list-keymap@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-ordered-list@3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-ordered-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-paragraph@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-paragraph@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-placeholder@3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-placeholder@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-strike@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-strike@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-table@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/extension-text@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-text@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-underline@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))':
+ '@tiptap/extension-underline@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
- '@tiptap/markdown@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)':
+ '@tiptap/markdown@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
marked: 15.0.12
- '@tiptap/pm@3.11.1':
+ '@tiptap/pm@3.13.0':
dependencies:
prosemirror-changeset: 2.3.1
prosemirror-collab: 1.3.1
@@ -9522,49 +9526,49 @@ snapshots:
prosemirror-transform: 1.10.5
prosemirror-view: 1.41.3
- '@tiptap/react@3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tiptap/react@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
'@types/react': 18.3.16
'@types/react-dom': 18.3.5(@types/react@18.3.16)
'@types/use-sync-external-store': 0.0.6
- fast-deep-equal: 3.1.3
+ fast-equals: 5.4.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
use-sync-external-store: 1.6.0(react@18.3.1)
optionalDependencies:
- '@tiptap/extension-bubble-menu': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-floating-menu': 3.11.1(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
+ '@tiptap/extension-bubble-menu': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-floating-menu': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
transitivePeerDependencies:
- '@floating-ui/dom'
- '@tiptap/starter-kit@3.11.1':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.11.1)
- '@tiptap/extension-blockquote': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-bold': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-bullet-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-code': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-code-block': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-document': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-dropcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-gapcursor': 3.11.1(@tiptap/extensions@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-hard-break': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-heading': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-horizontal-rule': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-italic': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-link': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-list-keymap': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-ordered-list': 3.11.1(@tiptap/extension-list@3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1))
- '@tiptap/extension-paragraph': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-strike': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-text': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extension-underline': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))
- '@tiptap/extensions': 3.11.1(@tiptap/core@3.13.0(@tiptap/pm@3.11.1))(@tiptap/pm@3.11.1)
- '@tiptap/pm': 3.11.1
+ '@tiptap/starter-kit@3.13.0':
+ dependencies:
+ '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
+ '@tiptap/extension-blockquote': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-bold': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-bullet-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-code': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-code-block': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-document': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-dropcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-gapcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-hard-break': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-heading': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-horizontal-rule': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-italic': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-link': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-list-keymap': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-ordered-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
+ '@tiptap/extension-paragraph': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-strike': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-text': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extension-underline': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
+ '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
+ '@tiptap/pm': 3.13.0
'@tootallnate/once@1.1.2': {}
@@ -11827,6 +11831,8 @@ snapshots:
fast-diff@1.3.0: {}
+ fast-equals@5.4.0: {}
+
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
From d3a29e7ad9dbb2454ea315602eabc58c48486113 Mon Sep 17 00:00:00 2001
From: LinkinStars
Date: Fri, 19 Dec 2025 12:23:41 +0800
Subject: [PATCH 34/92] feat(theme): add layout options for site theme
configuration
---
internal/base/constant/site_info.go | 3 +++
internal/migrations/init.go | 2 +-
internal/migrations/v5.go | 3 ++-
internal/schema/siteinfo_schema.go | 2 ++
internal/service/siteinfo/siteinfo_service.go | 3 +++
internal/service/siteinfo_common/siteinfo_service.go | 4 ++++
6 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/internal/base/constant/site_info.go b/internal/base/constant/site_info.go
index 2d6668347..81481baba 100644
--- a/internal/base/constant/site_info.go
+++ b/internal/base/constant/site_info.go
@@ -43,6 +43,9 @@ const (
ColorSchemeLight = "light"
ColorSchemeDark = "dark"
ColorSchemeSystem = "system"
+
+ ThemeLayoutFullWidth = "Full-width"
+ ThemeLayoutFixedWidth = "Fixed-width"
)
const (
diff --git a/internal/migrations/init.go b/internal/migrations/init.go
index 8a72794fe..5ffb37711 100644
--- a/internal/migrations/init.go
+++ b/internal/migrations/init.go
@@ -236,7 +236,7 @@ func (m *Mentor) initSiteInfoLegalConfig() {
}
func (m *Mentor) initSiteInfoThemeConfig() {
- themeConfig := `{"theme":"default","theme_config":{"default":{"navbar_style":"#0033ff","primary_color":"#0033ff"}}}`
+ themeConfig := fmt.Sprintf(`{"theme":"default","theme_config":{"default":{"navbar_style":"#0033ff","primary_color":"#0033ff"}},"layout":"%s"}`, constant.ThemeLayoutFullWidth)
_, m.err = m.engine.Context(m.ctx).Insert(&entity.SiteInfo{
Type: "theme",
Content: themeConfig,
diff --git a/internal/migrations/v5.go b/internal/migrations/v5.go
index 91d12f159..b988dc655 100644
--- a/internal/migrations/v5.go
+++ b/internal/migrations/v5.go
@@ -24,6 +24,7 @@ import (
"encoding/json"
"fmt"
+ "github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/entity"
"xorm.io/xorm"
)
@@ -50,7 +51,7 @@ func addThemeAndPrivateMode(ctx context.Context, x *xorm.Engine) error {
}
}
- themeConfig := `{"theme":"default","theme_config":{"default":{"navbar_style":"#0033ff","primary_color":"#0033ff"}}}`
+ themeConfig := fmt.Sprintf(`{"theme":"default","theme_config":{"default":{"navbar_style":"#0033ff","primary_color":"#0033ff"}},"layout":"%s"}`, constant.ThemeLayoutFullWidth)
themeSiteInfo := &entity.SiteInfo{
Type: "theme",
Content: themeConfig,
diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go
index 7ab657512..9eb919a0c 100644
--- a/internal/schema/siteinfo_schema.go
+++ b/internal/schema/siteinfo_schema.go
@@ -181,6 +181,7 @@ type SiteThemeReq struct {
Theme string `validate:"required,gt=0,lte=255" json:"theme"`
ThemeConfig map[string]any `validate:"omitempty" json:"theme_config"`
ColorScheme string `validate:"omitempty,gt=0,lte=100" json:"color_scheme"`
+ Layout string `validate:"omitempty,oneof=Full-width Fixed-width" json:"layout"`
}
type SiteSeoReq struct {
@@ -217,6 +218,7 @@ type SiteThemeResp struct {
Theme string `json:"theme"`
ThemeConfig map[string]any `json:"theme_config"`
ColorScheme string `json:"color_scheme"`
+ Layout string `json:"layout"`
}
func (s *SiteThemeResp) TrTheme(ctx context.Context) {
diff --git a/internal/service/siteinfo/siteinfo_service.go b/internal/service/siteinfo/siteinfo_service.go
index f355d09f4..1eb187067 100644
--- a/internal/service/siteinfo/siteinfo_service.go
+++ b/internal/service/siteinfo/siteinfo_service.go
@@ -252,6 +252,9 @@ func (s *SiteInfoService) SaveSiteCustomCssHTML(ctx context.Context, req *schema
// SaveSiteTheme save site custom html configuration
func (s *SiteInfoService) SaveSiteTheme(ctx context.Context, req *schema.SiteThemeReq) (err error) {
+ if len(req.Layout) == 0 {
+ req.Layout = constant.ThemeLayoutFullWidth
+ }
content, _ := json.Marshal(req)
data := &entity.SiteInfo{
Type: constant.SiteTypeTheme,
diff --git a/internal/service/siteinfo_common/siteinfo_service.go b/internal/service/siteinfo_common/siteinfo_service.go
index fda117229..8a4b13669 100644
--- a/internal/service/siteinfo_common/siteinfo_service.go
+++ b/internal/service/siteinfo_common/siteinfo_service.go
@@ -198,10 +198,14 @@ func (s *siteInfoCommonService) GetSiteCustomCssHTML(ctx context.Context) (resp
func (s *siteInfoCommonService) GetSiteTheme(ctx context.Context) (resp *schema.SiteThemeResp, err error) {
resp = &schema.SiteThemeResp{
ThemeOptions: schema.GetThemeOptions,
+ Layout: constant.ThemeLayoutFullWidth,
}
if err = s.GetSiteInfoByType(ctx, constant.SiteTypeTheme, resp); err != nil {
return nil, err
}
+ if resp.Layout == "" {
+ resp.Layout = constant.ThemeLayoutFullWidth
+ }
resp.TrTheme(ctx)
return resp, nil
}
From b5ae2d0351118a6274616987ce1d3c5283cf6d93 Mon Sep 17 00:00:00 2001
From: shuai
Date: Fri, 19 Dec 2025 14:09:38 +0800
Subject: [PATCH 35/92] fix: admin/themes add layout config
---
cmd/wire_gen.go | 24 ++------------
docs/docs.go | 19 -----------
docs/swagger.yaml | 17 ----------
i18n/en_US.yaml | 4 +++
internal/service/mock/siteinfo_repo_mock.go | 19 -----------
ui/src/common/interface.ts | 1 +
ui/src/components/Customize/index.tsx | 36 +++++++++++++++++++++
ui/src/components/Header/index.tsx | 8 +++--
ui/src/pages/{ => 404}/403/index.tsx | 0
ui/src/pages/Admin/Themes/index.tsx | 11 +++++++
ui/src/pages/Layout/index.tsx | 11 +++++--
ui/src/pages/SideNavLayout/index.tsx | 2 +-
ui/src/stores/themeSetting.ts | 2 ++
13 files changed, 72 insertions(+), 82 deletions(-)
rename ui/src/pages/{ => 404}/403/index.tsx (100%)
diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index aae1c6af6..dbfd640df 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -1,28 +1,8 @@
-//go:build !wireinject
-// +build !wireinject
-
-/*
- * 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.
- */
-
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
+//go:build !wireinject
+// +build !wireinject
package answercmd
diff --git a/docs/docs.go b/docs/docs.go
index 5e9d5b39d..fc4d9909e 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -1,22 +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.
- */
-
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index e0244083b..42df5cbf1 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.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.
-
basePath: /
definitions:
constant.NotificationChannelKey:
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index eac322dac..4ab6b34b2 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -2186,6 +2186,10 @@ ui:
primary_color:
label: Primary color
text: Modify the colors used by your themes
+ layout:
+ label: Layout
+ full_width: Full-width
+ fixed_width: Fixed-width
css_and_html:
page_title: CSS and HTML
custom_css:
diff --git a/internal/service/mock/siteinfo_repo_mock.go b/internal/service/mock/siteinfo_repo_mock.go
index a98ceb68c..0a1b31e84 100644
--- a/internal/service/mock/siteinfo_repo_mock.go
+++ b/internal/service/mock/siteinfo_repo_mock.go
@@ -1,22 +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.
- */
-
// Code generated by MockGen. DO NOT EDIT.
// Source: ./siteinfo_service.go
//
diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts
index f2901908f..3a77047e7 100644
--- a/ui/src/common/interface.ts
+++ b/ui/src/common/interface.ts
@@ -469,6 +469,7 @@ export type themeConfig = {
export interface AdminSettingsTheme {
theme: string;
color_scheme: string;
+ layout: string;
theme_options?: { label: string; value: string }[];
theme_config: Record;
}
diff --git a/ui/src/components/Customize/index.tsx b/ui/src/components/Customize/index.tsx
index c02db675f..52cc651b3 100644
--- a/ui/src/components/Customize/index.tsx
+++ b/ui/src/components/Customize/index.tsx
@@ -18,6 +18,7 @@
*/
import { FC, memo, useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
import { customizeStore } from '@/stores';
@@ -117,6 +118,8 @@ const Index: FC = () => {
const { custom_head, custom_header, custom_footer } = customizeStore(
(state) => state,
);
+ const { pathname } = useLocation();
+
useEffect(() => {
const isSeo = document.querySelector('meta[name="go-template"]');
if (!isSeo) {
@@ -125,8 +128,41 @@ const Index: FC = () => {
}, 1000);
handleCustomHeader(custom_header);
handleCustomFooter(custom_footer);
+ } else {
+ isSeo.remove();
}
}, [custom_head, custom_header, custom_footer]);
+
+ useEffect(() => {
+ /**
+ * description: Activate scripts with data-client attribute when route changes
+ */
+ const allScript = document.body.querySelectorAll('script[data-client]');
+ console.log('allScript', allScript);
+ allScript.forEach((scriptNode) => {
+ const script = document.createElement('script');
+ script.setAttribute('data-client', 'true');
+ // If the script is already wrapped in an IIFE, use it directly; otherwise, wrap it in an IIFE
+ if (
+ /^\s*\(\s*function\s*\(\s*\)\s*{/.test(
+ (scriptNode as HTMLScriptElement).text,
+ ) ||
+ /^\s*\(\s*\(\s*\)\s*=>\s*{/.test((scriptNode as HTMLScriptElement).text)
+ ) {
+ script.text = (scriptNode as HTMLScriptElement).text;
+ } else {
+ script.text = `(() => {${(scriptNode as HTMLScriptElement).text}})();`;
+ }
+ for (let i = 0; i < scriptNode.attributes.length; i += 1) {
+ const attr = scriptNode.attributes[i];
+ if (attr.name !== 'data-client') {
+ script.setAttribute(attr.name, attr.value);
+ }
+ }
+ scriptNode.parentElement?.replaceChild(script, scriptNode);
+ });
+ }, [pathname]);
+
return null;
};
diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx
index 2d151b880..4e9c8189a 100644
--- a/ui/src/components/Header/index.tsx
+++ b/ui/src/components/Header/index.tsx
@@ -83,7 +83,7 @@ const Header: FC = () => {
let navbarStyle = 'theme-light';
let themeMode = 'light';
- const { theme, theme_config } = themeSettingStore((_) => _);
+ const { theme, theme_config, layout } = themeSettingStore((_) => _);
if (theme_config?.[theme]?.navbar_style) {
// const color = theme_config[theme].navbar_style.startsWith('#')
themeMode = isLight(theme_config[theme].navbar_style) ? 'light' : 'dark';
@@ -113,7 +113,11 @@ const Header: FC = () => {
backgroundColor: theme_config[theme].navbar_style,
}}
id="header">
-
+
{
diff --git a/ui/src/pages/403/index.tsx b/ui/src/pages/404/403/index.tsx
similarity index 100%
rename from ui/src/pages/403/index.tsx
rename to ui/src/pages/404/403/index.tsx
diff --git a/ui/src/pages/Admin/Themes/index.tsx b/ui/src/pages/Admin/Themes/index.tsx
index 94eccbca1..db2a13daf 100644
--- a/ui/src/pages/Admin/Themes/index.tsx
+++ b/ui/src/pages/Admin/Themes/index.tsx
@@ -46,6 +46,13 @@ const Index: FC = () => {
enumNames: themeSetting?.theme_options?.map((_) => _.label),
default: themeSetting?.theme_options?.[0]?.value,
},
+ layout: {
+ type: 'string',
+ title: t('layout.label'),
+ enum: ['Full-width', 'Fixed-width'],
+ enumNames: [t('layout.full_width'), t('layout.fixed_width')],
+ default: themeSetting?.layout,
+ },
color_scheme: {
type: 'string',
title: t('color_scheme.label'),
@@ -77,6 +84,9 @@ const Index: FC = () => {
color_scheme: {
'ui:widget': 'select',
},
+ layout: {
+ 'ui:widget': 'select',
+ },
navbar_style: {
'ui:widget': 'input_group',
'ui:options': {
@@ -131,6 +141,7 @@ const Index: FC = () => {
const reqParams: Type.AdminSettingsTheme = {
theme: themeName,
color_scheme: formData.color_scheme.value,
+ layout: formData.layout.value,
theme_config: {
[themeName]: {
navbar_style: formData.navbar_style.value,
diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx
index 984e13f40..bd229165c 100644
--- a/ui/src/pages/Layout/index.tsx
+++ b/ui/src/pages/Layout/index.tsx
@@ -22,12 +22,14 @@ import { Outlet, useLocation, ScrollRestoration } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
+import classnames from 'classnames';
import {
toastStore,
loginToContinueStore,
errorCodeStore,
siteLealStore,
+ themeSettingStore,
} from '@/stores';
import {
Header,
@@ -56,7 +58,8 @@ const Layout: FC = () => {
const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
const { show: showLoginToContinueModal } = loginToContinueStore();
const { data: notificationData } = useQueryNotificationStatus();
-
+ const layout = themeSettingStore((state) => state.layout);
+ console.log(layout);
useEffect(() => {
// handle footnote links
const fixFootnoteLinks = () => {
@@ -209,7 +212,11 @@ const Layout: FC = () => {
revalidateOnFocus: false,
}}>
-
+
{httpStatusCode ? (
) : (
diff --git a/ui/src/pages/SideNavLayout/index.tsx b/ui/src/pages/SideNavLayout/index.tsx
index 907b9b281..b9bc38b9c 100644
--- a/ui/src/pages/SideNavLayout/index.tsx
+++ b/ui/src/pages/SideNavLayout/index.tsx
@@ -38,7 +38,7 @@ const Index: FC = () => {
-
+
diff --git a/ui/src/stores/themeSetting.ts b/ui/src/stores/themeSetting.ts
index 4b13d818f..2f1b1edb4 100644
--- a/ui/src/stores/themeSetting.ts
+++ b/ui/src/stores/themeSetting.ts
@@ -27,6 +27,7 @@ interface IType {
theme_config: AdminSettingsTheme['theme_config'];
theme_options: AdminSettingsTheme['theme_options'];
color_scheme: AdminSettingsTheme['color_scheme'];
+ layout: AdminSettingsTheme['layout'];
update: (params: AdminSettingsTheme) => void;
}
@@ -40,6 +41,7 @@ const store = create
((set) => ({
primary_color: DEFAULT_THEME_COLOR,
},
},
+ layout: 'full',
update: (params) =>
set((state) => {
// Compatibility default value is colored or light before v1.5.1
From 2ae309abc329f16148c6fcf62e88afb4a72ba72a Mon Sep 17 00:00:00 2001
From: shuai
Date: Fri, 19 Dec 2025 14:11:42 +0800
Subject: [PATCH 36/92] fix: delete log
---
ui/src/components/Customize/index.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/ui/src/components/Customize/index.tsx b/ui/src/components/Customize/index.tsx
index 52cc651b3..7da85b057 100644
--- a/ui/src/components/Customize/index.tsx
+++ b/ui/src/components/Customize/index.tsx
@@ -138,7 +138,6 @@ const Index: FC = () => {
* description: Activate scripts with data-client attribute when route changes
*/
const allScript = document.body.querySelectorAll('script[data-client]');
- console.log('allScript', allScript);
allScript.forEach((scriptNode) => {
const script = document.createElement('script');
script.setAttribute('data-client', 'true');
From 92d853ce3c85976c0718db0db29b1ac8efe5bb70 Mon Sep 17 00:00:00 2001
From: shuai
Date: Fri, 19 Dec 2025 14:14:37 +0800
Subject: [PATCH 37/92] fix: add i18n
---
i18n/zh_CN.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 53dca07e3..a86f89c38 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -2147,6 +2147,10 @@ ui:
primary_color:
label: 主色调
text: 修改您主题使用的颜色
+ layout:
+ label: 布局
+ full_width: 全宽
+ fixed_width: 定宽
css_and_html:
page_title: CSS 与 HTML
custom_css:
From f92d3a3481b87565af4c25838912c546264bf847 Mon Sep 17 00:00:00 2001
From: shuai
Date: Fri, 19 Dec 2025 15:45:19 +0800
Subject: [PATCH 38/92] fix: update zh_CN.ymal content
---
i18n/zh_CN.yaml | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index a86f89c38..a6c7f1983 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -2155,19 +2155,16 @@ ui:
page_title: CSS 与 HTML
custom_css:
label: 自定义 CSS
- text: >
-
+ text: 这将插入为 <link>
head:
label: 头部
- text: >
-
+ text: 这将插入到 </head> 之前。
header:
label: 页眉
- text: >
-
+ text: 这将插入到 <body> 之后。
footer:
label: 页脚
- text: 这将在 </body> 之前插入。
+ text: 这将插入到 </body> 之前。
sidebar:
label: 侧边栏
text: 这将插入侧边栏中。
From c8881889c85083bd386a1c2c542598ca3e97d6cc Mon Sep 17 00:00:00 2001
From: shuai
Date: Mon, 22 Dec 2025 17:29:17 +0800
Subject: [PATCH 39/92] fix: admin.themes layout initial value
---
ui/src/pages/Admin/Themes/index.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/ui/src/pages/Admin/Themes/index.tsx b/ui/src/pages/Admin/Themes/index.tsx
index db2a13daf..873dd7c9c 100644
--- a/ui/src/pages/Admin/Themes/index.tsx
+++ b/ui/src/pages/Admin/Themes/index.tsx
@@ -182,6 +182,7 @@ const Index: FC = () => {
: DEFAULT_THEME_COLOR;
formMeta.primary_color.value = themeConfig?.primary_color;
formData.color_scheme.value = setting?.color_scheme || 'system';
+ formData.layout.value = setting?.layout || 'Full-width';
setFormData({ ...formMeta });
}
});
From aa7e19b89675a75124d0d3141b8524909802eff0 Mon Sep 17 00:00:00 2001
From: robin
Date: Mon, 22 Dec 2025 18:25:42 +0800
Subject: [PATCH 40/92] Remove TipTap editor utility files including commands,
constants, error handling, events, position conversion, and table extension
to streamline the editor's functionality and reduce code complexity.
---
ui/package.json | 9 +-
ui/pnpm-lock.yaml | 671 +---------------
ui/src/components/Editor/RichEditor.tsx | 206 -----
ui/src/components/Editor/index.tsx | 40 +-
.../components/Editor/utils/tiptap/adapter.ts | 59 --
ui/src/components/Editor/utils/tiptap/base.ts | 358 ---------
.../Editor/utils/tiptap/commands.ts | 720 ------------------
.../Editor/utils/tiptap/constants.ts | 48 --
.../Editor/utils/tiptap/errorHandler.ts | 155 ----
.../components/Editor/utils/tiptap/events.ts | 104 ---
.../Editor/utils/tiptap/position.ts | 147 ----
.../Editor/utils/tiptap/tableExtension.ts | 81 --
12 files changed, 5 insertions(+), 2593 deletions(-)
delete mode 100644 ui/src/components/Editor/RichEditor.tsx
delete mode 100644 ui/src/components/Editor/utils/tiptap/adapter.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/base.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/commands.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/constants.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/errorHandler.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/events.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/position.ts
delete mode 100644 ui/src/components/Editor/utils/tiptap/tableExtension.ts
diff --git a/ui/package.json b/ui/package.json
index 005a6abac..b88fc12da 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -21,13 +21,6 @@
"@codemirror/language-data": "^6.5.0",
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.26.1",
- "@tiptap/core": "^3.13.0",
- "@tiptap/extension-image": "^3.13.0",
- "@tiptap/extension-placeholder": "^3.13.0",
- "@tiptap/extension-table": "^3.13.0",
- "@tiptap/markdown": "^3.13.0",
- "@tiptap/react": "^3.13.0",
- "@tiptap/starter-kit": "^3.13.0",
"axios": "^1.7.7",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.10.5",
@@ -107,4 +100,4 @@
"pnpm": ">=9"
},
"license": "MIT"
-}
\ No newline at end of file
+}
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 3fa75e7b4..89a37ab16 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -20,27 +20,6 @@ importers:
'@codemirror/view':
specifier: ^6.26.1
version: 6.35.3
- '@tiptap/core':
- specifier: ^3.13.0
- version: 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-image':
- specifier: ^3.13.0
- version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-placeholder':
- specifier: ^3.13.0
- version: 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-table':
- specifier: ^3.13.0
- version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/markdown':
- specifier: ^3.13.0
- version: 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/react':
- specifier: ^3.13.0
- version: 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@tiptap/starter-kit':
- specifier: ^3.13.0
- version: 3.13.0
axios:
specifier: ^1.7.7
version: 1.7.9
@@ -1245,15 +1224,6 @@ packages:
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- '@floating-ui/core@1.7.3':
- resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
-
- '@floating-ui/dom@1.7.4':
- resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
-
- '@floating-ui/utils@0.2.10':
- resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
-
'@fullhuman/postcss-purgecss@4.1.3':
resolution: {integrity: sha512-jqcsyfvq09VOsMXxJMPLRF6Fhg/NNltzWKnC9qtzva+QKTxerCO4esG6je7hbnmkpZtaDyPTwMBj9bzfWorsrw==}
peerDependencies:
@@ -1497,9 +1467,6 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
- '@remirror/core-constants@3.0.0':
- resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
-
'@restart/hooks@0.4.16':
resolution: {integrity: sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==}
peerDependencies:
@@ -1717,177 +1684,6 @@ packages:
peerDependencies:
'@testing-library/dom': '>=7.21.4'
- '@tiptap/core@3.13.0':
- resolution: {integrity: sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==}
- peerDependencies:
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-blockquote@3.13.0':
- resolution: {integrity: sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-bold@3.13.0':
- resolution: {integrity: sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-bubble-menu@3.13.0':
- resolution: {integrity: sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-bullet-list@3.13.0':
- resolution: {integrity: sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA==}
- peerDependencies:
- '@tiptap/extension-list': ^3.13.0
-
- '@tiptap/extension-code-block@3.13.0':
- resolution: {integrity: sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-code@3.13.0':
- resolution: {integrity: sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-document@3.13.0':
- resolution: {integrity: sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-dropcursor@3.13.0':
- resolution: {integrity: sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ==}
- peerDependencies:
- '@tiptap/extensions': ^3.13.0
-
- '@tiptap/extension-floating-menu@3.13.0':
- resolution: {integrity: sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA==}
- peerDependencies:
- '@floating-ui/dom': ^1.0.0
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-gapcursor@3.13.0':
- resolution: {integrity: sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA==}
- peerDependencies:
- '@tiptap/extensions': ^3.13.0
-
- '@tiptap/extension-hard-break@3.13.0':
- resolution: {integrity: sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-heading@3.13.0':
- resolution: {integrity: sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-horizontal-rule@3.13.0':
- resolution: {integrity: sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-image@3.13.0':
- resolution: {integrity: sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-italic@3.13.0':
- resolution: {integrity: sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-link@3.13.0':
- resolution: {integrity: sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-list-item@3.13.0':
- resolution: {integrity: sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw==}
- peerDependencies:
- '@tiptap/extension-list': ^3.13.0
-
- '@tiptap/extension-list-keymap@3.13.0':
- resolution: {integrity: sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g==}
- peerDependencies:
- '@tiptap/extension-list': ^3.13.0
-
- '@tiptap/extension-list@3.13.0':
- resolution: {integrity: sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-ordered-list@3.13.0':
- resolution: {integrity: sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g==}
- peerDependencies:
- '@tiptap/extension-list': ^3.13.0
-
- '@tiptap/extension-paragraph@3.13.0':
- resolution: {integrity: sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-placeholder@3.13.0':
- resolution: {integrity: sha512-Au4ktRBraQktX9gjSzGWyJV6kPof7+kOhzE8ej+rOMjIrHbx3DCHy1CJWftSO9BbqIyonjsFmm4nE+vjzZ3Z5Q==}
- peerDependencies:
- '@tiptap/extensions': ^3.13.0
-
- '@tiptap/extension-strike@3.13.0':
- resolution: {integrity: sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-table@3.13.0':
- resolution: {integrity: sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/extension-text@3.13.0':
- resolution: {integrity: sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extension-underline@3.13.0':
- resolution: {integrity: sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
-
- '@tiptap/extensions@3.13.0':
- resolution: {integrity: sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/markdown@3.13.0':
- resolution: {integrity: sha512-BI1GZxDFBrEeYbngbKh/si48tRSXO6HVGg7KzlfOwdncSD982/loG2KUnFIjoVGjmMzXNDWbI6O/eqfLVQPB5Q==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
-
- '@tiptap/pm@3.13.0':
- resolution: {integrity: sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==}
-
- '@tiptap/react@3.13.0':
- resolution: {integrity: sha512-VqpqNZ9qtPr3pWK4NsZYxXgLSEiAnzl6oS7tEGmkkvJbcGSC+F7R13Xc9twv/zT5QCLxaHdEbmxHbuAIkrMgJQ==}
- peerDependencies:
- '@tiptap/core': ^3.13.0
- '@tiptap/pm': ^3.13.0
- '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
- '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
- react: ^17.0.0 || ^18.0.0 || ^19.0.0
- react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
-
- '@tiptap/starter-kit@3.13.0':
- resolution: {integrity: sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg==}
-
'@tootallnate/once@1.1.2':
resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
engines: {node: '>= 6'}
@@ -2007,21 +1803,12 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
- '@types/linkify-it@5.0.0':
- resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
-
'@types/lodash@4.17.13':
resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==}
- '@types/markdown-it@14.1.2':
- resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
-
'@types/marked@4.3.2':
resolution: {integrity: sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==}
- '@types/mdurl@2.0.0':
- resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
-
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
@@ -2096,9 +1883,6 @@ packages:
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
- '@types/use-sync-external-store@0.0.6':
- resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
-
'@types/warning@3.0.3':
resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==}
@@ -3380,10 +3164,6 @@ packages:
entities@2.2.0:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
- entities@4.5.0:
- resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
- engines: {node: '>=0.12'}
-
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
@@ -3747,10 +3527,6 @@ packages:
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
- fast-equals@5.4.0:
- resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==}
- engines: {node: '>=6.0.0'}
-
fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
@@ -4775,12 +4551,6 @@ packages:
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
- linkify-it@5.0.0:
- resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
-
- linkifyjs@4.3.2:
- resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
-
lint-staged@15.5.0:
resolution: {integrity: sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==}
engines: {node: '>=18.12.0'}
@@ -4909,15 +4679,6 @@ packages:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
- markdown-it@14.1.0:
- resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
- hasBin: true
-
- marked@15.0.12:
- resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
- engines: {node: '>= 18'}
- hasBin: true
-
marked@4.3.0:
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
engines: {node: '>= 12'}
@@ -4929,9 +4690,6 @@ packages:
mdn-data@2.0.4:
resolution: {integrity: sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==}
- mdurl@2.0.0:
- resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
-
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -5210,9 +4968,6 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
- orderedmap@2.1.1:
- resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
-
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
@@ -5825,64 +5580,6 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
- prosemirror-changeset@2.3.1:
- resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
-
- prosemirror-collab@1.3.1:
- resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
-
- prosemirror-commands@1.7.1:
- resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
-
- prosemirror-dropcursor@1.8.2:
- resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
-
- prosemirror-gapcursor@1.4.0:
- resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==}
-
- prosemirror-history@1.5.0:
- resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
-
- prosemirror-inputrules@1.5.1:
- resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
-
- prosemirror-keymap@1.2.3:
- resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
-
- prosemirror-markdown@1.13.2:
- resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
-
- prosemirror-menu@1.2.5:
- resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
-
- prosemirror-model@1.25.4:
- resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
-
- prosemirror-schema-basic@1.2.4:
- resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
-
- prosemirror-schema-list@1.5.1:
- resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
-
- prosemirror-state@1.4.4:
- resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
-
- prosemirror-tables@1.8.1:
- resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==}
-
- prosemirror-trailing-node@3.0.0:
- resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
- peerDependencies:
- prosemirror-model: ^1.22.1
- prosemirror-state: ^1.4.2
- prosemirror-view: ^1.33.8
-
- prosemirror-transform@1.10.5:
- resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==}
-
- prosemirror-view@1.41.3:
- resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==}
-
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
@@ -5893,10 +5590,6 @@ packages:
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
- punycode.js@2.3.1:
- resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
- engines: {node: '>=6'}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -6242,9 +5935,6 @@ packages:
engines: {node: '>=10.0.0'}
hasBin: true
- rope-sequence@1.3.4:
- resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
-
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6911,9 +6601,6 @@ packages:
engines: {node: '>=4.2.0'}
hasBin: true
- uc.micro@2.1.0:
- resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
-
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@@ -8729,20 +8416,6 @@ snapshots:
'@eslint/js@8.57.1': {}
- '@floating-ui/core@1.7.3':
- dependencies:
- '@floating-ui/utils': 0.2.10
- optional: true
-
- '@floating-ui/dom@1.7.4':
- dependencies:
- '@floating-ui/core': 1.7.3
- '@floating-ui/utils': 0.2.10
- optional: true
-
- '@floating-ui/utils@0.2.10':
- optional: true
-
'@fullhuman/postcss-purgecss@4.1.3(postcss@8.4.49)':
dependencies:
postcss: 8.4.49
@@ -9129,8 +8802,6 @@ snapshots:
'@swc/helpers': 0.5.15
react: 18.3.1
- '@remirror/core-constants@3.0.0': {}
-
'@restart/hooks@0.4.16(react@18.3.1)':
dependencies:
dequal: 2.0.3
@@ -9374,202 +9045,6 @@ snapshots:
'@babel/runtime': 7.26.0
'@testing-library/dom': 8.20.1
- '@tiptap/core@3.13.0(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/pm': 3.13.0
-
- '@tiptap/extension-blockquote@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-bold@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-bubble-menu@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
- optional: true
-
- '@tiptap/extension-bullet-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-code-block@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
- '@tiptap/extension-code@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-document@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-dropcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-floating-menu@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@floating-ui/dom': 1.7.4
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
- optional: true
-
- '@tiptap/extension-gapcursor@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-hard-break@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-heading@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-horizontal-rule@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
- '@tiptap/extension-image@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-italic@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-link@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
- linkifyjs: 4.3.2
-
- '@tiptap/extension-list-item@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-list-keymap@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
- '@tiptap/extension-ordered-list@3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-paragraph@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-placeholder@3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-strike@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-table@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
- '@tiptap/extension-text@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extension-underline@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
-
- '@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
- '@tiptap/markdown@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
- marked: 15.0.12
-
- '@tiptap/pm@3.13.0':
- dependencies:
- prosemirror-changeset: 2.3.1
- prosemirror-collab: 1.3.1
- prosemirror-commands: 1.7.1
- prosemirror-dropcursor: 1.8.2
- prosemirror-gapcursor: 1.4.0
- prosemirror-history: 1.5.0
- prosemirror-inputrules: 1.5.1
- prosemirror-keymap: 1.2.3
- prosemirror-markdown: 1.13.2
- prosemirror-menu: 1.2.5
- prosemirror-model: 1.25.4
- prosemirror-schema-basic: 1.2.4
- prosemirror-schema-list: 1.5.1
- prosemirror-state: 1.4.4
- prosemirror-tables: 1.8.1
- prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)
- prosemirror-transform: 1.10.5
- prosemirror-view: 1.41.3
-
- '@tiptap/react@3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)(@types/react-dom@18.3.5(@types/react@18.3.16))(@types/react@18.3.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
- '@types/react': 18.3.16
- '@types/react-dom': 18.3.5(@types/react@18.3.16)
- '@types/use-sync-external-store': 0.0.6
- fast-equals: 5.4.0
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- use-sync-external-store: 1.6.0(react@18.3.1)
- optionalDependencies:
- '@tiptap/extension-bubble-menu': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-floating-menu': 3.13.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- transitivePeerDependencies:
- - '@floating-ui/dom'
-
- '@tiptap/starter-kit@3.13.0':
- dependencies:
- '@tiptap/core': 3.13.0(@tiptap/pm@3.13.0)
- '@tiptap/extension-blockquote': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-bold': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-bullet-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-code': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-code-block': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-document': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-dropcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-gapcursor': 3.13.0(@tiptap/extensions@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-hard-break': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-heading': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-horizontal-rule': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-italic': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-link': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-list': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/extension-list-item': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-list-keymap': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-ordered-list': 3.13.0(@tiptap/extension-list@3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0))
- '@tiptap/extension-paragraph': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-strike': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-text': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extension-underline': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))
- '@tiptap/extensions': 3.13.0(@tiptap/core@3.13.0(@tiptap/pm@3.13.0))(@tiptap/pm@3.13.0)
- '@tiptap/pm': 3.13.0
-
'@tootallnate/once@1.1.2': {}
'@trysound/sax@0.2.0': {}
@@ -9715,19 +9190,10 @@ snapshots:
'@types/json5@0.0.29': {}
- '@types/linkify-it@5.0.0': {}
-
'@types/lodash@4.17.13': {}
- '@types/markdown-it@14.1.2':
- dependencies:
- '@types/linkify-it': 5.0.0
- '@types/mdurl': 2.0.0
-
'@types/marked@4.3.2': {}
- '@types/mdurl@2.0.0': {}
-
'@types/mime@1.3.5': {}
'@types/minimist@1.2.5': {}
@@ -9798,8 +9264,6 @@ snapshots:
'@types/trusted-types@2.0.7': {}
- '@types/use-sync-external-store@0.0.6': {}
-
'@types/warning@3.0.3': {}
'@types/ws@8.5.13':
@@ -11233,8 +10697,6 @@ snapshots:
entities@2.2.0: {}
- entities@4.5.0: {}
-
environment@1.1.0: {}
error-ex@1.3.2:
@@ -11831,8 +11293,6 @@ snapshots:
fast-diff@1.3.0: {}
- fast-equals@5.4.0: {}
-
fast-glob@3.3.2:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -13126,12 +12586,6 @@ snapshots:
lines-and-columns@1.2.4: {}
- linkify-it@5.0.0:
- dependencies:
- uc.micro: 2.1.0
-
- linkifyjs@4.3.2: {}
-
lint-staged@15.5.0:
dependencies:
chalk: 5.4.1
@@ -13259,25 +12713,12 @@ snapshots:
map-obj@4.3.0: {}
- markdown-it@14.1.0:
- dependencies:
- argparse: 2.0.1
- entities: 4.5.0
- linkify-it: 5.0.0
- mdurl: 2.0.0
- punycode.js: 2.3.1
- uc.micro: 2.1.0
-
- marked@15.0.12: {}
-
marked@4.3.0: {}
mdn-data@2.0.14: {}
mdn-data@2.0.4: {}
- mdurl@2.0.0: {}
-
media-typer@0.3.0: {}
memfs@3.5.3:
@@ -13558,8 +12999,6 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
- orderedmap@2.1.1: {}
-
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
@@ -14151,109 +13590,6 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
- prosemirror-changeset@2.3.1:
- dependencies:
- prosemirror-transform: 1.10.5
-
- prosemirror-collab@1.3.1:
- dependencies:
- prosemirror-state: 1.4.4
-
- prosemirror-commands@1.7.1:
- dependencies:
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
-
- prosemirror-dropcursor@1.8.2:
- dependencies:
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
- prosemirror-view: 1.41.3
-
- prosemirror-gapcursor@1.4.0:
- dependencies:
- prosemirror-keymap: 1.2.3
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-view: 1.41.3
-
- prosemirror-history@1.5.0:
- dependencies:
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
- prosemirror-view: 1.41.3
- rope-sequence: 1.3.4
-
- prosemirror-inputrules@1.5.1:
- dependencies:
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
-
- prosemirror-keymap@1.2.3:
- dependencies:
- prosemirror-state: 1.4.4
- w3c-keyname: 2.2.8
-
- prosemirror-markdown@1.13.2:
- dependencies:
- '@types/markdown-it': 14.1.2
- markdown-it: 14.1.0
- prosemirror-model: 1.25.4
-
- prosemirror-menu@1.2.5:
- dependencies:
- crelt: 1.0.6
- prosemirror-commands: 1.7.1
- prosemirror-history: 1.5.0
- prosemirror-state: 1.4.4
-
- prosemirror-model@1.25.4:
- dependencies:
- orderedmap: 2.1.1
-
- prosemirror-schema-basic@1.2.4:
- dependencies:
- prosemirror-model: 1.25.4
-
- prosemirror-schema-list@1.5.1:
- dependencies:
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
-
- prosemirror-state@1.4.4:
- dependencies:
- prosemirror-model: 1.25.4
- prosemirror-transform: 1.10.5
- prosemirror-view: 1.41.3
-
- prosemirror-tables@1.8.1:
- dependencies:
- prosemirror-keymap: 1.2.3
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
- prosemirror-view: 1.41.3
-
- prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3):
- dependencies:
- '@remirror/core-constants': 3.0.0
- escape-string-regexp: 4.0.0
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-view: 1.41.3
-
- prosemirror-transform@1.10.5:
- dependencies:
- prosemirror-model: 1.25.4
-
- prosemirror-view@1.41.3:
- dependencies:
- prosemirror-model: 1.25.4
- prosemirror-state: 1.4.4
- prosemirror-transform: 1.10.5
-
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
@@ -14265,8 +13601,6 @@ snapshots:
dependencies:
punycode: 2.3.1
- punycode.js@2.3.1: {}
-
punycode@2.3.1: {}
purgecss-webpack-plugin@4.1.3(webpack@5.97.1(@swc/core@1.15.3)):
@@ -14725,8 +14059,6 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
- rope-sequence@1.3.4: {}
-
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -15481,8 +14813,6 @@ snapshots:
typescript@4.9.5: {}
- uc.micro@2.1.0: {}
-
unbox-primitive@1.0.2:
dependencies:
call-bind: 1.0.8
@@ -15549,6 +14879,7 @@ snapshots:
use-sync-external-store@1.6.0(react@18.3.1):
dependencies:
react: 18.3.1
+ optional: true
util-deprecate@1.0.2: {}
diff --git a/ui/src/components/Editor/RichEditor.tsx b/ui/src/components/Editor/RichEditor.tsx
deleted file mode 100644
index 55fca5a99..000000000
--- a/ui/src/components/Editor/RichEditor.tsx
+++ /dev/null
@@ -1,206 +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 { useEffect, useRef, useCallback } from 'react';
-
-import {
- useEditor,
- EditorContent,
- Editor as TipTapEditor,
-} from '@tiptap/react';
-import StarterKit from '@tiptap/starter-kit';
-import Placeholder from '@tiptap/extension-placeholder';
-import { Markdown } from '@tiptap/markdown';
-import Image from '@tiptap/extension-image';
-import { TableRow, TableCell, TableHeader } from '@tiptap/extension-table';
-
-import { Editor, BaseEditorProps } from './types';
-import { createTipTapAdapter } from './utils/tiptap/adapter';
-import { TableWithWrapper } from './utils/tiptap/tableExtension';
-
-interface RichEditorProps extends BaseEditorProps {}
-
-const RichEditor: React.FC = ({
- value,
- onChange,
- onFocus,
- onBlur,
- placeholder = '',
- autoFocus = false,
- onEditorReady,
-}) => {
- const lastSyncedValueRef = useRef(value);
- const adaptedEditorRef = useRef(null);
- const isInitializedRef = useRef(false);
- const isUpdatingFromPropsRef = useRef(false);
- const onEditorReadyRef = useRef(onEditorReady);
- const autoFocusRef = useRef(autoFocus);
- const initialValueRef = useRef(value);
-
- useEffect(() => {
- onEditorReadyRef.current = onEditorReady;
- autoFocusRef.current = autoFocus;
- }, [onEditorReady, autoFocus]);
-
- const isViewAvailable = (editorInstance: TipTapEditor | null): boolean => {
- if (!editorInstance) {
- return false;
- }
- if (editorInstance.isDestroyed) {
- return false;
- }
- return !!(editorInstance.view && editorInstance.state);
- };
-
- const handleCreate = useCallback(
- ({ editor: editorInstance }: { editor: TipTapEditor }) => {
- if (isInitializedRef.current || !isViewAvailable(editorInstance)) {
- return;
- }
-
- isInitializedRef.current = true;
-
- const initialValue = initialValueRef.current;
- if (initialValue && initialValue.trim() !== '') {
- editorInstance.commands.setContent(initialValue, {
- contentType: 'markdown',
- });
- lastSyncedValueRef.current = initialValue;
- }
-
- adaptedEditorRef.current = createTipTapAdapter(editorInstance);
- onEditorReadyRef.current?.(adaptedEditorRef.current);
-
- if (autoFocusRef.current) {
- editorInstance.commands.focus();
- }
- },
- [],
- );
-
- const handleUpdate = useCallback(
- ({ editor: editorInstance }: { editor: TipTapEditor }) => {
- if (onChange && !isUpdatingFromPropsRef.current) {
- const markdown = editorInstance.getMarkdown();
- onChange(markdown);
- lastSyncedValueRef.current = markdown;
- }
- },
- [onChange],
- );
-
- const handleFocus = useCallback(() => {
- onFocus?.();
- }, [onFocus]);
-
- const handleBlur = useCallback(() => {
- onBlur?.();
- }, [onBlur]);
-
- const editor = useEditor({
- extensions: [
- StarterKit,
- Markdown,
- Image,
- TableWithWrapper.configure({
- HTMLAttributes: {
- class: 'table table-bordered',
- style: {
- width: '100%',
- },
- },
- resizable: true,
- wrapperClass: 'table-responsive',
- }),
- TableRow.configure({
- HTMLAttributes: {
- class: 'table-row',
- },
- }),
- TableCell,
- TableHeader,
- Placeholder.configure({
- placeholder,
- }),
- ],
- onCreate: handleCreate,
- onUpdate: handleUpdate,
- onFocus: handleFocus,
- onBlur: handleBlur,
- editorProps: {
- attributes: {
- class: 'tiptap-editor fmt',
- },
- },
- });
-
- useEffect(() => {
- if (
- !editor ||
- !isInitializedRef.current ||
- !isViewAvailable(editor) ||
- value === lastSyncedValueRef.current
- ) {
- return;
- }
-
- try {
- const currentMarkdown = editor.getMarkdown();
- if (currentMarkdown !== value) {
- isUpdatingFromPropsRef.current = true;
- if (value && value.trim() !== '') {
- editor.commands.setContent(value, { contentType: 'markdown' });
- } else {
- editor.commands.clearContent();
- }
- lastSyncedValueRef.current = value || '';
- setTimeout(() => {
- isUpdatingFromPropsRef.current = false;
- }, 0);
- }
- } catch (error) {
- console.warn('Editor view not available when syncing value:', error);
- }
- }, [editor, value]);
-
- useEffect(() => {
- initialValueRef.current = value;
- lastSyncedValueRef.current = value;
- isInitializedRef.current = false;
- adaptedEditorRef.current = null;
- isUpdatingFromPropsRef.current = false;
-
- return () => {
- if (editor) {
- editor.destroy();
- }
- isInitializedRef.current = false;
- adaptedEditorRef.current = null;
- isUpdatingFromPropsRef.current = false;
- };
- }, [editor]);
-
- if (!editor) {
- return Loading editor...
;
- }
-
- return ;
-};
-
-export default RichEditor;
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index ebe04876c..c6136c3c8 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -51,7 +51,6 @@ import {
import { htmlRender } from './utils';
import Viewer from './Viewer';
import { EditorContext } from './EditorContext';
-import RichEditor from './RichEditor';
import MarkdownEditor from './MarkdownEditor';
import { Editor } from './types';
@@ -86,24 +85,11 @@ const MDEditor: ForwardRefRenderFunction = (
},
ref,
) => {
- const [mode, setMode] = useState<'markdown' | 'rich'>('markdown');
const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
useRenderPlugin(previewRef.current?.element);
- const handleModeChange = useCallback(
- (newMode: 'markdown' | 'rich') => {
- if (newMode === mode) {
- return;
- }
-
- setCurrentEditor(null);
- setMode(newMode);
- },
- [mode],
- );
-
const getHtml = useCallback(() => {
return previewRef.current?.getHtml();
}, []);
@@ -116,7 +102,7 @@ const MDEditor: ForwardRefRenderFunction = (
[getHtml],
);
- const EditorComponent = mode === 'markdown' ? MarkdownEditor : RichEditor;
+ const EditorComponent = MarkdownEditor;
return (
<>
@@ -149,30 +135,10 @@ const MDEditor: ForwardRefRenderFunction = (
-
- handleModeChange('markdown')}>
-
-
- handleModeChange('rich')}>
-
-
-
{
onChange?.(markdown);
@@ -186,7 +152,7 @@ const MDEditor: ForwardRefRenderFunction = (
}}
/>
- {mode === 'markdown' && }
+
>
);
};
diff --git a/ui/src/components/Editor/utils/tiptap/adapter.ts b/ui/src/components/Editor/utils/tiptap/adapter.ts
deleted file mode 100644
index 415edb175..000000000
--- a/ui/src/components/Editor/utils/tiptap/adapter.ts
+++ /dev/null
@@ -1,59 +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 { Editor as TipTapEditor } from '@tiptap/react';
-
-import { Editor, ExtendEditor } from '../../types';
-
-import { createBaseMethods } from './base';
-import { createEventMethods } from './events';
-import { createCommandMethods } from './commands';
-
-/**
- * Adapts TipTap editor to CodeMirror editor interface
- *
- * This adapter function converts TipTap editor's API to a CodeMirror-compatible interface,
- * enabling toolbar components to work properly in Rich mode. The adapter implements
- * the complete `ExtendEditor` interface, including base methods, event handling, and command methods.
- *
- * @param editor - TipTap editor instance
- * @returns Adapted editor instance that implements the unified Editor interface
- *
- * @example
- * ```typescript
- * const tipTapEditor = useEditor({ ... });
- * const adaptedEditor = createTipTapAdapter(tipTapEditor);
- * // Now you can use the unified API
- * adaptedEditor.insertBold('text');
- * adaptedEditor.insertHeading(1, 'Title');
- * ```
- */
-export function createTipTapAdapter(editor: TipTapEditor): Editor {
- const baseMethods = createBaseMethods(editor);
- const eventMethods = createEventMethods(editor);
- const commandMethods = createCommandMethods(editor);
-
- const editorAdapter: ExtendEditor = {
- ...baseMethods,
- ...eventMethods,
- ...commandMethods,
- };
-
- return editorAdapter as unknown as Editor;
-}
diff --git a/ui/src/components/Editor/utils/tiptap/base.ts b/ui/src/components/Editor/utils/tiptap/base.ts
deleted file mode 100644
index e088a9f5d..000000000
--- a/ui/src/components/Editor/utils/tiptap/base.ts
+++ /dev/null
@@ -1,358 +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 { Editor as TipTapEditor } from '@tiptap/react';
-
-import { Position } from '../../types';
-
-import {
- safeExecuteCommand,
- EditorErrorType,
- handleEditorError,
-} from './errorHandler';
-import {
- convertTipTapPositionToCodeMirror,
- convertCodeMirrorPositionToTipTap,
-} from './position';
-import { MARKDOWN_PATTERNS } from './constants';
-
-/**
- * Creates base methods module
- *
- * Provides core base methods for the editor, including:
- * - Content getter and setter (getValue, setValue)
- * - Selection operations (getSelection, replaceSelection)
- * - Cursor and selection position (getCursor, setSelection)
- * - Read-only state control (setReadOnly)
- *
- * @param editor - TipTap editor instance
- * @returns Object containing base methods
- */
-export function createBaseMethods(editor: TipTapEditor) {
- return {
- getValue: () => {
- return (
- safeExecuteCommand(
- () => editor.getMarkdown(),
- () => '',
- EditorErrorType.COMMAND_EXECUTION_FAILED,
- { function: 'getValue' },
- ) || ''
- );
- },
-
- setValue: (value: string) => {
- safeExecuteCommand(
- () => {
- editor.commands.setContent(value, { contentType: 'markdown' });
- },
- undefined,
- EditorErrorType.COMMAND_EXECUTION_FAILED,
- { function: 'setValue', valueLength: value.length },
- );
- },
-
- getSelection: () => {
- const { from, to } = editor.state.selection;
- return editor.state.doc.textBetween(from, to);
- },
-
- replaceSelection: (value: string) => {
- const inlineCodeMatch = value.match(MARKDOWN_PATTERNS.INLINE_CODE);
- if (inlineCodeMatch && value.length > 2) {
- const codeText = inlineCodeMatch[1];
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'text',
- text: codeText,
- marks: [{ type: 'code' }],
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- const codeBlockMatch = value.match(MARKDOWN_PATTERNS.CODE_BLOCK);
- if (codeBlockMatch) {
- const [, , lang, codeText] = codeBlockMatch;
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'codeBlock',
- attrs: lang ? { language: lang } : {},
- content: [
- {
- type: 'text',
- text: codeText,
- },
- ],
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
- if (imageMatch) {
- const [, alt, url] = imageMatch;
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'image',
- attrs: {
- src: url,
- alt: alt || '',
- },
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- const linkMatch = value.match(MARKDOWN_PATTERNS.LINK);
- if (linkMatch) {
- const [, text, url] = linkMatch;
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'text',
- text: text || url,
- marks: [
- {
- type: 'link',
- attrs: {
- href: url,
- },
- },
- ],
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- const autoLinkMatch = value.match(MARKDOWN_PATTERNS.AUTO_LINK);
- if (autoLinkMatch && value.length > 2) {
- const url = autoLinkMatch[1];
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'text',
- text: url,
- marks: [
- {
- type: 'link',
- attrs: {
- href: url,
- },
- },
- ],
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- if (MARKDOWN_PATTERNS.HORIZONTAL_RULE.test(value.trim())) {
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'horizontalRule',
- });
- },
- () => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- },
- );
- return;
- }
-
- safeExecuteCommand(() => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- });
- },
-
- focus: () => {
- editor.commands.focus();
- },
-
- getCursor: () => {
- try {
- const { from } = editor.state.selection;
- return convertTipTapPositionToCodeMirror(editor, from);
- } catch (error) {
- handleEditorError(
- error as Error,
- EditorErrorType.POSITION_CONVERSION_FAILED,
- {
- function: 'getCursor',
- },
- );
- return { line: 0, ch: 0 };
- }
- },
-
- setSelection: (anchor?: unknown, head?: unknown) => {
- try {
- if (
- anchor &&
- typeof anchor === 'object' &&
- 'line' in anchor &&
- 'ch' in anchor
- ) {
- const anchorPos = convertCodeMirrorPositionToTipTap(
- editor,
- anchor as Position,
- );
- let headPos = anchorPos;
-
- if (
- head &&
- typeof head === 'object' &&
- 'line' in head &&
- 'ch' in head
- ) {
- headPos = convertCodeMirrorPositionToTipTap(
- editor,
- head as Position,
- );
- }
-
- safeExecuteCommand(
- () => {
- editor.commands.setTextSelection({
- from: anchorPos,
- to: headPos,
- });
- },
- undefined,
- EditorErrorType.COMMAND_EXECUTION_FAILED,
- { function: 'setSelection', anchorPos, headPos },
- );
- } else {
- editor.commands.focus();
- }
- } catch (error) {
- handleEditorError(
- error as Error,
- EditorErrorType.COMMAND_EXECUTION_FAILED,
- {
- function: 'setSelection',
- anchor,
- head,
- },
- );
- safeExecuteCommand(
- () => {
- editor.commands.focus();
- },
- undefined,
- EditorErrorType.COMMAND_EXECUTION_FAILED,
- { function: 'setSelection', isFallback: true },
- );
- }
- },
-
- setReadOnly: (readOnly: boolean) => {
- editor.setEditable(!readOnly);
- },
-
- replaceRange: (value: string, from?: unknown, to?: unknown) => {
- if (from && to && typeof from === 'object' && typeof to === 'object') {
- const { from: currentFrom, to: currentTo } = editor.state.selection;
- const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
- if (imageMatch) {
- const [, alt, url] = imageMatch;
- safeExecuteCommand(
- () => {
- editor.commands.insertContentAt(
- { from: currentFrom, to: currentTo },
- {
- type: 'image',
- attrs: {
- src: url,
- alt: alt || '',
- },
- },
- );
- },
- () => {
- editor.commands.insertContentAt(
- { from: currentFrom, to: currentTo },
- value,
- { contentType: 'markdown' },
- );
- },
- );
- return;
- }
-
- safeExecuteCommand(() => {
- editor.commands.insertContentAt(
- { from: currentFrom, to: currentTo },
- value,
- { contentType: 'markdown' },
- );
- });
- } else {
- const imageMatch = value.match(MARKDOWN_PATTERNS.IMAGE);
- if (imageMatch) {
- const [, alt, url] = imageMatch;
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'image',
- attrs: {
- src: url,
- alt: alt || '',
- },
- });
- },
- () => {
- editor.commands.insertContent(value, {
- contentType: 'markdown',
- });
- },
- );
- return;
- }
-
- safeExecuteCommand(() => {
- editor.commands.insertContent(value, { contentType: 'markdown' });
- });
- }
- },
- };
-}
diff --git a/ui/src/components/Editor/utils/tiptap/commands.ts b/ui/src/components/Editor/utils/tiptap/commands.ts
deleted file mode 100644
index 08e2defc5..000000000
--- a/ui/src/components/Editor/utils/tiptap/commands.ts
+++ /dev/null
@@ -1,720 +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 { Editor as TipTapEditor } from '@tiptap/react';
-import { Command } from '@codemirror/view';
-
-import { Level } from '../../types';
-
-import { safeExecuteCommand, logWarning } from './errorHandler';
-import { MARKDOWN_PATTERNS } from './constants';
-
-/**
- * Creates command methods module
- *
- * Provides command methods for the editor, including:
- * - Low-level methods: wrapText, replaceLines, appendBlock (for internal editor use)
- * - Semantic methods: insertBold, insertHeading, insertImage, etc. (for toolbar use)
- * - State query methods: isBold, isHeading, etc.
- *
- * @param editor - TipTap editor instance
- * @returns Object containing all command methods
- */
-export function createCommandMethods(editor: TipTapEditor) {
- return {
- wrapText: (before: string, after?: string, defaultText?: string) => {
- const { from, to } = editor.state.selection;
- const actualAfter = after || before;
-
- if (before === '**' && actualAfter === '**') {
- if (from === to) {
- if (defaultText) {
- const insertPos = from;
- editor.commands.insertContent(defaultText);
- editor.commands.setTextSelection({
- from: insertPos,
- to: insertPos + defaultText.length,
- });
- editor.commands.toggleBold();
- } else {
- editor.commands.toggleBold();
- }
- } else {
- editor.commands.toggleBold();
- }
- return;
- }
-
- if (before === '*' && actualAfter === '*') {
- if (from === to) {
- if (defaultText) {
- const insertPos = from;
- editor.commands.insertContent(defaultText);
- editor.commands.setTextSelection({
- from: insertPos,
- to: insertPos + defaultText.length,
- });
- editor.commands.toggleItalic();
- } else {
- editor.commands.toggleItalic();
- }
- } else {
- editor.commands.toggleItalic();
- }
- return;
- }
-
- if (before === '`' && actualAfter === '`') {
- if (from === to) {
- if (defaultText) {
- const insertPos = from;
- editor.commands.insertContent(defaultText);
- editor.commands.setTextSelection({
- from: insertPos,
- to: insertPos + defaultText.length,
- });
- editor.commands.toggleCode();
- } else {
- editor.commands.toggleCode();
- }
- } else {
- editor.commands.toggleCode();
- }
- return;
- }
-
- if (before === '```\n' && actualAfter === '\n```') {
- if (from === to) {
- const codeBlockText = defaultText
- ? `\`\`\`\n${defaultText}\n\`\`\``
- : '```\n\n```';
- safeExecuteCommand(
- () => {
- editor.commands.insertContent(codeBlockText, {
- contentType: 'markdown',
- });
- },
- () => {
- editor.commands.insertContent({
- type: 'codeBlock',
- content: defaultText
- ? [
- {
- type: 'text',
- text: defaultText,
- },
- ]
- : [],
- });
- },
- );
- } else {
- const selectedText = editor.state.doc.textBetween(from, to);
- safeExecuteCommand(
- () => {
- editor.commands.insertContentAt(
- { from, to },
- {
- type: 'codeBlock',
- content: [
- {
- type: 'text',
- text: selectedText,
- },
- ],
- },
- );
- },
- () => {
- const codeBlockText = `\`\`\`\n${selectedText}\n\`\`\``;
- editor.commands.insertContentAt({ from, to }, codeBlockText, {
- contentType: 'markdown',
- });
- },
- );
- }
- return;
- }
-
- if (from === to) {
- const text = before + (defaultText || '') + actualAfter;
- editor.commands.insertContent(text, { contentType: 'markdown' });
- } else {
- const selectedText = editor.state.doc.textBetween(from, to);
- const wrappedText = before + selectedText + actualAfter;
- editor.commands.insertContentAt({ from, to }, wrappedText, {
- contentType: 'markdown',
- });
- }
- },
-
- replaceLines: (replace: Parameters['map']>[0]) => {
- const { from } = editor.state.selection;
- const $pos = editor.state.doc.resolve(from);
- const block = $pos.parent;
- const lineText = block.textContent;
- const newText = replace(lineText, 0, [lineText]) as string;
-
- const finalText = newText || ' ';
- const headingMatch = finalText.match(MARKDOWN_PATTERNS.HEADING);
- if (headingMatch) {
- const [, hashes, text] = headingMatch;
- const level = hashes.length;
- const start = $pos.start($pos.depth);
- const end = $pos.end($pos.depth);
-
- if (start < 0 || end < 0 || start > end) {
- logWarning('Invalid position range for heading', { start, end });
- return;
- }
-
- const headingText = text.trim() || 'Heading';
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'heading',
- attrs: { level },
- content: [
- {
- type: 'text',
- text: headingText,
- },
- ],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'heading',
- attrs: { level },
- content: [
- {
- type: 'text',
- text: headingText,
- },
- ],
- },
- );
- }
- },
- () => {
- const markdownText = finalText.trim() || `# Heading`;
- if (start === end) {
- editor.commands.insertContent(markdownText, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- markdownText,
- { contentType: 'markdown' },
- );
- }
- },
- );
- return;
- }
-
- if (finalText.startsWith('> ')) {
- const quoteText = finalText.slice(2).trim();
- const start = $pos.start($pos.depth);
- const end = $pos.end($pos.depth);
-
- if (start < 0 || end < 0 || start > end) {
- logWarning('Invalid position range for heading', { start, end });
- return;
- }
-
- const quoteMarkdown = quoteText ? `> ${quoteText}` : '> ';
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(quoteMarkdown, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- quoteMarkdown,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: quoteText ? [{ type: 'text', text: quoteText }] : [],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: quoteText ? [{ type: 'text', text: quoteText }] : [],
- },
- );
- }
- },
- );
- return;
- }
-
- const olMatchOriginal = lineText.match(
- MARKDOWN_PATTERNS.ORDERED_LIST_ORIGINAL,
- );
- const olMatchNew = finalText.match(MARKDOWN_PATTERNS.ORDERED_LIST_NEW);
-
- if (olMatchOriginal || olMatchNew) {
- const isInOrderedList = editor.isActive('orderedList');
- const start = $pos.start($pos.depth);
- const end = $pos.end($pos.depth);
-
- if (start < 0 || end < 0 || start > end) {
- logWarning('Invalid position range for ordered list', {
- start,
- end,
- });
- return;
- }
-
- if (olMatchOriginal && !olMatchNew) {
- const textContent = finalText.trim();
- const contentToInsert = textContent || 'Paragraph';
-
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(contentToInsert, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- contentToInsert,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- },
- );
- }
- },
- );
- if (isInOrderedList) {
- safeExecuteCommand(() => {
- editor.chain().focus().toggleOrderedList().run();
- });
- }
- } else if (!olMatchOriginal && olMatchNew) {
- const [, , text] = olMatchNew;
- const textContent = text.trim();
- const contentToInsert = textContent || 'List item';
-
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(contentToInsert, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- contentToInsert,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- },
- );
- }
- },
- );
- if (!isInOrderedList) {
- safeExecuteCommand(() => {
- editor.chain().focus().toggleOrderedList().run();
- });
- }
- }
- return;
- }
- const ulMatchOriginal = lineText.match(
- MARKDOWN_PATTERNS.UNORDERED_LIST_ORIGINAL,
- );
- const ulMatchNew = finalText.match(MARKDOWN_PATTERNS.UNORDERED_LIST_NEW);
-
- if (ulMatchOriginal || ulMatchNew) {
- const isInBulletList = editor.isActive('bulletList');
- const start = $pos.start($pos.depth);
- const end = $pos.end($pos.depth);
-
- if (start < 0 || end < 0 || start > end) {
- logWarning('Invalid position range for unordered list', {
- start,
- end,
- });
- return;
- }
-
- if (ulMatchOriginal && !ulMatchNew) {
- const textContent = finalText.trim();
- const contentToInsert = textContent || 'Paragraph';
-
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(contentToInsert, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- contentToInsert,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- },
- );
- }
- },
- );
- if (isInBulletList) {
- safeExecuteCommand(() => {
- editor.chain().focus().toggleBulletList().run();
- });
- }
- } else if (!ulMatchOriginal && ulMatchNew) {
- const [, text] = ulMatchNew;
- const textContent = text.trim();
- const contentToInsert = textContent || 'List item';
-
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(contentToInsert, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- contentToInsert,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- },
- );
- }
- },
- );
- if (!isInBulletList) {
- safeExecuteCommand(() => {
- editor.chain().focus().toggleBulletList().run();
- });
- }
- }
- return;
- }
-
- const start = $pos.start($pos.depth);
- const end = $pos.end($pos.depth);
-
- if (start < 0 || end < 0 || start > end) {
- logWarning('Invalid position range', {
- start,
- end,
- function: 'replaceLines',
- });
- return;
- }
-
- const contentToInsert = finalText.trim() || ' ';
-
- safeExecuteCommand(
- () => {
- if (start === end) {
- editor.commands.insertContent(contentToInsert, {
- contentType: 'markdown',
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- contentToInsert,
- { contentType: 'markdown' },
- );
- }
- },
- () => {
- if (start === end) {
- editor.commands.insertContent({
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- });
- } else {
- editor.commands.insertContentAt(
- { from: start, to: end },
- {
- type: 'paragraph',
- content: [{ type: 'text', text: contentToInsert }],
- },
- );
- }
- },
- );
- },
-
- appendBlock: (content: string) => {
- if (MARKDOWN_PATTERNS.HORIZONTAL_RULE.test(content.trim())) {
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'horizontalRule',
- });
- },
- () => {
- editor.commands.insertContent(content, {
- contentType: 'markdown',
- });
- },
- );
- return;
- }
-
- safeExecuteCommand(() => {
- editor.commands.insertContent(`\n\n${content}`, {
- contentType: 'markdown',
- });
- });
- },
-
- addKeyMap: (keyMap: Record) => {
- Object.keys(keyMap).forEach(() => {});
- },
- insertBold: (text?: string) => {
- if (text) {
- const { from } = editor.state.selection;
- editor.commands.insertContent(text);
- editor.commands.setTextSelection({ from, to: from + text.length });
- }
- editor.commands.toggleBold();
- },
-
- insertItalic: (text?: string) => {
- if (text) {
- const { from } = editor.state.selection;
- editor.commands.insertContent(text);
- editor.commands.setTextSelection({ from, to: from + text.length });
- }
- editor.commands.toggleItalic();
- },
-
- insertCode: (text?: string) => {
- if (text) {
- const { from } = editor.state.selection;
- editor.commands.insertContent(text);
- editor.commands.setTextSelection({ from, to: from + text.length });
- }
- editor.commands.toggleCode();
- },
-
- insertStrikethrough: (text?: string) => {
- if (text) {
- const { from } = editor.state.selection;
- editor.commands.insertContent(text);
- editor.commands.setTextSelection({ from, to: from + text.length });
- }
- editor.commands.toggleStrike();
- },
-
- insertHeading: (level: Level, text?: string) => {
- if (text) {
- // Insert heading using TipTap's native API to ensure proper structure
- safeExecuteCommand(
- () => {
- editor.commands.insertContent({
- type: 'heading',
- attrs: { level },
- content: [
- {
- type: 'text',
- text,
- },
- ],
- });
- // Select only the text part (excluding the heading node structure)
- // After insertion, the cursor is at the end of the heading
- // We need to select backwards from the current position
- const { to } = editor.state.selection;
- editor.commands.setTextSelection({
- from: to - text.length,
- to,
- });
- },
- () => {
- // Fallback: use markdown format
- const headingText = `${'#'.repeat(level)} ${text}`;
- editor.commands.insertContent(headingText, {
- contentType: 'markdown',
- });
- },
- );
- } else {
- editor.commands.toggleHeading({ level });
- }
- },
-
- insertBlockquote: (text?: string) => {
- if (text) {
- const { from } = editor.state.selection;
- const blockquoteText = `> ${text}`;
-
- // Use chain to ensure selection happens after insertion
- editor
- .chain()
- .focus()
- .insertContent(blockquoteText, { contentType: 'markdown' })
- .setTextSelection({
- from: from + 1,
- to: from + 1 + text.length,
- })
- .run();
- } else {
- editor.commands.toggleBlockquote();
- }
- },
-
- insertCodeBlock: (language?: string, code?: string) => {
- const lang = language || '';
- const codeText = code || 'code here';
- editor.commands.insertContent(`\`\`\`${lang}\n${codeText}\n\`\`\``, {
- contentType: 'markdown',
- });
- },
-
- insertHorizontalRule: () => {
- editor.commands.setHorizontalRule();
- },
-
- insertOrderedList: () => {
- editor.commands.toggleOrderedList();
- },
-
- insertUnorderedList: () => {
- editor.commands.toggleBulletList();
- },
-
- toggleOrderedList: () => {
- editor.commands.toggleOrderedList();
- },
-
- toggleUnorderedList: () => {
- editor.commands.toggleBulletList();
- },
-
- insertLink: (url: string, text?: string) => {
- const linkText = text || url;
- editor.commands.insertContent(`[${linkText}](${url})`, {
- contentType: 'markdown',
- });
- },
-
- insertImage: (url: string, alt?: string) => {
- editor.commands.setImage({ src: url, alt: alt || 'image' });
- },
-
- insertTable: (rows = 3, cols = 3) => {
- editor.commands.insertTable({
- rows,
- cols,
- withHeaderRow: true,
- });
- },
-
- indent: () => {
- editor.commands.sinkListItem('listItem');
- },
-
- outdent: () => {
- editor.commands.liftListItem('listItem');
- },
-
- isBold: () => editor.isActive('bold'),
- isItalic: () => editor.isActive('italic'),
- isHeading: (level?: number) => {
- if (level) {
- return editor.isActive('heading', { level });
- }
- return editor.isActive('heading');
- },
- isBlockquote: () => editor.isActive('blockquote'),
- isCodeBlock: () => editor.isActive('codeBlock'),
- isOrderedList: () => editor.isActive('orderedList'),
- isUnorderedList: () => editor.isActive('bulletList'),
- };
-}
diff --git a/ui/src/components/Editor/utils/tiptap/constants.ts b/ui/src/components/Editor/utils/tiptap/constants.ts
deleted file mode 100644
index a4ff7de42..000000000
--- a/ui/src/components/Editor/utils/tiptap/constants.ts
+++ /dev/null
@@ -1,48 +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.
- */
-
-/**
- * Markdown pattern matching regular expression constants
- *
- * Defines regular expression patterns for parsing and matching Markdown syntax.
- * These patterns are used to convert Markdown syntax to TipTap nodes, or from TipTap nodes
- * to Markdown format.
- *
- * @example
- * ```typescript
- * const headingMatch = text.match(MARKDOWN_PATTERNS.HEADING);
- * if (headingMatch) {
- * const level = headingMatch[1].length; // Number of #
- * const text = headingMatch[2]; // Heading text
- * }
- * ```
- */
-export const MARKDOWN_PATTERNS = {
- HEADING: /^(#{1,6})\s+(.+)$/,
- ORDERED_LIST_ORIGINAL: /^(\s{0,})(\d+)\.\s/,
- ORDERED_LIST_NEW: /^(\d+)\.\s*(.*)$/,
- UNORDERED_LIST_ORIGINAL: /^(\s{0,})(-|\*)\s/,
- UNORDERED_LIST_NEW: /^[-*]\s*(.*)$/,
- INLINE_CODE: /^`(.+?)`$/,
- CODE_BLOCK: /^(\n)?```(\w+)?\n([\s\S]*?)\n```(\n)?$/,
- IMAGE: /^!\[([^\]]*)\]\(([^)]+)\)$/,
- LINK: /^\[([^\]]*)\]\(([^)]+)\)$/,
- AUTO_LINK: /^<(.+?)>$/,
- HORIZONTAL_RULE: /^-{3,}$/,
-} as const;
diff --git a/ui/src/components/Editor/utils/tiptap/errorHandler.ts b/ui/src/components/Editor/utils/tiptap/errorHandler.ts
deleted file mode 100644
index c8f4e7a3b..000000000
--- a/ui/src/components/Editor/utils/tiptap/errorHandler.ts
+++ /dev/null
@@ -1,155 +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.
- */
-
-/**
- * Editor error type enumeration
- *
- * Defines various error types that may occur in the editor, used for error classification and handling.
- */
-export enum EditorErrorType {
- COMMAND_EXECUTION_FAILED = 'COMMAND_EXECUTION_FAILED',
- POSITION_CONVERSION_FAILED = 'POSITION_CONVERSION_FAILED',
- CONTENT_PARSING_FAILED = 'CONTENT_PARSING_FAILED',
- EVENT_LISTENER_FAILED = 'EVENT_LISTENER_FAILED',
-}
-
-/**
- * Editor error interface
- */
-export interface EditorError {
- type: EditorErrorType;
- message: string;
- originalError?: Error;
- context?: Record;
- timestamp: number;
-}
-
-/**
- * Handles editor errors with unified log format
- *
- * Unified error handling for the editor, recording error information, context, and stack traces
- * for easier problem identification and debugging.
- *
- * @param error - Original error object
- * @param type - Error type
- * @param context - Optional context information (function name, parameters, etc.)
- * @returns Processed error object
- *
- * @example
- * ```typescript
- * try {
- * editor.commands.insertContent(content);
- * } catch (error) {
- * handleEditorError(error, EditorErrorType.COMMAND_EXECUTION_FAILED, {
- * function: 'insertContent',
- * content: content.substring(0, 50),
- * });
- * }
- * ```
- */
-export function handleEditorError(
- error: Error,
- type: EditorErrorType,
- context?: Record,
-): EditorError {
- const editorError: EditorError = {
- type,
- message: error.message,
- originalError: error,
- context,
- timestamp: Date.now(),
- };
-
- console.error(`[Editor Error] ${type}:`, {
- message: editorError.message,
- context: editorError.context,
- stack: error.stack,
- });
-
- return editorError;
-}
-
-/**
- * Safely executes TipTap command with error handling and fallback strategy
- *
- * Automatically catches errors when executing TipTap commands. If a fallback function is provided,
- * it attempts to execute the fallback operation when the main command fails. All errors are uniformly recorded.
- *
- * @param command - Main command function to execute
- * @param fallback - Optional fallback function to execute when main command fails
- * @param errorType - Error type, defaults to COMMAND_EXECUTION_FAILED
- * @param context - Optional context information
- * @returns Command execution result, returns undefined if failed and no fallback
- *
- * @example
- * ```typescript
- * const result = safeExecuteCommand(
- * () => editor.commands.insertContent(content),
- * () => editor.commands.insertContent(content, { contentType: 'markdown' }),
- * EditorErrorType.COMMAND_EXECUTION_FAILED,
- * { function: 'insertContent', contentLength: content.length }
- * );
- * ```
- */
-export function safeExecuteCommand(
- command: () => T,
- fallback?: () => T,
- errorType: EditorErrorType = EditorErrorType.COMMAND_EXECUTION_FAILED,
- context?: Record,
-): T | undefined {
- try {
- return command();
- } catch (error) {
- handleEditorError(error as Error, errorType, context);
- if (fallback) {
- try {
- return fallback();
- } catch (fallbackError) {
- handleEditorError(fallbackError as Error, errorType, {
- ...context,
- isFallback: true,
- });
- }
- }
- return undefined;
- }
-}
-
-/**
- * Logs warning information (for non-fatal errors)
- *
- * Records non-fatal warning information to alert potential issues without affecting functionality.
- *
- * @param message - Warning message
- * @param context - Optional context information
- *
- * @example
- * ```typescript
- * if (start < 0 || end < 0) {
- * logWarning('Invalid position range', { start, end, function: 'setSelection' });
- * return;
- * }
- * ```
- */
-export function logWarning(
- message: string,
- context?: Record,
-): void {
- console.warn(`[Editor Warning] ${message}`, context || {});
-}
diff --git a/ui/src/components/Editor/utils/tiptap/events.ts b/ui/src/components/Editor/utils/tiptap/events.ts
deleted file mode 100644
index 2a86440de..000000000
--- a/ui/src/components/Editor/utils/tiptap/events.ts
+++ /dev/null
@@ -1,104 +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 { Editor as TipTapEditor } from '@tiptap/react';
-
-import { logWarning } from './errorHandler';
-
-/**
- * Checks if editor view is available
- */
-function isViewAvailable(editor: TipTapEditor): boolean {
- if (!editor) {
- return false;
- }
- if (editor.isDestroyed) {
- return false;
- }
- return !!(editor.view && editor.state);
-}
-
-/**
- * Creates event handling methods module
- *
- * Provides event handling methods for the editor, including:
- * - on: Register event listeners (change, focus, blur, dragenter, dragover, drop, paste)
- * - off: Remove event listeners
- *
- * Note: For DOM events (dragenter, dragover, drop, paste),
- * the editor view must be mounted before binding events.
- *
- * @param editor - TipTap editor instance
- * @returns Object containing event handling methods
- */
-export function createEventMethods(editor: TipTapEditor) {
- return {
- on: (event: string, callback: (e?: unknown) => void) => {
- if (event === 'change') {
- editor.on('update', callback);
- } else if (event === 'focus') {
- editor.on('focus', callback);
- } else if (event === 'blur') {
- editor.on('blur', callback);
- } else if (
- event === 'dragenter' ||
- event === 'dragover' ||
- event === 'drop' ||
- event === 'paste'
- ) {
- if (!isViewAvailable(editor)) {
- logWarning(
- 'TipTap editor view is not available yet. Event listener not attached.',
- {
- event,
- },
- );
- return;
- }
- editor.view.dom.addEventListener(event, callback as EventListener);
- }
- },
-
- off: (event: string, callback: (e?: unknown) => void) => {
- if (
- (event === 'dragenter' ||
- event === 'dragover' ||
- event === 'drop' ||
- event === 'paste') &&
- !isViewAvailable(editor)
- ) {
- return;
- }
- if (event === 'change') {
- editor.off('update', callback);
- } else if (event === 'focus') {
- editor.off('focus', callback);
- } else if (event === 'blur') {
- editor.off('blur', callback);
- } else if (
- event === 'dragenter' ||
- event === 'dragover' ||
- event === 'drop' ||
- event === 'paste'
- ) {
- editor.view.dom.removeEventListener(event, callback as EventListener);
- }
- },
- };
-}
diff --git a/ui/src/components/Editor/utils/tiptap/position.ts b/ui/src/components/Editor/utils/tiptap/position.ts
deleted file mode 100644
index 378908b88..000000000
--- a/ui/src/components/Editor/utils/tiptap/position.ts
+++ /dev/null
@@ -1,147 +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 { Editor as TipTapEditor } from '@tiptap/react';
-
-import { Position } from '../../types';
-
-import { handleEditorError, EditorErrorType } from './errorHandler';
-
-/**
- * Converts TipTap position to CodeMirror Position
- *
- * TipTap uses document tree-based node positions (character index), while CodeMirror uses
- * line-based positions (line number and column number). This function converts between them.
- *
- * @param editor - TipTap editor instance
- * @param pos - TipTap position index (character position)
- * @returns CodeMirror position object with line and ch properties
- *
- * @example
- * ```typescript
- * const tipTapPos = 100; // Character position
- * const codeMirrorPos = convertTipTapPositionToCodeMirror(editor, tipTapPos);
- * // { line: 5, ch: 10 }
- * ```
- */
-export function convertTipTapPositionToCodeMirror(
- editor: TipTapEditor,
- pos: number,
-): Position {
- try {
- const { doc } = editor.state;
- let line = 0;
- let ch = 0;
- let currentPos = 0;
-
- for (let i = 0; i < doc.content.childCount; i += 1) {
- const child = doc.content.child(i);
- const childSize = child.nodeSize;
-
- if (currentPos + childSize > pos) {
- const text = child.textContent;
- const relativePos = pos - currentPos;
-
- const textBeforePos = text.substring(0, relativePos);
- const newlineMatches = textBeforePos.match(/\n/g);
- line += newlineMatches ? newlineMatches.length : 0;
- ch = relativePos - textBeforePos.lastIndexOf('\n') - 1;
- break;
- }
-
- const text = child.textContent;
- const newlineMatches = text.match(/\n/g);
- line += newlineMatches ? newlineMatches.length : 0;
- currentPos += childSize;
- }
-
- return { line, ch };
- } catch (error) {
- handleEditorError(
- error as Error,
- EditorErrorType.POSITION_CONVERSION_FAILED,
- {
- function: 'convertTipTapPositionToCodeMirror',
- position: pos,
- },
- );
- return { line: 0, ch: 0 };
- }
-}
-
-/**
- * Converts CodeMirror Position to TipTap position index
- *
- * Converts CodeMirror's line-based position to TipTap's character index position.
- *
- * @param editor - TipTap editor instance
- * @param position - CodeMirror position object with line and ch properties
- * @returns TipTap position index (character position)
- *
- * @example
- * ```typescript
- * const codeMirrorPos = { line: 5, ch: 10 };
- * const tipTapPos = convertCodeMirrorPositionToTipTap(editor, codeMirrorPos);
- * // 100 (character position)
- * ```
- */
-export function convertCodeMirrorPositionToTipTap(
- editor: TipTapEditor,
- position: Position,
-): number {
- try {
- const { doc } = editor.state;
- let currentLine = 0;
- let currentPos = 0;
-
- for (let i = 0; i < doc.content.childCount; i += 1) {
- const child = doc.content.child(i);
- const text = child.textContent;
- const lines = text.split('\n');
-
- if (currentLine + lines.length - 1 >= position.line) {
- const lineInNode = position.line - currentLine;
- const { ch: posInLine } = position;
-
- let pos = 0;
- for (let j = 0; j < lineInNode; j += 1) {
- pos += lines[j].length + 1; // +1 for newline
- }
- pos += posInLine;
-
- return currentPos + pos;
- }
-
- currentLine += lines.length - 1;
- currentPos += child.nodeSize;
- }
-
- return doc.content.size;
- } catch (error) {
- handleEditorError(
- error as Error,
- EditorErrorType.POSITION_CONVERSION_FAILED,
- {
- function: 'convertCodeMirrorPositionToTipTap',
- position,
- },
- );
- return editor.state.doc.content.size;
- }
-}
diff --git a/ui/src/components/Editor/utils/tiptap/tableExtension.ts b/ui/src/components/Editor/utils/tiptap/tableExtension.ts
deleted file mode 100644
index 70e68f77a..000000000
--- a/ui/src/components/Editor/utils/tiptap/tableExtension.ts
+++ /dev/null
@@ -1,81 +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 { Table, TableOptions } from '@tiptap/extension-table';
-import type { NodeViewRendererProps } from '@tiptap/core';
-
-interface TableWrapperOptions extends TableOptions {
- wrapperClass?: string;
-}
-
-export const TableWithWrapper = Table.extend({
- addOptions() {
- const parentOptions = (this.parent?.() || {}) as Partial;
- return {
- ...parentOptions,
- HTMLAttributes: parentOptions.HTMLAttributes || {},
- wrapperClass: 'table-responsive',
- } as TableWrapperOptions;
- },
-
- addNodeView() {
- return (props: NodeViewRendererProps) => {
- const { node } = props;
- const wrapperClass = this.options.wrapperClass || 'table-responsive';
-
- const dom = document.createElement('div');
- dom.className = wrapperClass;
-
- const table = document.createElement('table');
-
- const htmlAttrs = this.options.HTMLAttributes || {};
- if (htmlAttrs.class) {
- table.className = htmlAttrs.class as string;
- }
- if (htmlAttrs.style && typeof htmlAttrs.style === 'object') {
- Object.assign(table.style, htmlAttrs.style);
- }
-
- const colgroup = document.createElement('colgroup');
- if (node.firstChild) {
- const { childCount } = node.firstChild;
- for (let i = 0; i < childCount; i += 1) {
- const col = document.createElement('col');
- colgroup.appendChild(col);
- }
- }
- table.appendChild(colgroup);
-
- const tbody = document.createElement('tbody');
- table.appendChild(tbody);
- dom.appendChild(table);
-
- return {
- dom,
- contentDOM: tbody,
- update: (updatedNode) => {
- if (updatedNode.type !== node.type) {
- return false;
- }
- return true;
- },
- };
- };
- },
-});
From 78714e83cfcc2641abbb4e63d6557894f59128a3 Mon Sep 17 00:00:00 2001
From: robin
Date: Thu, 25 Dec 2025 11:50:55 +0800
Subject: [PATCH 41/92] feat(editor): implement image upload functionality with
validation and hooks
---
ui/src/components/Editor/ToolBars/image.tsx | 102 +-------------
.../components/Editor/hooks/useImageUpload.ts | 129 ++++++++++++++++++
ui/src/components/Editor/index.tsx | 83 ++++++++++-
ui/src/utils/pluginKit/index.ts | 39 ++++++
ui/src/utils/pluginKit/interface.ts | 2 +
5 files changed, 259 insertions(+), 96 deletions(-)
create mode 100644 ui/src/components/Editor/hooks/useImageUpload.ts
diff --git a/ui/src/components/Editor/ToolBars/image.tsx b/ui/src/components/Editor/ToolBars/image.tsx
index 52b2e5461..0783e22e4 100644
--- a/ui/src/components/Editor/ToolBars/image.tsx
+++ b/ui/src/components/Editor/ToolBars/image.tsx
@@ -21,12 +21,10 @@ import { useEffect, useState, memo, useContext } from 'react';
import { Button, Form, Modal, Tab, Tabs } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
-import { Modal as AnswerModal } from '@/components';
import ToolItem from '../toolItem';
import { EditorContext } from '../EditorContext';
import { Editor } from '../types';
-import { uploadImage } from '@/services';
-import { writeSettingStore } from '@/stores';
+import { useImageUpload } from '../hooks/useImageUpload';
const Image = () => {
const editor = useContext(EditorContext);
@@ -40,12 +38,7 @@ const Image = () => {
}
}, [editor]);
const { t } = useTranslation('translation', { keyPrefix: 'editor' });
- const {
- max_image_size = 4,
- max_attachment_size = 8,
- authorized_image_extensions = [],
- authorized_attachment_extensions = [],
- } = writeSettingStore((state) => state.write);
+ const { verifyImageSize, uploadFiles } = useImageUpload();
const loadingText = `![${t('image.uploading')}...]()`;
@@ -69,89 +62,6 @@ const Image = () => {
errorMsg: '',
});
- const verifyImageSize = (files: FileList) => {
- if (files.length === 0) {
- return false;
- }
-
- /**
- * When allowing attachments to be uploaded, verification logic for attachment information has been added. In order to avoid abnormal judgment caused by the order of drag and drop upload, the drag and drop upload verification of attachments and the drag and drop upload of images are put together.
- *
- */
- const canUploadAttachment = authorized_attachment_extensions.length > 0;
- const allowedAllType = [
- ...authorized_image_extensions,
- ...authorized_attachment_extensions,
- ];
- const unSupportFiles = Array.from(files).filter((file) => {
- const fileName = file.name.toLowerCase();
- return canUploadAttachment
- ? !allowedAllType.find((v) => fileName.endsWith(v))
- : file.type.indexOf('image') === -1;
- });
-
- if (unSupportFiles.length > 0) {
- AnswerModal.confirm({
- content: canUploadAttachment
- ? t('file.not_supported', { file_type: allowedAllType.join(', ') })
- : t('image.form_image.fields.file.msg.only_image'),
- showCancel: false,
- });
- return false;
- }
-
- const otherFiles = Array.from(files).filter((file) => {
- return file.type.indexOf('image') === -1;
- });
-
- if (canUploadAttachment && otherFiles.length > 0) {
- const attachmentOverSizeFiles = otherFiles.filter(
- (file) => file.size / 1024 / 1024 > max_attachment_size,
- );
- if (attachmentOverSizeFiles.length > 0) {
- AnswerModal.confirm({
- content: t('file.max_size', { size: max_attachment_size }),
- showCancel: false,
- });
- return false;
- }
- }
-
- const imageFiles = Array.from(files).filter(
- (file) => file.type.indexOf('image') > -1,
- );
- const oversizedImages = imageFiles.filter(
- (file) => file.size / 1024 / 1024 > max_image_size,
- );
- if (oversizedImages.length > 0) {
- AnswerModal.confirm({
- content: t('image.form_image.fields.file.msg.max_size', {
- size: max_image_size,
- }),
- showCancel: false,
- });
- return false;
- }
-
- return true;
- };
-
- const upload = (
- files: FileList,
- ): Promise<{ url: string; name: string; type: string }[]> => {
- const promises = Array.from(files).map(async (file) => {
- const type = file.type.indexOf('image') > -1 ? 'post' : 'post_attachment';
- const url = await uploadImage({ file, type });
-
- return {
- name: file.name,
- url,
- type,
- };
- });
-
- return Promise.all(promises);
- };
function dragenter(e) {
e.stopPropagation();
e.preventDefault();
@@ -178,7 +88,7 @@ const Image = () => {
editorState.replaceSelection(loadingText);
editorState.setReadOnly(true);
- const urls = await upload(fileList)
+ const urls = await uploadFiles(fileList)
.catch(() => {
editorState.replaceRange('', startPos, endPos);
})
@@ -217,7 +127,7 @@ const Image = () => {
editorState?.replaceSelection(loadingText);
editorState?.setReadOnly(true);
- upload(clipboard.files)
+ uploadFiles(clipboard.files)
.then((urls) => {
const text = urls.map(({ name, url, type }) => {
return `${type === 'post' ? '!' : ''}[${name}](${url})`;
@@ -358,6 +268,8 @@ const Image = () => {
setVisible(true);
};
+ const { uploadSingleFile } = useImageUpload();
+
const onUpload = async (e) => {
if (!editor) {
return;
@@ -369,7 +281,7 @@ const Image = () => {
return;
}
- uploadImage({ file: e.target.files[0], type: 'post' }).then((url) => {
+ uploadSingleFile(e.target.files[0]).then((url) => {
setLink({ ...link, value: url });
setImageName({ ...imageName, value: files[0].name });
});
diff --git a/ui/src/components/Editor/hooks/useImageUpload.ts b/ui/src/components/Editor/hooks/useImageUpload.ts
new file mode 100644
index 000000000..bf97e24d6
--- /dev/null
+++ b/ui/src/components/Editor/hooks/useImageUpload.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { useTranslation } from 'react-i18next';
+
+import { Modal as AnswerModal } from '@/components';
+import { uploadImage } from '@/services';
+import { writeSettingStore } from '@/stores';
+
+export const useImageUpload = () => {
+ const { t } = useTranslation('translation', { keyPrefix: 'editor' });
+ const {
+ max_image_size = 4,
+ max_attachment_size = 8,
+ authorized_image_extensions = [],
+ authorized_attachment_extensions = [],
+ } = writeSettingStore((state) => state.write);
+
+ const verifyImageSize = (files: FileList | File[]): boolean => {
+ const fileArray = Array.isArray(files) ? files : Array.from(files);
+
+ if (fileArray.length === 0) {
+ return false;
+ }
+
+ const canUploadAttachment = authorized_attachment_extensions.length > 0;
+ const allowedAllType = [
+ ...authorized_image_extensions,
+ ...authorized_attachment_extensions,
+ ];
+
+ const unSupportFiles = fileArray.filter((file) => {
+ const fileName = file.name.toLowerCase();
+ return canUploadAttachment
+ ? !allowedAllType.find((v) => fileName.endsWith(v))
+ : file.type.indexOf('image') === -1;
+ });
+
+ if (unSupportFiles.length > 0) {
+ AnswerModal.confirm({
+ content: canUploadAttachment
+ ? t('file.not_supported', { file_type: allowedAllType.join(', ') })
+ : t('image.form_image.fields.file.msg.only_image'),
+ showCancel: false,
+ });
+ return false;
+ }
+
+ const otherFiles = fileArray.filter((file) => {
+ return file.type.indexOf('image') === -1;
+ });
+
+ if (canUploadAttachment && otherFiles.length > 0) {
+ const attachmentOverSizeFiles = otherFiles.filter(
+ (file) => file.size / 1024 / 1024 > max_attachment_size,
+ );
+ if (attachmentOverSizeFiles.length > 0) {
+ AnswerModal.confirm({
+ content: t('file.max_size', { size: max_attachment_size }),
+ showCancel: false,
+ });
+ return false;
+ }
+ }
+
+ const imageFiles = fileArray.filter(
+ (file) => file.type.indexOf('image') > -1,
+ );
+ const oversizedImages = imageFiles.filter(
+ (file) => file.size / 1024 / 1024 > max_image_size,
+ );
+ if (oversizedImages.length > 0) {
+ AnswerModal.confirm({
+ content: t('image.form_image.fields.file.msg.max_size', {
+ size: max_image_size,
+ }),
+ showCancel: false,
+ });
+ return false;
+ }
+
+ return true;
+ };
+
+ const uploadFiles = (
+ files: FileList | File[],
+ ): Promise<{ url: string; name: string; type: string }[]> => {
+ const fileArray = Array.isArray(files) ? files : Array.from(files);
+ const promises = fileArray.map(async (file) => {
+ const type = file.type.indexOf('image') > -1 ? 'post' : 'post_attachment';
+ const url = await uploadImage({ file, type });
+
+ return {
+ name: file.name,
+ url,
+ type,
+ };
+ });
+
+ return Promise.all(promises);
+ };
+
+ const uploadSingleFile = async (file: File): Promise => {
+ const type = file.type.indexOf('image') > -1 ? 'post' : 'post_attachment';
+ return uploadImage({ file, type });
+ };
+
+ return {
+ verifyImageSize,
+ uploadFiles,
+ uploadSingleFile,
+ };
+};
diff --git a/ui/src/components/Editor/index.tsx b/ui/src/components/Editor/index.tsx
index c6136c3c8..9c12bbb18 100644
--- a/ui/src/components/Editor/index.tsx
+++ b/ui/src/components/Editor/index.tsx
@@ -24,13 +24,21 @@ import {
forwardRef,
useImperativeHandle,
useCallback,
+ useEffect,
} from 'react';
+import { Spinner } from 'react-bootstrap';
import classNames from 'classnames';
-import { PluginType, useRenderPlugin } from '@/utils/pluginKit';
+import {
+ PluginType,
+ useRenderPlugin,
+ getReplacementPlugin,
+} from '@/utils/pluginKit';
+import { writeSettingStore } from '@/stores';
import PluginRender, { PluginSlot } from '../PluginRender';
+import { useImageUpload } from './hooks/useImageUpload';
import {
BlockQuote,
Bold,
@@ -87,6 +95,32 @@ const MDEditor: ForwardRefRenderFunction = (
) => {
const [currentEditor, setCurrentEditor] = useState(null);
const previewRef = useRef<{ getHtml; element } | null>(null);
+ const [fullEditorPlugin, setFullEditorPlugin] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const { verifyImageSize, uploadSingleFile } = useImageUpload();
+ const {
+ max_image_size = 4,
+ authorized_image_extensions = [],
+ authorized_attachment_extensions = [],
+ } = writeSettingStore((state) => state.write);
+
+ useEffect(() => {
+ let mounted = true;
+
+ const loadPlugin = async () => {
+ const plugin = await getReplacementPlugin(PluginType.EditorReplacement);
+ if (mounted) {
+ setFullEditorPlugin(plugin);
+ setIsLoading(false);
+ }
+ };
+
+ loadPlugin();
+
+ return () => {
+ mounted = false;
+ };
+ }, []);
useRenderPlugin(previewRef.current?.element);
@@ -104,6 +138,53 @@ const MDEditor: ForwardRefRenderFunction = (
const EditorComponent = MarkdownEditor;
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ if (fullEditorPlugin) {
+ const FullEditorComponent = fullEditorPlugin.component;
+
+ const handleImageUpload = async (file: File | string): Promise => {
+ if (typeof file === 'string') {
+ return file;
+ }
+
+ if (!verifyImageSize([file])) {
+ throw new Error('File validation failed');
+ }
+
+ return uploadSingleFile(file);
+ };
+
+ return (
+
+ );
+ }
+
return (
<>
diff --git a/ui/src/utils/pluginKit/index.ts b/ui/src/utils/pluginKit/index.ts
index a217d8936..b62d3f59d 100644
--- a/ui/src/utils/pluginKit/index.ts
+++ b/ui/src/utils/pluginKit/index.ts
@@ -53,6 +53,8 @@ class Plugins {
private initializationError: Error | null = null;
+ private replacementPlugins: Map
= new Map();
+
constructor() {
this.initialization = this.init();
}
@@ -176,6 +178,23 @@ class Plugins {
return;
}
+ // Handle singleton plugins (only one per type allowed)
+ const mode = plugin.info.registrationMode || 'multiple';
+ if (mode === 'singleton') {
+ const existingPlugin = this.replacementPlugins.get(plugin.info.type);
+ if (existingPlugin) {
+ const error = new Error(
+ `[PluginKit] Plugin conflict: ` +
+ `Cannot register '${plugin.info.slug_name}' because '${existingPlugin.info.slug_name}' ` +
+ `is already registered as a singleton plugin of type '${plugin.info.type}'. ` +
+ `Only one singleton plugin per type is allowed.`,
+ );
+ console.error(error.message);
+ throw error;
+ }
+ this.replacementPlugins.set(plugin.info.type, plugin);
+ }
+
if (plugin.i18nConfig) {
initI18nResource(plugin.i18nConfig);
}
@@ -207,6 +226,10 @@ class Plugins {
error: this.initializationError,
};
}
+
+ getReplacementPlugin(type: PluginType): Plugin | null {
+ return this.replacementPlugins.get(type) || null;
+ }
}
const plugins = new Plugins();
@@ -242,6 +265,21 @@ const validateRoutePlugin = async (slugName) => {
return Boolean(registeredPlugin?.enabled);
};
+const getReplacementPlugin = async (
+ type: PluginType,
+): Promise => {
+ try {
+ await plugins.initialization;
+ return plugins.getReplacementPlugin(type);
+ } catch (error) {
+ console.error(
+ `[PluginKit] Failed to get replacement plugin of type ${type}:`,
+ error,
+ );
+ return null;
+ }
+};
+
const mergeRoutePlugins = async (routes) => {
const routePlugins = await getRoutePlugins();
if (routePlugins.length === 0) {
@@ -348,6 +386,7 @@ export {
mergeRoutePlugins,
useCaptchaPlugin,
useRenderPlugin,
+ getReplacementPlugin,
PluginType,
};
export default plugins;
diff --git a/ui/src/utils/pluginKit/interface.ts b/ui/src/utils/pluginKit/interface.ts
index a1b641d86..1a2c4bee7 100644
--- a/ui/src/utils/pluginKit/interface.ts
+++ b/ui/src/utils/pluginKit/interface.ts
@@ -26,6 +26,7 @@ export enum PluginType {
Connector = 'connector',
Search = 'search',
Editor = 'editor',
+ EditorReplacement = 'editor_replacement',
Route = 'route',
Captcha = 'captcha',
Render = 'render',
@@ -38,6 +39,7 @@ export interface PluginInfo {
name?: string;
description?: string;
route?: string;
+ registrationMode?: 'multiple' | 'singleton';
}
export interface Plugin {
From 762e8a739d39e37aada9367bb7d6749022ea4425 Mon Sep 17 00:00:00 2001
From: robin
Date: Thu, 25 Dec 2025 11:51:14 +0800
Subject: [PATCH 42/92] fix(gitignore): correct node_modules entry and remove
specific plugin exceptions
---
ui/.gitignore | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/ui/.gitignore b/ui/.gitignore
index a73983ee7..3b1e96bd4 100644
--- a/ui/.gitignore
+++ b/ui/.gitignore
@@ -1,7 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
-/node_modules
+node_modules
/.pnp
.pnp.js
@@ -36,6 +36,4 @@ package-lock.json
/src/plugins/*
!/src/plugins/builtin
!/src/plugins/Demo
-!/src/plugins/answer-chart
-!/src/plugins/answer-formula
/src/plugins/*/*.go
From 61d9bf34d3f9c60ee6db426ba4e6a90e76a1500a Mon Sep 17 00:00:00 2001
From: liqiang46
Date: Fri, 26 Dec 2025 23:00:24 +0800
Subject: [PATCH 43/92] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=80=E4=BD=B3?=
=?UTF-8?q?=E8=AF=84=E8=AE=BA=E8=B6=8A=E6=9D=83=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在AcceptAnswer方法中添加了安全检查,确保要设置为最佳答案的回答确实属于该问题。
这可以防止攻击者将其他问题的回答设置为当前问题的最佳答案。
安全问题:越权设置最佳评论
修复方法:验证acceptedAnswerInfo.QuestionID == req.QuestionID
---
internal/service/content/answer_service.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go
index f904b82f0..d3aab20b3 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -455,6 +455,11 @@ func (as *AnswerService) AcceptAnswer(ctx context.Context, req *schema.AcceptAns
if !exist {
return errors.BadRequest(reason.AnswerNotFound)
}
+
+ // check answer belong to question
+ if acceptedAnswerInfo.QuestionID != req.QuestionID {
+ return errors.BadRequest(reason.AnswerNotFound)
+ }
acceptedAnswerInfo.ID = uid.DeShortID(acceptedAnswerInfo.ID)
}
From 42f8947e7c4bbd586b8496621079830880378346 Mon Sep 17 00:00:00 2001
From: Douglas Cortez
Date: Wed, 24 Dec 2025 00:24:21 -0300
Subject: [PATCH 44/92] fix(notification): use SSO provider for external_id
lookup in notifications
---
.../user_external_login_repo.go | 2 +-
.../notification/new_question_notification.go | 15 +++++++++------
.../service/notification_common/notification.go | 15 +++++++--------
3 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/internal/repo/user_external_login/user_external_login_repo.go b/internal/repo/user_external_login/user_external_login_repo.go
index c797e461d..b5cf85e86 100644
--- a/internal/repo/user_external_login/user_external_login_repo.go
+++ b/internal/repo/user_external_login/user_external_login_repo.go
@@ -87,7 +87,7 @@ func (ur *userExternalLoginRepo) GetByUserID(ctx context.Context, provider, user
func (ur *userExternalLoginRepo) GetUserExternalLoginList(ctx context.Context, userID string) (
resp []*entity.UserExternalLogin, err error) {
resp = make([]*entity.UserExternalLogin, 0)
- err = ur.data.DB.Context(ctx).Where("user_id = ?", userID).Find(&resp)
+ err = ur.data.DB.Context(ctx).Where("user_id = ?", userID).OrderBy("updated_at DESC").Find(&resp)
if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
}
diff --git a/internal/service/notification/new_question_notification.go b/internal/service/notification/new_question_notification.go
index 2f83042b2..085b3ec45 100644
--- a/internal/service/notification/new_question_notification.go
+++ b/internal/service/notification/new_question_notification.go
@@ -238,14 +238,17 @@ func (ns *ExternalNotificationService) syncNewQuestionNotificationToPlugin(ctx c
}
}
- userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, subscriberUserID)
+ externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, subscriberUserID)
if err != nil {
- log.Errorf("get user external login info failed: %v", err)
- return nil
- }
- if exist {
- newMsg.ReceiverExternalID = userInfo.ExternalID
+ log.Errorf("get user external login list failed for user %s: %v", subscriberUserID, err)
+ } else if len(externalLogins) > 0 {
+ newMsg.ReceiverExternalID = externalLogins[0].ExternalID
+ if len(externalLogins) > 1 {
+ log.Debugf("user %s has %d SSO logins, using most recent: provider=%s",
+ subscriberUserID, len(externalLogins), externalLogins[0].Provider)
+ }
}
+
fn.Notify(newMsg)
}
return nil
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 0bbd1865f..5dbadcb96 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -423,15 +423,14 @@ func (ns *NotificationCommon) syncNotificationToPlugin(ctx context.Context, objI
}
}
+ externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, msg.ReceiverUserID)
+ if err != nil {
+ log.Errorf("get user external login list failed for user %s: %v", msg.ReceiverUserID, err)
+ } else if len(externalLogins) > 0 {
+ pluginNotificationMsg.ReceiverExternalID = externalLogins[0].ExternalID
+ }
+
_ = plugin.CallNotification(func(fn plugin.Notification) error {
- userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
- if err != nil {
- log.Errorf("get user external login info failed: %v", err)
- return nil
- }
- if exist {
- pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
- }
fn.Notify(pluginNotificationMsg)
return nil
})
From c8908b7b3405e1bd927c6019e35630526a29c484 Mon Sep 17 00:00:00 2001
From: Douglas Cortez
Date: Fri, 26 Dec 2025 12:18:26 -0300
Subject: [PATCH 45/92] fix(review): notifications from the specific external
system will take precedence
---
.../service/notification/new_question_notification.go | 10 ++++++++++
internal/service/notification_common/notification.go | 8 ++++++++
2 files changed, 18 insertions(+)
diff --git a/internal/service/notification/new_question_notification.go b/internal/service/notification/new_question_notification.go
index 085b3ec45..0a5471873 100644
--- a/internal/service/notification/new_question_notification.go
+++ b/internal/service/notification/new_question_notification.go
@@ -238,6 +238,7 @@ func (ns *ExternalNotificationService) syncNewQuestionNotificationToPlugin(ctx c
}
}
+ // Get all external logins as fallback
externalLogins, err := ns.userExternalLoginRepo.GetUserExternalLoginList(ctx, subscriberUserID)
if err != nil {
log.Errorf("get user external login list failed for user %s: %v", subscriberUserID, err)
@@ -249,6 +250,15 @@ func (ns *ExternalNotificationService) syncNewQuestionNotificationToPlugin(ctx c
}
}
+ // Try to get external login specific to this plugin (takes precedence over fallback)
+ userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, subscriberUserID)
+ if err != nil {
+ log.Errorf("get user external login info failed: %v", err)
+ return nil
+ }
+ if exist {
+ newMsg.ReceiverExternalID = userInfo.ExternalID
+ }
fn.Notify(newMsg)
}
return nil
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 5dbadcb96..94ce86b16 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -431,6 +431,14 @@ func (ns *NotificationCommon) syncNotificationToPlugin(ctx context.Context, objI
}
_ = plugin.CallNotification(func(fn plugin.Notification) error {
+ userInfo, exist, err := ns.userExternalLoginRepo.GetByUserID(ctx, fn.Info().SlugName, msg.ReceiverUserID)
+ if err != nil {
+ log.Errorf("get user external login info failed: %v", err)
+ return nil
+ }
+ if exist {
+ pluginNotificationMsg.ReceiverExternalID = userInfo.ExternalID
+ }
fn.Notify(pluginNotificationMsg)
return nil
})
From 57f31ec7eb38ee9b4bf2f74f18a0416e74d752b9 Mon Sep 17 00:00:00 2001
From: Yusuke Tanaka
Date: Thu, 18 Dec 2025 19:48:44 +0900
Subject: [PATCH 46/92] fix: expand avatar column length from 1024 to 2048
The avatar column was too short to store long URLs from external OAuth
providers. When users log in via connector-google plugin, the Google
profile picture URL can exceed 1024 characters, causing a database error:
Error 1406 (22001): Data too long for column 'avatar' at row 1
This change expands the avatar column from VARCHAR(1024) to VARCHAR(2048).
Note: While URLs can technically exceed 2048 characters per specification,
2048 is the practical limit supported by most browsers and services.
URLs longer than 2048 characters are extremely rare in real-world usage.
---
internal/entity/user_entity.go | 2 +-
internal/migrations/migrations.go | 1 +
internal/migrations/v29.go | 37 +++++++++++++++++++++++++++++++
3 files changed, 39 insertions(+), 1 deletion(-)
create mode 100644 internal/migrations/v29.go
diff --git a/internal/entity/user_entity.go b/internal/entity/user_entity.go
index 66d612926..8e63fc95a 100644
--- a/internal/entity/user_entity.go
+++ b/internal/entity/user_entity.go
@@ -60,7 +60,7 @@ type User struct {
Status int `xorm:"not null default 1 INT(11) status"`
AuthorityGroup int `xorm:"not null default 1 INT(11) authority_group"`
DisplayName string `xorm:"not null default '' VARCHAR(30) display_name"`
- Avatar string `xorm:"not null default '' VARCHAR(1024) avatar"`
+ Avatar string `xorm:"not null default '' VARCHAR(2048) avatar"`
Mobile string `xorm:"not null VARCHAR(20) mobile"`
Bio string `xorm:"not null TEXT bio"`
BioHTML string `xorm:"not null TEXT bio_html"`
diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go
index 2fbfbb7fd..7ea0bc984 100644
--- a/internal/migrations/migrations.go
+++ b/internal/migrations/migrations.go
@@ -104,6 +104,7 @@ var migrations = []Migration{
NewMigration("v1.5.1", "add plugin kv storage", addPluginKVStorage, true),
NewMigration("v1.6.0", "move user config to interface", moveUserConfigToInterface, true),
NewMigration("v1.7.0", "add optional tags", addOptionalTags, true),
+ NewMigration("v1.7.1", "expand avatar column length", expandAvatarColumnLength, false),
}
func GetMigrations() []Migration {
diff --git a/internal/migrations/v29.go b/internal/migrations/v29.go
new file mode 100644
index 000000000..82e120d1b
--- /dev/null
+++ b/internal/migrations/v29.go
@@ -0,0 +1,37 @@
+/*
+ * 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 migrations
+
+import (
+ "context"
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func expandAvatarColumnLength(ctx context.Context, x *xorm.Engine) error {
+ type User struct {
+ Avatar string `xorm:"not null default '' VARCHAR(2048) avatar"`
+ }
+ if err := x.Context(ctx).Sync(new(User)); err != nil {
+ return fmt.Errorf("expand avatar column length failed: %w", err)
+ }
+ return nil
+}
From a1f0b0963b889f11d43963022282e665dd4dc135 Mon Sep 17 00:00:00 2001
From: Yusuke Tanaka
Date: Fri, 26 Dec 2025 11:39:16 +0900
Subject: [PATCH 47/92] fix: update migration version from v1.7.1 to v1.7.2
---
internal/migrations/migrations.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/migrations/migrations.go b/internal/migrations/migrations.go
index 7ea0bc984..9fda8c34d 100644
--- a/internal/migrations/migrations.go
+++ b/internal/migrations/migrations.go
@@ -104,7 +104,7 @@ var migrations = []Migration{
NewMigration("v1.5.1", "add plugin kv storage", addPluginKVStorage, true),
NewMigration("v1.6.0", "move user config to interface", moveUserConfigToInterface, true),
NewMigration("v1.7.0", "add optional tags", addOptionalTags, true),
- NewMigration("v1.7.1", "expand avatar column length", expandAvatarColumnLength, false),
+ NewMigration("v1.7.2", "expand avatar column length", expandAvatarColumnLength, false),
}
func GetMigrations() []Migration {
From d7d692bb37f2d83b49a2466800eddf4aade7f94e Mon Sep 17 00:00:00 2001
From: shuai
Date: Wed, 31 Dec 2025 15:37:38 +0800
Subject: [PATCH 48/92] fix: Fixed-layout navigation aligns with the width of
the main content.
---
ui/src/components/Header/index.scss | 21 +++++++++++++++++++++
ui/src/components/Header/index.tsx | 4 ++--
2 files changed, 23 insertions(+), 2 deletions(-)
diff --git a/ui/src/components/Header/index.scss b/ui/src/components/Header/index.scss
index f1df512d4..e9536b084 100644
--- a/ui/src/components/Header/index.scss
+++ b/ui/src/components/Header/index.scss
@@ -30,6 +30,11 @@
max-height: 2rem;
}
+ .fixed-width {
+ padding-left: 28px;
+ padding-right: 36px;
+ }
+
.create-icon {
color: var(--bs-nav-link-color);
}
@@ -120,6 +125,9 @@
@media (max-width: 1199.9px) {
#header {
+ .fixed-width {
+ padding-right: 48px;
+ }
.nav-grow {
flex-grow: 1 !important;
}
@@ -134,8 +142,21 @@
}
}
+@media screen and (max-width: 991px) {
+ #header {
+ .fixed-width {
+ padding-left: 40px;
+ padding-right: 48px;
+ }
+ }
+}
+
@media screen and (max-width: 767px) {
#header {
+ .fixed-width {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
.nav-text {
flex: 1;
white-space: nowrap;
diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx
index 4e9c8189a..22aad5aa1 100644
--- a/ui/src/components/Header/index.tsx
+++ b/ui/src/components/Header/index.tsx
@@ -115,8 +115,8 @@ const Header: FC = () => {
id="header">
Date: Tue, 9 Dec 2025 01:08:57 +0100
Subject: [PATCH 49/92] refactor(queue): improve queues
* fix race condition for registering handler
* add close method
* use generics to reduce duplication
* rename packages to drop underscore for go convention
* rename interface to drop stutter with package name
Signed-off-by: ferhat elmas
---
cmd/wire_gen.go | 54 ++---
internal/base/queue/queue.go | 120 ++++++++++
internal/base/queue/queue_test.go | 211 ++++++++++++++++++
internal/controller/template_controller.go | 6 +-
internal/repo/activity/answer_repo.go | 6 +-
internal/repo/activity/vote_repo.go | 6 +-
internal/service/activity_common/activity.go | 6 +-
.../service/activity_queue/activity_queue.go | 69 ------
.../service/activityqueue/activity_queue.go | 31 +++
internal/service/badge/badge_award_service.go | 6 +-
internal/service/badge/badge_event_handler.go | 6 +-
internal/service/comment/comment_service.go | 22 +-
internal/service/content/answer_service.go | 22 +-
internal/service/content/question_service.go | 22 +-
internal/service/content/revision_service.go | 12 +-
internal/service/content/user_service.go | 6 +-
internal/service/content/vote_service.go | 6 +-
internal/service/event_queue/event_queue.go | 69 ------
internal/service/eventqueue/event_queue.go | 31 +++
internal/service/meta/meta_service.go | 6 +-
.../external_notification_queue.go | 69 ------
internal/service/notice_queue/notice_queue.go | 69 ------
internal/service/noticequeue/notice_queue.go | 37 +++
.../notification/external_notification.go | 6 +-
.../notification_common/notification.go | 6 +-
internal/service/provider.go | 14 +-
internal/service/question_common/question.go | 6 +-
internal/service/report/report_service.go | 6 +-
internal/service/review/review_service.go | 10 +-
internal/service/tag/tag_service.go | 6 +-
internal/service/tag_common/tag_common.go | 6 +-
31 files changed, 553 insertions(+), 399 deletions(-)
create mode 100644 internal/base/queue/queue.go
create mode 100644 internal/base/queue/queue_test.go
delete mode 100644 internal/service/activity_queue/activity_queue.go
create mode 100644 internal/service/activityqueue/activity_queue.go
delete mode 100644 internal/service/event_queue/event_queue.go
create mode 100644 internal/service/eventqueue/event_queue.go
delete mode 100644 internal/service/notice_queue/external_notification_queue.go
delete mode 100644 internal/service/notice_queue/notice_queue.go
create mode 100644 internal/service/noticequeue/notice_queue.go
diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index aae1c6af6..22a70f29d 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -72,7 +72,7 @@ import (
"github.com/apache/answer/internal/service/action"
activity2 "github.com/apache/answer/internal/service/activity"
activity_common2 "github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/internal/service/answer_common"
auth2 "github.com/apache/answer/internal/service/auth"
badge2 "github.com/apache/answer/internal/service/badge"
@@ -83,14 +83,14 @@ import (
config2 "github.com/apache/answer/internal/service/config"
"github.com/apache/answer/internal/service/content"
"github.com/apache/answer/internal/service/dashboard"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
export2 "github.com/apache/answer/internal/service/export"
file_record2 "github.com/apache/answer/internal/service/file_record"
"github.com/apache/answer/internal/service/follow"
"github.com/apache/answer/internal/service/importer"
meta2 "github.com/apache/answer/internal/service/meta"
"github.com/apache/answer/internal/service/meta_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/notification"
"github.com/apache/answer/internal/service/notification_common"
"github.com/apache/answer/internal/service/object_info"
@@ -172,29 +172,29 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
revisionRepo := revision.NewRevisionRepo(dataData, uniqueIDRepo)
revisionService := revision_common.NewRevisionService(revisionRepo, userRepo)
- activityQueueService := activity_queue.NewActivityQueueService()
- tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, activityQueueService)
+ v := activityqueue.NewService()
+ tagCommonService := tag_common2.NewTagCommonService(tagCommonRepo, tagRelRepo, tagRepo, revisionService, siteInfoCommonService, v)
collectionRepo := collection.NewCollectionRepo(dataData, uniqueIDRepo)
collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
answerCommon := answercommon.NewAnswerCommon(answerRepo)
metaRepo := meta.NewMetaRepo(dataData)
metaCommonService := metacommon.NewMetaCommonService(metaRepo)
- questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, activityQueueService, revisionRepo, siteInfoCommonService, dataData)
- eventQueueService := event_queue.NewEventQueueService()
+ questionCommon := questioncommon.NewQuestionCommon(questionRepo, answerRepo, voteRepo, followRepo, tagCommonService, userCommon, collectionCommon, answerCommon, metaCommonService, configService, v, revisionRepo, siteInfoCommonService, dataData)
+ v2 := eventqueue.NewService()
fileRecordRepo := file_record.NewFileRecordRepo(dataData)
fileRecordService := file_record2.NewFileRecordService(fileRecordRepo, revisionRepo, serviceConf, siteInfoCommonService, userCommon)
- userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, eventQueueService, fileRecordService)
+ userService := content.NewUserService(userRepo, userActiveActivityRepo, activityRepo, emailService, authService, siteInfoCommonService, userRoleRelService, userCommon, userExternalLoginService, userNotificationConfigRepo, userNotificationConfigService, questionCommon, v2, fileRecordService)
captchaRepo := captcha.NewCaptchaRepo(dataData)
captchaService := action.NewCaptchaService(captchaRepo)
userController := controller.NewUserController(authService, userService, captchaService, emailService, siteInfoCommonService, userNotificationConfigService)
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagCommonRepo, tagCommonService)
- notificationQueueService := notice_queue.NewNotificationQueueService()
- externalNotificationQueueService := notice_queue.NewNewQuestionNotificationQueueService()
+ v3 := noticequeue.NewService()
+ v4 := noticequeue.NewExternalService()
reviewRepo := review.NewReviewRepo(dataData)
- reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, externalNotificationQueueService, tagCommonService, questionCommon, notificationQueueService, siteInfoCommonService, commentCommonRepo)
- commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, notificationQueueService, externalNotificationQueueService, activityQueueService, eventQueueService, reviewService)
+ reviewService := review2.NewReviewService(reviewRepo, objService, userCommon, userRepo, questionRepo, answerRepo, userRoleRelService, v4, tagCommonService, questionCommon, v3, siteInfoCommonService, commentCommonRepo)
+ commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo, emailService, userRepo, v3, v4, v, v2, reviewService)
rolePowerRelRepo := role.NewRolePowerRelRepo(dataData)
rolePowerRelService := role2.NewRolePowerRelService(rolePowerRelRepo, userRoleRelService)
rankService := rank2.NewRankService(userCommon, userRankRepo, objService, userRoleRelService, rolePowerRelService, configService)
@@ -202,17 +202,17 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
rateLimitMiddleware := middleware.NewRateLimitMiddleware(limitRepo)
commentController := controller.NewCommentController(commentService, rankService, captchaService, rateLimitMiddleware)
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
- tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, activityQueueService)
- answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
+ tagService := tag2.NewTagService(tagRepo, tagCommonService, revisionService, followRepo, siteInfoCommonService, v)
+ answerActivityRepo := activity.NewAnswerActivityRepo(dataData, activityRepo, userRankRepo, v3)
answerActivityService := activity2.NewAnswerActivityService(answerActivityRepo, configService)
- externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, externalNotificationQueueService, userExternalLoginRepo, siteInfoCommonService)
- questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, notificationQueueService, externalNotificationQueueService, activityQueueService, siteInfoCommonService, externalNotificationService, reviewService, configService, eventQueueService, reviewRepo)
- answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, notificationQueueService, externalNotificationQueueService, activityQueueService, reviewService, eventQueueService)
+ externalNotificationService := notification.NewExternalNotificationService(dataData, userNotificationConfigRepo, followRepo, emailService, userRepo, v4, userExternalLoginRepo, siteInfoCommonService)
+ questionService := content.NewQuestionService(activityRepo, questionRepo, answerRepo, tagCommonService, tagService, questionCommon, userCommon, userRepo, userRoleRelService, revisionService, metaCommonService, collectionCommon, answerActivityService, emailService, v3, v4, v, siteInfoCommonService, externalNotificationService, reviewService, configService, v2, reviewRepo)
+ answerService := content.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo, emailService, userRoleRelService, v3, v4, v, reviewService, v2)
reportHandle := report_handle.NewReportHandle(questionService, answerService, commentService)
- reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, eventQueueService)
+ reportService := report2.NewReportService(reportRepo, objService, userCommon, answerRepo, questionRepo, commentCommonRepo, reportHandle, configService, v2)
reportController := controller.NewReportController(reportService, rankService, captchaService)
- contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, notificationQueueService)
- voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, eventQueueService)
+ contentVoteRepo := activity.NewVoteRepo(dataData, activityRepo, userRankRepo, v3)
+ voteService := content.NewVoteService(contentVoteRepo, configService, questionRepo, answerRepo, commentCommonRepo, objService, v2)
voteController := controller.NewVoteController(voteService, rankService, captchaService)
tagController := controller.NewTagController(tagService, tagCommonService, rankService)
followFollowRepo := activity.NewFollowRepo(dataData, uniqueIDRepo, activityRepo)
@@ -228,7 +228,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
searchService := content.NewSearchService(searchParser, searchRepo)
searchController := controller.NewSearchController(searchService, captchaService)
reviewActivityRepo := activity.NewReviewActivityRepo(dataData, activityRepo, userRankRepo, configService)
- contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, notificationQueueService, activityQueueService, reportRepo, reviewService, reviewActivityRepo)
+ contentRevisionService := content.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService, objService, questionRepo, answerRepo, tagRepo, tagCommonService, v3, v, reportRepo, reviewService, reviewActivityRepo)
revisionController := controller.NewRevisionController(contentRevisionService, rankService)
rankController := controller.NewRankController(rankService)
userAdminRepo := user.NewUserAdminRepo(dataData, authRepo)
@@ -244,7 +244,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
siteInfoService := siteinfo.NewSiteInfoService(siteInfoRepo, siteInfoCommonService, emailService, tagCommonService, configService, questionCommon, fileRecordService)
siteInfoController := controller_admin.NewSiteInfoController(siteInfoService)
controllerSiteInfoController := controller.NewSiteInfoController(siteInfoCommonService)
- notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, notificationQueueService, userExternalLoginRepo, siteInfoCommonService)
+ notificationCommon := notificationcommon.NewNotificationCommon(dataData, notificationRepo, userCommon, activityRepo, followRepo, objService, v3, userExternalLoginRepo, siteInfoCommonService)
badgeRepo := badge.NewBadgeRepo(dataData, uniqueIDRepo)
notificationService := notification.NewNotificationService(dataData, notificationRepo, notificationCommon, revisionService, userRepo, reportRepo, reviewService, badgeRepo)
notificationController := controller.NewNotificationController(notificationService, rankService)
@@ -253,7 +253,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
uploaderService := uploader.NewUploaderService(serviceConf, siteInfoCommonService, fileRecordService)
uploadController := controller.NewUploadController(uploaderService)
activityActivityRepo := activity.NewActivityRepo(dataData, configService)
- activityCommon := activity_common2.NewActivityCommon(activityRepo, activityQueueService)
+ activityCommon := activity_common2.NewActivityCommon(activityRepo, v)
commentCommonService := comment_common.NewCommentCommonService(commentCommonRepo)
activityService := activity2.NewActivityService(activityActivityRepo, userCommon, activityCommon, tagCommonService, objService, commentCommonService, revisionService, metaCommonService, configService)
activityController := controller.NewActivityController(activityService)
@@ -265,12 +265,12 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
permissionController := controller.NewPermissionController(rankService)
userPluginController := controller.NewUserPluginController(pluginCommonService)
reviewController := controller.NewReviewController(reviewService, rankService, captchaService)
- metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, eventQueueService)
+ metaService := meta2.NewMetaService(metaCommonService, userCommon, answerRepo, questionRepo, v2)
metaController := controller.NewMetaController(metaService)
badgeGroupRepo := badge_group.NewBadgeGroupRepo(dataData, uniqueIDRepo)
eventRuleRepo := badge.NewEventRuleRepo(dataData)
- badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, notificationQueueService)
- badgeEventService := badge2.NewBadgeEventService(dataData, eventQueueService, badgeRepo, eventRuleRepo, badgeAwardService)
+ badgeAwardService := badge2.NewBadgeAwardService(badgeAwardRepo, badgeRepo, userCommon, objService, v3)
+ badgeEventService := badge2.NewBadgeEventService(dataData, v2, badgeRepo, eventRuleRepo, badgeAwardService)
badgeService := badge2.NewBadgeService(badgeRepo, badgeGroupRepo, badgeAwardRepo, badgeEventService, siteInfoCommonService)
badgeController := controller.NewBadgeController(badgeService, badgeAwardService)
controller_adminBadgeController := controller_admin.NewBadgeController(badgeService)
@@ -281,7 +281,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
avatarMiddleware := middleware.NewAvatarMiddleware(serviceConf, uploaderService)
shortIDMiddleware := middleware.NewShortIDMiddleware(siteInfoCommonService)
templateRenderController := templaterender.NewTemplateRenderController(questionService, userService, tagService, answerService, commentService, siteInfoCommonService, questionRepo)
- templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, eventQueueService, userService, questionService)
+ templateController := controller.NewTemplateController(templateRenderController, siteInfoCommonService, v2, userService, questionService)
templateRouter := router.NewTemplateRouter(templateController, templateRenderController, siteInfoController, authUserMiddleware)
connectorController := controller.NewConnectorController(siteInfoCommonService, emailService, userExternalLoginService)
userCenterLoginService := user_external_login2.NewUserCenterLoginService(userRepo, userCommon, userExternalLoginRepo, userActiveActivityRepo, siteInfoCommonService)
diff --git a/internal/base/queue/queue.go b/internal/base/queue/queue.go
new file mode 100644
index 000000000..ae23d341c
--- /dev/null
+++ b/internal/base/queue/queue.go
@@ -0,0 +1,120 @@
+/*
+ * 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 queue
+
+import (
+ "context"
+ "sync"
+
+ "github.com/segmentfault/pacman/log"
+)
+
+// Queue is a generic message queue service that processes messages asynchronously.
+// It is thread-safe and supports graceful shutdown.
+type Queue[T any] struct {
+ name string
+ queue chan T
+ handler func(ctx context.Context, msg T) error
+ mu sync.RWMutex
+ closed bool
+ wg sync.WaitGroup
+}
+
+// New creates a new queue with the given name and buffer size.
+func New[T any](name string, bufferSize int) *Queue[T] {
+ q := &Queue[T]{
+ name: name,
+ queue: make(chan T, bufferSize),
+ }
+ q.startWorker()
+ return q
+}
+
+// Send enqueues a message to be processed asynchronously.
+// It will block if the queue is full.
+func (q *Queue[T]) Send(ctx context.Context, msg T) {
+ q.mu.RLock()
+ closed := q.closed
+ q.mu.RUnlock()
+
+ if closed {
+ log.Warnf("[%s] queue is closed, dropping message", q.name)
+ return
+ }
+
+ select {
+ case q.queue <- msg:
+ log.Debugf("[%s] enqueued message: %+v", q.name, msg)
+ case <-ctx.Done():
+ log.Warnf("[%s] context cancelled while sending message", q.name)
+ }
+}
+
+// RegisterHandler sets the handler function for processing messages.
+// This is thread-safe and can be called at any time.
+func (q *Queue[T]) RegisterHandler(handler func(ctx context.Context, msg T) error) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.handler = handler
+}
+
+// Close gracefully shuts down the queue, waiting for pending messages to be processed.
+func (q *Queue[T]) Close() {
+ q.mu.Lock()
+ if q.closed {
+ q.mu.Unlock()
+ return
+ }
+ q.closed = true
+ q.mu.Unlock()
+
+ close(q.queue)
+ q.wg.Wait()
+ log.Infof("[%s] queue closed", q.name)
+}
+
+// startWorker starts the background goroutine that processes messages.
+func (q *Queue[T]) startWorker() {
+ q.wg.Add(1)
+ go func() {
+ defer q.wg.Done()
+ for msg := range q.queue {
+ q.processMessage(msg)
+ }
+ }()
+}
+
+// processMessage handles a single message with proper synchronization.
+func (q *Queue[T]) processMessage(msg T) {
+ q.mu.RLock()
+ handler := q.handler
+ q.mu.RUnlock()
+
+ if handler == nil {
+ log.Warnf("[%s] no handler registered, dropping message: %+v", q.name, msg)
+ return
+ }
+
+ // Use background context for async processing
+ // TODO: Consider adding timeout or using a derived context
+ if err := handler(context.TODO(), msg); err != nil {
+ log.Errorf("[%s] handler error: %v", q.name, err)
+ }
+}
diff --git a/internal/base/queue/queue_test.go b/internal/base/queue/queue_test.go
new file mode 100644
index 000000000..79355fb72
--- /dev/null
+++ b/internal/base/queue/queue_test.go
@@ -0,0 +1,211 @@
+/*
+ * 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 queue
+
+import (
+ "context"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+)
+
+type testMessage struct {
+ ID int
+ Data string
+}
+
+func TestQueue_SendAndReceive(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+ defer q.Close()
+
+ received := make(chan *testMessage, 1)
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ received <- msg
+ return nil
+ })
+
+ msg := &testMessage{ID: 1, Data: "hello"}
+ q.Send(context.Background(), msg)
+
+ select {
+ case r := <-received:
+ if r.ID != msg.ID || r.Data != msg.Data {
+ t.Errorf("received message mismatch: got %+v, want %+v", r, msg)
+ }
+ case <-time.After(time.Second):
+ t.Fatal("timeout waiting for message")
+ }
+}
+
+func TestQueue_MultipleMessages(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+ defer q.Close()
+
+ var count atomic.Int32
+ var wg sync.WaitGroup
+ numMessages := 100
+ wg.Add(numMessages)
+
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ count.Add(1)
+ wg.Done()
+ return nil
+ })
+
+ for i := range numMessages {
+ q.Send(context.Background(), &testMessage{ID: i})
+ }
+
+ done := make(chan struct{})
+ go func() {
+ wg.Wait()
+ close(done)
+ }()
+
+ select {
+ case <-done:
+ if int(count.Load()) != numMessages {
+ t.Errorf("expected %d messages, got %d", numMessages, count.Load())
+ }
+ case <-time.After(5 * time.Second):
+ t.Fatalf("timeout: only received %d of %d messages", count.Load(), numMessages)
+ }
+}
+
+func TestQueue_NoHandlerDropsMessage(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+ defer q.Close()
+
+ // Send without handler - should not panic
+ q.Send(context.Background(), &testMessage{ID: 1})
+
+ // Give time for the message to be processed (dropped)
+ time.Sleep(100 * time.Millisecond)
+}
+
+func TestQueue_RegisterHandlerAfterSend(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+ defer q.Close()
+
+ received := make(chan *testMessage, 1)
+
+ // Send first
+ q.Send(context.Background(), &testMessage{ID: 1})
+
+ // Small delay then register handler
+ time.Sleep(50 * time.Millisecond)
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ received <- msg
+ return nil
+ })
+
+ // Send another message that should be received
+ q.Send(context.Background(), &testMessage{ID: 2})
+
+ select {
+ case r := <-received:
+ if r.ID != 2 {
+ // First message was dropped (no handler), second should be received
+ t.Logf("received message ID: %d", r.ID)
+ }
+ case <-time.After(time.Second):
+ t.Fatal("timeout waiting for message")
+ }
+}
+
+func TestQueue_Close(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+
+ var count atomic.Int32
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ count.Add(1)
+ return nil
+ })
+
+ // Send some messages
+ for i := range 5 {
+ q.Send(context.Background(), &testMessage{ID: i})
+ }
+
+ // Close and wait
+ q.Close()
+
+ // All messages should have been processed
+ if count.Load() != 5 {
+ t.Errorf("expected 5 messages processed, got %d", count.Load())
+ }
+
+ // Sending after close should not panic
+ q.Send(context.Background(), &testMessage{ID: 99})
+}
+
+func TestQueue_ConcurrentSend(t *testing.T) {
+ q := New[*testMessage]("test", 100)
+ defer q.Close()
+
+ var count atomic.Int32
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ count.Add(1)
+ return nil
+ })
+
+ var wg sync.WaitGroup
+ numGoroutines := 10
+ messagesPerGoroutine := 100
+
+ for i := range numGoroutines {
+ wg.Add(1)
+ go func(id int) {
+ defer wg.Done()
+ for j := range messagesPerGoroutine {
+ q.Send(context.Background(), &testMessage{ID: id*1000 + j})
+ }
+ }(i)
+ }
+
+ wg.Wait()
+
+ // Wait for processing
+ time.Sleep(500 * time.Millisecond)
+
+ expected := int32(numGoroutines * messagesPerGoroutine)
+ if count.Load() != expected {
+ t.Errorf("expected %d messages, got %d", expected, count.Load())
+ }
+}
+
+func TestQueue_ConcurrentRegisterHandler(t *testing.T) {
+ q := New[*testMessage]("test", 10)
+ defer q.Close()
+
+ // Concurrently register handlers - should not race
+ var wg sync.WaitGroup
+ for range 10 {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ return nil
+ })
+ }()
+ }
+ wg.Wait()
+}
diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go
index 257b02fa4..e6b94f4f2 100644
--- a/internal/controller/template_controller.go
+++ b/internal/controller/template_controller.go
@@ -32,7 +32,7 @@ import (
"github.com/apache/answer/internal/base/middleware"
"github.com/apache/answer/internal/base/pager"
"github.com/apache/answer/internal/service/content"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/plugin"
"github.com/apache/answer/internal/base/constant"
@@ -59,7 +59,7 @@ type TemplateController struct {
cssPath string
templateRenderController *templaterender.TemplateRenderController
siteInfoService siteinfo_common.SiteInfoCommonService
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
userService *content.UserService
questionService *content.QuestionService
}
@@ -68,7 +68,7 @@ type TemplateController struct {
func NewTemplateController(
templateRenderController *templaterender.TemplateRenderController,
siteInfoService siteinfo_common.SiteInfoCommonService,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
userService *content.UserService,
questionService *content.QuestionService,
) *TemplateController {
diff --git a/internal/repo/activity/answer_repo.go b/internal/repo/activity/answer_repo.go
index 4aca874a7..96813f50c 100644
--- a/internal/repo/activity/answer_repo.go
+++ b/internal/repo/activity/answer_repo.go
@@ -34,7 +34,7 @@ import (
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/rank"
"github.com/apache/answer/pkg/converter"
"github.com/segmentfault/pacman/errors"
@@ -46,7 +46,7 @@ type AnswerActivityRepo struct {
data *data.Data
activityRepo activity_common.ActivityRepo
userRankRepo rank.UserRankRepo
- notificationQueueService notice_queue.NotificationQueueService
+ notificationQueueService noticequeue.Service
}
// NewAnswerActivityRepo new repository
@@ -54,7 +54,7 @@ func NewAnswerActivityRepo(
data *data.Data,
activityRepo activity_common.ActivityRepo,
userRankRepo rank.UserRankRepo,
- notificationQueueService notice_queue.NotificationQueueService,
+ notificationQueueService noticequeue.Service,
) activity.AnswerActivityRepo {
return &AnswerActivityRepo{
data: data,
diff --git a/internal/repo/activity/vote_repo.go b/internal/repo/activity/vote_repo.go
index f2d2be5f8..389ae18d8 100644
--- a/internal/repo/activity/vote_repo.go
+++ b/internal/repo/activity/vote_repo.go
@@ -28,7 +28,7 @@ import (
"github.com/segmentfault/pacman/log"
"github.com/apache/answer/internal/base/constant"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/pkg/converter"
"github.com/apache/answer/internal/base/pager"
@@ -51,7 +51,7 @@ type VoteRepo struct {
data *data.Data
activityRepo activity_common.ActivityRepo
userRankRepo rank.UserRankRepo
- notificationQueueService notice_queue.NotificationQueueService
+ notificationQueueService noticequeue.Service
}
// NewVoteRepo new repository
@@ -59,7 +59,7 @@ func NewVoteRepo(
data *data.Data,
activityRepo activity_common.ActivityRepo,
userRankRepo rank.UserRankRepo,
- notificationQueueService notice_queue.NotificationQueueService,
+ notificationQueueService noticequeue.Service,
) content.VoteRepo {
return &VoteRepo{
data: data,
diff --git a/internal/service/activity_common/activity.go b/internal/service/activity_common/activity.go
index 74f73a755..3d2efd6a3 100644
--- a/internal/service/activity_common/activity.go
+++ b/internal/service/activity_common/activity.go
@@ -25,7 +25,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/pkg/converter"
"github.com/apache/answer/pkg/uid"
"github.com/segmentfault/pacman/log"
@@ -49,13 +49,13 @@ type ActivityRepo interface {
type ActivityCommon struct {
activityRepo ActivityRepo
- activityQueueService activity_queue.ActivityQueueService
+ activityQueueService activityqueue.Service
}
// NewActivityCommon new activity common
func NewActivityCommon(
activityRepo ActivityRepo,
- activityQueueService activity_queue.ActivityQueueService,
+ activityQueueService activityqueue.Service,
) *ActivityCommon {
activity := &ActivityCommon{
activityRepo: activityRepo,
diff --git a/internal/service/activity_queue/activity_queue.go b/internal/service/activity_queue/activity_queue.go
deleted file mode 100644
index 7b8c1e3b8..000000000
--- a/internal/service/activity_queue/activity_queue.go
+++ /dev/null
@@ -1,69 +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.
- */
-
-package activity_queue
-
-import (
- "context"
-
- "github.com/apache/answer/internal/schema"
- "github.com/segmentfault/pacman/log"
-)
-
-type ActivityQueueService interface {
- Send(ctx context.Context, msg *schema.ActivityMsg)
- RegisterHandler(handler func(ctx context.Context, msg *schema.ActivityMsg) error)
-}
-
-type activityQueueService struct {
- Queue chan *schema.ActivityMsg
- Handler func(ctx context.Context, msg *schema.ActivityMsg) error
-}
-
-func (ns *activityQueueService) Send(ctx context.Context, msg *schema.ActivityMsg) {
- ns.Queue <- msg
-}
-
-func (ns *activityQueueService) RegisterHandler(
- handler func(ctx context.Context, msg *schema.ActivityMsg) error) {
- ns.Handler = handler
-}
-
-func (ns *activityQueueService) working() {
- go func() {
- for msg := range ns.Queue {
- log.Debugf("received activity %+v", msg)
- if ns.Handler == nil {
- log.Warnf("no handler for activity")
- continue
- }
- if err := ns.Handler(context.Background(), msg); err != nil {
- log.Error(err)
- }
- }
- }()
-}
-
-// NewActivityQueueService create a new activity queue service
-func NewActivityQueueService() ActivityQueueService {
- ns := &activityQueueService{}
- ns.Queue = make(chan *schema.ActivityMsg, 128)
- ns.working()
- return ns
-}
diff --git a/internal/service/activityqueue/activity_queue.go b/internal/service/activityqueue/activity_queue.go
new file mode 100644
index 000000000..d32caf5e9
--- /dev/null
+++ b/internal/service/activityqueue/activity_queue.go
@@ -0,0 +1,31 @@
+/*
+ * 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 activityqueue
+
+import (
+ "github.com/apache/answer/internal/base/queue"
+ "github.com/apache/answer/internal/schema"
+)
+
+type Service = *queue.Queue[*schema.ActivityMsg]
+
+func NewService() Service {
+ return queue.New[*schema.ActivityMsg]("activity", 128)
+}
diff --git a/internal/service/badge/badge_award_service.go b/internal/service/badge/badge_award_service.go
index 982c1d1a4..0799b87c0 100644
--- a/internal/service/badge/badge_award_service.go
+++ b/internal/service/badge/badge_award_service.go
@@ -28,7 +28,7 @@ import (
"github.com/apache/answer/internal/base/translator"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/object_info"
usercommon "github.com/apache/answer/internal/service/user_common"
"github.com/apache/answer/pkg/uid"
@@ -62,7 +62,7 @@ type BadgeAwardService struct {
badgeRepo BadgeRepo
userCommon *usercommon.UserCommon
objectInfoService *object_info.ObjService
- notificationQueueService notice_queue.NotificationQueueService
+ notificationQueueService noticequeue.Service
}
func NewBadgeAwardService(
@@ -70,7 +70,7 @@ func NewBadgeAwardService(
badgeRepo BadgeRepo,
userCommon *usercommon.UserCommon,
objectInfoService *object_info.ObjService,
- notificationQueueService notice_queue.NotificationQueueService,
+ notificationQueueService noticequeue.Service,
) *BadgeAwardService {
return &BadgeAwardService{
badgeAwardRepo: badgeAwardRepo,
diff --git a/internal/service/badge/badge_event_handler.go b/internal/service/badge/badge_event_handler.go
index 24cabf29b..0a9a84c0f 100644
--- a/internal/service/badge/badge_event_handler.go
+++ b/internal/service/badge/badge_event_handler.go
@@ -25,13 +25,13 @@ import (
"github.com/apache/answer/internal/base/data"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/segmentfault/pacman/log"
)
type BadgeEventService struct {
data *data.Data
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
badgeRepo BadgeRepo
eventRuleRepo EventRuleRepo
badgeAwardService *BadgeAwardService
@@ -45,7 +45,7 @@ type EventRuleRepo interface {
func NewBadgeEventService(
data *data.Data,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
badgeRepo BadgeRepo,
eventRuleRepo EventRuleRepo,
badgeAwardService *BadgeAwardService,
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go
index dc599e6df..30ff43c6b 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -22,7 +22,7 @@ package comment
import (
"context"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/service/review"
"time"
@@ -33,10 +33,10 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/internal/service/comment_common"
"github.com/apache/answer/internal/service/export"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/object_info"
"github.com/apache/answer/internal/service/permission"
usercommon "github.com/apache/answer/internal/service/user_common"
@@ -88,10 +88,10 @@ type CommentService struct {
objectInfoService *object_info.ObjService
emailService *export.EmailService
userRepo usercommon.UserRepo
- notificationQueueService notice_queue.NotificationQueueService
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService
- activityQueueService activity_queue.ActivityQueueService
- eventQueueService event_queue.EventQueueService
+ notificationQueueService noticequeue.Service
+ externalNotificationQueueService noticequeue.ExternalService
+ activityQueueService activityqueue.Service
+ eventQueueService eventqueue.Service
reviewService *review.ReviewService
}
@@ -104,10 +104,10 @@ func NewCommentService(
voteCommon activity_common.VoteRepo,
emailService *export.EmailService,
userRepo usercommon.UserRepo,
- notificationQueueService notice_queue.NotificationQueueService,
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService,
- activityQueueService activity_queue.ActivityQueueService,
- eventQueueService event_queue.EventQueueService,
+ notificationQueueService noticequeue.Service,
+ externalNotificationQueueService noticequeue.ExternalService,
+ activityQueueService activityqueue.Service,
+ eventQueueService eventqueue.Service,
reviewService *review.ReviewService,
) *CommentService {
return &CommentService{
diff --git a/internal/service/content/answer_service.go b/internal/service/content/answer_service.go
index d3aab20b3..2ad875177 100644
--- a/internal/service/content/answer_service.go
+++ b/internal/service/content/answer_service.go
@@ -24,7 +24,7 @@ import (
"encoding/json"
"time"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/reason"
@@ -32,11 +32,11 @@ import (
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
answercommon "github.com/apache/answer/internal/service/answer_common"
collectioncommon "github.com/apache/answer/internal/service/collection_common"
"github.com/apache/answer/internal/service/export"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/permission"
questioncommon "github.com/apache/answer/internal/service/question_common"
"github.com/apache/answer/internal/service/review"
@@ -65,11 +65,11 @@ type AnswerService struct {
voteRepo activity_common.VoteRepo
emailService *export.EmailService
roleService *role.UserRoleRelService
- notificationQueueService notice_queue.NotificationQueueService
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService
- activityQueueService activity_queue.ActivityQueueService
+ notificationQueueService noticequeue.Service
+ externalNotificationQueueService noticequeue.ExternalService
+ activityQueueService activityqueue.Service
reviewService *review.ReviewService
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
}
func NewAnswerService(
@@ -85,11 +85,11 @@ func NewAnswerService(
voteRepo activity_common.VoteRepo,
emailService *export.EmailService,
roleService *role.UserRoleRelService,
- notificationQueueService notice_queue.NotificationQueueService,
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService,
- activityQueueService activity_queue.ActivityQueueService,
+ notificationQueueService noticequeue.Service,
+ externalNotificationQueueService noticequeue.ExternalService,
+ activityQueueService activityqueue.Service,
reviewService *review.ReviewService,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
) *AnswerService {
return &AnswerService{
answerRepo: answerRepo,
diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go
index b8372a72e..bc3ac0bb6 100644
--- a/internal/service/content/question_service.go
+++ b/internal/service/content/question_service.go
@@ -25,7 +25,7 @@ import (
"strings"
"time"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/plugin"
"github.com/apache/answer/internal/base/constant"
@@ -38,13 +38,13 @@ import (
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
answercommon "github.com/apache/answer/internal/service/answer_common"
collectioncommon "github.com/apache/answer/internal/service/collection_common"
"github.com/apache/answer/internal/service/config"
"github.com/apache/answer/internal/service/export"
metacommon "github.com/apache/answer/internal/service/meta_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/notification"
"github.com/apache/answer/internal/service/permission"
questioncommon "github.com/apache/answer/internal/service/question_common"
@@ -84,14 +84,14 @@ type QuestionService struct {
collectionCommon *collectioncommon.CollectionCommon
answerActivityService *activity.AnswerActivityService
emailService *export.EmailService
- notificationQueueService notice_queue.NotificationQueueService
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService
- activityQueueService activity_queue.ActivityQueueService
+ notificationQueueService noticequeue.Service
+ externalNotificationQueueService noticequeue.ExternalService
+ activityQueueService activityqueue.Service
siteInfoService siteinfo_common.SiteInfoCommonService
newQuestionNotificationService *notification.ExternalNotificationService
reviewService *review.ReviewService
configService *config.ConfigService
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
reviewRepo review.ReviewRepo
}
@@ -110,14 +110,14 @@ func NewQuestionService(
collectionCommon *collectioncommon.CollectionCommon,
answerActivityService *activity.AnswerActivityService,
emailService *export.EmailService,
- notificationQueueService notice_queue.NotificationQueueService,
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService,
- activityQueueService activity_queue.ActivityQueueService,
+ notificationQueueService noticequeue.Service,
+ externalNotificationQueueService noticequeue.ExternalService,
+ activityQueueService activityqueue.Service,
siteInfoService siteinfo_common.SiteInfoCommonService,
newQuestionNotificationService *notification.ExternalNotificationService,
reviewService *review.ReviewService,
configService *config.ConfigService,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
reviewRepo review.ReviewRepo,
) *QuestionService {
return &QuestionService{
diff --git a/internal/service/content/revision_service.go b/internal/service/content/revision_service.go
index 4ac08e769..13ec65b7d 100644
--- a/internal/service/content/revision_service.go
+++ b/internal/service/content/revision_service.go
@@ -32,9 +32,9 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
answercommon "github.com/apache/answer/internal/service/answer_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/object_info"
questioncommon "github.com/apache/answer/internal/service/question_common"
"github.com/apache/answer/internal/service/report_common"
@@ -62,8 +62,8 @@ type RevisionService struct {
answerRepo answercommon.AnswerRepo
tagRepo tag_common.TagRepo
tagCommon *tag_common.TagCommonService
- notificationQueueService notice_queue.NotificationQueueService
- activityQueueService activity_queue.ActivityQueueService
+ notificationQueueService noticequeue.Service
+ activityQueueService activityqueue.Service
reportRepo report_common.ReportRepo
reviewService *review.ReviewService
reviewActivity activity.ReviewActivityRepo
@@ -79,8 +79,8 @@ func NewRevisionService(
answerRepo answercommon.AnswerRepo,
tagRepo tag_common.TagRepo,
tagCommon *tag_common.TagCommonService,
- notificationQueueService notice_queue.NotificationQueueService,
- activityQueueService activity_queue.ActivityQueueService,
+ notificationQueueService noticequeue.Service,
+ activityQueueService activityqueue.Service,
reportRepo report_common.ReportRepo,
reviewService *review.ReviewService,
reviewActivity activity.ReviewActivityRepo,
diff --git a/internal/service/content/user_service.go b/internal/service/content/user_service.go
index e9cc35788..711d6caa0 100644
--- a/internal/service/content/user_service.go
+++ b/internal/service/content/user_service.go
@@ -25,7 +25,7 @@ import (
"fmt"
"time"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/pkg/token"
"github.com/apache/answer/internal/base/constant"
@@ -68,7 +68,7 @@ type UserService struct {
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo
userNotificationConfigService *user_notification_config.UserNotificationConfigService
questionService *questioncommon.QuestionCommon
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
fileRecordService *file_record.FileRecordService
}
@@ -84,7 +84,7 @@ func NewUserService(userRepo usercommon.UserRepo,
userNotificationConfigRepo user_notification_config.UserNotificationConfigRepo,
userNotificationConfigService *user_notification_config.UserNotificationConfigService,
questionService *questioncommon.QuestionCommon,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
fileRecordService *file_record.FileRecordService,
) *UserService {
return &UserService{
diff --git a/internal/service/content/vote_service.go b/internal/service/content/vote_service.go
index aa6150497..1f74769f5 100644
--- a/internal/service/content/vote_service.go
+++ b/internal/service/content/vote_service.go
@@ -24,7 +24,7 @@ import (
"fmt"
"strings"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
@@ -62,7 +62,7 @@ type VoteService struct {
answerRepo answercommon.AnswerRepo
commentCommonRepo comment_common.CommentCommonRepo
objectService *object_info.ObjService
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
}
func NewVoteService(
@@ -72,7 +72,7 @@ func NewVoteService(
answerRepo answercommon.AnswerRepo,
commentCommonRepo comment_common.CommentCommonRepo,
objectService *object_info.ObjService,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
) *VoteService {
return &VoteService{
voteRepo: voteRepo,
diff --git a/internal/service/event_queue/event_queue.go b/internal/service/event_queue/event_queue.go
deleted file mode 100644
index 77dc302b5..000000000
--- a/internal/service/event_queue/event_queue.go
+++ /dev/null
@@ -1,69 +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.
- */
-
-package event_queue
-
-import (
- "context"
-
- "github.com/apache/answer/internal/schema"
- "github.com/segmentfault/pacman/log"
-)
-
-type EventQueueService interface {
- Send(ctx context.Context, msg *schema.EventMsg)
- RegisterHandler(handler func(ctx context.Context, msg *schema.EventMsg) error)
-}
-
-type eventQueueService struct {
- Queue chan *schema.EventMsg
- Handler func(ctx context.Context, msg *schema.EventMsg) error
-}
-
-func (ns *eventQueueService) Send(ctx context.Context, msg *schema.EventMsg) {
- ns.Queue <- msg
-}
-
-func (ns *eventQueueService) RegisterHandler(
- handler func(ctx context.Context, msg *schema.EventMsg) error) {
- ns.Handler = handler
-}
-
-func (ns *eventQueueService) working() {
- go func() {
- for msg := range ns.Queue {
- log.Debugf("received badge %+v", msg)
- if ns.Handler == nil {
- log.Warnf("no handler for badge")
- continue
- }
- if err := ns.Handler(context.Background(), msg); err != nil {
- log.Error(err)
- }
- }
- }()
-}
-
-// NewEventQueueService create a new badge queue service
-func NewEventQueueService() EventQueueService {
- ns := &eventQueueService{}
- ns.Queue = make(chan *schema.EventMsg, 128)
- ns.working()
- return ns
-}
diff --git a/internal/service/eventqueue/event_queue.go b/internal/service/eventqueue/event_queue.go
new file mode 100644
index 000000000..8d3a22392
--- /dev/null
+++ b/internal/service/eventqueue/event_queue.go
@@ -0,0 +1,31 @@
+/*
+ * 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 eventqueue
+
+import (
+ "github.com/apache/answer/internal/base/queue"
+ "github.com/apache/answer/internal/schema"
+)
+
+type Service = *queue.Queue[*schema.EventMsg]
+
+func NewService() Service {
+ return queue.New[*schema.EventMsg]("event", 128)
+}
diff --git a/internal/service/meta/meta_service.go b/internal/service/meta/meta_service.go
index c1ca7c619..e48e8f468 100644
--- a/internal/service/meta/meta_service.go
+++ b/internal/service/meta/meta_service.go
@@ -26,7 +26,7 @@ import (
"strconv"
"strings"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
@@ -48,7 +48,7 @@ type MetaService struct {
userCommon *usercommon.UserCommon
questionRepo questioncommon.QuestionRepo
answerRepo answercommon.AnswerRepo
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
}
func NewMetaService(
@@ -56,7 +56,7 @@ func NewMetaService(
userCommon *usercommon.UserCommon,
answerRepo answercommon.AnswerRepo,
questionRepo questioncommon.QuestionRepo,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
) *MetaService {
return &MetaService{
metaCommonService: metaCommonService,
diff --git a/internal/service/notice_queue/external_notification_queue.go b/internal/service/notice_queue/external_notification_queue.go
deleted file mode 100644
index 6322a77ec..000000000
--- a/internal/service/notice_queue/external_notification_queue.go
+++ /dev/null
@@ -1,69 +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.
- */
-
-package notice_queue
-
-import (
- "context"
-
- "github.com/apache/answer/internal/schema"
- "github.com/segmentfault/pacman/log"
-)
-
-type ExternalNotificationQueueService interface {
- Send(ctx context.Context, msg *schema.ExternalNotificationMsg)
- RegisterHandler(handler func(ctx context.Context, msg *schema.ExternalNotificationMsg) error)
-}
-
-type externalNotificationQueueService struct {
- Queue chan *schema.ExternalNotificationMsg
- Handler func(ctx context.Context, msg *schema.ExternalNotificationMsg) error
-}
-
-func (ns *externalNotificationQueueService) Send(ctx context.Context, msg *schema.ExternalNotificationMsg) {
- ns.Queue <- msg
-}
-
-func (ns *externalNotificationQueueService) RegisterHandler(
- handler func(ctx context.Context, msg *schema.ExternalNotificationMsg) error) {
- ns.Handler = handler
-}
-
-func (ns *externalNotificationQueueService) working() {
- go func() {
- for msg := range ns.Queue {
- log.Debugf("received notification %+v", msg)
- if ns.Handler == nil {
- log.Warnf("no handler for notification")
- continue
- }
- if err := ns.Handler(context.Background(), msg); err != nil {
- log.Error(err)
- }
- }
- }()
-}
-
-// NewNewQuestionNotificationQueueService create a new notification queue service
-func NewNewQuestionNotificationQueueService() ExternalNotificationQueueService {
- ns := &externalNotificationQueueService{}
- ns.Queue = make(chan *schema.ExternalNotificationMsg, 128)
- ns.working()
- return ns
-}
diff --git a/internal/service/notice_queue/notice_queue.go b/internal/service/notice_queue/notice_queue.go
deleted file mode 100644
index 22b733e32..000000000
--- a/internal/service/notice_queue/notice_queue.go
+++ /dev/null
@@ -1,69 +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.
- */
-
-package notice_queue
-
-import (
- "context"
-
- "github.com/apache/answer/internal/schema"
- "github.com/segmentfault/pacman/log"
-)
-
-type NotificationQueueService interface {
- Send(ctx context.Context, msg *schema.NotificationMsg)
- RegisterHandler(handler func(ctx context.Context, msg *schema.NotificationMsg) error)
-}
-
-type notificationQueueService struct {
- Queue chan *schema.NotificationMsg
- Handler func(ctx context.Context, msg *schema.NotificationMsg) error
-}
-
-func (ns *notificationQueueService) Send(ctx context.Context, msg *schema.NotificationMsg) {
- ns.Queue <- msg
-}
-
-func (ns *notificationQueueService) RegisterHandler(
- handler func(ctx context.Context, msg *schema.NotificationMsg) error) {
- ns.Handler = handler
-}
-
-func (ns *notificationQueueService) working() {
- go func() {
- for msg := range ns.Queue {
- log.Debugf("received notification %+v", msg)
- if ns.Handler == nil {
- log.Warnf("no handler for notification")
- continue
- }
- if err := ns.Handler(context.Background(), msg); err != nil {
- log.Error(err)
- }
- }
- }()
-}
-
-// NewNotificationQueueService create a new notification queue service
-func NewNotificationQueueService() NotificationQueueService {
- ns := ¬ificationQueueService{}
- ns.Queue = make(chan *schema.NotificationMsg, 128)
- ns.working()
- return ns
-}
diff --git a/internal/service/noticequeue/notice_queue.go b/internal/service/noticequeue/notice_queue.go
new file mode 100644
index 000000000..138f9ce61
--- /dev/null
+++ b/internal/service/noticequeue/notice_queue.go
@@ -0,0 +1,37 @@
+/*
+ * 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 noticequeue
+
+import (
+ "github.com/apache/answer/internal/base/queue"
+ "github.com/apache/answer/internal/schema"
+)
+
+type Service = *queue.Queue[*schema.NotificationMsg]
+
+func NewService() Service {
+ return queue.New[*schema.NotificationMsg]("notification", 128)
+}
+
+type ExternalService = *queue.Queue[*schema.ExternalNotificationMsg]
+
+func NewExternalService() ExternalService {
+ return queue.New[*schema.ExternalNotificationMsg]("external_notification", 128)
+}
diff --git a/internal/service/notification/external_notification.go b/internal/service/notification/external_notification.go
index d6bdd2fb7..425a8c2bb 100644
--- a/internal/service/notification/external_notification.go
+++ b/internal/service/notification/external_notification.go
@@ -28,7 +28,7 @@ import (
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity_common"
"github.com/apache/answer/internal/service/export"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/siteinfo_common"
usercommon "github.com/apache/answer/internal/service/user_common"
"github.com/apache/answer/internal/service/user_external_login"
@@ -42,7 +42,7 @@ type ExternalNotificationService struct {
followRepo activity_common.FollowRepo
emailService *export.EmailService
userRepo usercommon.UserRepo
- notificationQueueService notice_queue.ExternalNotificationQueueService
+ notificationQueueService noticequeue.ExternalService
userExternalLoginRepo user_external_login.UserExternalLoginRepo
siteInfoService siteinfo_common.SiteInfoCommonService
}
@@ -53,7 +53,7 @@ func NewExternalNotificationService(
followRepo activity_common.FollowRepo,
emailService *export.EmailService,
userRepo usercommon.UserRepo,
- notificationQueueService notice_queue.ExternalNotificationQueueService,
+ notificationQueueService noticequeue.ExternalService,
userExternalLoginRepo user_external_login.UserExternalLoginRepo,
siteInfoService siteinfo_common.SiteInfoCommonService,
) *ExternalNotificationService {
diff --git a/internal/service/notification_common/notification.go b/internal/service/notification_common/notification.go
index 94ce86b16..aa3f4106c 100644
--- a/internal/service/notification_common/notification.go
+++ b/internal/service/notification_common/notification.go
@@ -35,7 +35,7 @@ import (
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/object_info"
usercommon "github.com/apache/answer/internal/service/user_common"
"github.com/apache/answer/pkg/uid"
@@ -66,7 +66,7 @@ type NotificationCommon struct {
followRepo activity_common.FollowRepo
userCommon *usercommon.UserCommon
objectInfoService *object_info.ObjService
- notificationQueueService notice_queue.NotificationQueueService
+ notificationQueueService noticequeue.Service
userExternalLoginRepo user_external_login.UserExternalLoginRepo
siteInfoService siteinfo_common.SiteInfoCommonService
}
@@ -78,7 +78,7 @@ func NewNotificationCommon(
activityRepo activity_common.ActivityRepo,
followRepo activity_common.FollowRepo,
objectInfoService *object_info.ObjService,
- notificationQueueService notice_queue.NotificationQueueService,
+ notificationQueueService noticequeue.Service,
userExternalLoginRepo user_external_login.UserExternalLoginRepo,
siteInfoService siteinfo_common.SiteInfoCommonService,
) *NotificationCommon {
diff --git a/internal/service/provider.go b/internal/service/provider.go
index 65535f41b..f6d954709 100644
--- a/internal/service/provider.go
+++ b/internal/service/provider.go
@@ -23,7 +23,7 @@ import (
"github.com/apache/answer/internal/service/action"
"github.com/apache/answer/internal/service/activity"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
answercommon "github.com/apache/answer/internal/service/answer_common"
"github.com/apache/answer/internal/service/auth"
"github.com/apache/answer/internal/service/badge"
@@ -34,14 +34,14 @@ import (
"github.com/apache/answer/internal/service/config"
"github.com/apache/answer/internal/service/content"
"github.com/apache/answer/internal/service/dashboard"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/service/export"
"github.com/apache/answer/internal/service/file_record"
"github.com/apache/answer/internal/service/follow"
"github.com/apache/answer/internal/service/importer"
"github.com/apache/answer/internal/service/meta"
metacommon "github.com/apache/answer/internal/service/meta_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/notification"
notficationcommon "github.com/apache/answer/internal/service/notification_common"
"github.com/apache/answer/internal/service/object_info"
@@ -114,14 +114,14 @@ var ProviderSetService = wire.NewSet(
user_external_login.NewUserCenterLoginService,
plugin_common.NewPluginCommonService,
config.NewConfigService,
- notice_queue.NewNotificationQueueService,
- activity_queue.NewActivityQueueService,
+ noticequeue.NewService,
+ activityqueue.NewService,
user_notification_config.NewUserNotificationConfigService,
notification.NewExternalNotificationService,
- notice_queue.NewNewQuestionNotificationQueueService,
+ noticequeue.NewExternalService,
review.NewReviewService,
meta.NewMetaService,
- event_queue.NewEventQueueService,
+ eventqueue.NewService,
badge.NewBadgeService,
badge.NewBadgeEventService,
badge.NewBadgeAwardService,
diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go
index 846dea894..557a5db15 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -34,7 +34,7 @@ import (
"github.com/apache/answer/internal/base/handler"
"github.com/apache/answer/internal/base/reason"
"github.com/apache/answer/internal/service/activity_common"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/internal/service/config"
metacommon "github.com/apache/answer/internal/service/meta_common"
"github.com/apache/answer/internal/service/revision"
@@ -103,7 +103,7 @@ type QuestionCommon struct {
AnswerCommon *answercommon.AnswerCommon
metaCommonService *metacommon.MetaCommonService
configService *config.ConfigService
- activityQueueService activity_queue.ActivityQueueService
+ activityQueueService activityqueue.Service
revisionRepo revision.RevisionRepo
siteInfoService siteinfo_common.SiteInfoCommonService
data *data.Data
@@ -119,7 +119,7 @@ func NewQuestionCommon(questionRepo QuestionRepo,
answerCommon *answercommon.AnswerCommon,
metaCommonService *metacommon.MetaCommonService,
configService *config.ConfigService,
- activityQueueService activity_queue.ActivityQueueService,
+ activityQueueService activityqueue.Service,
revisionRepo revision.RevisionRepo,
siteInfoService siteinfo_common.SiteInfoCommonService,
data *data.Data,
diff --git a/internal/service/report/report_service.go b/internal/service/report/report_service.go
index d32ccdabf..84c15d597 100644
--- a/internal/service/report/report_service.go
+++ b/internal/service/report/report_service.go
@@ -22,7 +22,7 @@ package report
import (
"encoding/json"
- "github.com/apache/answer/internal/service/event_queue"
+ "github.com/apache/answer/internal/service/eventqueue"
"github.com/apache/answer/internal/base/constant"
"github.com/apache/answer/internal/base/handler"
@@ -57,7 +57,7 @@ type ReportService struct {
commentCommonRepo comment_common.CommentCommonRepo
reportHandle *report_handle.ReportHandle
configService *config.ConfigService
- eventQueueService event_queue.EventQueueService
+ eventQueueService eventqueue.Service
}
// NewReportService new report service
@@ -70,7 +70,7 @@ func NewReportService(
commentCommonRepo comment_common.CommentCommonRepo,
reportHandle *report_handle.ReportHandle,
configService *config.ConfigService,
- eventQueueService event_queue.EventQueueService,
+ eventQueueService eventqueue.Service,
) *ReportService {
return &ReportService{
reportRepo: reportRepo,
diff --git a/internal/service/review/review_service.go b/internal/service/review/review_service.go
index a23b9ee43..bbb142894 100644
--- a/internal/service/review/review_service.go
+++ b/internal/service/review/review_service.go
@@ -29,7 +29,7 @@ import (
"github.com/apache/answer/internal/schema"
answercommon "github.com/apache/answer/internal/service/answer_common"
commentcommon "github.com/apache/answer/internal/service/comment_common"
- "github.com/apache/answer/internal/service/notice_queue"
+ "github.com/apache/answer/internal/service/noticequeue"
"github.com/apache/answer/internal/service/object_info"
questioncommon "github.com/apache/answer/internal/service/question_common"
"github.com/apache/answer/internal/service/role"
@@ -66,8 +66,8 @@ type ReviewService struct {
userRoleService *role.UserRoleRelService
tagCommon *tagcommon.TagCommonService
questionCommon *questioncommon.QuestionCommon
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService
- notificationQueueService notice_queue.NotificationQueueService
+ externalNotificationQueueService noticequeue.ExternalService
+ notificationQueueService noticequeue.Service
siteInfoService siteinfo_common.SiteInfoCommonService
commentCommonRepo commentcommon.CommentCommonRepo
}
@@ -81,10 +81,10 @@ func NewReviewService(
questionRepo questioncommon.QuestionRepo,
answerRepo answercommon.AnswerRepo,
userRoleService *role.UserRoleRelService,
- externalNotificationQueueService notice_queue.ExternalNotificationQueueService,
+ externalNotificationQueueService noticequeue.ExternalService,
tagCommon *tagcommon.TagCommonService,
questionCommon *questioncommon.QuestionCommon,
- notificationQueueService notice_queue.NotificationQueueService,
+ notificationQueueService noticequeue.Service,
siteInfoService siteinfo_common.SiteInfoCommonService,
commentCommonRepo commentcommon.CommentCommonRepo,
) *ReviewService {
diff --git a/internal/service/tag/tag_service.go b/internal/service/tag/tag_service.go
index e61bfa06e..640f06b69 100644
--- a/internal/service/tag/tag_service.go
+++ b/internal/service/tag/tag_service.go
@@ -25,7 +25,7 @@ import (
"strings"
"github.com/apache/answer/internal/base/constant"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/internal/service/revision_common"
"github.com/apache/answer/internal/service/siteinfo_common"
tagcommonser "github.com/apache/answer/internal/service/tag_common"
@@ -50,7 +50,7 @@ type TagService struct {
revisionService *revision_common.RevisionService
followCommon activity_common.FollowRepo
siteInfoService siteinfo_common.SiteInfoCommonService
- activityQueueService activity_queue.ActivityQueueService
+ activityQueueService activityqueue.Service
}
// NewTagService new tag service
@@ -60,7 +60,7 @@ func NewTagService(
revisionService *revision_common.RevisionService,
followCommon activity_common.FollowRepo,
siteInfoService siteinfo_common.SiteInfoCommonService,
- activityQueueService activity_queue.ActivityQueueService,
+ activityQueueService activityqueue.Service,
) *TagService {
return &TagService{
tagRepo: tagRepo,
diff --git a/internal/service/tag_common/tag_common.go b/internal/service/tag_common/tag_common.go
index 87c10bcc9..9ca8e100f 100644
--- a/internal/service/tag_common/tag_common.go
+++ b/internal/service/tag_common/tag_common.go
@@ -33,7 +33,7 @@ import (
"github.com/apache/answer/internal/base/validator"
"github.com/apache/answer/internal/entity"
"github.com/apache/answer/internal/schema"
- "github.com/apache/answer/internal/service/activity_queue"
+ "github.com/apache/answer/internal/service/activityqueue"
"github.com/apache/answer/internal/service/revision_common"
"github.com/apache/answer/internal/service/siteinfo_common"
"github.com/apache/answer/pkg/converter"
@@ -89,7 +89,7 @@ type TagCommonService struct {
tagRelRepo TagRelRepo
tagRepo TagRepo
siteInfoService siteinfo_common.SiteInfoCommonService
- activityQueueService activity_queue.ActivityQueueService
+ activityQueueService activityqueue.Service
}
// NewTagCommonService new tag service
@@ -99,7 +99,7 @@ func NewTagCommonService(
tagRepo TagRepo,
revisionService *revision_common.RevisionService,
siteInfoService siteinfo_common.SiteInfoCommonService,
- activityQueueService activity_queue.ActivityQueueService,
+ activityQueueService activityqueue.Service,
) *TagCommonService {
return &TagCommonService{
tagCommonRepo: tagCommonRepo,
From 5ff6106d37807ef79fd04ab4ba56a43a961bf6fe Mon Sep 17 00:00:00 2001
From: ferhat elmas
Date: Tue, 6 Jan 2026 22:25:29 +0100
Subject: [PATCH 50/92] fix: address comments and add a test
Signed-off-by: ferhat elmas
---
internal/base/queue/queue.go | 16 +++++--
internal/base/queue/queue_test.go | 42 +++++++++++++++++++
.../service/activityqueue/activity_queue.go | 2 +-
internal/service/eventqueue/event_queue.go | 2 +-
internal/service/noticequeue/notice_queue.go | 4 +-
5 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/internal/base/queue/queue.go b/internal/base/queue/queue.go
index ae23d341c..b3a8757a2 100644
--- a/internal/base/queue/queue.go
+++ b/internal/base/queue/queue.go
@@ -26,6 +26,17 @@ import (
"github.com/segmentfault/pacman/log"
)
+type Service[T any] interface {
+ // Send enqueues a message to be processed asynchronously.
+ Send(ctx context.Context, msg T)
+
+ // RegisterHandler sets the handler function for processing messages.
+ RegisterHandler(handler func(ctx context.Context, msg T) error)
+
+ // Close gracefully shuts down the queue, waiting for pending messages to be processed.
+ Close()
+}
+
// Queue is a generic message queue service that processes messages asynchronously.
// It is thread-safe and supports graceful shutdown.
type Queue[T any] struct {
@@ -51,10 +62,9 @@ func New[T any](name string, bufferSize int) *Queue[T] {
// It will block if the queue is full.
func (q *Queue[T]) Send(ctx context.Context, msg T) {
q.mu.RLock()
- closed := q.closed
- q.mu.RUnlock()
+ defer q.mu.RUnlock()
- if closed {
+ if q.closed {
log.Warnf("[%s] queue is closed, dropping message", q.name)
return
}
diff --git a/internal/base/queue/queue_test.go b/internal/base/queue/queue_test.go
index 79355fb72..23f0fda75 100644
--- a/internal/base/queue/queue_test.go
+++ b/internal/base/queue/queue_test.go
@@ -21,6 +21,7 @@ package queue
import (
"context"
+ "fmt"
"sync"
"sync/atomic"
"testing"
@@ -209,3 +210,44 @@ func TestQueue_ConcurrentRegisterHandler(t *testing.T) {
}
wg.Wait()
}
+
+// TestQueue_SendCloseRace is a regression test for the race condition between
+// Send and Close. Without proper synchronization, concurrent Send and Close
+// calls could cause a "send on closed channel" panic.
+// Run with: go test -race -run TestQueue_SendCloseRace
+func TestQueue_SendCloseRace(t *testing.T) {
+ for i := range 100 {
+ t.Run(fmt.Sprintf("iteration_%d", i), func(t *testing.T) {
+ // Use large buffer to avoid blocking on channel send while holding RLock
+ q := New[*testMessage]("test-race", 1000)
+ q.RegisterHandler(func(ctx context.Context, msg *testMessage) error {
+ return nil
+ })
+
+ var wg sync.WaitGroup
+
+ // Use cancellable context so senders can exit when Close is called
+ ctx, cancel := context.WithCancel(context.Background())
+
+ // Start multiple senders
+ for j := range 10 {
+ wg.Add(1)
+ go func(id int) {
+ defer wg.Done()
+ for k := range 100 {
+ q.Send(ctx, &testMessage{ID: id*1000 + k})
+ }
+ }(j)
+ }
+
+ // Close while senders are still running
+ go func() {
+ time.Sleep(time.Microsecond * 10)
+ cancel() // Cancel context to unblock any waiting senders
+ q.Close()
+ }()
+
+ wg.Wait()
+ })
+ }
+}
diff --git a/internal/service/activityqueue/activity_queue.go b/internal/service/activityqueue/activity_queue.go
index d32caf5e9..2210977bd 100644
--- a/internal/service/activityqueue/activity_queue.go
+++ b/internal/service/activityqueue/activity_queue.go
@@ -24,7 +24,7 @@ import (
"github.com/apache/answer/internal/schema"
)
-type Service = *queue.Queue[*schema.ActivityMsg]
+type Service queue.Service[*schema.ActivityMsg]
func NewService() Service {
return queue.New[*schema.ActivityMsg]("activity", 128)
diff --git a/internal/service/eventqueue/event_queue.go b/internal/service/eventqueue/event_queue.go
index 8d3a22392..e93a83636 100644
--- a/internal/service/eventqueue/event_queue.go
+++ b/internal/service/eventqueue/event_queue.go
@@ -24,7 +24,7 @@ import (
"github.com/apache/answer/internal/schema"
)
-type Service = *queue.Queue[*schema.EventMsg]
+type Service queue.Service[*schema.EventMsg]
func NewService() Service {
return queue.New[*schema.EventMsg]("event", 128)
diff --git a/internal/service/noticequeue/notice_queue.go b/internal/service/noticequeue/notice_queue.go
index 138f9ce61..5e4d4b0f9 100644
--- a/internal/service/noticequeue/notice_queue.go
+++ b/internal/service/noticequeue/notice_queue.go
@@ -24,13 +24,13 @@ import (
"github.com/apache/answer/internal/schema"
)
-type Service = *queue.Queue[*schema.NotificationMsg]
+type Service queue.Service[*schema.NotificationMsg]
func NewService() Service {
return queue.New[*schema.NotificationMsg]("notification", 128)
}
-type ExternalService = *queue.Queue[*schema.ExternalNotificationMsg]
+type ExternalService queue.Service[*schema.ExternalNotificationMsg]
func NewExternalService() ExternalService {
return queue.New[*schema.ExternalNotificationMsg]("external_notification", 128)
From 1fbb802e8f4f79bae635b746f44ef7cdc671d89b Mon Sep 17 00:00:00 2001
From: maishivamhoo123
Date: Tue, 23 Dec 2025 14:17:38 +0530
Subject: [PATCH 51/92] feat: load optional .env file and add .env.example
---
.env.example | 36 +
.gitignore | 3 +
README.md | 9 +
cmd/main.go | 6 +
data/conf/config.yaml | 25 +
data/i18n/af_ZA.yaml | 1384 +++++++++++++++++++++++
data/i18n/ar_SA.yaml | 1384 +++++++++++++++++++++++
data/i18n/az_AZ.yaml | 1371 +++++++++++++++++++++++
data/i18n/bal_BA.yaml | 1371 +++++++++++++++++++++++
data/i18n/ban_ID.yaml | 1371 +++++++++++++++++++++++
data/i18n/bn_BD.yaml | 1371 +++++++++++++++++++++++
data/i18n/bs_BA.yaml | 1371 +++++++++++++++++++++++
data/i18n/ca_ES.yaml | 1384 +++++++++++++++++++++++
data/i18n/cs_CZ.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/cy_GB.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/da_DK.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/de_DE.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/el_GR.yaml | 1384 +++++++++++++++++++++++
data/i18n/en_US.yaml | 2395 ++++++++++++++++++++++++++++++++++++++++
data/i18n/es_ES.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/fa_IR.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/fi_FI.yaml | 1384 +++++++++++++++++++++++
data/i18n/fr_FR.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/he_IL.yaml | 1384 +++++++++++++++++++++++
data/i18n/hi_IN.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/hu_HU.yaml | 1384 +++++++++++++++++++++++
data/i18n/hy_AM.yaml | 1371 +++++++++++++++++++++++
data/i18n/i18n.yaml | 64 ++
data/i18n/id_ID.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/it_IT.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/ja_JP.yaml | 2360 ++++++++++++++++++++++++++++++++++++++++
data/i18n/ko_KR.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/ml_IN.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/nl_NL.yaml | 1384 +++++++++++++++++++++++
data/i18n/no_NO.yaml | 1385 +++++++++++++++++++++++
data/i18n/pl_PL.yaml | 2414 +++++++++++++++++++++++++++++++++++++++++
data/i18n/pt_BR.yaml | 1381 +++++++++++++++++++++++
data/i18n/pt_PT.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/ro_RO.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/ru_RU.yaml | 2360 ++++++++++++++++++++++++++++++++++++++++
data/i18n/sk_SK.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/sq_AL.yaml | 1371 +++++++++++++++++++++++
data/i18n/sr_SP.yaml | 1384 +++++++++++++++++++++++
data/i18n/sv_SE.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/te_IN.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/tr_TR.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/uk_UA.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/vi_VN.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/zh_CN.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
data/i18n/zh_TW.yaml | 2359 ++++++++++++++++++++++++++++++++++++++++
go.mod | 1 +
go.sum | 2 +
52 files changed, 86392 insertions(+)
create mode 100644 .env.example
create mode 100644 data/conf/config.yaml
create mode 100644 data/i18n/af_ZA.yaml
create mode 100644 data/i18n/ar_SA.yaml
create mode 100644 data/i18n/az_AZ.yaml
create mode 100644 data/i18n/bal_BA.yaml
create mode 100644 data/i18n/ban_ID.yaml
create mode 100644 data/i18n/bn_BD.yaml
create mode 100644 data/i18n/bs_BA.yaml
create mode 100644 data/i18n/ca_ES.yaml
create mode 100644 data/i18n/cs_CZ.yaml
create mode 100644 data/i18n/cy_GB.yaml
create mode 100644 data/i18n/da_DK.yaml
create mode 100644 data/i18n/de_DE.yaml
create mode 100644 data/i18n/el_GR.yaml
create mode 100644 data/i18n/en_US.yaml
create mode 100644 data/i18n/es_ES.yaml
create mode 100644 data/i18n/fa_IR.yaml
create mode 100644 data/i18n/fi_FI.yaml
create mode 100644 data/i18n/fr_FR.yaml
create mode 100644 data/i18n/he_IL.yaml
create mode 100644 data/i18n/hi_IN.yaml
create mode 100644 data/i18n/hu_HU.yaml
create mode 100644 data/i18n/hy_AM.yaml
create mode 100644 data/i18n/i18n.yaml
create mode 100644 data/i18n/id_ID.yaml
create mode 100644 data/i18n/it_IT.yaml
create mode 100644 data/i18n/ja_JP.yaml
create mode 100644 data/i18n/ko_KR.yaml
create mode 100644 data/i18n/ml_IN.yaml
create mode 100644 data/i18n/nl_NL.yaml
create mode 100644 data/i18n/no_NO.yaml
create mode 100644 data/i18n/pl_PL.yaml
create mode 100644 data/i18n/pt_BR.yaml
create mode 100644 data/i18n/pt_PT.yaml
create mode 100644 data/i18n/ro_RO.yaml
create mode 100644 data/i18n/ru_RU.yaml
create mode 100644 data/i18n/sk_SK.yaml
create mode 100644 data/i18n/sq_AL.yaml
create mode 100644 data/i18n/sr_SP.yaml
create mode 100644 data/i18n/sv_SE.yaml
create mode 100644 data/i18n/te_IN.yaml
create mode 100644 data/i18n/tr_TR.yaml
create mode 100644 data/i18n/uk_UA.yaml
create mode 100644 data/i18n/vi_VN.yaml
create mode 100644 data/i18n/zh_CN.yaml
create mode 100644 data/i18n/zh_TW.yaml
diff --git a/.env.example b/.env.example
new file mode 100644
index 000000000..b08736cdf
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,36 @@
+# Installation
+INSTALL_PORT=80
+AUTO_INSTALL=false
+
+# Database
+DB_TYPE=
+DB_USERNAME=
+DB_PASSWORD=
+DB_HOST=
+DB_NAME=
+DB_FILE=
+
+# Site
+LANGUAGE=en-US
+SITE_NAME=Apache Answer
+SITE_URL=
+CONTACT_EMAIL=
+
+# Admin
+ADMIN_NAME=
+ADMIN_PASSWORD=
+ADMIN_EMAIL=
+
+# Content
+EXTERNAL_CONTENT_DISPLAY=ask_before_display
+
+# Swagger
+SWAGGER_HOST=
+SWAGGER_ADDRESS_PORT=
+
+# Server
+SITE_ADDR=0.0.0.0:3000
+
+# Logging
+LOG_LEVEL=INFO
+LOG_PATH=
diff --git a/.gitignore b/.gitignore
index 257ef31d6..ba66f51a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,6 @@ dist/
# Lint setup generated file
.husky/
+
+# Environment variables
+.env
\ No newline at end of file
diff --git a/README.md b/README.md
index 6e96c516a..ffa171694 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,15 @@ You can also check out the [plugins here](https://answer.apache.org/plugins).
- [mockgen](https://github.com/uber-go/mock?tab=readme-ov-file#installation) >= 0.6.0
- [wire](https://github.com/google/wire/) >= 0.5.0
+### Environment Configuration
+
+ You may create a `.env` file in the project root.
+ Copy `.env.example` and fill required values.
+
+ ```bash
+ cp .env.example .env
+
+
### Build
```bash
diff --git a/cmd/main.go b/cmd/main.go
index f166d2309..1f8153001 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -31,12 +31,18 @@ import (
"github.com/apache/answer/internal/base/path"
"github.com/apache/answer/internal/schema"
"github.com/gin-gonic/gin"
+ "github.com/joho/godotenv"
"github.com/segmentfault/pacman"
"github.com/segmentfault/pacman/contrib/log/zap"
"github.com/segmentfault/pacman/contrib/server/http"
"github.com/segmentfault/pacman/log"
)
+func init() {
+ // Load .env if present, ignore error to keep backward compatibility
+ _ = godotenv.Load()
+}
+
// go build -ldflags "-X github.com/apache/answer/cmd.Version=x.y.z"
var (
// Name is the name of the project
diff --git a/data/conf/config.yaml b/data/conf/config.yaml
new file mode 100644
index 000000000..6419bb538
--- /dev/null
+++ b/data/conf/config.yaml
@@ -0,0 +1,25 @@
+debug: false
+server:
+ http:
+ addr: 0.0.0.0:80
+data:
+ database:
+ driver: sqlite3
+ connection: ./data/answer.db
+ cache:
+ file_path: data/cache/cache.db
+i18n:
+ bundle_dir: data/i18n
+service_config:
+ upload_path: data/uploads
+ clean_up_uploads: true
+ clean_orphan_uploads_period_hours: 48
+ purge_deleted_files_period_days: 30
+swaggerui:
+ show: true
+ protocol: http
+ host: 127.0.0.1
+ address: :80
+ui:
+ base_url: ""
+ api_base_url: ""
diff --git a/data/i18n/af_ZA.yaml b/data/i18n/af_ZA.yaml
new file mode 100644
index 000000000..0121bde1a
--- /dev/null
+++ b/data/i18n/af_ZA.yaml
@@ -0,0 +1,1384 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Success.
+ unknown:
+ other: Unknown error.
+ request_format_error:
+ other: Request format is not valid.
+ unauthorized_error:
+ other: Unauthorized.
+ database_error:
+ other: Data server error.
+ role:
+ name:
+ user:
+ other: User
+ admin:
+ other: Admin
+ moderator:
+ other: Moderator
+ description:
+ user:
+ other: Default with no special access.
+ admin:
+ other: Have the full power to access the site.
+ moderator:
+ other: Has access to all posts except admin settings.
+ email:
+ other: Email
+ password:
+ other: Password
+ email_or_password_wrong_error:
+ other: Email and password do not match.
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: Answer do not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_update:
+ other: No permission to update.
+ comment:
+ edit_without_permission:
+ other: Comment are not allowed to edit.
+ not_found:
+ other: Comment not found.
+ cannot_edit_after_deadline:
+ other: The comment time has been too long to modify.
+ email:
+ duplicate:
+ other: Email already exists.
+ need_to_be_verified:
+ other: Email should be verified.
+ verify_url_expired:
+ other: Email verified URL has expired, please resend the email.
+ lang:
+ not_found:
+ other: Language file not found.
+ object:
+ captcha_verification_failed:
+ other: Captcha wrong.
+ disallow_follow:
+ other: You are not allowed to follow.
+ disallow_vote:
+ other: You are not allowed to vote.
+ disallow_vote_your_self:
+ other: You can't vote for your own post.
+ not_found:
+ other: Object not found.
+ verification_failed:
+ other: Verification failed.
+ email_or_password_incorrect:
+ other: Email and password do not match.
+ old_password_verification_failed:
+ other: The old password verification failed
+ new_password_same_as_previous_setting:
+ other: The new password is the same as the previous one.
+ question:
+ not_found:
+ other: Question not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_close:
+ other: No permission to close.
+ cannot_update:
+ other: No permission to update.
+ rank:
+ fail_to_meet_the_condition:
+ other: Rank fail to meet the condition.
+ report:
+ handle_failed:
+ other: Report handle failed.
+ not_found:
+ other: Report not found.
+ tag:
+ not_found:
+ other: Tag not found.
+ recommend_tag_not_found:
+ other: Recommend Tag is not exist.
+ recommend_tag_enter:
+ other: Please enter at least one required tag.
+ not_contain_synonym_tags:
+ other: Should not contain synonym tags.
+ cannot_update:
+ other: No permission to update.
+ cannot_set_synonym_as_itself:
+ other: You cannot set the synonym of the current tag as itself.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: The From Name cannot be a email address.
+ theme:
+ not_found:
+ other: Theme not found.
+ revision:
+ review_underway:
+ other: Can't edit currently, there is a version in the review queue.
+ no_permission:
+ other: No permission to Revision.
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: User not found.
+ suspended:
+ other: User has been suspended.
+ username_invalid:
+ other: Username is invalid.
+ username_duplicate:
+ other: Username is already in use.
+ set_avatar:
+ other: Avatar set failed.
+ cannot_update_your_role:
+ other: You cannot modify your role.
+ not_allowed_registration:
+ other: Currently the site is not open for registration
+ config:
+ read_config_failed:
+ other: Read config failed
+ database:
+ connection_failed:
+ other: Database connection failed
+ create_table_failed:
+ other: Create table failed
+ install:
+ create_config_failed:
+ other: Can't create the config.yaml file.
+ upload:
+ unsupported_file_format:
+ other: Unsupported file format.
+ report:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
+ rude:
+ name:
+ other: rude or abusive
+ desc:
+ other: A reasonable person would find this content inappropriate for respectful discourse.
+ duplicate:
+ name:
+ other: a duplicate
+ desc:
+ other: This question has been asked before and already has an answer.
+ not_answer:
+ name:
+ other: not an answer
+ desc:
+ other: This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.
+ not_need:
+ name:
+ other: no longer needed
+ desc:
+ other: This comment is outdated, conversational or not relevant to this post.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires staff attention for another reason not listed above.
+ question:
+ close:
+ duplicate:
+ name:
+ other: spam
+ desc:
+ other: This question has been asked before and already has an answer.
+ guideline:
+ name:
+ other: a community-specific reason
+ desc:
+ other: This question doesn't meet a community guideline.
+ multiple:
+ name:
+ other: needs details or clarity
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires another reason not listed above.
+ operation_type:
+ asked:
+ other: asked
+ answered:
+ other: answered
+ modified:
+ other: modified
+ notification:
+ action:
+ update_question:
+ other: updated question
+ answer_the_question:
+ other: answered question
+ update_answer:
+ other: updated answer
+ accept_answer:
+ other: accepted answer
+ comment_question:
+ other: commented question
+ comment_answer:
+ other: commented answer
+ reply_to_you:
+ other: replied to you
+ mention_you:
+ other: mentioned you
+ your_question_is_closed:
+ other: Your question has been closed
+ your_question_was_deleted:
+ other: Your question has been deleted
+ your_answer_was_deleted:
+ other: Your answer has been deleted
+ your_comment_was_deleted:
+ other: Your comment has been deleted
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comments
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ feedback:
+ characters: content must be at least 6 characters in length.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to {{site_name}}
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ characters: content must be at least 6 characters in length.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to {{site_name}}
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: After you've done that, click "Next" button.
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ counts:
+ views: views
+ votes: votes
+ answers: answers
+ accepted: Accepted
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as <link>
+ head:
+ label: Head
+ text: This will insert before </head>
+ header:
+ label: Header
+ text: This will insert after <body>
+ footer:
+ label: Footer
+ text: This will insert before </body>.
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/ar_SA.yaml b/data/i18n/ar_SA.yaml
new file mode 100644
index 000000000..25c086f11
--- /dev/null
+++ b/data/i18n/ar_SA.yaml
@@ -0,0 +1,1384 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Success.
+ unknown:
+ other: Unknown error.
+ request_format_error:
+ other: Request format is not valid.
+ unauthorized_error:
+ other: Unauthorized.
+ database_error:
+ other: Data server error.
+ role:
+ name:
+ user:
+ other: User
+ admin:
+ other: Admin
+ moderator:
+ other: Moderator
+ description:
+ user:
+ other: Default with no special access.
+ admin:
+ other: Have the full power to access the site.
+ moderator:
+ other: Has access to all posts except admin settings.
+ email:
+ other: Email
+ password:
+ other: Password
+ email_or_password_wrong_error:
+ other: Email and password do not match.
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: Answer do not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_update:
+ other: No permission to update.
+ comment:
+ edit_without_permission:
+ other: Comment are not allowed to edit.
+ not_found:
+ other: Comment not found.
+ cannot_edit_after_deadline:
+ other: The comment time has been too long to modify.
+ email:
+ duplicate:
+ other: Email already exists.
+ need_to_be_verified:
+ other: Email should be verified.
+ verify_url_expired:
+ other: Email verified URL has expired, please resend the email.
+ lang:
+ not_found:
+ other: Language file not found.
+ object:
+ captcha_verification_failed:
+ other: Captcha wrong.
+ disallow_follow:
+ other: You are not allowed to follow.
+ disallow_vote:
+ other: You are not allowed to vote.
+ disallow_vote_your_self:
+ other: You can't vote for your own post.
+ not_found:
+ other: Object not found.
+ verification_failed:
+ other: Verification failed.
+ email_or_password_incorrect:
+ other: Email and password do not match.
+ old_password_verification_failed:
+ other: The old password verification failed
+ new_password_same_as_previous_setting:
+ other: The new password is the same as the previous one.
+ question:
+ not_found:
+ other: Question not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_close:
+ other: No permission to close.
+ cannot_update:
+ other: No permission to update.
+ rank:
+ fail_to_meet_the_condition:
+ other: Rank fail to meet the condition.
+ report:
+ handle_failed:
+ other: Report handle failed.
+ not_found:
+ other: Report not found.
+ tag:
+ not_found:
+ other: Tag not found.
+ recommend_tag_not_found:
+ other: Recommend Tag is not exist.
+ recommend_tag_enter:
+ other: Please enter at least one required tag.
+ not_contain_synonym_tags:
+ other: Should not contain synonym tags.
+ cannot_update:
+ other: No permission to update.
+ cannot_set_synonym_as_itself:
+ other: You cannot set the synonym of the current tag as itself.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: The From Name cannot be a email address.
+ theme:
+ not_found:
+ other: Theme not found.
+ revision:
+ review_underway:
+ other: Can't edit currently, there is a version in the review queue.
+ no_permission:
+ other: No permission to Revision.
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: User not found.
+ suspended:
+ other: User has been suspended.
+ username_invalid:
+ other: Username is invalid.
+ username_duplicate:
+ other: Username is already in use.
+ set_avatar:
+ other: Avatar set failed.
+ cannot_update_your_role:
+ other: You cannot modify your role.
+ not_allowed_registration:
+ other: Currently the site is not open for registration
+ config:
+ read_config_failed:
+ other: Read config failed
+ database:
+ connection_failed:
+ other: Database connection failed
+ create_table_failed:
+ other: Create table failed
+ install:
+ create_config_failed:
+ other: Can't create the config.yaml file.
+ upload:
+ unsupported_file_format:
+ other: Unsupported file format.
+ report:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
+ rude:
+ name:
+ other: rude or abusive
+ desc:
+ other: A reasonable person would find this content inappropriate for respectful discourse.
+ duplicate:
+ name:
+ other: a duplicate
+ desc:
+ other: This question has been asked before and already has an answer.
+ not_answer:
+ name:
+ other: not an answer
+ desc:
+ other: This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.
+ not_need:
+ name:
+ other: no longer needed
+ desc:
+ other: This comment is outdated, conversational or not relevant to this post.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires staff attention for another reason not listed above.
+ question:
+ close:
+ duplicate:
+ name:
+ other: spam
+ desc:
+ other: This question has been asked before and already has an answer.
+ guideline:
+ name:
+ other: a community-specific reason
+ desc:
+ other: This question doesn't meet a community guideline.
+ multiple:
+ name:
+ other: needs details or clarity
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires another reason not listed above.
+ operation_type:
+ asked:
+ other: asked
+ answered:
+ other: answered
+ modified:
+ other: modified
+ notification:
+ action:
+ update_question:
+ other: updated question
+ answer_the_question:
+ other: answered question
+ update_answer:
+ other: updated answer
+ accept_answer:
+ other: accepted answer
+ comment_question:
+ other: commented question
+ comment_answer:
+ other: commented answer
+ reply_to_you:
+ other: replied to you
+ mention_you:
+ other: mentioned you
+ your_question_is_closed:
+ other: Your question has been closed
+ your_question_was_deleted:
+ other: Your question has been deleted
+ your_answer_was_deleted:
+ other: Your answer has been deleted
+ your_comment_was_deleted:
+ other: Your comment has been deleted
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comments
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ feedback:
+ characters: content must be at least 6 characters in length.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to {{site_name}}
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ characters: content must be at least 6 characters in length.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to {{site_name}}
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: After you've done that, click "Next" button.
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ counts:
+ views: views
+ votes: votes
+ answers: answers
+ accepted: Accepted
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/az_AZ.yaml b/data/i18n/az_AZ.yaml
new file mode 100644
index 000000000..4249c5f03
--- /dev/null
+++ b/data/i18n/az_AZ.yaml
@@ -0,0 +1,1371 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: "Success."
+ unknown:
+ other: "Unknown error."
+ request_format_error:
+ other: "Request format is not valid."
+ unauthorized_error:
+ other: "Unauthorized."
+ database_error:
+ other: "Data server error."
+ role:
+ name:
+ user:
+ other: "User"
+ admin:
+ other: "Admin"
+ moderator:
+ other: "Moderator"
+ description:
+ user:
+ other: "Default with no special access."
+ admin:
+ other: "Have the full power to access the site."
+ moderator:
+ other: "Has access to all posts except admin settings."
+ email:
+ other: "Email"
+ password:
+ other: "Password"
+ email_or_password_wrong_error:
+ other: "Email and password do not match."
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: "Answer do not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_update:
+ other: "No permission to update."
+ comment:
+ edit_without_permission:
+ other: "Comment are not allowed to edit."
+ not_found:
+ other: "Comment not found."
+ email:
+ duplicate:
+ other: "Email already exists."
+ need_to_be_verified:
+ other: "Email should be verified."
+ verify_url_expired:
+ other: "Email verified URL has expired, please resend the email."
+ lang:
+ not_found:
+ other: "Language file not found."
+ object:
+ captcha_verification_failed:
+ other: "Captcha wrong."
+ disallow_follow:
+ other: "You are not allowed to follow."
+ disallow_vote:
+ other: "You are not allowed to vote."
+ disallow_vote_your_self:
+ other: "You can't vote for your own post."
+ not_found:
+ other: "Object not found."
+ verification_failed:
+ other: "Verification failed."
+ email_or_password_incorrect:
+ other: "Email and password do not match."
+ old_password_verification_failed:
+ other: "The old password verification failed"
+ new_password_same_as_previous_setting:
+ other: "The new password is the same as the previous one."
+ question:
+ not_found:
+ other: "Question not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_close:
+ other: "No permission to close."
+ cannot_update:
+ other: "No permission to update."
+ rank:
+ fail_to_meet_the_condition:
+ other: "Rank fail to meet the condition."
+ report:
+ handle_failed:
+ other: "Report handle failed."
+ not_found:
+ other: "Report not found."
+ tag:
+ not_found:
+ other: "Tag not found."
+ recommend_tag_not_found:
+ other: "Recommend Tag is not exist."
+ recommend_tag_enter:
+ other: "Please enter at least one required tag."
+ not_contain_synonym_tags:
+ other: "Should not contain synonym tags."
+ cannot_update:
+ other: "No permission to update."
+ cannot_set_synonym_as_itself:
+ other: "You cannot set the synonym of the current tag as itself."
+ smtp:
+ config_from_name_cannot_be_email:
+ other: "The From Name cannot be a email address."
+ theme:
+ not_found:
+ other: "Theme not found."
+ revision:
+ review_underway:
+ other: "Can't edit currently, there is a version in the review queue."
+ no_permission:
+ other: "No permission to Revision."
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: "User not found."
+ suspended:
+ other: "User has been suspended."
+ username_invalid:
+ other: "Username is invalid."
+ username_duplicate:
+ other: "Username is already in use."
+ set_avatar:
+ other: "Avatar set failed."
+ cannot_update_your_role:
+ other: "You cannot modify your role."
+ not_allowed_registration:
+ other: "Currently the site is not open for registration"
+ config:
+ read_config_failed:
+ other: "Read config failed"
+ database:
+ connection_failed:
+ other: "Database connection failed"
+ create_table_failed:
+ other: "Create table failed"
+ install:
+ create_config_failed:
+ other: "Can't create the config.yaml file."
+ report:
+ spam:
+ name:
+ other: "spam"
+ desc:
+ other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
+ rude:
+ name:
+ other: "rude or abusive"
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ duplicate:
+ name:
+ other: "a duplicate"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ not_answer:
+ name:
+ other: "not an answer"
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
+ not_need:
+ name:
+ other: "no longer needed"
+ desc:
+ other: "This comment is outdated, conversational or not relevant to this post."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires staff attention for another reason not listed above."
+ question:
+ close:
+ duplicate:
+ name:
+ other: "spam"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ guideline:
+ name:
+ other: "a community-specific reason"
+ desc:
+ other: "This question doesn't meet a community guideline."
+ multiple:
+ name:
+ other: "needs details or clarity"
+ desc:
+ other: "This question currently includes multiple questions in one. It should focus on one problem only."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires another reason not listed above."
+ operation_type:
+ asked:
+ other: "asked"
+ answered:
+ other: "answered"
+ modified:
+ other: "modified"
+ notification:
+ action:
+ update_question:
+ other: "updated question"
+ answer_the_question:
+ other: "answered question"
+ update_answer:
+ other: "updated answer"
+ accept_answer:
+ other: "accepted answer"
+ comment_question:
+ other: "commented question"
+ comment_answer:
+ other: "commented answer"
+ reply_to_you:
+ other: "replied to you"
+ mention_you:
+ other: "mentioned you"
+ your_question_is_closed:
+ other: "Your question has been closed"
+ your_question_was_deleted:
+ other: "Your question has been deleted"
+ your_answer_was_deleted:
+ other: "Your answer has been deleted"
+ your_comment_was_deleted:
+ other: "Your comment has been deleted"
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comment
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to Answer
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name up to 30 characters
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username up to 30 characters
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to Answer
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: "After you've done that, click “Next” button."
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: display_name must be at 2 - 30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/bal_BA.yaml b/data/i18n/bal_BA.yaml
new file mode 100644
index 000000000..4249c5f03
--- /dev/null
+++ b/data/i18n/bal_BA.yaml
@@ -0,0 +1,1371 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: "Success."
+ unknown:
+ other: "Unknown error."
+ request_format_error:
+ other: "Request format is not valid."
+ unauthorized_error:
+ other: "Unauthorized."
+ database_error:
+ other: "Data server error."
+ role:
+ name:
+ user:
+ other: "User"
+ admin:
+ other: "Admin"
+ moderator:
+ other: "Moderator"
+ description:
+ user:
+ other: "Default with no special access."
+ admin:
+ other: "Have the full power to access the site."
+ moderator:
+ other: "Has access to all posts except admin settings."
+ email:
+ other: "Email"
+ password:
+ other: "Password"
+ email_or_password_wrong_error:
+ other: "Email and password do not match."
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: "Answer do not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_update:
+ other: "No permission to update."
+ comment:
+ edit_without_permission:
+ other: "Comment are not allowed to edit."
+ not_found:
+ other: "Comment not found."
+ email:
+ duplicate:
+ other: "Email already exists."
+ need_to_be_verified:
+ other: "Email should be verified."
+ verify_url_expired:
+ other: "Email verified URL has expired, please resend the email."
+ lang:
+ not_found:
+ other: "Language file not found."
+ object:
+ captcha_verification_failed:
+ other: "Captcha wrong."
+ disallow_follow:
+ other: "You are not allowed to follow."
+ disallow_vote:
+ other: "You are not allowed to vote."
+ disallow_vote_your_self:
+ other: "You can't vote for your own post."
+ not_found:
+ other: "Object not found."
+ verification_failed:
+ other: "Verification failed."
+ email_or_password_incorrect:
+ other: "Email and password do not match."
+ old_password_verification_failed:
+ other: "The old password verification failed"
+ new_password_same_as_previous_setting:
+ other: "The new password is the same as the previous one."
+ question:
+ not_found:
+ other: "Question not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_close:
+ other: "No permission to close."
+ cannot_update:
+ other: "No permission to update."
+ rank:
+ fail_to_meet_the_condition:
+ other: "Rank fail to meet the condition."
+ report:
+ handle_failed:
+ other: "Report handle failed."
+ not_found:
+ other: "Report not found."
+ tag:
+ not_found:
+ other: "Tag not found."
+ recommend_tag_not_found:
+ other: "Recommend Tag is not exist."
+ recommend_tag_enter:
+ other: "Please enter at least one required tag."
+ not_contain_synonym_tags:
+ other: "Should not contain synonym tags."
+ cannot_update:
+ other: "No permission to update."
+ cannot_set_synonym_as_itself:
+ other: "You cannot set the synonym of the current tag as itself."
+ smtp:
+ config_from_name_cannot_be_email:
+ other: "The From Name cannot be a email address."
+ theme:
+ not_found:
+ other: "Theme not found."
+ revision:
+ review_underway:
+ other: "Can't edit currently, there is a version in the review queue."
+ no_permission:
+ other: "No permission to Revision."
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: "User not found."
+ suspended:
+ other: "User has been suspended."
+ username_invalid:
+ other: "Username is invalid."
+ username_duplicate:
+ other: "Username is already in use."
+ set_avatar:
+ other: "Avatar set failed."
+ cannot_update_your_role:
+ other: "You cannot modify your role."
+ not_allowed_registration:
+ other: "Currently the site is not open for registration"
+ config:
+ read_config_failed:
+ other: "Read config failed"
+ database:
+ connection_failed:
+ other: "Database connection failed"
+ create_table_failed:
+ other: "Create table failed"
+ install:
+ create_config_failed:
+ other: "Can't create the config.yaml file."
+ report:
+ spam:
+ name:
+ other: "spam"
+ desc:
+ other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
+ rude:
+ name:
+ other: "rude or abusive"
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ duplicate:
+ name:
+ other: "a duplicate"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ not_answer:
+ name:
+ other: "not an answer"
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
+ not_need:
+ name:
+ other: "no longer needed"
+ desc:
+ other: "This comment is outdated, conversational or not relevant to this post."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires staff attention for another reason not listed above."
+ question:
+ close:
+ duplicate:
+ name:
+ other: "spam"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ guideline:
+ name:
+ other: "a community-specific reason"
+ desc:
+ other: "This question doesn't meet a community guideline."
+ multiple:
+ name:
+ other: "needs details or clarity"
+ desc:
+ other: "This question currently includes multiple questions in one. It should focus on one problem only."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires another reason not listed above."
+ operation_type:
+ asked:
+ other: "asked"
+ answered:
+ other: "answered"
+ modified:
+ other: "modified"
+ notification:
+ action:
+ update_question:
+ other: "updated question"
+ answer_the_question:
+ other: "answered question"
+ update_answer:
+ other: "updated answer"
+ accept_answer:
+ other: "accepted answer"
+ comment_question:
+ other: "commented question"
+ comment_answer:
+ other: "commented answer"
+ reply_to_you:
+ other: "replied to you"
+ mention_you:
+ other: "mentioned you"
+ your_question_is_closed:
+ other: "Your question has been closed"
+ your_question_was_deleted:
+ other: "Your question has been deleted"
+ your_answer_was_deleted:
+ other: "Your answer has been deleted"
+ your_comment_was_deleted:
+ other: "Your comment has been deleted"
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comment
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to Answer
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name up to 30 characters
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username up to 30 characters
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to Answer
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: "After you've done that, click “Next” button."
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: display_name must be at 2 - 30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/ban_ID.yaml b/data/i18n/ban_ID.yaml
new file mode 100644
index 000000000..4249c5f03
--- /dev/null
+++ b/data/i18n/ban_ID.yaml
@@ -0,0 +1,1371 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: "Success."
+ unknown:
+ other: "Unknown error."
+ request_format_error:
+ other: "Request format is not valid."
+ unauthorized_error:
+ other: "Unauthorized."
+ database_error:
+ other: "Data server error."
+ role:
+ name:
+ user:
+ other: "User"
+ admin:
+ other: "Admin"
+ moderator:
+ other: "Moderator"
+ description:
+ user:
+ other: "Default with no special access."
+ admin:
+ other: "Have the full power to access the site."
+ moderator:
+ other: "Has access to all posts except admin settings."
+ email:
+ other: "Email"
+ password:
+ other: "Password"
+ email_or_password_wrong_error:
+ other: "Email and password do not match."
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: "Answer do not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_update:
+ other: "No permission to update."
+ comment:
+ edit_without_permission:
+ other: "Comment are not allowed to edit."
+ not_found:
+ other: "Comment not found."
+ email:
+ duplicate:
+ other: "Email already exists."
+ need_to_be_verified:
+ other: "Email should be verified."
+ verify_url_expired:
+ other: "Email verified URL has expired, please resend the email."
+ lang:
+ not_found:
+ other: "Language file not found."
+ object:
+ captcha_verification_failed:
+ other: "Captcha wrong."
+ disallow_follow:
+ other: "You are not allowed to follow."
+ disallow_vote:
+ other: "You are not allowed to vote."
+ disallow_vote_your_self:
+ other: "You can't vote for your own post."
+ not_found:
+ other: "Object not found."
+ verification_failed:
+ other: "Verification failed."
+ email_or_password_incorrect:
+ other: "Email and password do not match."
+ old_password_verification_failed:
+ other: "The old password verification failed"
+ new_password_same_as_previous_setting:
+ other: "The new password is the same as the previous one."
+ question:
+ not_found:
+ other: "Question not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_close:
+ other: "No permission to close."
+ cannot_update:
+ other: "No permission to update."
+ rank:
+ fail_to_meet_the_condition:
+ other: "Rank fail to meet the condition."
+ report:
+ handle_failed:
+ other: "Report handle failed."
+ not_found:
+ other: "Report not found."
+ tag:
+ not_found:
+ other: "Tag not found."
+ recommend_tag_not_found:
+ other: "Recommend Tag is not exist."
+ recommend_tag_enter:
+ other: "Please enter at least one required tag."
+ not_contain_synonym_tags:
+ other: "Should not contain synonym tags."
+ cannot_update:
+ other: "No permission to update."
+ cannot_set_synonym_as_itself:
+ other: "You cannot set the synonym of the current tag as itself."
+ smtp:
+ config_from_name_cannot_be_email:
+ other: "The From Name cannot be a email address."
+ theme:
+ not_found:
+ other: "Theme not found."
+ revision:
+ review_underway:
+ other: "Can't edit currently, there is a version in the review queue."
+ no_permission:
+ other: "No permission to Revision."
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: "User not found."
+ suspended:
+ other: "User has been suspended."
+ username_invalid:
+ other: "Username is invalid."
+ username_duplicate:
+ other: "Username is already in use."
+ set_avatar:
+ other: "Avatar set failed."
+ cannot_update_your_role:
+ other: "You cannot modify your role."
+ not_allowed_registration:
+ other: "Currently the site is not open for registration"
+ config:
+ read_config_failed:
+ other: "Read config failed"
+ database:
+ connection_failed:
+ other: "Database connection failed"
+ create_table_failed:
+ other: "Create table failed"
+ install:
+ create_config_failed:
+ other: "Can't create the config.yaml file."
+ report:
+ spam:
+ name:
+ other: "spam"
+ desc:
+ other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
+ rude:
+ name:
+ other: "rude or abusive"
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ duplicate:
+ name:
+ other: "a duplicate"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ not_answer:
+ name:
+ other: "not an answer"
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
+ not_need:
+ name:
+ other: "no longer needed"
+ desc:
+ other: "This comment is outdated, conversational or not relevant to this post."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires staff attention for another reason not listed above."
+ question:
+ close:
+ duplicate:
+ name:
+ other: "spam"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ guideline:
+ name:
+ other: "a community-specific reason"
+ desc:
+ other: "This question doesn't meet a community guideline."
+ multiple:
+ name:
+ other: "needs details or clarity"
+ desc:
+ other: "This question currently includes multiple questions in one. It should focus on one problem only."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires another reason not listed above."
+ operation_type:
+ asked:
+ other: "asked"
+ answered:
+ other: "answered"
+ modified:
+ other: "modified"
+ notification:
+ action:
+ update_question:
+ other: "updated question"
+ answer_the_question:
+ other: "answered question"
+ update_answer:
+ other: "updated answer"
+ accept_answer:
+ other: "accepted answer"
+ comment_question:
+ other: "commented question"
+ comment_answer:
+ other: "commented answer"
+ reply_to_you:
+ other: "replied to you"
+ mention_you:
+ other: "mentioned you"
+ your_question_is_closed:
+ other: "Your question has been closed"
+ your_question_was_deleted:
+ other: "Your question has been deleted"
+ your_answer_was_deleted:
+ other: "Your answer has been deleted"
+ your_comment_was_deleted:
+ other: "Your comment has been deleted"
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comment
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to Answer
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name up to 30 characters
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username up to 30 characters
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to Answer
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: "After you've done that, click “Next” button."
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: display_name must be at 2 - 30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/bn_BD.yaml b/data/i18n/bn_BD.yaml
new file mode 100644
index 000000000..4249c5f03
--- /dev/null
+++ b/data/i18n/bn_BD.yaml
@@ -0,0 +1,1371 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: "Success."
+ unknown:
+ other: "Unknown error."
+ request_format_error:
+ other: "Request format is not valid."
+ unauthorized_error:
+ other: "Unauthorized."
+ database_error:
+ other: "Data server error."
+ role:
+ name:
+ user:
+ other: "User"
+ admin:
+ other: "Admin"
+ moderator:
+ other: "Moderator"
+ description:
+ user:
+ other: "Default with no special access."
+ admin:
+ other: "Have the full power to access the site."
+ moderator:
+ other: "Has access to all posts except admin settings."
+ email:
+ other: "Email"
+ password:
+ other: "Password"
+ email_or_password_wrong_error:
+ other: "Email and password do not match."
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: "Answer do not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_update:
+ other: "No permission to update."
+ comment:
+ edit_without_permission:
+ other: "Comment are not allowed to edit."
+ not_found:
+ other: "Comment not found."
+ email:
+ duplicate:
+ other: "Email already exists."
+ need_to_be_verified:
+ other: "Email should be verified."
+ verify_url_expired:
+ other: "Email verified URL has expired, please resend the email."
+ lang:
+ not_found:
+ other: "Language file not found."
+ object:
+ captcha_verification_failed:
+ other: "Captcha wrong."
+ disallow_follow:
+ other: "You are not allowed to follow."
+ disallow_vote:
+ other: "You are not allowed to vote."
+ disallow_vote_your_self:
+ other: "You can't vote for your own post."
+ not_found:
+ other: "Object not found."
+ verification_failed:
+ other: "Verification failed."
+ email_or_password_incorrect:
+ other: "Email and password do not match."
+ old_password_verification_failed:
+ other: "The old password verification failed"
+ new_password_same_as_previous_setting:
+ other: "The new password is the same as the previous one."
+ question:
+ not_found:
+ other: "Question not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_close:
+ other: "No permission to close."
+ cannot_update:
+ other: "No permission to update."
+ rank:
+ fail_to_meet_the_condition:
+ other: "Rank fail to meet the condition."
+ report:
+ handle_failed:
+ other: "Report handle failed."
+ not_found:
+ other: "Report not found."
+ tag:
+ not_found:
+ other: "Tag not found."
+ recommend_tag_not_found:
+ other: "Recommend Tag is not exist."
+ recommend_tag_enter:
+ other: "Please enter at least one required tag."
+ not_contain_synonym_tags:
+ other: "Should not contain synonym tags."
+ cannot_update:
+ other: "No permission to update."
+ cannot_set_synonym_as_itself:
+ other: "You cannot set the synonym of the current tag as itself."
+ smtp:
+ config_from_name_cannot_be_email:
+ other: "The From Name cannot be a email address."
+ theme:
+ not_found:
+ other: "Theme not found."
+ revision:
+ review_underway:
+ other: "Can't edit currently, there is a version in the review queue."
+ no_permission:
+ other: "No permission to Revision."
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: "User not found."
+ suspended:
+ other: "User has been suspended."
+ username_invalid:
+ other: "Username is invalid."
+ username_duplicate:
+ other: "Username is already in use."
+ set_avatar:
+ other: "Avatar set failed."
+ cannot_update_your_role:
+ other: "You cannot modify your role."
+ not_allowed_registration:
+ other: "Currently the site is not open for registration"
+ config:
+ read_config_failed:
+ other: "Read config failed"
+ database:
+ connection_failed:
+ other: "Database connection failed"
+ create_table_failed:
+ other: "Create table failed"
+ install:
+ create_config_failed:
+ other: "Can't create the config.yaml file."
+ report:
+ spam:
+ name:
+ other: "spam"
+ desc:
+ other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
+ rude:
+ name:
+ other: "rude or abusive"
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ duplicate:
+ name:
+ other: "a duplicate"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ not_answer:
+ name:
+ other: "not an answer"
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
+ not_need:
+ name:
+ other: "no longer needed"
+ desc:
+ other: "This comment is outdated, conversational or not relevant to this post."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires staff attention for another reason not listed above."
+ question:
+ close:
+ duplicate:
+ name:
+ other: "spam"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ guideline:
+ name:
+ other: "a community-specific reason"
+ desc:
+ other: "This question doesn't meet a community guideline."
+ multiple:
+ name:
+ other: "needs details or clarity"
+ desc:
+ other: "This question currently includes multiple questions in one. It should focus on one problem only."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires another reason not listed above."
+ operation_type:
+ asked:
+ other: "asked"
+ answered:
+ other: "answered"
+ modified:
+ other: "modified"
+ notification:
+ action:
+ update_question:
+ other: "updated question"
+ answer_the_question:
+ other: "answered question"
+ update_answer:
+ other: "updated answer"
+ accept_answer:
+ other: "accepted answer"
+ comment_question:
+ other: "commented question"
+ comment_answer:
+ other: "commented answer"
+ reply_to_you:
+ other: "replied to you"
+ mention_you:
+ other: "mentioned you"
+ your_question_is_closed:
+ other: "Your question has been closed"
+ your_question_was_deleted:
+ other: "Your question has been deleted"
+ your_answer_was_deleted:
+ other: "Your answer has been deleted"
+ your_comment_was_deleted:
+ other: "Your comment has been deleted"
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comment
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to Answer
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name up to 30 characters
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username up to 30 characters
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to Answer
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: "After you've done that, click “Next” button."
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: display_name must be at 2 - 30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/bs_BA.yaml b/data/i18n/bs_BA.yaml
new file mode 100644
index 000000000..4249c5f03
--- /dev/null
+++ b/data/i18n/bs_BA.yaml
@@ -0,0 +1,1371 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: "Success."
+ unknown:
+ other: "Unknown error."
+ request_format_error:
+ other: "Request format is not valid."
+ unauthorized_error:
+ other: "Unauthorized."
+ database_error:
+ other: "Data server error."
+ role:
+ name:
+ user:
+ other: "User"
+ admin:
+ other: "Admin"
+ moderator:
+ other: "Moderator"
+ description:
+ user:
+ other: "Default with no special access."
+ admin:
+ other: "Have the full power to access the site."
+ moderator:
+ other: "Has access to all posts except admin settings."
+ email:
+ other: "Email"
+ password:
+ other: "Password"
+ email_or_password_wrong_error:
+ other: "Email and password do not match."
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: "Answer do not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_update:
+ other: "No permission to update."
+ comment:
+ edit_without_permission:
+ other: "Comment are not allowed to edit."
+ not_found:
+ other: "Comment not found."
+ email:
+ duplicate:
+ other: "Email already exists."
+ need_to_be_verified:
+ other: "Email should be verified."
+ verify_url_expired:
+ other: "Email verified URL has expired, please resend the email."
+ lang:
+ not_found:
+ other: "Language file not found."
+ object:
+ captcha_verification_failed:
+ other: "Captcha wrong."
+ disallow_follow:
+ other: "You are not allowed to follow."
+ disallow_vote:
+ other: "You are not allowed to vote."
+ disallow_vote_your_self:
+ other: "You can't vote for your own post."
+ not_found:
+ other: "Object not found."
+ verification_failed:
+ other: "Verification failed."
+ email_or_password_incorrect:
+ other: "Email and password do not match."
+ old_password_verification_failed:
+ other: "The old password verification failed"
+ new_password_same_as_previous_setting:
+ other: "The new password is the same as the previous one."
+ question:
+ not_found:
+ other: "Question not found."
+ cannot_deleted:
+ other: "No permission to delete."
+ cannot_close:
+ other: "No permission to close."
+ cannot_update:
+ other: "No permission to update."
+ rank:
+ fail_to_meet_the_condition:
+ other: "Rank fail to meet the condition."
+ report:
+ handle_failed:
+ other: "Report handle failed."
+ not_found:
+ other: "Report not found."
+ tag:
+ not_found:
+ other: "Tag not found."
+ recommend_tag_not_found:
+ other: "Recommend Tag is not exist."
+ recommend_tag_enter:
+ other: "Please enter at least one required tag."
+ not_contain_synonym_tags:
+ other: "Should not contain synonym tags."
+ cannot_update:
+ other: "No permission to update."
+ cannot_set_synonym_as_itself:
+ other: "You cannot set the synonym of the current tag as itself."
+ smtp:
+ config_from_name_cannot_be_email:
+ other: "The From Name cannot be a email address."
+ theme:
+ not_found:
+ other: "Theme not found."
+ revision:
+ review_underway:
+ other: "Can't edit currently, there is a version in the review queue."
+ no_permission:
+ other: "No permission to Revision."
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: "User not found."
+ suspended:
+ other: "User has been suspended."
+ username_invalid:
+ other: "Username is invalid."
+ username_duplicate:
+ other: "Username is already in use."
+ set_avatar:
+ other: "Avatar set failed."
+ cannot_update_your_role:
+ other: "You cannot modify your role."
+ not_allowed_registration:
+ other: "Currently the site is not open for registration"
+ config:
+ read_config_failed:
+ other: "Read config failed"
+ database:
+ connection_failed:
+ other: "Database connection failed"
+ create_table_failed:
+ other: "Create table failed"
+ install:
+ create_config_failed:
+ other: "Can't create the config.yaml file."
+ report:
+ spam:
+ name:
+ other: "spam"
+ desc:
+ other: "This post is an advertisement, or vandalism. It is not useful or relevant to the current topic."
+ rude:
+ name:
+ other: "rude or abusive"
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ duplicate:
+ name:
+ other: "a duplicate"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ not_answer:
+ name:
+ other: "not an answer"
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether."
+ not_need:
+ name:
+ other: "no longer needed"
+ desc:
+ other: "This comment is outdated, conversational or not relevant to this post."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires staff attention for another reason not listed above."
+ question:
+ close:
+ duplicate:
+ name:
+ other: "spam"
+ desc:
+ other: "This question has been asked before and already has an answer."
+ guideline:
+ name:
+ other: "a community-specific reason"
+ desc:
+ other: "This question doesn't meet a community guideline."
+ multiple:
+ name:
+ other: "needs details or clarity"
+ desc:
+ other: "This question currently includes multiple questions in one. It should focus on one problem only."
+ other:
+ name:
+ other: "something else"
+ desc:
+ other: "This post requires another reason not listed above."
+ operation_type:
+ asked:
+ other: "asked"
+ answered:
+ other: "answered"
+ modified:
+ other: "modified"
+ notification:
+ action:
+ update_question:
+ other: "updated question"
+ answer_the_question:
+ other: "answered question"
+ update_answer:
+ other: "updated answer"
+ accept_answer:
+ other: "accepted answer"
+ comment_question:
+ other: "commented question"
+ comment_answer:
+ other: "commented answer"
+ reply_to_you:
+ other: "replied to you"
+ mention_you:
+ other: "mentioned you"
+ your_question_is_closed:
+ other: "Your question has been closed"
+ your_question_was_deleted:
+ other: "Your question has been deleted"
+ your_answer_was_deleted:
+ other: "Your answer has been deleted"
+ your_comment_was_deleted:
+ other: "Your comment has been deleted"
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comment
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to Answer
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name up to 30 characters
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username up to 30 characters
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to Answer
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: "After you've done that, click “Next” button."
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: display_name must be at 2 - 30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8 - 32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the “logo” setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, “square icon” will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/ca_ES.yaml b/data/i18n/ca_ES.yaml
new file mode 100644
index 000000000..25c086f11
--- /dev/null
+++ b/data/i18n/ca_ES.yaml
@@ -0,0 +1,1384 @@
+# 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.
+
+#The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Success.
+ unknown:
+ other: Unknown error.
+ request_format_error:
+ other: Request format is not valid.
+ unauthorized_error:
+ other: Unauthorized.
+ database_error:
+ other: Data server error.
+ role:
+ name:
+ user:
+ other: User
+ admin:
+ other: Admin
+ moderator:
+ other: Moderator
+ description:
+ user:
+ other: Default with no special access.
+ admin:
+ other: Have the full power to access the site.
+ moderator:
+ other: Has access to all posts except admin settings.
+ email:
+ other: Email
+ password:
+ other: Password
+ email_or_password_wrong_error:
+ other: Email and password do not match.
+ error:
+ admin:
+ email_or_password_wrong:
+ other: Email and password do not match.
+ answer:
+ not_found:
+ other: Answer do not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_update:
+ other: No permission to update.
+ comment:
+ edit_without_permission:
+ other: Comment are not allowed to edit.
+ not_found:
+ other: Comment not found.
+ cannot_edit_after_deadline:
+ other: The comment time has been too long to modify.
+ email:
+ duplicate:
+ other: Email already exists.
+ need_to_be_verified:
+ other: Email should be verified.
+ verify_url_expired:
+ other: Email verified URL has expired, please resend the email.
+ lang:
+ not_found:
+ other: Language file not found.
+ object:
+ captcha_verification_failed:
+ other: Captcha wrong.
+ disallow_follow:
+ other: You are not allowed to follow.
+ disallow_vote:
+ other: You are not allowed to vote.
+ disallow_vote_your_self:
+ other: You can't vote for your own post.
+ not_found:
+ other: Object not found.
+ verification_failed:
+ other: Verification failed.
+ email_or_password_incorrect:
+ other: Email and password do not match.
+ old_password_verification_failed:
+ other: The old password verification failed
+ new_password_same_as_previous_setting:
+ other: The new password is the same as the previous one.
+ question:
+ not_found:
+ other: Question not found.
+ cannot_deleted:
+ other: No permission to delete.
+ cannot_close:
+ other: No permission to close.
+ cannot_update:
+ other: No permission to update.
+ rank:
+ fail_to_meet_the_condition:
+ other: Rank fail to meet the condition.
+ report:
+ handle_failed:
+ other: Report handle failed.
+ not_found:
+ other: Report not found.
+ tag:
+ not_found:
+ other: Tag not found.
+ recommend_tag_not_found:
+ other: Recommend Tag is not exist.
+ recommend_tag_enter:
+ other: Please enter at least one required tag.
+ not_contain_synonym_tags:
+ other: Should not contain synonym tags.
+ cannot_update:
+ other: No permission to update.
+ cannot_set_synonym_as_itself:
+ other: You cannot set the synonym of the current tag as itself.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: The From Name cannot be a email address.
+ theme:
+ not_found:
+ other: Theme not found.
+ revision:
+ review_underway:
+ other: Can't edit currently, there is a version in the review queue.
+ no_permission:
+ other: No permission to Revision.
+ user:
+ email_or_password_wrong:
+ other:
+ other: Email and password do not match.
+ not_found:
+ other: User not found.
+ suspended:
+ other: User has been suspended.
+ username_invalid:
+ other: Username is invalid.
+ username_duplicate:
+ other: Username is already in use.
+ set_avatar:
+ other: Avatar set failed.
+ cannot_update_your_role:
+ other: You cannot modify your role.
+ not_allowed_registration:
+ other: Currently the site is not open for registration
+ config:
+ read_config_failed:
+ other: Read config failed
+ database:
+ connection_failed:
+ other: Database connection failed
+ create_table_failed:
+ other: Create table failed
+ install:
+ create_config_failed:
+ other: Can't create the config.yaml file.
+ upload:
+ unsupported_file_format:
+ other: Unsupported file format.
+ report:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
+ rude:
+ name:
+ other: rude or abusive
+ desc:
+ other: A reasonable person would find this content inappropriate for respectful discourse.
+ duplicate:
+ name:
+ other: a duplicate
+ desc:
+ other: This question has been asked before and already has an answer.
+ not_answer:
+ name:
+ other: not an answer
+ desc:
+ other: This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.
+ not_need:
+ name:
+ other: no longer needed
+ desc:
+ other: This comment is outdated, conversational or not relevant to this post.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires staff attention for another reason not listed above.
+ question:
+ close:
+ duplicate:
+ name:
+ other: spam
+ desc:
+ other: This question has been asked before and already has an answer.
+ guideline:
+ name:
+ other: a community-specific reason
+ desc:
+ other: This question doesn't meet a community guideline.
+ multiple:
+ name:
+ other: needs details or clarity
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other:
+ name:
+ other: something else
+ desc:
+ other: This post requires another reason not listed above.
+ operation_type:
+ asked:
+ other: asked
+ answered:
+ other: answered
+ modified:
+ other: modified
+ notification:
+ action:
+ update_question:
+ other: updated question
+ answer_the_question:
+ other: answered question
+ update_answer:
+ other: updated answer
+ accept_answer:
+ other: accepted answer
+ comment_question:
+ other: commented question
+ comment_answer:
+ other: commented answer
+ reply_to_you:
+ other: replied to you
+ mention_you:
+ other: mentioned you
+ your_question_is_closed:
+ other: Your question has been closed
+ your_question_was_deleted:
+ other: Your question has been deleted
+ your_answer_was_deleted:
+ other: Your answer has been deleted
+ your_comment_was_deleted:
+ other: Your comment has been deleted
+#The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ edit_tag: Edit Tag
+ ask_a_question: Add Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ all_read: Mark all as read
+ show_more: Show more
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language (optional)
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal Rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image File
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed 4 MB.
+ desc:
+ label: Description (optional)
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered List
+ unordered_list:
+ text: Bulleted List
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL Slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description (optional)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ content: >-
+ We do not allow deleting tag with posts.
Please remove this tag from the posts first.
+ content2: Are you sure you wish to delete?
+ close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ form:
+ fields:
+ revision:
+ label: Revision
+ display_name:
+ label: Display Name
+ slug_name:
+ label: URL Slug
+ info: URL slug up to 35 characters.
+ desc:
+ label: Description
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: Show more comments
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ Use comments to reply to other users or notify them of changes. If you are adding new information, edit your post instead of commenting.
+ edit_answer:
+ title: Edit Answer
+ default_reason: Edit answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ feedback:
+ characters: content must be at least 6 characters in length.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ ask:
+ title: Add Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: Be specific and imagine you're asking a question to another person
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit Summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: "Describe what your question is about, at least one tag is required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ search:
+ placeholder: Search
+ footer:
+ build_on: >-
+ Built on <1> Answer 1>- the open-source software that powers Q&A communities. Made with love © {{cc}}.
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ login:
+ page_title: Welcome to {{site_name}}
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "A-Z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ page_title: Welcome to {{site_name}}
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New Email
+ msg:
+ empty: Email cannot be empty.
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm New Password
+ settings:
+ page_title: Settings
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display Name
+ msg: Display name cannot be empty.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile Image
+ gravatar: Gravatar
+ gravatar_text: You can change image on <1>gravatar.com1>
+ custom: Custom
+ btn_refresh: Refresh
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About Me (optional)
+ website:
+ label: Website (optional)
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location (optional)
+ placeholder: "City, Country"
+ notification:
+ heading: Notifications
+ email:
+ label: Email Notifications
+ radio: "Answers to your questions, comments, and more"
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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.
+ password_title: Password
+ current_pass:
+ label: Current Password
+ msg:
+ empty: Current Password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New Password
+ pass_confirm:
+ label: Confirm New Password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface Language
+ text: User interface language. It will change when you refresh the page.
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ related_question:
+ title: Related Questions
+ btn: Add question
+ answers: answers
+ question_detail:
+ Asked: Asked
+ asked: asked
+ update: Modified
+ edit: edited
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ characters: content must be at least 6 characters in length.
+ reopen:
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ success: This post has been reopened
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_question_deleted: This post has been deleted
+ tip_answer_deleted: This answer has been deleted
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ save: Save
+ delete: Delete
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ add_question: Add question
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post
+ modal_confirm:
+ title: Error...
+ account_result:
+ page_title: Welcome to {{site_name}}
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ invalid: >-
+ Sorry, this account confirmation link is no longer valid. Perhaps your account is already active?
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ newest: Newest
+ score: Score
+ edit_profile: Edit Profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ upvote: upvote
+ downvote: downvote
+ mod_short: Mod
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please Choose a Language
+ db_type:
+ label: Database Engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database Host
+ placeholder: "db:3306"
+ msg: Database Host cannot be empty.
+ db_name:
+ label: Database Name
+ placeholder: answer
+ msg: Database Name cannot be empty.
+ db_file:
+ label: Database File
+ placeholder: /data/answer.db
+ msg: Database File cannot be empty.
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: After you've done that, click "Next" button.
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site Name
+ msg: Site Name cannot be empty.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ contact_email:
+ label: Contact Email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact Email cannot be empty.
+ incorrect: Contact Email incorrect format.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ counts:
+ views: views
+ votes: votes
+ answers: answers
+ accepted: Accepted
+ page_404:
+ desc: "Unfortunately, this page doesn't exist."
+ back_home: Back to homepage
+ page_50X:
+ desc: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ css-html: CSS/HTML
+ login: Login
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site Statistics
+ questions: "Questions:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ active_users: "Active users:"
+ flags: "Flags:"
+ site_health_status: Site Health Status
+ version: "Version:"
+ https: "HTTPS:"
+ uploading_files: "Uploading files:"
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System Info
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ answer_links: Answer Links
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ created: Created
+ action: Action
+ review: Review
+ change_modal:
+ title: Change user status to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ normal_name: normal
+ normal_desc: A normal user can ask and answer questions.
+ suspended_name: suspended
+ suspended_desc: A suspended user can't log in.
+ deleted_name: deleted
+ deleted_desc: "Delete profile, authentication associations."
+ inactive_name: inactive
+ inactive_desc: An inactive user must re-validate their email.
+ confirm_title: Delete this user
+ confirm_content: Are you sure you want to delete this user? This is permanent!
+ confirm_btn: Delete
+ msg:
+ empty: Please select a reason.
+ status_modal:
+ title: "Change {{ type }} status to..."
+ normal_name: normal
+ normal_desc: A normal post available to everyone.
+ closed_name: closed
+ closed_desc: "A closed question can't answer, but still can edit, vote and comment."
+ deleted_name: deleted
+ deleted_desc: All reputation gained and lost will be restored.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_next: Next
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created Time
+ delete_at: Deleted Time
+ suspend_at: Suspended Time
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ display_name:
+ label: Display Name
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ questions:
+ page_title: Questions
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ normal: Normal
+ deleted: Deleted
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site Name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short Site Description (optional)
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site Description (optional)
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact Email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ interface:
+ page_title: Interface
+ logo:
+ label: Logo (optional)
+ msg: Site logo cannot be empty.
+ text: You can upload your image or <1>reset1> it to the site title text.
+ theme:
+ label: Theme
+ msg: Theme cannot be empty.
+ text: Select an existing theme.
+ language:
+ label: Interface Language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From Email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From Name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP Host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ none: None
+ smtp_port:
+ label: SMTP Port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP Username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP Password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test Email Recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP Authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo (optional)
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile Logo (optional)
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ square_icon:
+ label: Square Icon (optional)
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon (optional)
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of Service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy Policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ write:
+ page_title: Write
+ recommend_tags:
+ label: Recommend Tags
+ text: "Please input tag slug above, one tag per line."
+ required_tag:
+ title: Required Tag
+ label: Set recommend tag as required
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved Tags
+ text: "Reserved tags can only be added to a post by moderator."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ navbar_style:
+ label: Navbar Style
+ text: Select an existing theme.
+ primary_color:
+ label: Primary Color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: This will insert as
+ head:
+ label: Head
+ text: This will insert before
+ header:
+ label: Header
+ text: This will insert after
+ footer:
+ label: Footer
+ text: This will insert before .
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ form:
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores
+ users_with_the_most_vote: Users who voted the most
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
diff --git a/data/i18n/cs_CZ.yaml b/data/i18n/cs_CZ.yaml
new file mode 100644
index 000000000..dbb217faf
--- /dev/null
+++ b/data/i18n/cs_CZ.yaml
@@ -0,0 +1,2359 @@
+# 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.
+
+# The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Úspěch.
+ unknown:
+ other: Neznámá chyba.
+ request_format_error:
+ other: Formát požadavku není platný.
+ unauthorized_error:
+ other: Neautorizováno.
+ database_error:
+ other: Chyba datového serveru.
+ forbidden_error:
+ other: Zakázáno.
+ duplicate_request_error:
+ other: Duplicitní odeslání.
+ action:
+ report:
+ other: Nahlásit
+ edit:
+ other: Upravit
+ delete:
+ other: Smazat
+ close:
+ other: Zavřít
+ reopen:
+ other: Znovu otevřít
+ forbidden_error:
+ other: Zakázáno.
+ pin:
+ other: Připnout
+ hide:
+ other: Skrýt
+ unpin:
+ other: Odepnout
+ show:
+ other: Zobrazit
+ invite_someone_to_answer:
+ other: Upravit
+ undelete:
+ other: Obnovit
+ merge:
+ other: Sloučit
+ role:
+ name:
+ user:
+ other: Uživatel
+ admin:
+ other: Administrátor
+ moderator:
+ other: Moderátor
+ description:
+ user:
+ other: Výchozí bez zvláštního přístupu.
+ admin:
+ other: Má plnou kontrolu nad stránkou.
+ moderator:
+ other: Má přístup ke všem příspěvkům kromě admin nastavení.
+ privilege:
+ level_1:
+ description:
+ other: Úroveň 1 (méně reputace je vyžadováno pro soukromý tým, skupinu)
+ level_2:
+ description:
+ other: Úroveň 2 (nízká reputace je vyžadována pro startovací komunitu)
+ level_3:
+ description:
+ other: Úroveň 3 (vysoká reputace je vyžadována pro vyspělou komunitu)
+ level_custom:
+ description:
+ other: Vlastní úroveň
+ rank_question_add_label:
+ other: Položit dotaz
+ rank_answer_add_label:
+ other: Napsat odpověď
+ rank_comment_add_label:
+ other: Napsat komentář
+ rank_report_add_label:
+ other: Nahlásit
+ rank_comment_vote_up_label:
+ other: Hlasovat pro komentář
+ rank_link_url_limit_label:
+ other: Zveřejnit více než 2 odkazy najednou
+ rank_question_vote_up_label:
+ other: Hlasovat pro dotaz
+ rank_answer_vote_up_label:
+ other: Hlasovat pro odpověď
+ rank_question_vote_down_label:
+ other: Hlasovat proti otázce
+ rank_answer_vote_down_label:
+ other: Hlasovat proti odpovědi
+ rank_invite_someone_to_answer_label:
+ other: Pozvěte někoho, aby odpověděl
+ rank_tag_add_label:
+ other: Vytvořit nový štítek
+ rank_tag_edit_label:
+ other: Upravit popis štítku (vyžaduje kontrolu)
+ rank_question_edit_label:
+ other: Upravit dotaz někoho jiného (vyžaduje kontrolu)
+ rank_answer_edit_label:
+ other: Upravit odpověď někoho jiného (vyžaduje kontrolu)
+ rank_question_edit_without_review_label:
+ other: Upravit dotaz někoho jiného (bez kontroly)
+ rank_answer_edit_without_review_label:
+ other: Upravit odpověď někoho jiného (bez kontroly)
+ rank_question_audit_label:
+ other: Zkontrolovat úpravy dotazu
+ rank_answer_audit_label:
+ other: Zkontrolovat úpravy odpovědí
+ rank_tag_audit_label:
+ other: Zkontrolovat úpravy štítků
+ rank_tag_edit_without_review_label:
+ other: Upravit popis štítku (bez kontroly)
+ rank_tag_synonym_label:
+ other: Správa synonym štítků
+ email:
+ other: Email
+ e_mail:
+ other: Email
+ password:
+ other: Heslo
+ pass:
+ other: Heslo
+ old_pass:
+ other: Current password
+ original_text:
+ other: Tento příspěvek
+ email_or_password_wrong_error:
+ other: Email a heslo nesouhlasí.
+ error:
+ common:
+ invalid_url:
+ other: Neplatná URL.
+ status_invalid:
+ other: Neplatný stav.
+ password:
+ space_invalid:
+ other: Heslo nesmí obsahovat mezery.
+ admin:
+ cannot_update_their_password:
+ other: Nemůžete změnit své heslo.
+ cannot_edit_their_profile:
+ other: Nemůžete upravovat svůj profil.
+ cannot_modify_self_status:
+ other: Nemůžete změnit svůj stav.
+ email_or_password_wrong:
+ other: Email a heslo nesouhlasí.
+ answer:
+ not_found:
+ other: Odpověď nebyla nalezena.
+ cannot_deleted:
+ other: Nemáte právo mazat.
+ cannot_update:
+ other: Nemáte právo aktualizovat.
+ question_closed_cannot_add:
+ other: Dotazy jsou uzavřené a není možno je přidávat.
+ content_cannot_empty:
+ other: Answer content cannot be empty.
+ comment:
+ edit_without_permission:
+ other: Nejsou povoleny úpravy komentáře.
+ not_found:
+ other: Komentář nebyl nalezen.
+ cannot_edit_after_deadline:
+ other: Tento komentář byl pro úpravy příliš dlouhý.
+ content_cannot_empty:
+ other: Comment content cannot be empty.
+ email:
+ duplicate:
+ other: Email už existuje.
+ need_to_be_verified:
+ other: Email musí být ověřen.
+ verify_url_expired:
+ other: Platnost ověřovacího URL vypršela, pošlete si ověřovací email znovu.
+ illegal_email_domain_error:
+ other: Email z této domény není povolen. Použijte jinou doménu.
+ lang:
+ not_found:
+ other: Jazykový soubor nenalezen.
+ object:
+ captcha_verification_failed:
+ other: Nesprávně vyplněná Captcha.
+ disallow_follow:
+ other: Nemáte oprávnění sledovat.
+ disallow_vote:
+ other: Nemáte oprávnění hlasovat.
+ disallow_vote_your_self:
+ other: Nemůžete hlasovat pro svůj vlastní příspěvek.
+ not_found:
+ other: Objekt nenalezen.
+ verification_failed:
+ other: Ověření se nezdařilo.
+ email_or_password_incorrect:
+ other: Email a heslo nesouhlasí.
+ old_password_verification_failed:
+ other: Ověření starého hesla selhalo
+ new_password_same_as_previous_setting:
+ other: Nové heslo je stejné jako předchozí.
+ already_deleted:
+ other: Tento příspěvek byl odstraněn.
+ meta:
+ object_not_found:
+ other: Meta objekt nenalezen
+ question:
+ already_deleted:
+ other: Tento příspěvek byl odstraněn.
+ under_review:
+ other: Váš příspěvek čeká na kontrolu. Bude viditelný po jeho schválení.
+ not_found:
+ other: Dotaz nenalezen.
+ cannot_deleted:
+ other: Nemáte oprávnění k mazání.
+ cannot_close:
+ other: Nemáte oprávnění k uzavření.
+ cannot_update:
+ other: Nemáte oprávnění pro aktualizaci.
+ content_cannot_empty:
+ other: Content cannot be empty.
+ content_less_than_minimum:
+ other: Not enough content entered.
+ rank:
+ fail_to_meet_the_condition:
+ other: Hodnost reputace nesplňuje podmínku.
+ vote_fail_to_meet_the_condition:
+ other: Děkujeme za zpětnou vazbu. Potřebujete alespoň úroveň {{.Rank}}, abyste mohli hlasovat.
+ no_enough_rank_to_operate:
+ other: Potřebujete alespoň úroveň {{.Rank}} k provedení této akce.
+ report:
+ handle_failed:
+ other: Report selhal.
+ not_found:
+ other: Report nebyl nalezen.
+ tag:
+ already_exist:
+ other: Štítek již existuje.
+ not_found:
+ other: Štítek nebyl nalezen.
+ recommend_tag_not_found:
+ other: Doporučený štítek nebyl nalezen.
+ recommend_tag_enter:
+ other: Zadejte prosím alespoň jeden povinný štítek.
+ not_contain_synonym_tags:
+ other: Nemělo by obsahovat synonyma štítků.
+ cannot_update:
+ other: Nemáte oprávnění pro aktualizaci.
+ is_used_cannot_delete:
+ other: Nemůžete odstranit štítek, který se používá.
+ cannot_set_synonym_as_itself:
+ other: Aktuální štítek nelze jako synonymum stejného štítku.
+ minimum_count:
+ other: Not enough tags were entered.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: Jméno odesílatele nemůže být emailová adresa.
+ theme:
+ not_found:
+ other: Motiv nebyl nalezen.
+ revision:
+ review_underway:
+ other: V současné době nelze upravit, čeká na kontrolu.
+ no_permission:
+ other: Nemáte oprávnění k revizi.
+ user:
+ external_login_missing_user_id:
+ other: Platforma třetí strany neposkytuje unikátní UserID, takže se nemůžete přihlásit, kontaktujte prosím správce webových stránek.
+ external_login_unbinding_forbidden:
+ other: Před odebráním tohoto typu přihlášení nastavte přihlašovací heslo pro svůj účet.
+ email_or_password_wrong:
+ other:
+ other: Email a heslo nesouhlasí.
+ not_found:
+ other: Uživatel nebyl nalezen.
+ suspended:
+ other: Uživatelský účet byl pozastaven.
+ username_invalid:
+ other: Uživatelské jméno je neplatné.
+ username_duplicate:
+ other: Uživatelské jméno je již použito.
+ set_avatar:
+ other: Nastavení avataru se nezdařilo.
+ cannot_update_your_role:
+ other: Nemůžete upravovat svoji roli.
+ not_allowed_registration:
+ other: Registrace nejsou povolené.
+ not_allowed_login_via_password:
+ other: Přihlášení přes heslo není povolené.
+ access_denied:
+ other: Přístup zamítnut
+ page_access_denied:
+ other: Nemáte přístup k této stránce.
+ add_bulk_users_format_error:
+ other: "Chyba formátu pole {{.Field}} poblíž '{{.Content}}' na řádku {{.Line}}. {{.ExtraMessage}}"
+ add_bulk_users_amount_error:
+ other: "Počet uživatelů, které přidáte najednou, by měl být v rozsahu 1-{{.MaxAmount}}."
+ status_suspended_forever:
+ other: "This user was suspended forever. This user doesn't meet a community guideline."
+ status_suspended_until:
+ other: "This user was suspended until {{.SuspendedUntil}}. This user doesn't meet a community guideline."
+ status_deleted:
+ other: "This user was deleted."
+ status_inactive:
+ other: "This user is inactive."
+ config:
+ read_config_failed:
+ other: Načtení konfigurace selhalo
+ database:
+ connection_failed:
+ other: Spojení s databází selhalo
+ create_table_failed:
+ other: Vytvoření tabulky selhalo
+ install:
+ create_config_failed:
+ other: Soubor config.yaml nelze vytvořit.
+ upload:
+ unsupported_file_format:
+ other: Nepodporovaný formát souboru.
+ site_info:
+ config_not_found:
+ other: Konfigurace webu nebyla nalezena.
+ badge:
+ object_not_found:
+ other: Objekt odznaku nebyl nalezen
+ reason:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: Tento příspěvek je reklama nebo vandalismus. Není užitečný ani relevantní pro aktuální téma.
+ rude_or_abusive:
+ name:
+ other: hrubý nebo zneužívající
+ desc:
+ other: "Rozumný člověk by tento obsah považoval za nevhodný pro slušnou konverzaci."
+ a_duplicate:
+ name:
+ other: duplicita
+ desc:
+ other: Tento dotaz byl položen dříve a již má odpověď.
+ placeholder:
+ other: Zadejte existující odkaz na dotaz
+ not_a_answer:
+ name:
+ other: není odpověď
+ desc:
+ other: "Toto bylo zveřejněno jako odpověď, ale nesnaží se odpovědět na dotaz. Měla by to být úprava, komentář, nebo úplně jiný dotaz."
+ no_longer_needed:
+ name:
+ other: již není potřeba
+ desc:
+ other: Tento komentář je zastaralý, konverzační nebo není relevantní pro tento příspěvek.
+ something:
+ name:
+ other: jiný důvod
+ desc:
+ other: Tento příspěvek vyžaduje pozornost moderátorů z jiného důvodu, který není uveden výše.
+ placeholder:
+ other: Dejte nám vědět konkrétně, v čem je problém
+ community_specific:
+ name:
+ other: důvod specifický pro komunitu
+ desc:
+ other: Tento dotaz nesplňuje pravidla komunity.
+ not_clarity:
+ name:
+ other: vyžaduje detaily nebo upřesnění
+ desc:
+ other: Tento dotaz v současné době obsahuje více otázek. Měl by se zaměřit pouze na jeden problém.
+ looks_ok:
+ name:
+ other: vypadá v pořádku
+ desc:
+ other: Tento příspěvek je dobrý tak jak je, nemá nízkou kvalitu.
+ needs_edit:
+ name:
+ other: potřebuje úpravu, kterou jsem udělal(a)
+ desc:
+ other: Zlepšete a opravte problémy s tímto příspěvkem.
+ needs_close:
+ name:
+ other: potřebuje zavřít
+ desc:
+ other: Na uzavřený dotaz není možné odpovídat, ale stále může být upraven a je možné pro něj hlasovat a komentovat jej.
+ needs_delete:
+ name:
+ other: potřebuje smazat
+ desc:
+ other: Tento příspěvek bude odstraněn.
+ question:
+ close:
+ duplicate:
+ name:
+ other: spam
+ desc:
+ other: Tento dotaz byl položena dříve a již má odpověď.
+ guideline:
+ name:
+ other: důvod specifický pro komunitu
+ desc:
+ other: Tento dotaz nesplňuje pravidla komunity.
+ multiple:
+ name:
+ other: vyžaduje detaily nebo upřesnění
+ desc:
+ other: Tento dotaz v současné době obsahuje více otázek. Měla by se zaměřit pouze na jeden problém.
+ other:
+ name:
+ other: jiný důvod
+ desc:
+ other: Tento příspěvek vyžaduje pozornost moderátorů z jiného důvodu, který není uveden výše.
+ operation_type:
+ asked:
+ other: dotázáno
+ answered:
+ other: zodpovězeno
+ modified:
+ other: upraveno
+ deleted_title:
+ other: Smazat dotaz
+ questions_title:
+ other: Dotazy
+ tag:
+ tags_title:
+ other: Štítky
+ no_description:
+ other: Štítek nemá žádný popis.
+ notification:
+ action:
+ update_question:
+ other: upravený dotaz
+ answer_the_question:
+ other: položil(a) dotaz
+ update_answer:
+ other: upravil(a) odpověď
+ accept_answer:
+ other: přijal(a) odpověď
+ comment_question:
+ other: okomentoval(a) dotaz
+ comment_answer:
+ other: okomentoval(a) odpověď
+ reply_to_you:
+ other: vám odpověděl(a)
+ mention_you:
+ other: vás zmínil(a)
+ your_question_is_closed:
+ other: Váš dotaz byl uzavřen
+ your_question_was_deleted:
+ other: Váš dotaz byl odstraněn
+ your_answer_was_deleted:
+ other: Vaše odpověď byla smazána
+ your_comment_was_deleted:
+ other: Váš komentář byl odstraněn
+ up_voted_question:
+ other: hlasoval(a) pro dotaz
+ down_voted_question:
+ other: hlasoval(a) proti dotazu
+ up_voted_answer:
+ other: hlasoval(a) pro odpověď
+ down_voted_answer:
+ other: hlasoval(a) proti odpovědi
+ up_voted_comment:
+ other: hlasoval(a) pro komentář
+ invited_you_to_answer:
+ other: vás pozval, abyste odpověděl(a)
+ earned_badge:
+ other: Získali jste odznak "{{.BadgeName}}"
+ email_tpl:
+ change_email:
+ title:
+ other: "[{{.SiteName}}] Potvrďte svůj nový email"
+ body:
+ other: "Confirm your new email address for {{.SiteName}} by clicking on the following link: \n{{.ChangeEmailUrl}} \n\nIf you did not request this change, please ignore this email. \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ new_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} odpověděl(a) na váš dotaz"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ invited_you_to_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} invited you to answer"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \nI think you may know the answer. \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_comment:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.CommentSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_question:
+ title:
+ other: "[{{.SiteName}}] New question: {{.QuestionTitle}}"
+ body:
+ other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ pass_reset:
+ title:
+ other: "[{{.SiteName }}] Obnova hesla"
+ body:
+ other: "Somebody asked to reset your password on {{.SiteName}}. \n\nIf it was not you, you can safely ignore this email. \n\nClick the following link to choose a new password: \n{{.PassResetUrl}} \n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ register:
+ title:
+ other: "[{{.SiteName}}] Potvrďte svůj nový účet"
+ body:
+ other: "Welcome to {{.SiteName}}! \n\nClick the following link to confirm and activate your new account: \n{{.RegisterUrl}} \n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ test:
+ title:
+ other: "[{{.SiteName}}] Zkušební email"
+ body:
+ other: "This is a test email.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ action_activity_type:
+ upvote:
+ other: hlasovat pro
+ upvoted:
+ other: hlasováno pro
+ downvote:
+ other: hlasovat proti
+ downvoted:
+ other: hlasováno proti
+ accept:
+ other: přijmout
+ accepted:
+ other: přijato
+ edit:
+ other: upravit
+ review:
+ queued_post:
+ other: Příspěvek ve frontě
+ flagged_post:
+ other: Nahlášený příspěvek
+ suggested_post_edit:
+ other: Navrhované úpravy
+ reaction:
+ tooltip:
+ other: "{{ .Names }} a {{ .Count }} dalších..."
+ badge:
+ default_badges:
+ autobiographer:
+ name:
+ other: Životopisec
+ desc:
+ other: Profil vyplněn.
+ certified:
+ name:
+ other: Certifikovaný
+ desc:
+ other: Tutoriál pro nové uživatele dokončen.
+ editor:
+ name:
+ other: Editor
+ desc:
+ other: První úprava příspěvku.
+ first_flag:
+ name:
+ other: První nahlášení
+ desc:
+ other: První nahlášení příspěvku.
+ first_upvote:
+ name:
+ other: První hlas pro
+ desc:
+ other: První hlas pro příspěvek.
+ first_link:
+ name:
+ other: První odkaz
+ desc:
+ other: First added a link to another post.
+ first_reaction:
+ name:
+ other: First Reaction
+ desc:
+ other: First reacted to the post.
+ first_share:
+ name:
+ other: První sdílení
+ desc:
+ other: První sdílení příspěvku.
+ scholar:
+ name:
+ other: Scholar
+ desc:
+ other: Asked a question and accepted an answer.
+ commentator:
+ name:
+ other: Commentator
+ desc:
+ other: Napište 5 komentářů.
+ new_user_of_the_month:
+ name:
+ other: Nový uživatel měsíce
+ desc:
+ other: Výjimečný přínos ve svém prvním měsíci na stránce.
+ read_guidelines:
+ name:
+ other: Přečíst pravidla
+ desc:
+ other: Přečtěte si [pravidla komunity].
+ reader:
+ name:
+ other: Čtenář
+ desc:
+ other: Přečtěte si všechny odpovědi v tématu s více než 10 odpověďmi.
+ welcome:
+ name:
+ other: Vítejte
+ desc:
+ other: Obdržel(a) hlas.
+ nice_share:
+ name:
+ other: Povedené sdílení
+ desc:
+ other: Sdílel(a) příspěvek s 25 unikátními návštěvníky.
+ good_share:
+ name:
+ other: Dobré sdílení
+ desc:
+ other: Sdílel(a) příspěvek s 300 unikátními návštěvníky.
+ great_share:
+ name:
+ other: Skvělé sdílení
+ desc:
+ other: Sdílel(a) příspěvek s 1000 unikátními návštěvníky.
+ out_of_love:
+ name:
+ other: Optimista
+ desc:
+ other: Využito 50 hlasů pro za den.
+ higher_love:
+ name:
+ other: Vytrvalý optimista
+ desc:
+ other: 5 krát využito 50 hlasů pro za den.
+ crazy_in_love:
+ name:
+ other: Bláznivý optimista
+ desc:
+ other: 20 krát využito 50 hlasů pro za den.
+ promoter:
+ name:
+ other: Promotér
+ desc:
+ other: Pozval(a) uživatele.
+ campaigner:
+ name:
+ other: Campaigner
+ desc:
+ other: Pozval(a) 3 uživatele.
+ champion:
+ name:
+ other: Champion
+ desc:
+ other: Invited 5 members.
+ thank_you:
+ name:
+ other: Thank You
+ desc:
+ other: Has 20 up voted posts and gave 10 up votes.
+ gives_back:
+ name:
+ other: Gives Back
+ desc:
+ other: Has 100 up voted posts and gave 100 up votes.
+ empathetic:
+ name:
+ other: Empathetic
+ desc:
+ other: Has 500 up voted posts and gave 1000 up votes.
+ enthusiast:
+ name:
+ other: Enthusiast
+ desc:
+ other: Visited 10 consecutive days.
+ aficionado:
+ name:
+ other: Aficionado
+ desc:
+ other: Visited 100 consecutive days.
+ devotee:
+ name:
+ other: Devotee
+ desc:
+ other: Visited 365 consecutive days.
+ anniversary:
+ name:
+ other: Anniversary
+ desc:
+ other: Active member for a year, posted at least once.
+ appreciated:
+ name:
+ other: Appreciated
+ desc:
+ other: Received 1 up vote on 20 posts.
+ respected:
+ name:
+ other: Respected
+ desc:
+ other: Received 2 up votes on 100 posts.
+ admired:
+ name:
+ other: Admired
+ desc:
+ other: Received 5 up votes on 300 posts.
+ solved:
+ name:
+ other: Solved
+ desc:
+ other: Have an answer be accepted.
+ guidance_counsellor:
+ name:
+ other: Guidance Counsellor
+ desc:
+ other: Have 10 answers be accepted.
+ know_it_all:
+ name:
+ other: Know-it-All
+ desc:
+ other: Have 50 answers be accepted.
+ solution_institution:
+ name:
+ other: Solution Institution
+ desc:
+ other: Have 150 answers be accepted.
+ nice_answer:
+ name:
+ other: Nice Answer
+ desc:
+ other: Answer score of 10 or more.
+ good_answer:
+ name:
+ other: Good Answer
+ desc:
+ other: Answer score of 25 or more.
+ great_answer:
+ name:
+ other: Great Answer
+ desc:
+ other: Answer score of 50 or more.
+ nice_question:
+ name:
+ other: Nice Question
+ desc:
+ other: Question score of 10 or more.
+ good_question:
+ name:
+ other: Good Question
+ desc:
+ other: Question score of 25 or more.
+ great_question:
+ name:
+ other: Great Question
+ desc:
+ other: Question score of 50 or more.
+ popular_question:
+ name:
+ other: Popular Question
+ desc:
+ other: Question with 500 views.
+ notable_question:
+ name:
+ other: Notable Question
+ desc:
+ other: Question with 1,000 views.
+ famous_question:
+ name:
+ other: Famous Question
+ desc:
+ other: Question with 5,000 views.
+ popular_link:
+ name:
+ other: Popular Link
+ desc:
+ other: Posted an external link with 50 clicks.
+ hot_link:
+ name:
+ other: Hot Link
+ desc:
+ other: Posted an external link with 300 clicks.
+ famous_link:
+ name:
+ other: Famous Link
+ desc:
+ other: Posted an external link with 100 clicks.
+ default_badge_groups:
+ getting_started:
+ name:
+ other: Getting Started
+ community:
+ name:
+ other: Community
+ posting:
+ name:
+ other: Posting
+# The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: How to Format
+ desc: >-
+ mention a post: #post_id
to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Prev
+ next: Next
+ page_title:
+ question: Question
+ questions: Questions
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ create_tag: Create Tag
+ edit_tag: Edit Tag
+ ask_a_question: Create Question
+ edit_question: Edit Question
+ edit_answer: Edit Answer
+ search: Search
+ posts_containing: Posts containing
+ settings: Settings
+ notifications: Notifications
+ login: Log In
+ sign_up: Sign Up
+ account_recovery: Account Recovery
+ account_activation: Account Activation
+ confirm_email: Confirm Email
+ account_suspended: Account Suspended
+ admin: Admin
+ change_email: Modify Email
+ install: Answer Installation
+ upgrade: Answer Upgrade
+ maintenance: Website Maintenance
+ users: Users
+ oauth_callback: Processing
+ http_404: HTTP Error 404
+ http_50X: HTTP Error 500
+ http_403: HTTP Error 403
+ logout: Log Out
+ posts: Posts
+ notifications:
+ title: Notifications
+ inbox: Inbox
+ achievement: Achievements
+ new_alerts: New alerts
+ all_read: Mark all as read
+ show_more: Show more
+ someone: Someone
+ inbox_type:
+ all: All
+ posts: Posts
+ invites: Invites
+ votes: Votes
+ answer: Answer
+ question: Question
+ badge_award: Badge
+ suspended:
+ title: Your Account has been Suspended
+ until_time: "Your account was suspended until {{ time }}."
+ forever: This user was suspended forever.
+ end: You don't meet a community guideline.
+ contact_us: Contact us
+ editor:
+ blockquote:
+ text: Blockquote
+ bold:
+ text: Strong
+ chart:
+ text: Chart
+ flow_chart: Flow chart
+ sequence_diagram: Sequence diagram
+ class_diagram: Class diagram
+ state_diagram: State diagram
+ entity_relationship_diagram: Entity relationship diagram
+ user_defined_diagram: User defined diagram
+ gantt_chart: Gantt chart
+ pie_chart: Pie chart
+ code:
+ text: Code Sample
+ add_code: Add code sample
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code cannot be empty.
+ language:
+ label: Language
+ placeholder: Automatic detection
+ btn_cancel: Cancel
+ btn_confirm: Add
+ formula:
+ text: Formula
+ options:
+ inline: Inline formula
+ block: Block formula
+ heading:
+ text: Heading
+ options:
+ h1: Heading 1
+ h2: Heading 2
+ h3: Heading 3
+ h4: Heading 4
+ h5: Heading 5
+ h6: Heading 6
+ help:
+ text: Help
+ hr:
+ text: Horizontal rule
+ image:
+ text: Image
+ add_image: Add image
+ tab_image: Upload image
+ form_image:
+ fields:
+ file:
+ label: Image file
+ btn: Select image
+ msg:
+ empty: File cannot be empty.
+ only_image: Only image files are allowed.
+ max_size: File size cannot exceed {{size}} MB.
+ desc:
+ label: Description
+ tab_url: Image URL
+ form_url:
+ fields:
+ url:
+ label: Image URL
+ msg:
+ empty: Image URL cannot be empty.
+ name:
+ label: Description
+ btn_cancel: Cancel
+ btn_confirm: Add
+ uploading: Uploading
+ indent:
+ text: Indent
+ outdent:
+ text: Outdent
+ italic:
+ text: Emphasis
+ link:
+ text: Hyperlink
+ add_link: Add hyperlink
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL cannot be empty.
+ name:
+ label: Description
+ btn_cancel: Cancel
+ btn_confirm: Add
+ ordered_list:
+ text: Numbered list
+ unordered_list:
+ text: Bulleted list
+ table:
+ text: Table
+ heading: Heading
+ cell: Cell
+ file:
+ text: Attach files
+ not_supported: "Don’t support that file type. Try again with {{file_type}}."
+ max_size: "Attach files size cannot exceed {{size}} MB."
+ close_modal:
+ title: I am closing this post as...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ report_modal:
+ flag_title: I am flagging to report this post as...
+ close_title: I am closing this post as...
+ review_question_title: Review question
+ review_answer_title: Review answer
+ review_comment_title: Review comment
+ btn_cancel: Cancel
+ btn_submit: Submit
+ remark:
+ empty: Cannot be empty.
+ msg:
+ empty: Please select a reason.
+ not_a_url: URL format is incorrect.
+ url_not_match: URL origin does not match the current website.
+ tag_modal:
+ title: Create new tag
+ form:
+ fields:
+ display_name:
+ label: Display name
+ msg:
+ empty: Display name cannot be empty.
+ range: Display name up to 35 characters.
+ slug_name:
+ label: URL slug
+ desc: URL slug up to 35 characters.
+ msg:
+ empty: URL slug cannot be empty.
+ range: URL slug up to 35 characters.
+ character: URL slug contains unallowed character set.
+ desc:
+ label: Description
+ revision:
+ label: Revision
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_cancel: Cancel
+ btn_submit: Submit
+ btn_post: Post new tag
+ tag_info:
+ created_at: Created
+ edited_at: Edited
+ history: History
+ synonyms:
+ title: Synonyms
+ text: The following tags will be remapped to
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ tip_with_posts: >-
+ We do not allow deleting tag with posts .
Please remove this tag from the posts first.
+ tip_with_synonyms: >-
+ We do not allow deleting tag with synonyms .
Please remove the synonyms from this tag first.
+ tip: Are you sure you wish to delete?
+ close: Close
+ merge:
+ title: Merge tag
+ source_tag_title: Source tag
+ source_tag_description: The source tag and its associated data will be remapped to the target tag.
+ target_tag_title: Target tag
+ target_tag_description: A synonym between these two tags will be created after merging.
+ no_results: No tags matched
+ btn_submit: Submit
+ btn_close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ default_first_reason: Add tag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ hours: hours
+ days: days
+ month: month
+ months: months
+ year: year
+ reaction:
+ heart: heart
+ smile: smile
+ frown: frown
+ btn_label: add or remove reactions
+ undo_emoji: undo {{ emoji }} reaction
+ react_emoji: react with {{ emoji }}
+ unreact_emoji: unreact with {{ emoji }}
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: "{{count}} more comments"
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ 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: Edit Answer
+ default_reason: Edit answer
+ default_first_reason: Add answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ feedback:
+ characters: content must be at least 6 characters in length.
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Popular
+ name: Name
+ newest: Newest
+ button_follow: Follow
+ button_following: Following
+ tag_label: questions
+ search_placeholder: Filter by tag name
+ no_desc: The tag has no description.
+ more: More
+ wiki: Wiki
+ ask:
+ title: Create Question
+ edit_title: Edit Question
+ default_reason: Edit question
+ default_first_reason: Create question
+ similar_questions: Similar questions
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Title
+ placeholder: What's your topic? Be specific.
+ msg:
+ empty: Title cannot be empty.
+ range: Title up to 150 characters
+ body:
+ label: Body
+ msg:
+ empty: Body cannot be empty.
+ hint:
+ optional_body: Describe what the question is about.
+ minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ tags:
+ label: Tags
+ msg:
+ empty: Tags cannot be empty.
+ answer:
+ label: Answer
+ msg:
+ empty: Answer cannot be empty.
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_post_question: Post your question
+ btn_save_edits: Save edits
+ answer_question: Answer your own question
+ post_question&answer: Post your question and answer
+ tag_selector:
+ add_btn: Add tag
+ create_btn: Create new tag
+ search_tag: Search tag
+ hint: Describe what your content is about, at least one tag is required.
+ hint_zero_tags: Describe what your content is about.
+ hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ badges: Badges
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ bookmark: Bookmarks
+ moderation: Moderation
+ search:
+ placeholder: Search
+ footer:
+ build_on: Powered by <1> Apache Answer 1>
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ resend_email:
+ url_label: Are you sure you want to resend the activation email?
+ url_text: You can also give the activation link above to the user.
+ login:
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New email
+ msg:
+ empty: Email cannot be empty.
+ oauth:
+ connect: Connect with {{ auth_name }}
+ remove: Remove {{ auth_name }}
+ oauth_bind_email:
+ subtitle: Add a recovery email to your account.
+ btn_update: Update email address
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ modal_title: Email already existes.
+ modal_content: This email address already registered. Are you sure you want to connect to the existing account?
+ modal_cancel: Change email
+ modal_confirm: Connect to the existing account
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm new password
+ settings:
+ page_title: Settings
+ goto_modify: Go to modify
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display name
+ msg: Display name cannot be empty.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile image
+ gravatar: Gravatar
+ gravatar_text: You can change image on
+ custom: Custom
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About me
+ website:
+ label: Website
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location
+ placeholder: "City, Country"
+ notification:
+ heading: Email Notifications
+ turn_on: Turn on
+ inbox:
+ label: Inbox notifications
+ description: Answers to your questions, comments, invites, and more.
+ all_new_question:
+ label: All new questions
+ description: Get notified of all new questions. Up to 50 questions per week.
+ all_new_question_for_following_tags:
+ label: All new questions for following tags
+ description: Get notified of new questions for following tags.
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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:
+ label: Current password
+ msg: Password cannot be empty.
+ password_title: Password
+ current_pass:
+ label: Current password
+ msg:
+ empty: Current password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New password
+ pass_confirm:
+ label: Confirm new password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface language
+ text: User interface language. It will change when you refresh the page.
+ my_logins:
+ title: My logins
+ label: Log in or sign up on this site using these accounts.
+ modal_title: Remove login
+ modal_content: Are you sure you want to remove this login from your account?
+ modal_confirm_btn: Remove
+ remove_success: Removed successfully
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ sent_success: Sent successfully
+ related_question:
+ title: Related
+ answers: answers
+ linked_question:
+ title: Linked
+ description: Posts linked to
+ no_linked_question: No contents linked from this content.
+ invite_to_answer:
+ title: Pozvěte další uživatele
+ desc: Pozvěte lidi, o kterých si myslíte, že mohou odpovědět.
+ invite: Invite to answer
+ add: Add people
+ search: Search people
+ question_detail:
+ action: Action
+ created: Created
+ Asked: Asked
+ asked: asked
+ update: Modified
+ Edited: Edited
+ edit: edited
+ commented: commented
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ follow_tip: Follow this question to receive notifications
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ useful: Useful
+ question_useful: It is useful and clear
+ question_un_useful: It is unclear or not useful
+ question_bookmark: Bookmark this question
+ answer_useful: It is useful
+ answer_un_useful: It is not useful
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ oldest: Oldest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ edit_answer: Edit my existing answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ characters: content must be at least 6 characters in length.
+ tips:
+ header_1: Thanks for your answer
+ li1_1: Please be sure to answer the question . Provide details and share your research.
+ li1_2: Back up any statements you make with references or personal experience.
+ header_2: But avoid ...
+ li2_1: Asking for help, seeking clarification, or responding to other answers.
+ reopen:
+ confirm_btn: Reopen
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ list:
+ confirm_btn: List
+ title: List this 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?
+ pin:
+ title: Pin this post
+ content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
+ confirm_btn: Pin
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_answer_deleted: This answer has been deleted
+ undelete_title: Undelete this post
+ undelete_desc: Are you sure you wish to undelete?
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ edit: Edit
+ save: Save
+ delete: Delete
+ undelete: Undelete
+ list: List
+ unlist: Unlist
+ unlisted: Unlisted
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ create: Create
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ discard_draft: Discard draft
+ pinned: Pinned
+ all: All
+ question: Question
+ answer: Answer
+ comment: Comment
+ refresh: Refresh
+ resend: Resend
+ deactivate: Deactivate
+ active: Active
+ suspend: Suspend
+ unsuspend: Unsuspend
+ close: Close
+ reopen: Reopen
+ ok: OK
+ light: Light
+ dark: Dark
+ system_setting: System setting
+ default: Default
+ reset: Reset
+ tag: Tag
+ post_lowercase: post
+ filter: Filter
+ ignore: Ignore
+ submit: Submit
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ deleted_permanently: Deleted permanently
+ pending: Pending
+ more: More
+ view: View
+ card: Card
+ compact: Compact
+ display_below: Display below
+ always_display: Always display
+ or: or
+ back_sites: Back to sites
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ counts_loading: "... Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post.
+ modal_confirm:
+ title: Error...
+ delete_permanently:
+ title: Delete permanently
+ content: Are you sure you want to delete permanently?
+ account_result:
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ oops: Oops!
+ invalid: The link you used no longer works.
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ x_posts: "{{ count }} Posts"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ frequent: Frequent
+ recommend: Recommend
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ badges: Badges
+ newest: Newest
+ score: Score
+ edit_profile: Edit profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ comma: ","
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ content_empty: No posts found.
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ downvoted: downvoted
+ mod_short: MOD
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ recent_badges: Recent Badges
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please choose a language
+ db_type:
+ label: Database engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database host
+ placeholder: "db:3306"
+ msg: Database host cannot be empty.
+ db_name:
+ label: Database name
+ placeholder: answer
+ msg: Database name cannot be empty.
+ db_file:
+ label: Database file
+ placeholder: /data/answer.db
+ msg: Database file cannot be empty.
+ ssl_enabled:
+ label: Enable SSL
+ ssl_enabled_on:
+ label: On
+ ssl_enabled_off:
+ label: Off
+ ssl_mode:
+ label: SSL Mode
+ ssl_root_cert:
+ placeholder: sslrootcert file path
+ msg: Path to sslrootcert file cannot be empty
+ ssl_cert:
+ placeholder: sslcert file path
+ msg: Path to sslcert file cannot be empty
+ ssl_key:
+ placeholder: sslkey file path
+ msg: Path to sslkey file cannot be empty
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: After you've done that, click "Next" button.
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site name
+ msg: Site name cannot be empty.
+ msg_max_length: Site name must be at maximum 30 characters in length.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ max_length: Site URL must be at maximum 512 characters in length.
+ contact_email:
+ label: Contact email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact email cannot be empty.
+ incorrect: Contact email incorrect format.
+ login_required:
+ label: Private
+ switch: Login required
+ text: Only logged in users can access this community.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ msg_max_length: Name must be between 2 to 30 characters in length.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ msg_min_length: Password must be at least 8 characters in length.
+ msg_max_length: Password must be at maximum 32 characters in length.
+ admin_confirm_password:
+ label: "Confirm Password"
+ text: "Please re-enter your password to confirm."
+ msg: "Confirm password does not match."
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ counts:
+ views: views
+ votes: votes
+ answers: answers
+ accepted: Accepted
+ page_error:
+ http_error: HTTP Error {{ code }}
+ desc_403: You don't have permission to access this page.
+ desc_404: Unfortunately, this page doesn't exist.
+ desc_50X: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ badges: Badges
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ terms: Terms
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ login: Login
+ privileges: Privileges
+ plugins: Plugins
+ installed_plugins: Installed Plugins
+ apperance: Appearance
+ website_welcome: Welcome to {{site_name}}
+ user_center:
+ login: Login
+ qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
+ login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
+ badges:
+ modal:
+ title: Congratulations
+ content: You've earned a new badge.
+ close: Close
+ confirm: View badges
+ title: Badges
+ awarded: Awarded
+ earned_×: Earned ×{{ number }}
+ ×_awarded: "{{ number }} awarded"
+ can_earn_multiple: You can earn this multiple times.
+ earned: Earned
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site statistics
+ questions: "Questions:"
+ resolved: "Resolved:"
+ unanswered: "Unanswered:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ users: "Users:"
+ flags: "Flags:"
+ reviews: "Reviews:"
+ site_health: Site health
+ version: "Version:"
+ https: "HTTPS:"
+ upload_folder: "Upload folder:"
+ run_mode: "Running mode:"
+ private: Private
+ public: Public
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System info
+ go_version: "Go version:"
+ database: "Database:"
+ database_size: "Database size:"
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ links: Links
+ plugins: Plugins
+ github: GitHub
+ blog: Blog
+ contact: Contact
+ forum: Forum
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ writable: Writable
+ not_writable: Not writable
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ flagged_type: Flagged {{ type }}
+ created: Created
+ action: Action
+ review: Review
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ edit_profile_modal:
+ title: Edit profile
+ form:
+ fields:
+ display_name:
+ label: Display name
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ msg_range: Username must be 2-30 characters in length.
+ email:
+ label: Email
+ msg_invalid: Invalid Email Address.
+ edit_success: Edited successfully
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ users:
+ label: Bulk add user
+ placeholder: "John Smith, john@example.com, BUSYopr2\nAlice, alice@example.com, fpDntV8q"
+ text: Separate “name, email, password” with commas. One user per line.
+ msg: "Please enter the user's email, one per line."
+ display_name:
+ label: Display name
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created time
+ delete_at: Deleted time
+ suspend_at: Suspended time
+ suspend_until: Suspend until
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ more: More
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ edit_profile: Edit profile
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ deactivate_user:
+ title: Deactivate user
+ content: An inactive user must re-validate their email.
+ delete_user:
+ title: Delete this user
+ content: Are you sure you want to delete this user? This is permanent!
+ remove: Remove their content
+ label: Remove all questions, answers, comments, etc.
+ text: Don’t check this if you wish to only delete the user’s account.
+ suspend_user:
+ title: Suspend this user
+ content: A suspended user can't log in.
+ label: How long will the user be suspended for?
+ forever: Forever
+ questions:
+ page_title: Questions
+ unlisted: Unlisted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ pending: Pending
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short site description
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site description
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ check_update:
+ label: Software updates
+ text: Automatically check for updates
+ interface:
+ page_title: Interface
+ language:
+ label: Interface language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: Gravatar base URL
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ tls: TLS
+ none: None
+ smtp_port:
+ label: SMTP port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test email recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile logo
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ square_icon:
+ label: Square icon
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ external_content_display:
+ label: External content
+ text: "Content includes images, videos, and media embedded from external websites."
+ always_display: Always display external content
+ ask_before_display: Ask before displaying external content
+ write:
+ page_title: Write
+ min_content:
+ label: Minimum question body length
+ text: Minimum allowed question body length in characters.
+ restrict_answer:
+ title: Answer write
+ label: Každý uživatel může napsat pouze jednu odpověď na stejný dotaz
+ text: "Turn off to allow users to write multiple answers to the same question, which may cause answers to be unfocused."
+ min_tags:
+ label: "Minimum tags per question"
+ text: "Minimum number of tags required in a question."
+ recommend_tags:
+ label: Recommend tags
+ text: "Recommend tags will show in the dropdown list by default."
+ msg:
+ contain_reserved: "recommended tags cannot contain reserved tags"
+ required_tag:
+ title: Set required tags
+ label: Set “Recommend tags” as required tags
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved tags
+ text: "Reserved tags can only be used by moderator."
+ image_size:
+ label: Max image size (MB)
+ text: "The maximum image upload size."
+ attachment_size:
+ label: Max attachment size (MB)
+ text: "The maximum attachment files upload size."
+ image_megapixels:
+ label: Max image megapixels
+ text: "Maximum number of megapixels allowed for an image."
+ image_extensions:
+ label: Authorized image extensions
+ text: "A list of file extensions allowed for image display, separate with commas."
+ attachment_extensions:
+ label: Authorized attachment extensions
+ text: "A list of file extensions allowed for upload, separate with commas. WARNING: Allowing uploads may cause security issues."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ color_scheme:
+ label: Color scheme
+ navbar_style:
+ label: Navbar background style
+ primary_color:
+ label: Primary color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: >
+
+ head:
+ label: Head
+ text: >
+
+ header:
+ label: Header
+ text: >
+
+ footer:
+ label: Footer
+ text: This will insert before </body>.
+ sidebar:
+ label: Sidebar
+ text: This will insert in sidebar.
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ email_registration:
+ title: Email registration
+ label: Allow email registration
+ text: Turn off to prevent anyone creating new account through email.
+ allowed_email_domains:
+ title: Allowed email domains
+ text: Email domains that users must register accounts with. One domain per line. Ignored when empty.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ password_login:
+ title: Password login
+ label: Allow email and password login
+ text: "WARNING: If turn off, you may be unable to log in if you have not previously configured other login method."
+ installed_plugins:
+ title: Installed Plugins
+ plugin_link: Plugins extend and expand the functionality. You may find plugins in the <1>Plugin Repository1>.
+ filter:
+ all: All
+ active: Active
+ inactive: Inactive
+ outdated: Outdated
+ plugins:
+ label: Plugins
+ text: Select an existing plugin.
+ name: Name
+ version: Version
+ status: Status
+ action: Action
+ deactivate: Deactivate
+ activate: Activate
+ settings: Settings
+ settings_users:
+ title: Users
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: URL základny Gravatar
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ profile_editable:
+ title: Profile editable
+ allow_update_display_name:
+ label: Allow users to change their display name
+ allow_update_username:
+ label: Allow users to change their username
+ allow_update_avatar:
+ label: Allow users to change their profile image
+ allow_update_bio:
+ label: Allow users to change their about me
+ allow_update_website:
+ label: Allow users to change their website
+ allow_update_location:
+ label: Allow users to change their location
+ privilege:
+ title: Privileges
+ 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
+ badges:
+ action: Action
+ active: Active
+ activate: Activate
+ all: All
+ awards: Awards
+ deactivate: Deactivate
+ filter:
+ placeholder: Filter by name, badge:id
+ group: Group
+ inactive: Inactive
+ name: Name
+ show_logs: Show logs
+ status: Status
+ title: Badges
+ form:
+ optional: (optional)
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ select: Select
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ 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
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ pin: pinned
+ unpin: unpinned
+ show: listed
+ hide: unlisted
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores this week
+ users_with_the_most_vote: Users who voted the most this week
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
+ prompt:
+ leave_page: Are you sure you want to leave the page?
+ changes_not_save: Your changes may not be saved.
+ draft:
+ discard_confirm: Are you sure you want to discard your draft?
+ messages:
+ post_deleted: This post has been deleted.
+ post_cancel_deleted: This post has been undeleted.
+ post_pin: This post has been pinned.
+ post_unpin: This post has been unpinned.
+ post_hide_list: This post has been hidden from list.
+ post_show_list: This post has been shown to list.
+ post_reopen: This post has been reopened.
+ 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_closed: This post has been closed.
+ answer_deleted: This answer has been deleted.
+ answer_cancel_deleted: This answer has been undeleted.
+ change_user_role: This user's role has been changed.
+ user_inactive: This user is already inactive.
+ user_normal: This user is already normal.
+ user_suspended: This user has been suspended.
+ user_deleted: This user has been deleted.
+ badge_activated: This badge has been activated.
+ badge_inactivated: This badge has been inactivated.
+ users_deleted: These users have been deleted.
+ posts_deleted: These questions have been deleted.
+ answers_deleted: These answers have been deleted.
+ copy: Copy to clipboard
+ copied: Copied
+ external_content_warning: External images/media are not displayed.
+
+
diff --git a/data/i18n/cy_GB.yaml b/data/i18n/cy_GB.yaml
new file mode 100644
index 000000000..6f27a61f5
--- /dev/null
+++ b/data/i18n/cy_GB.yaml
@@ -0,0 +1,2359 @@
+# 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.
+
+# The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Llwyddiant.
+ unknown:
+ other: Gwall anhysbys.
+ request_format_error:
+ other: Nid yw fformat y cais yn ddilys.
+ unauthorized_error:
+ other: Anawdurdodedig.
+ database_error:
+ other: Gwall gweinydd data.
+ forbidden_error:
+ other: Forbidden.
+ duplicate_request_error:
+ other: Duplicate submission.
+ action:
+ report:
+ other: Tynnu sylw
+ edit:
+ other: Golygu
+ delete:
+ other: Dileu
+ close:
+ other: Cau
+ reopen:
+ other: Ailagor
+ forbidden_error:
+ other: Forbidden.
+ pin:
+ other: Pinio
+ hide:
+ other: Dad-restru
+ unpin:
+ other: Dadbinio
+ show:
+ other: Rhestr
+ invite_someone_to_answer:
+ other: Edit
+ undelete:
+ other: Undelete
+ merge:
+ other: Merge
+ role:
+ name:
+ user:
+ other: Defnyddiwr
+ admin:
+ other: Gweinyddwr
+ moderator:
+ other: Cymedrolwr
+ description:
+ user:
+ other: Diofyn heb unrhyw fynediad arbennig.
+ admin:
+ other: Bod â'r pŵer llawn i gael mynediad i'r safle.
+ moderator:
+ other: Mae ganddo fynediad i bob post ac eithrio gosodiadau gweinyddol.
+ privilege:
+ level_1:
+ description:
+ other: Level 1 (less reputation required for private team, group)
+ level_2:
+ description:
+ other: Level 2 (low reputation required for startup community)
+ level_3:
+ description:
+ other: Level 3 (high reputation required for mature community)
+ level_custom:
+ description:
+ other: Custom Level
+ rank_question_add_label:
+ other: Ask question
+ rank_answer_add_label:
+ other: Write answer
+ rank_comment_add_label:
+ other: Write comment
+ rank_report_add_label:
+ other: Flag
+ rank_comment_vote_up_label:
+ other: Upvote comment
+ rank_link_url_limit_label:
+ other: Post more than 2 links at a time
+ rank_question_vote_up_label:
+ other: Upvote question
+ rank_answer_vote_up_label:
+ other: Upvote answer
+ rank_question_vote_down_label:
+ other: Downvote question
+ rank_answer_vote_down_label:
+ other: Downvote answer
+ rank_invite_someone_to_answer_label:
+ other: Invite someone to answer
+ rank_tag_add_label:
+ other: Create new tag
+ rank_tag_edit_label:
+ other: Edit tag description (need to review)
+ rank_question_edit_label:
+ other: Edit other's question (need to review)
+ rank_answer_edit_label:
+ other: Edit other's answer (need to review)
+ rank_question_edit_without_review_label:
+ other: Edit other's question without review
+ rank_answer_edit_without_review_label:
+ other: Edit other's answer without review
+ rank_question_audit_label:
+ other: Review question edits
+ rank_answer_audit_label:
+ other: Review answer edits
+ rank_tag_audit_label:
+ other: Review tag edits
+ rank_tag_edit_without_review_label:
+ other: Edit tag description without review
+ rank_tag_synonym_label:
+ other: Manage tag synonyms
+ email:
+ other: Ebost
+ e_mail:
+ other: Email
+ password:
+ other: Cyfrinair
+ pass:
+ other: Password
+ old_pass:
+ other: Current password
+ original_text:
+ other: This post
+ email_or_password_wrong_error:
+ other: Nid yw e-bost a chyfrinair yn cyfateb.
+ error:
+ common:
+ invalid_url:
+ other: Invalid URL.
+ status_invalid:
+ other: Invalid status.
+ password:
+ space_invalid:
+ other: Password cannot contain spaces.
+ admin:
+ cannot_update_their_password:
+ other: Ni allwch addasu eich cyfrinair.
+ cannot_edit_their_profile:
+ other: You cannot modify your profile.
+ cannot_modify_self_status:
+ other: Ni allwch addasu eich statws.
+ email_or_password_wrong:
+ other: Nid yw e-bost a chyfrinair yn cyfateb.
+ answer:
+ not_found:
+ other: Ni cheir yr ateb.
+ cannot_deleted:
+ other: Dim caniatâd i ddileu.
+ cannot_update:
+ other: Dim caniatâd i ddiweddaru.
+ question_closed_cannot_add:
+ other: Mae cwestiynau ar gau ac ni ellir eu hychwanegu.
+ content_cannot_empty:
+ other: Answer content cannot be empty.
+ comment:
+ edit_without_permission:
+ other: Nid oes modd golygu sylwadau.
+ not_found:
+ other: Sylw heb ei ganfod.
+ cannot_edit_after_deadline:
+ other: Mae'r amser sylwadau wedi bod yn rhy hir i'w addasu.
+ content_cannot_empty:
+ other: Comment content cannot be empty.
+ email:
+ duplicate:
+ other: E-bost yn bodoli eisoes.
+ need_to_be_verified:
+ other: Dylid gwirio e-bost.
+ verify_url_expired:
+ other: Mae'r URL wedi'i wirio gan e-bost wedi dod i ben, anfonwch yr e-bost eto.
+ illegal_email_domain_error:
+ other: Email is not allowed from that email domain. Please use another one.
+ lang:
+ not_found:
+ other: Ffeil iaith heb ei chanfod.
+ object:
+ captcha_verification_failed:
+ other: Captcha anghywir.
+ disallow_follow:
+ other: Ni chaniateir i chi ddilyn.
+ disallow_vote:
+ other: Ni chaniateir i chi pleidleisio.
+ disallow_vote_your_self:
+ other: Ni allwch bleidleisio dros eich post eich hun.
+ not_found:
+ other: Heb ganfod y gwrthrych.
+ verification_failed:
+ other: Methodd y dilysu.
+ email_or_password_incorrect:
+ other: Nid yw e-bost a chyfrinair yn cyfateb.
+ old_password_verification_failed:
+ other: Methodd yr hen ddilysiad cyfrinair
+ new_password_same_as_previous_setting:
+ other: Mae'r cyfrinair newydd yr un fath â'r un blaenorol.
+ already_deleted:
+ other: This post has been deleted.
+ meta:
+ object_not_found:
+ other: Meta object not found
+ question:
+ already_deleted:
+ other: Mae'r postiad hwn wedi'i ddileu.
+ under_review:
+ other: Your post is awaiting review. It will be visible after it has been approved.
+ not_found:
+ other: Cwestiwn heb ei ganfod.
+ cannot_deleted:
+ other: Dim caniatâd i ddileu.
+ cannot_close:
+ other: Dim caniatâd i cau.
+ cannot_update:
+ other: Dim caniatâd i ddiweddaru.
+ content_cannot_empty:
+ other: Content cannot be empty.
+ content_less_than_minimum:
+ other: Not enough content entered.
+ rank:
+ fail_to_meet_the_condition:
+ other: Reputation rank fail to meet the condition.
+ vote_fail_to_meet_the_condition:
+ other: Thanks for the feedback. You need at least {{.Rank}} reputation to cast a vote.
+ no_enough_rank_to_operate:
+ other: You need at least {{.Rank}} reputation to do this.
+ report:
+ handle_failed:
+ other: Methodd handlen yr adroddiad.
+ not_found:
+ other: Heb ganfod yr adroddiad.
+ tag:
+ already_exist:
+ other: Mae tag eisoes yn bodoli.
+ not_found:
+ other: Tag heb ei ddarganfod.
+ recommend_tag_not_found:
+ other: Recommend tag is not exist.
+ recommend_tag_enter:
+ other: Rhowch o leiaf un tag gofynnol.
+ not_contain_synonym_tags:
+ other: Ni ddylai gynnwys tagiau cyfystyr.
+ cannot_update:
+ other: Dim caniatâd i ddiweddaru.
+ is_used_cannot_delete:
+ other: You cannot delete a tag that is in use.
+ cannot_set_synonym_as_itself:
+ other: Ni allwch osod cyfystyr y tag cyfredol fel ei hun.
+ minimum_count:
+ other: Not enough tags were entered.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: The from name cannot be a email address.
+ theme:
+ not_found:
+ other: Thema heb ei ddarganfod.
+ revision:
+ review_underway:
+ other: Methu â golygu ar hyn o bryd, mae fersiwn yn y ciw adolygu.
+ no_permission:
+ other: No permission to revise.
+ user:
+ external_login_missing_user_id:
+ other: The third-party platform does not provide a unique UserID, so you cannot login, please contact the website administrator.
+ external_login_unbinding_forbidden:
+ other: Please set a login password for your account before you remove this login.
+ email_or_password_wrong:
+ other:
+ other: Nid yw e-bost a chyfrinair yn cyfateb.
+ not_found:
+ other: Defnyddwr heb ei ddarganfod.
+ suspended:
+ other: Mae'r defnyddiwr hwn wedi'i atal.
+ username_invalid:
+ other: Mae'r enw defnyddiwr yn annilys.
+ username_duplicate:
+ other: Cymerwyd yr enw defnyddiwr eisoes.
+ set_avatar:
+ other: Methodd set avatar.
+ cannot_update_your_role:
+ other: Ni allwch addasu eich rôl.
+ not_allowed_registration:
+ other: Currently the site is not open for registration.
+ not_allowed_login_via_password:
+ other: Currently the site is not allowed to login via password.
+ access_denied:
+ other: Access denied
+ page_access_denied:
+ other: You do not have access to this page.
+ add_bulk_users_format_error:
+ other: "Error {{.Field}} format near '{{.Content}}' at line {{.Line}}. {{.ExtraMessage}}"
+ add_bulk_users_amount_error:
+ other: "The number of users you add at once should be in the range of 1-{{.MaxAmount}}."
+ status_suspended_forever:
+ other: "This user was suspended forever. This user doesn't meet a community guideline."
+ status_suspended_until:
+ other: "This user was suspended until {{.SuspendedUntil}}. This user doesn't meet a community guideline."
+ status_deleted:
+ other: "This user was deleted."
+ status_inactive:
+ other: "This user is inactive."
+ config:
+ read_config_failed:
+ other: Wedi methu darllen y ffurfwedd
+ database:
+ connection_failed:
+ other: Methodd cysylltiad cronfa ddata
+ create_table_failed:
+ other: Methwyd creu tabl
+ install:
+ create_config_failed:
+ other: Methu creu'r ffeil config.yaml.
+ upload:
+ unsupported_file_format:
+ other: Fformat ffeil heb ei gefnogi.
+ site_info:
+ config_not_found:
+ other: Site config not found.
+ badge:
+ object_not_found:
+ other: Badge object not found
+ reason:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
+ rude_or_abusive:
+ name:
+ other: rude or abusive
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ a_duplicate:
+ name:
+ other: a duplicate
+ desc:
+ other: This question has been asked before and already has an answer.
+ placeholder:
+ other: Enter the existing question link
+ not_a_answer:
+ name:
+ other: not an answer
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question,or deleted altogether."
+ no_longer_needed:
+ name:
+ other: no longer needed
+ desc:
+ other: This comment is outdated, conversational or not relevant to this post.
+ something:
+ name:
+ other: something else
+ desc:
+ other: This post requires staff attention for another reason not listed above.
+ placeholder:
+ other: Let us know specifically what you are concerned about
+ community_specific:
+ name:
+ other: a community-specific reason
+ desc:
+ other: This question doesn't meet a community guideline.
+ not_clarity:
+ name:
+ other: needs details or clarity
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ looks_ok:
+ name:
+ other: looks OK
+ desc:
+ other: This post is good as-is and not low quality.
+ needs_edit:
+ name:
+ other: needs edit, and I did it
+ desc:
+ other: Improve and correct problems with this post yourself.
+ needs_close:
+ name:
+ other: needs close
+ desc:
+ other: A closed question can't answer, but still can edit, vote and comment.
+ needs_delete:
+ name:
+ other: needs delete
+ desc:
+ other: This post will be deleted.
+ question:
+ close:
+ duplicate:
+ name:
+ other: sbam
+ desc:
+ other: Mae'r cwestiwn hwn wedi'i ofyn o'r blaen ac mae ganddo ateb yn barod.
+ guideline:
+ name:
+ other: rheswm cymunedol-benodol
+ desc:
+ other: Nid yw'r cwestiwn hwn yn bodloni canllaw cymunedol.
+ multiple:
+ name:
+ other: angen manylion neu eglurder
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other:
+ name:
+ other: rhywbeth arall
+ desc:
+ other: Mae'r swydd hon angen reswm arall nad yw wedi'i restru uchod.
+ operation_type:
+ asked:
+ other: gofynnodd
+ answered:
+ other: atebodd
+ modified:
+ other: wedi newid
+ deleted_title:
+ other: Deleted question
+ questions_title:
+ other: Questions
+ tag:
+ tags_title:
+ other: Tags
+ no_description:
+ other: The tag has no description.
+ notification:
+ action:
+ update_question:
+ other: cwestiwn wedi'i ddiweddaru
+ answer_the_question:
+ other: cwestiwn wedi ei ateb
+ update_answer:
+ other: ateb wedi'i ddiweddaru
+ accept_answer:
+ other: ateb derbyniol
+ comment_question:
+ other: cwestiwn a wnaed
+ comment_answer:
+ other: ateb a wnaed
+ reply_to_you:
+ other: atebodd i chi
+ mention_you:
+ other: wedi sôn amdanoch
+ your_question_is_closed:
+ other: Mae eich cwestiwn wedi’i gau
+ your_question_was_deleted:
+ other: Mae eich cwestiwn wedi’i dileu
+ your_answer_was_deleted:
+ other: Mae eich ateb wedi’i dileu
+ your_comment_was_deleted:
+ other: Mae eich sylw wedi’i dileu
+ up_voted_question:
+ other: upvoted question
+ down_voted_question:
+ other: downvoted question
+ up_voted_answer:
+ other: upvoted answer
+ down_voted_answer:
+ other: downvoted answer
+ up_voted_comment:
+ other: upvoted comment
+ invited_you_to_answer:
+ other: invited you to answer
+ earned_badge:
+ other: You've earned the "{{.BadgeName}}" badge
+ email_tpl:
+ change_email:
+ title:
+ other: "[{{.SiteName}}] Confirm your new email address"
+ body:
+ other: "Confirm your new email address for {{.SiteName}} by clicking on the following link: \n{{.ChangeEmailUrl}} \n\nIf you did not request this change, please ignore this email. \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ new_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} answered your question"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ invited_you_to_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} invited you to answer"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \nI think you may know the answer. \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_comment:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} commented on your post"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.CommentSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_question:
+ title:
+ other: "[{{.SiteName}}] New question: {{.QuestionTitle}}"
+ body:
+ other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ pass_reset:
+ title:
+ other: "[{{.SiteName }}] Password reset"
+ body:
+ other: "Somebody asked to reset your password on {{.SiteName}}. \n\nIf it was not you, you can safely ignore this email. \n\nClick the following link to choose a new password: \n{{.PassResetUrl}} \n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ register:
+ title:
+ other: "[{{.SiteName}}] Confirm your new account"
+ body:
+ other: "Welcome to {{.SiteName}}! \n\nClick the following link to confirm and activate your new account: \n{{.RegisterUrl}} \n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ test:
+ title:
+ other: "[{{.SiteName}}] Test Email"
+ body:
+ other: "This is a test email.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ action_activity_type:
+ upvote:
+ other: upvote
+ upvoted:
+ other: upvoted
+ downvote:
+ other: downvote
+ downvoted:
+ other: downvoted
+ accept:
+ other: accept
+ accepted:
+ other: accepted
+ edit:
+ other: edit
+ review:
+ queued_post:
+ other: Queued post
+ flagged_post:
+ other: Flagged post
+ suggested_post_edit:
+ other: Suggested edits
+ reaction:
+ tooltip:
+ other: "{{ .Names }} and {{ .Count }} more..."
+ badge:
+ default_badges:
+ autobiographer:
+ name:
+ other: Autobiographer
+ desc:
+ other: Filled out profile information.
+ certified:
+ name:
+ other: Certified
+ desc:
+ other: Completed our new user tutorial.
+ editor:
+ name:
+ other: Editor
+ desc:
+ other: First post edit.
+ first_flag:
+ name:
+ other: First Flag
+ desc:
+ other: First flagged a post.
+ first_upvote:
+ name:
+ other: First Upvote
+ desc:
+ other: First up voted a post.
+ first_link:
+ name:
+ other: First Link
+ desc:
+ other: First added a link to another post.
+ first_reaction:
+ name:
+ other: First Reaction
+ desc:
+ other: First reacted to the post.
+ first_share:
+ name:
+ other: First Share
+ desc:
+ other: First shared a post.
+ scholar:
+ name:
+ other: Scholar
+ desc:
+ other: Asked a question and accepted an answer.
+ commentator:
+ name:
+ other: Commentator
+ desc:
+ other: Leave 5 comments.
+ new_user_of_the_month:
+ name:
+ other: New User of the Month
+ desc:
+ other: Outstanding contributions in their first month.
+ read_guidelines:
+ name:
+ other: Read Guidelines
+ desc:
+ other: Read the [community guidelines].
+ reader:
+ name:
+ other: Reader
+ desc:
+ other: Read every answers in a topic with more than 10 answers.
+ welcome:
+ name:
+ other: Welcome
+ desc:
+ other: Received a up vote.
+ nice_share:
+ name:
+ other: Nice Share
+ desc:
+ other: Shared a post with 25 unique visitors.
+ good_share:
+ name:
+ other: Good Share
+ desc:
+ other: Shared a post with 300 unique visitors.
+ great_share:
+ name:
+ other: Great Share
+ desc:
+ other: Shared a post with 1000 unique visitors.
+ out_of_love:
+ name:
+ other: Out of Love
+ desc:
+ other: Used 50 up votes in a day.
+ higher_love:
+ name:
+ other: Higher Love
+ desc:
+ other: Used 50 up votes in a day 5 times.
+ crazy_in_love:
+ name:
+ other: Crazy in Love
+ desc:
+ other: Used 50 up votes in a day 20 times.
+ promoter:
+ name:
+ other: Promoter
+ desc:
+ other: Invited a user.
+ campaigner:
+ name:
+ other: Campaigner
+ desc:
+ other: Invited 3 basic users.
+ champion:
+ name:
+ other: Champion
+ desc:
+ other: Invited 5 members.
+ thank_you:
+ name:
+ other: Thank You
+ desc:
+ other: Has 20 up voted posts and gave 10 up votes.
+ gives_back:
+ name:
+ other: Gives Back
+ desc:
+ other: Has 100 up voted posts and gave 100 up votes.
+ empathetic:
+ name:
+ other: Empathetic
+ desc:
+ other: Has 500 up voted posts and gave 1000 up votes.
+ enthusiast:
+ name:
+ other: Enthusiast
+ desc:
+ other: Visited 10 consecutive days.
+ aficionado:
+ name:
+ other: Aficionado
+ desc:
+ other: Visited 100 consecutive days.
+ devotee:
+ name:
+ other: Devotee
+ desc:
+ other: Visited 365 consecutive days.
+ anniversary:
+ name:
+ other: Anniversary
+ desc:
+ other: Active member for a year, posted at least once.
+ appreciated:
+ name:
+ other: Appreciated
+ desc:
+ other: Received 1 up vote on 20 posts.
+ respected:
+ name:
+ other: Respected
+ desc:
+ other: Received 2 up votes on 100 posts.
+ admired:
+ name:
+ other: Admired
+ desc:
+ other: Received 5 up votes on 300 posts.
+ solved:
+ name:
+ other: Solved
+ desc:
+ other: Have an answer be accepted.
+ guidance_counsellor:
+ name:
+ other: Guidance Counsellor
+ desc:
+ other: Have 10 answers be accepted.
+ know_it_all:
+ name:
+ other: Know-it-All
+ desc:
+ other: Have 50 answers be accepted.
+ solution_institution:
+ name:
+ other: Solution Institution
+ desc:
+ other: Have 150 answers be accepted.
+ nice_answer:
+ name:
+ other: Nice Answer
+ desc:
+ other: Answer score of 10 or more.
+ good_answer:
+ name:
+ other: Good Answer
+ desc:
+ other: Answer score of 25 or more.
+ great_answer:
+ name:
+ other: Great Answer
+ desc:
+ other: Answer score of 50 or more.
+ nice_question:
+ name:
+ other: Nice Question
+ desc:
+ other: Question score of 10 or more.
+ good_question:
+ name:
+ other: Good Question
+ desc:
+ other: Question score of 25 or more.
+ great_question:
+ name:
+ other: Great Question
+ desc:
+ other: Question score of 50 or more.
+ popular_question:
+ name:
+ other: Popular Question
+ desc:
+ other: Question with 500 views.
+ notable_question:
+ name:
+ other: Notable Question
+ desc:
+ other: Question with 1,000 views.
+ famous_question:
+ name:
+ other: Famous Question
+ desc:
+ other: Question with 5,000 views.
+ popular_link:
+ name:
+ other: Popular Link
+ desc:
+ other: Posted an external link with 50 clicks.
+ hot_link:
+ name:
+ other: Hot Link
+ desc:
+ other: Posted an external link with 300 clicks.
+ famous_link:
+ name:
+ other: Famous Link
+ desc:
+ other: Posted an external link with 100 clicks.
+ default_badge_groups:
+ getting_started:
+ name:
+ other: Getting Started
+ community:
+ name:
+ other: Community
+ posting:
+ name:
+ other: Posting
+# The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: Sut i Fformatio
+ desc: >-
+ mention a post: #post_id
to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Cynt
+ next: Nesaf
+ page_title:
+ question: Cwestiwn
+ questions: Cwestiynau
+ tag: Tag
+ tags: Tagiau
+ tag_wiki: tag wiki
+ create_tag: Creu Tag
+ edit_tag: Golygu Tag
+ ask_a_question: Create Question
+ edit_question: Golygu Cwestiwn
+ edit_answer: Golygu Ateb
+ search: Chwiliwch
+ posts_containing: Postiadau yn cynnwys
+ settings: Gosodiadau
+ notifications: Hysbysiadau
+ login: Mewngofnodi
+ sign_up: Cofrestru
+ account_recovery: Adfer Cyfrif
+ account_activation: Ysgogi Cyfrif
+ confirm_email: Cadarnhau e-bost
+ account_suspended: Cyfrif wedi'i atal
+ admin: Gweinyddu
+ change_email: Addasu E-bost
+ install: Ateb Gosod
+ upgrade: Ateb Uwchraddio
+ maintenance: Cynnal a Chadw Gwefan
+ users: Defnyddwyr
+ oauth_callback: Processing
+ http_404: Gwall HTTP 404
+ http_50X: Gwall HTTP 500
+ http_403: Gwall HTTP 403
+ logout: Log Out
+ posts: Posts
+ notifications:
+ title: Hysbysiadau
+ inbox: Mewnflwch
+ achievement: Llwyddiannau
+ new_alerts: New alerts
+ all_read: Marciwch y cyfan fel wedi'i ddarllen
+ show_more: Dangos mwy
+ someone: Someone
+ inbox_type:
+ all: All
+ posts: Posts
+ invites: Invites
+ votes: Votes
+ answer: Answer
+ question: Question
+ badge_award: Badge
+ suspended:
+ title: Mae'ch Cyfrif wedi'i Atal
+ until_time: "Cafodd eich cyfrif ei atal tan {{ time }}."
+ forever: Cafodd y defnyddiwr hwn ei atal am byth.
+ end: Nid ydych yn arwain cymunedol.
+ contact_us: Contact us
+ editor:
+ blockquote:
+ text: Dyfyniad
+ bold:
+ text: Cryf
+ chart:
+ text: Siart
+ flow_chart: Siart llif
+ sequence_diagram: Diagram dilyniant
+ class_diagram: Diagram dosbarth
+ state_diagram: Diagram cyflwr
+ entity_relationship_diagram: Diagram perthynas endid
+ user_defined_diagram: Diagram wedi'i ddiffinio gan y defnyddiwr
+ gantt_chart: Siart Gantt
+ pie_chart: Siart cylch
+ code:
+ text: Sampl côd
+ add_code: Ychwanegu sampl côd
+ form:
+ fields:
+ code:
+ label: Côd
+ msg:
+ empty: Ni all côd fod yn wag.
+ language:
+ label: Iaith
+ placeholder: Synhwyriad awtomatig
+ btn_cancel: Canslo
+ btn_confirm: Ychwanegu
+ formula:
+ text: Fformiwla
+ options:
+ inline: Fformiwla mewn-lein
+ block: Fformiwla bloc
+ heading:
+ text: Pennawd
+ options:
+ h1: Pennawd 1
+ h2: Pennawd 2
+ h3: Pennawd 3
+ h4: Pennawd 4
+ h5: Pennawd 5
+ h6: Pennawd 6
+ help:
+ text: Cymorth
+ hr:
+ text: Horizontal rule
+ image:
+ text: Delwedd
+ add_image: Ychwanegu delwedd
+ tab_image: Uwchlwytho delwedd
+ form_image:
+ fields:
+ file:
+ label: Image file
+ btn: Dewis delwedd
+ msg:
+ empty: Ni all ffeil fod yn wag.
+ only_image: Dim ond ffeiliau delwedd a ganiateir.
+ max_size: File size cannot exceed {{size}} MB.
+ desc:
+ label: Disgrifiad
+ tab_url: URL delwedd
+ form_url:
+ fields:
+ url:
+ label: URL delwedd
+ msg:
+ empty: Ni all URL delwedd fod yn wag.
+ name:
+ label: Disgrifiad
+ btn_cancel: Canslo
+ btn_confirm: Ychwanegu
+ uploading: Wrthi'n uwchlwytho
+ indent:
+ text: Mewnoliad
+ outdent:
+ text: Alloliad
+ italic:
+ text: Pwyslais
+ link:
+ text: Hypergyswllt
+ add_link: Ychwanegu hypergyswllt
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: Ni all URL fod yn wag.
+ name:
+ label: Disgrifiad
+ btn_cancel: Canslo
+ btn_confirm: Ychwanegu
+ ordered_list:
+ text: Numbered list
+ unordered_list:
+ text: Bulleted list
+ table:
+ text: Tabl
+ heading: Pennawd
+ cell: Cell
+ file:
+ text: Attach files
+ not_supported: "Don’t support that file type. Try again with {{file_type}}."
+ max_size: "Attach files size cannot exceed {{size}} MB."
+ close_modal:
+ title: Rwy'n cau'r post hon fel...
+ btn_cancel: Canslo
+ btn_submit: Cyflwyno
+ remark:
+ empty: Ni all fod yn wag.
+ msg:
+ empty: Dewis rheswm.
+ report_modal:
+ flag_title: Dwi'n tynnu sylw i adrodd y swydd hon fel...
+ close_title: Rwy'n cau'r post hon fel...
+ review_question_title: Adolygu cwestiwn
+ review_answer_title: Adolygu ateb
+ review_comment_title: Adolygu sylwad
+ btn_cancel: Canslo
+ btn_submit: Cyflwyno
+ remark:
+ empty: Ni all fod yn wag.
+ msg:
+ empty: Dewis rheswm.
+ not_a_url: URL format is incorrect.
+ url_not_match: URL origin does not match the current website.
+ tag_modal:
+ title: Creu tag newydd
+ form:
+ fields:
+ display_name:
+ label: Display name
+ msg:
+ empty: Ni all fod enw dangos yn wag.
+ range: Enw arddangos hyd at 35 nod.
+ slug_name:
+ label: URL slug
+ desc: Slug URL hyd at 35 nod.
+ msg:
+ empty: Ni all Slug URL fod yn wag.
+ range: Slug URL hyd at 35 nod.
+ character: Mae slug URL yn cynnwys set nodau na caniateir.
+ desc:
+ label: Disgrifiad
+ revision:
+ label: Revision
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_cancel: Canslo
+ btn_submit: Cyflwyno
+ btn_post: Post tag newydd
+ tag_info:
+ created_at: Creuwyd
+ edited_at: Golygwyd
+ history: Hanes
+ synonyms:
+ title: Cyfystyron
+ text: Bydd y tagiau canlynol yn cael eu hail-fapio i
+ empty: No synonyms found.
+ btn_add: Add a synonym
+ btn_edit: Edit
+ btn_save: Save
+ synonyms_text: The following tags will be remapped to
+ delete:
+ title: Delete this tag
+ tip_with_posts: >-
+ We do not allow deleting tag with posts .
Please remove this tag from the posts first.
+ tip_with_synonyms: >-
+ We do not allow deleting tag with synonyms .
Please remove the synonyms from this tag first.
+ tip: Are you sure you wish to delete?
+ close: Close
+ merge:
+ title: Merge tag
+ source_tag_title: Source tag
+ source_tag_description: The source tag and its associated data will be remapped to the target tag.
+ target_tag_title: Target tag
+ target_tag_description: A synonym between these two tags will be created after merging.
+ no_results: No tags matched
+ btn_submit: Submit
+ btn_close: Close
+ edit_tag:
+ title: Edit Tag
+ default_reason: Edit tag
+ default_first_reason: Add tag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ dates:
+ long_date: MMM D
+ long_date_with_year: "MMM D, YYYY"
+ long_date_with_time: "MMM D, YYYY [at] HH:mm"
+ now: now
+ x_seconds_ago: "{{count}}s ago"
+ x_minutes_ago: "{{count}}m ago"
+ x_hours_ago: "{{count}}h ago"
+ hour: hour
+ day: day
+ hours: hours
+ days: days
+ month: month
+ months: months
+ year: year
+ reaction:
+ heart: heart
+ smile: smile
+ frown: frown
+ btn_label: add or remove reactions
+ undo_emoji: undo {{ emoji }} reaction
+ react_emoji: react with {{ emoji }}
+ unreact_emoji: unreact with {{ emoji }}
+ comment:
+ btn_add_comment: Add comment
+ reply_to: Reply to
+ btn_reply: Reply
+ btn_edit: Edit
+ btn_delete: Delete
+ btn_flag: Flag
+ btn_save_edits: Save edits
+ btn_cancel: Cancel
+ show_more: "{{count}} more comments"
+ tip_question: >-
+ Use comments to ask for more information or suggest improvements. Avoid answering questions in comments.
+ tip_answer: >-
+ 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: Edit Answer
+ default_reason: Edit answer
+ default_first_reason: Add answer
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Answer
+ feedback:
+ characters: content must be at least 6 characters in length.
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
+ btn_save_edits: Save edits
+ btn_cancel: Canslo
+ tags:
+ title: Tagiau
+ sort_buttons:
+ popular: Poblogaidd
+ name: Enw
+ newest: Newest
+ button_follow: Dilyn
+ button_following: Yn dilyn
+ tag_label: cwestiynau
+ search_placeholder: Hidlo yn ôl enw tag
+ no_desc: Nid oes gan y tag unrhyw ddisgrifiad.
+ more: Mwy
+ wiki: Wiki
+ ask:
+ title: Create Question
+ edit_title: Golygu Cwestiwn
+ default_reason: Golygu Cwestiwn
+ default_first_reason: Create question
+ similar_questions: Cwestiynau tebyg
+ form:
+ fields:
+ revision:
+ label: Diwygiad
+ title:
+ label: Teitl
+ placeholder: What's your topic? Be specific.
+ msg:
+ empty: Ni all teitl fod yn wag.
+ range: Teitl hyd at 20 nod
+ body:
+ label: Corff
+ msg:
+ empty: Ni all corff fod yn wag.
+ hint:
+ optional_body: Describe what the question is about.
+ minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ tags:
+ label: Tagiau
+ msg:
+ empty: Ni all tagiau fod yn wag.
+ answer:
+ label: Ateb
+ msg:
+ empty: Ni all ateb fod yn wag.
+ edit_summary:
+ label: Edit summary
+ placeholder: >-
+ Eglurwch yn fyr eich newidiadau (sillafu wedi'i gywiro, gramadeg sefydlog, fformatio gwell)
+ btn_post_question: Post cweistiwn
+ btn_save_edits: Cadw golygiadau
+ answer_question: Atebwch eich cwestiwn eich hun
+ post_question&answer: Postiwch eich cwestiwn ac ateb
+ tag_selector:
+ add_btn: Ychwanegu tag
+ create_btn: Creu tag newydd
+ search_tag: Chwilio tag
+ hint: Describe what your content is about, at least one tag is required.
+ hint_zero_tags: Describe what your content is about.
+ hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ no_result: No tags matched
+ tag_required_text: Required tag (at least one)
+ header:
+ nav:
+ question: Questions
+ tag: Tags
+ user: Users
+ badges: Badges
+ profile: Profile
+ setting: Settings
+ logout: Log out
+ admin: Admin
+ review: Review
+ bookmark: Bookmarks
+ moderation: Moderation
+ search:
+ placeholder: Search
+ footer:
+ build_on: Powered by <1> Apache Answer 1>
+ upload_img:
+ name: Change
+ loading: loading...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Type the text above
+ msg:
+ empty: Captcha cannot be empty.
+ inactive:
+ first: >-
+ You're almost done! We sent an activation mail to {{mail}} . Please follow the instructions in the mail to activate your account.
+ info: "If it doesn't arrive, check your spam folder."
+ another: >-
+ We sent another activation email to you at {{mail}} . It might take a few minutes for it to arrive; be sure to check your spam folder.
+ btn_name: Resend activation email
+ change_btn_name: Change email
+ msg:
+ empty: Cannot be empty.
+ resend_email:
+ url_label: Are you sure you want to resend the activation email?
+ url_text: You can also give the activation link above to the user.
+ login:
+ login_to_continue: Log in to continue
+ info_sign: Don't have an account? <1>Sign up1>
+ info_login: Already have an account? <1>Log in1>
+ agreements: By registering, you agree to the <1>privacy policy1> and <3>terms of service3>.
+ forgot_pass: Forgot password?
+ name:
+ label: Name
+ msg:
+ empty: Name cannot be empty.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ different: The passwords entered on both sides are inconsistent
+ account_forgot:
+ page_title: Forgot Your Password
+ btn_name: Send me recovery email
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ change_email:
+ btn_cancel: Cancel
+ btn_update: Update email address
+ send_success: >-
+ If an account matches {{mail}} , you should receive an email with instructions on how to reset your password shortly.
+ email:
+ label: New email
+ msg:
+ empty: Email cannot be empty.
+ oauth:
+ connect: Connect with {{ auth_name }}
+ remove: Remove {{ auth_name }}
+ oauth_bind_email:
+ subtitle: Add a recovery email to your account.
+ btn_update: Update email address
+ email:
+ label: Email
+ msg:
+ empty: Email cannot be empty.
+ modal_title: Email already existes.
+ modal_content: This email address already registered. Are you sure you want to connect to the existing account?
+ modal_cancel: Change email
+ modal_confirm: Connect to the existing account
+ password_reset:
+ page_title: Password Reset
+ btn_name: Reset my password
+ reset_success: >-
+ You successfully changed your password; you will be redirected to the log in page.
+ link_invalid: >-
+ Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
+ to_login: Continue to log in page
+ password:
+ label: Password
+ msg:
+ empty: Password cannot be empty.
+ length: The length needs to be between 8 and 32
+ different: The passwords entered on both sides are inconsistent
+ password_confirm:
+ label: Confirm new password
+ settings:
+ page_title: Settings
+ goto_modify: Go to modify
+ nav:
+ profile: Profile
+ notification: Notifications
+ account: Account
+ interface: Interface
+ profile:
+ heading: Profile
+ btn_name: Save
+ display_name:
+ label: Display name
+ msg: Display name cannot be empty.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ caption: People can mention you as "@username".
+ msg: Username cannot be empty.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profile image
+ gravatar: Gravatar
+ gravatar_text: You can change image on
+ custom: Custom
+ custom_text: You can upload your image.
+ default: System
+ msg: Please upload an avatar
+ bio:
+ label: About me
+ website:
+ label: Website
+ placeholder: "https://example.com"
+ msg: Website incorrect format
+ location:
+ label: Location
+ placeholder: "City, Country"
+ notification:
+ heading: Email Notifications
+ turn_on: Turn on
+ inbox:
+ label: Inbox notifications
+ description: Answers to your questions, comments, invites, and more.
+ all_new_question:
+ label: All new questions
+ description: Get notified of all new questions. Up to 50 questions per week.
+ all_new_question_for_following_tags:
+ label: All new questions for following tags
+ description: Get notified of new questions for following tags.
+ account:
+ heading: Account
+ change_email_btn: Change email
+ change_pass_btn: Change password
+ 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:
+ label: Current password
+ msg: Password cannot be empty.
+ password_title: Password
+ current_pass:
+ label: Current password
+ msg:
+ empty: Current password cannot be empty.
+ length: The length needs to be between 8 and 32.
+ different: The two entered passwords do not match.
+ new_pass:
+ label: New password
+ pass_confirm:
+ label: Confirm new password
+ interface:
+ heading: Interface
+ lang:
+ label: Interface language
+ text: User interface language. It will change when you refresh the page.
+ my_logins:
+ title: My logins
+ label: Log in or sign up on this site using these accounts.
+ modal_title: Remove login
+ modal_content: Are you sure you want to remove this login from your account?
+ modal_confirm_btn: Remove
+ remove_success: Removed successfully
+ toast:
+ update: update success
+ update_password: Password changed successfully.
+ flag_success: Thanks for flagging.
+ forbidden_operate_self: Forbidden to operate on yourself
+ review: Your revision will show after review.
+ sent_success: Sent successfully
+ related_question:
+ title: Related
+ answers: answers
+ linked_question:
+ title: Linked
+ description: Posts linked to
+ no_linked_question: No contents linked from this content.
+ invite_to_answer:
+ title: People Asked
+ desc: Select people who you think might know the answer.
+ invite: Invite to answer
+ add: Add people
+ search: Search people
+ question_detail:
+ action: Action
+ created: Created
+ Asked: Asked
+ asked: asked
+ update: Modified
+ Edited: Edited
+ edit: edited
+ commented: commented
+ Views: Viewed
+ Follow: Follow
+ Following: Following
+ follow_tip: Follow this question to receive notifications
+ answered: answered
+ closed_in: Closed in
+ show_exist: Show existing question.
+ useful: Useful
+ question_useful: It is useful and clear
+ question_un_useful: It is unclear or not useful
+ question_bookmark: Bookmark this question
+ answer_useful: It is useful
+ answer_un_useful: It is not useful
+ answers:
+ title: Answers
+ score: Score
+ newest: Newest
+ oldest: Oldest
+ btn_accept: Accept
+ btn_accepted: Accepted
+ write_answer:
+ title: Your Answer
+ edit_answer: Edit my existing answer
+ btn_name: Post your answer
+ add_another_answer: Add another answer
+ confirm_title: Continue to answer
+ continue: Continue
+ confirm_info: >-
+ Are you sure you want to add another answer?
You could use the edit link to refine and improve your existing answer, instead.
+ empty: Answer cannot be empty.
+ characters: content must be at least 6 characters in length.
+ tips:
+ header_1: Thanks for your answer
+ li1_1: Please be sure to answer the question . Provide details and share your research.
+ li1_2: Back up any statements you make with references or personal experience.
+ header_2: But avoid ...
+ li2_1: Asking for help, seeking clarification, or responding to other answers.
+ reopen:
+ confirm_btn: Reopen
+ title: Reopen this post
+ content: Are you sure you want to reopen?
+ list:
+ confirm_btn: List
+ title: List this 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?
+ pin:
+ title: Pin this post
+ content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
+ confirm_btn: Pin
+ delete:
+ title: Delete this post
+ question: >-
+ We do not recommend deleting questions with answers because doing so deprives future readers of this knowledge.Repeated deletion of answered questions can result in your account being blocked from asking. Are you sure you wish to delete?
+ answer_accepted: >-
+
We do not recommend deleting accepted answer because doing so deprives future readers of this knowledge.
Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
+ other: Are you sure you wish to delete?
+ tip_answer_deleted: This answer has been deleted
+ undelete_title: Undelete this post
+ undelete_desc: Are you sure you wish to undelete?
+ btns:
+ confirm: Confirm
+ cancel: Cancel
+ edit: Edit
+ save: Save
+ delete: Delete
+ undelete: Undelete
+ list: List
+ unlist: Unlist
+ unlisted: Unlisted
+ login: Log in
+ signup: Sign up
+ logout: Log out
+ verify: Verify
+ create: Create
+ approve: Approve
+ reject: Reject
+ skip: Skip
+ discard_draft: Discard draft
+ pinned: Pinned
+ all: All
+ question: Question
+ answer: Answer
+ comment: Comment
+ refresh: Refresh
+ resend: Resend
+ deactivate: Deactivate
+ active: Active
+ suspend: Suspend
+ unsuspend: Unsuspend
+ close: Close
+ reopen: Reopen
+ ok: OK
+ light: Light
+ dark: Dark
+ system_setting: System setting
+ default: Default
+ reset: Reset
+ tag: Tag
+ post_lowercase: post
+ filter: Filter
+ ignore: Ignore
+ submit: Submit
+ normal: Normal
+ closed: Closed
+ deleted: Deleted
+ deleted_permanently: Deleted permanently
+ pending: Pending
+ more: More
+ view: View
+ card: Card
+ compact: Compact
+ display_below: Display below
+ always_display: Always display
+ or: or
+ back_sites: Back to sites
+ search:
+ title: Search Results
+ keywords: Keywords
+ options: Options
+ follow: Follow
+ following: Following
+ counts: "{{count}} Results"
+ counts_loading: "... Results"
+ more: More
+ sort_btns:
+ relevance: Relevance
+ newest: Newest
+ active: Active
+ score: Score
+ more: More
+ tips:
+ title: Advanced Search Tips
+ tag: "<1>[tag]1> search with a tag"
+ user: "<1>user:username1> search by author"
+ answer: "<1>answers:01> unanswered questions"
+ score: "<1>score:31> posts with a 3+ score"
+ question: "<1>is:question1> search questions"
+ is_answer: "<1>is:answer1> search answers"
+ empty: We couldn't find anything. Try different or less specific keywords.
+ share:
+ name: Share
+ copy: Copy link
+ via: Share post via...
+ copied: Copied
+ facebook: Share to Facebook
+ twitter: Share to X
+ cannot_vote_for_self: You can't vote for your own post.
+ modal_confirm:
+ title: Error...
+ delete_permanently:
+ title: Delete permanently
+ content: Are you sure you want to delete permanently?
+ account_result:
+ success: Your new account is confirmed; you will be redirected to the home page.
+ link: Continue to homepage
+ oops: Oops!
+ invalid: The link you used no longer works.
+ confirm_new_email: Your email has been updated.
+ confirm_new_email_invalid: >-
+ Sorry, this confirmation link is no longer valid. Perhaps your email was already changed?
+ unsubscribe:
+ page_title: Unsubscribe
+ success_title: Unsubscribe Successful
+ success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
+ link: Change settings
+ question:
+ following_tags: Following Tags
+ edit: Edit
+ save: Save
+ follow_tag_tip: Follow tags to curate your list of questions.
+ hot_questions: Hot Questions
+ all_questions: All Questions
+ x_questions: "{{ count }} Questions"
+ x_answers: "{{ count }} answers"
+ x_posts: "{{ count }} Posts"
+ questions: Questions
+ answers: Answers
+ newest: Newest
+ active: Active
+ hot: Hot
+ frequent: Frequent
+ recommend: Recommend
+ score: Score
+ unanswered: Unanswered
+ modified: modified
+ answered: answered
+ asked: asked
+ closed: closed
+ follow_a_tag: Follow a tag
+ more: More
+ personal:
+ overview: Overview
+ answers: Answers
+ answer: answer
+ questions: Questions
+ question: question
+ bookmarks: Bookmarks
+ reputation: Reputation
+ comments: Comments
+ votes: Votes
+ badges: Badges
+ newest: Newest
+ score: Score
+ edit_profile: Edit profile
+ visited_x_days: "Visited {{ count }} days"
+ viewed: Viewed
+ joined: Joined
+ comma: ","
+ last_login: Seen
+ about_me: About Me
+ about_me_empty: "// Hello, World !"
+ top_answers: Top Answers
+ top_questions: Top Questions
+ stats: Stats
+ list_empty: No posts found. Perhaps you'd like to select a different tab?
+ content_empty: No posts found.
+ accepted: Accepted
+ answered: answered
+ asked: asked
+ downvoted: downvoted
+ mod_short: MOD
+ mod_long: Moderators
+ x_reputation: reputation
+ x_votes: votes received
+ x_answers: answers
+ x_questions: questions
+ recent_badges: Recent Badges
+ install:
+ title: Installation
+ next: Next
+ done: Done
+ config_yaml_error: Can't create the config.yaml file.
+ lang:
+ label: Please choose a language
+ db_type:
+ label: Database engine
+ db_username:
+ label: Username
+ placeholder: root
+ msg: Username cannot be empty.
+ db_password:
+ label: Password
+ placeholder: root
+ msg: Password cannot be empty.
+ db_host:
+ label: Database host
+ placeholder: "db:3306"
+ msg: Database host cannot be empty.
+ db_name:
+ label: Database name
+ placeholder: answer
+ msg: Database name cannot be empty.
+ db_file:
+ label: Database file
+ placeholder: /data/answer.db
+ msg: Database file cannot be empty.
+ ssl_enabled:
+ label: Enable SSL
+ ssl_enabled_on:
+ label: On
+ ssl_enabled_off:
+ label: Off
+ ssl_mode:
+ label: SSL Mode
+ ssl_root_cert:
+ placeholder: sslrootcert file path
+ msg: Path to sslrootcert file cannot be empty
+ ssl_cert:
+ placeholder: sslcert file path
+ msg: Path to sslcert file cannot be empty
+ ssl_key:
+ placeholder: sslkey file path
+ msg: Path to sslkey file cannot be empty
+ config_yaml:
+ title: Create config.yaml
+ label: The config.yaml file created.
+ desc: >-
+ You can create the <1>config.yaml1> file manually in the <1>/var/wwww/xxx/1> directory and paste the following text into it.
+ info: After you've done that, click "Next" button.
+ site_information: Site Information
+ admin_account: Admin Account
+ site_name:
+ label: Site name
+ msg: Site name cannot be empty.
+ msg_max_length: Site name must be at maximum 30 characters in length.
+ site_url:
+ label: Site URL
+ text: The address of your site.
+ msg:
+ empty: Site URL cannot be empty.
+ incorrect: Site URL incorrect format.
+ max_length: Site URL must be at maximum 512 characters in length.
+ contact_email:
+ label: Contact email
+ text: Email address of key contact responsible for this site.
+ msg:
+ empty: Contact email cannot be empty.
+ incorrect: Contact email incorrect format.
+ login_required:
+ label: Private
+ switch: Login required
+ text: Only logged in users can access this community.
+ admin_name:
+ label: Name
+ msg: Name cannot be empty.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ msg_max_length: Name must be between 2 to 30 characters in length.
+ admin_password:
+ label: Password
+ text: >-
+ You will need this password to log in. Please store it in a secure location.
+ msg: Password cannot be empty.
+ msg_min_length: Password must be at least 8 characters in length.
+ msg_max_length: Password must be at maximum 32 characters in length.
+ admin_confirm_password:
+ label: "Confirm Password"
+ text: "Please re-enter your password to confirm."
+ msg: "Confirm password does not match."
+ admin_email:
+ label: Email
+ text: You will need this email to log in.
+ msg:
+ empty: Email cannot be empty.
+ incorrect: Email incorrect format.
+ ready_title: Your site is ready
+ ready_desc: >-
+ If you ever feel like changing more settings, visit <1>admin section1>; find it in the site menu.
+ good_luck: "Have fun, and good luck!"
+ warn_title: Warning
+ warn_desc: >-
+ The file <1>config.yaml1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
+ install_now: You may try <1>installing now1>.
+ installed: Already installed
+ installed_desc: >-
+ You appear to have already installed. To reinstall please clear your old database tables first.
+ db_failed: Database connection failed
+ db_failed_desc: >-
+ This either means that the database information in your <1>config.yaml1> file is incorrect or that contact with the database server could not be established. This could mean your host's database server is down.
+ counts:
+ views: views
+ votes: votes
+ answers: answers
+ accepted: Accepted
+ page_error:
+ http_error: HTTP Error {{ code }}
+ desc_403: You don't have permission to access this page.
+ desc_404: Unfortunately, this page doesn't exist.
+ desc_50X: The server encountered an error and could not complete your request.
+ back_home: Back to homepage
+ page_maintenance:
+ desc: "We are under maintenance, we'll be back soon."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Contents
+ questions: Questions
+ answers: Answers
+ users: Users
+ badges: Badges
+ flags: Flags
+ settings: Settings
+ general: General
+ interface: Interface
+ smtp: SMTP
+ branding: Branding
+ legal: Legal
+ write: Write
+ terms: Terms
+ tos: Terms of Service
+ privacy: Privacy
+ seo: SEO
+ customize: Customize
+ themes: Themes
+ login: Login
+ privileges: Privileges
+ plugins: Plugins
+ installed_plugins: Installed Plugins
+ apperance: Appearance
+ website_welcome: Welcome to {{site_name}}
+ user_center:
+ login: Login
+ qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
+ login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
+ badges:
+ modal:
+ title: Congratulations
+ content: You've earned a new badge.
+ close: Close
+ confirm: View badges
+ title: Badges
+ awarded: Awarded
+ earned_×: Earned ×{{ number }}
+ ×_awarded: "{{ number }} awarded"
+ can_earn_multiple: You can earn this multiple times.
+ earned: Earned
+ admin:
+ admin_header:
+ title: Admin
+ dashboard:
+ title: Dashboard
+ welcome: Welcome to Admin!
+ site_statistics: Site statistics
+ questions: "Questions:"
+ resolved: "Resolved:"
+ unanswered: "Unanswered:"
+ answers: "Answers:"
+ comments: "Comments:"
+ votes: "Votes:"
+ users: "Users:"
+ flags: "Flags:"
+ reviews: "Reviews:"
+ site_health: Site health
+ version: "Version:"
+ https: "HTTPS:"
+ upload_folder: "Upload folder:"
+ run_mode: "Running mode:"
+ private: Private
+ public: Public
+ smtp: "SMTP:"
+ timezone: "Timezone:"
+ system_info: System info
+ go_version: "Go version:"
+ database: "Database:"
+ database_size: "Database size:"
+ storage_used: "Storage used:"
+ uptime: "Uptime:"
+ links: Links
+ plugins: Plugins
+ github: GitHub
+ blog: Blog
+ contact: Contact
+ forum: Forum
+ documents: Documents
+ feedback: Feedback
+ support: Support
+ review: Review
+ config: Config
+ update_to: Update to
+ latest: Latest
+ check_failed: Check failed
+ "yes": "Yes"
+ "no": "No"
+ not_allowed: Not allowed
+ allowed: Allowed
+ enabled: Enabled
+ disabled: Disabled
+ writable: Writable
+ not_writable: Not writable
+ flags:
+ title: Flags
+ pending: Pending
+ completed: Completed
+ flagged: Flagged
+ flagged_type: Flagged {{ type }}
+ created: Created
+ action: Action
+ review: Review
+ user_role_modal:
+ title: Change user role to...
+ btn_cancel: Cancel
+ btn_submit: Submit
+ new_password_modal:
+ title: Set new password
+ form:
+ fields:
+ password:
+ label: Password
+ text: The user will be logged out and need to login again.
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ edit_profile_modal:
+ title: Edit profile
+ form:
+ fields:
+ display_name:
+ label: Display name
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Username
+ msg_range: Username must be 2-30 characters in length.
+ email:
+ label: Email
+ msg_invalid: Invalid Email Address.
+ edit_success: Edited successfully
+ btn_cancel: Cancel
+ btn_submit: Submit
+ user_modal:
+ title: Add new user
+ form:
+ fields:
+ users:
+ label: Bulk add user
+ placeholder: "John Smith, john@example.com, BUSYopr2\nAlice, alice@example.com, fpDntV8q"
+ text: Separate “name, email, password” with commas. One user per line.
+ msg: "Please enter the user's email, one per line."
+ display_name:
+ label: Display name
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: Email
+ msg: Email is not valid.
+ password:
+ label: Password
+ msg: Password must be at 8-32 characters in length.
+ btn_cancel: Cancel
+ btn_submit: Submit
+ users:
+ title: Users
+ name: Name
+ email: Email
+ reputation: Reputation
+ created_at: Created time
+ delete_at: Deleted time
+ suspend_at: Suspended time
+ suspend_until: Suspend until
+ status: Status
+ role: Role
+ action: Action
+ change: Change
+ all: All
+ staff: Staff
+ more: More
+ inactive: Inactive
+ suspended: Suspended
+ deleted: Deleted
+ normal: Normal
+ Moderator: Moderator
+ Admin: Admin
+ User: User
+ filter:
+ placeholder: "Filter by name, user:id"
+ set_new_password: Set new password
+ edit_profile: Edit profile
+ change_status: Change status
+ change_role: Change role
+ show_logs: Show logs
+ add_user: Add user
+ deactivate_user:
+ title: Deactivate user
+ content: An inactive user must re-validate their email.
+ delete_user:
+ title: Delete this user
+ content: Are you sure you want to delete this user? This is permanent!
+ remove: Remove their content
+ label: Remove all questions, answers, comments, etc.
+ text: Don’t check this if you wish to only delete the user’s account.
+ suspend_user:
+ title: Suspend this user
+ content: A suspended user can't log in.
+ label: How long will the user be suspended for?
+ forever: Forever
+ questions:
+ page_title: Questions
+ unlisted: Unlisted
+ post: Post
+ votes: Votes
+ answers: Answers
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ pending: Pending
+ filter:
+ placeholder: "Filter by title, question:id"
+ answers:
+ page_title: Answers
+ post: Post
+ votes: Votes
+ created: Created
+ status: Status
+ action: Action
+ change: Change
+ filter:
+ placeholder: "Filter by title, answer:id"
+ general:
+ page_title: General
+ name:
+ label: Site name
+ msg: Site name cannot be empty.
+ text: "The name of this site, as used in the title tag."
+ site_url:
+ label: Site URL
+ msg: Site url cannot be empty.
+ validate: Please enter a valid URL.
+ text: The address of your site.
+ short_desc:
+ label: Short site description
+ msg: Short site description cannot be empty.
+ text: "Short description, as used in the title tag on homepage."
+ desc:
+ label: Site description
+ msg: Site description cannot be empty.
+ text: "Describe this site in one sentence, as used in the meta description tag."
+ contact_email:
+ label: Contact email
+ msg: Contact email cannot be empty.
+ validate: Contact email is not valid.
+ text: Email address of key contact responsible for this site.
+ check_update:
+ label: Software updates
+ text: Automatically check for updates
+ interface:
+ page_title: Interface
+ language:
+ label: Interface language
+ msg: Interface language cannot be empty.
+ text: User interface language. It will change when you refresh the page.
+ time_zone:
+ label: Timezone
+ msg: Timezone cannot be empty.
+ text: Choose a city in the same timezone as you.
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: Gravatar base URL
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: From email
+ msg: From email cannot be empty.
+ text: The email address which emails are sent from.
+ from_name:
+ label: From name
+ msg: From name cannot be empty.
+ text: The name which emails are sent from.
+ smtp_host:
+ label: SMTP host
+ msg: SMTP host cannot be empty.
+ text: Your mail server.
+ encryption:
+ label: Encryption
+ msg: Encryption cannot be empty.
+ text: For most servers SSL is the recommended option.
+ ssl: SSL
+ tls: TLS
+ none: None
+ smtp_port:
+ label: SMTP port
+ msg: SMTP port must be number 1 ~ 65535.
+ text: The port to your mail server.
+ smtp_username:
+ label: SMTP username
+ msg: SMTP username cannot be empty.
+ smtp_password:
+ label: SMTP password
+ msg: SMTP password cannot be empty.
+ test_email_recipient:
+ label: Test email recipients
+ text: Provide email address that will receive test sends.
+ msg: Test email recipients is invalid
+ smtp_authentication:
+ label: Enable authentication
+ title: SMTP authentication
+ msg: SMTP authentication cannot be empty.
+ "yes": "Yes"
+ "no": "No"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo
+ msg: Logo cannot be empty.
+ text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
+ mobile_logo:
+ label: Mobile logo
+ text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
+ square_icon:
+ label: Square icon
+ msg: Square icon cannot be empty.
+ text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
+ favicon:
+ label: Favicon
+ text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
+ legal:
+ page_title: Legal
+ terms_of_service:
+ label: Terms of service
+ text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
+ privacy_policy:
+ label: Privacy policy
+ text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
+ external_content_display:
+ label: External content
+ text: "Content includes images, videos, and media embedded from external websites."
+ always_display: Always display external content
+ ask_before_display: Ask before displaying external content
+ write:
+ page_title: Write
+ min_content:
+ label: Minimum question body length
+ text: Minimum allowed question body length in characters.
+ restrict_answer:
+ title: Answer write
+ label: Each user can only write one answer for each question
+ text: "Turn off to allow users to write multiple answers to the same question, which may cause answers to be unfocused."
+ min_tags:
+ label: "Minimum tags per question"
+ text: "Minimum number of tags required in a question."
+ recommend_tags:
+ label: Recommend tags
+ text: "Recommend tags will show in the dropdown list by default."
+ msg:
+ contain_reserved: "recommended tags cannot contain reserved tags"
+ required_tag:
+ title: Set required tags
+ label: Set “Recommend tags” as required tags
+ text: "Every new question must have at least one recommend tag."
+ reserved_tags:
+ label: Reserved tags
+ text: "Reserved tags can only be used by moderator."
+ image_size:
+ label: Max image size (MB)
+ text: "The maximum image upload size."
+ attachment_size:
+ label: Max attachment size (MB)
+ text: "The maximum attachment files upload size."
+ image_megapixels:
+ label: Max image megapixels
+ text: "Maximum number of megapixels allowed for an image."
+ image_extensions:
+ label: Authorized image extensions
+ text: "A list of file extensions allowed for image display, separate with commas."
+ attachment_extensions:
+ label: Authorized attachment extensions
+ text: "A list of file extensions allowed for upload, separate with commas. WARNING: Allowing uploads may cause security issues."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Custom URL structures can improve the usability, and forward-compatibility of your links.
+ robots:
+ label: robots.txt
+ text: This will permanently override any related site settings.
+ themes:
+ page_title: Themes
+ themes:
+ label: Themes
+ text: Select an existing theme.
+ color_scheme:
+ label: Color scheme
+ navbar_style:
+ label: Navbar background style
+ primary_color:
+ label: Primary color
+ text: Modify the colors used by your themes
+ css_and_html:
+ page_title: CSS and HTML
+ custom_css:
+ label: Custom CSS
+ text: >
+
+ head:
+ label: Head
+ text: >
+
+ header:
+ label: Header
+ text: >
+
+ footer:
+ label: Footer
+ text: This will insert before </body>.
+ sidebar:
+ label: Sidebar
+ text: This will insert in sidebar.
+ login:
+ page_title: Login
+ membership:
+ title: Membership
+ label: Allow new registrations
+ text: Turn off to prevent anyone from creating a new account.
+ email_registration:
+ title: Email registration
+ label: Allow email registration
+ text: Turn off to prevent anyone creating new account through email.
+ allowed_email_domains:
+ title: Allowed email domains
+ text: Email domains that users must register accounts with. One domain per line. Ignored when empty.
+ private:
+ title: Private
+ label: Login required
+ text: Only logged in users can access this community.
+ password_login:
+ title: Password login
+ label: Allow email and password login
+ text: "WARNING: If turn off, you may be unable to log in if you have not previously configured other login method."
+ installed_plugins:
+ title: Installed Plugins
+ plugin_link: Plugins extend and expand the functionality. You may find plugins in the <1>Plugin Repository1>.
+ filter:
+ all: All
+ active: Active
+ inactive: Inactive
+ outdated: Outdated
+ plugins:
+ label: Plugins
+ text: Select an existing plugin.
+ name: Name
+ version: Version
+ status: Status
+ action: Action
+ deactivate: Deactivate
+ activate: Activate
+ settings: Settings
+ settings_users:
+ title: Users
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: Gravatar Base URL
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ profile_editable:
+ title: Profile editable
+ allow_update_display_name:
+ label: Allow users to change their display name
+ allow_update_username:
+ label: Allow users to change their username
+ allow_update_avatar:
+ label: Allow users to change their profile image
+ allow_update_bio:
+ label: Allow users to change their about me
+ allow_update_website:
+ label: Allow users to change their website
+ allow_update_location:
+ label: Allow users to change their location
+ privilege:
+ title: Privileges
+ 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
+ badges:
+ action: Action
+ active: Active
+ activate: Activate
+ all: All
+ awards: Awards
+ deactivate: Deactivate
+ filter:
+ placeholder: Filter by name, badge:id
+ group: Group
+ inactive: Inactive
+ name: Name
+ show_logs: Show logs
+ status: Status
+ title: Badges
+ form:
+ optional: (optional)
+ empty: cannot be empty
+ invalid: is invalid
+ btn_submit: Save
+ not_found_props: "Required property {{ key }} not found."
+ select: Select
+ page_review:
+ review: Review
+ proposed: proposed
+ question_edit: Question edit
+ answer_edit: Answer edit
+ tag_edit: Tag edit
+ edit_summary: Edit summary
+ edit_question: Edit question
+ edit_answer: Edit answer
+ edit_tag: Edit tag
+ empty: No review tasks left.
+ 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
+ timeline:
+ undeleted: undeleted
+ deleted: deleted
+ downvote: downvote
+ upvote: upvote
+ accept: accept
+ cancelled: cancelled
+ commented: commented
+ rollback: rollback
+ edited: edited
+ answered: answered
+ asked: asked
+ closed: closed
+ reopened: reopened
+ created: created
+ pin: pinned
+ unpin: unpinned
+ show: listed
+ hide: unlisted
+ title: "History for"
+ tag_title: "Timeline for"
+ show_votes: "Show votes"
+ n_or_a: N/A
+ title_for_question: "Timeline for"
+ title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
+ title_for_tag: "Timeline for tag"
+ datetime: Datetime
+ type: Type
+ by: By
+ comment: Comment
+ no_data: "We couldn't find anything."
+ users:
+ title: Users
+ users_with_the_most_reputation: Users with the highest reputation scores this week
+ users_with_the_most_vote: Users who voted the most this week
+ staffs: Our community staff
+ reputation: reputation
+ votes: votes
+ prompt:
+ leave_page: Are you sure you want to leave the page?
+ changes_not_save: Your changes may not be saved.
+ draft:
+ discard_confirm: Are you sure you want to discard your draft?
+ messages:
+ post_deleted: This post has been deleted.
+ post_cancel_deleted: This post has been undeleted.
+ post_pin: This post has been pinned.
+ post_unpin: This post has been unpinned.
+ post_hide_list: This post has been hidden from list.
+ post_show_list: This post has been shown to list.
+ post_reopen: This post has been reopened.
+ 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_closed: This post has been closed.
+ answer_deleted: This answer has been deleted.
+ answer_cancel_deleted: This answer has been undeleted.
+ change_user_role: This user's role has been changed.
+ user_inactive: This user is already inactive.
+ user_normal: This user is already normal.
+ user_suspended: This user has been suspended.
+ user_deleted: This user has been deleted.
+ badge_activated: This badge has been activated.
+ badge_inactivated: This badge has been inactivated.
+ users_deleted: These users have been deleted.
+ posts_deleted: These questions have been deleted.
+ answers_deleted: These answers have been deleted.
+ copy: Copy to clipboard
+ copied: Copied
+ external_content_warning: External images/media are not displayed.
+
+
diff --git a/data/i18n/da_DK.yaml b/data/i18n/da_DK.yaml
new file mode 100644
index 000000000..63da094c4
--- /dev/null
+++ b/data/i18n/da_DK.yaml
@@ -0,0 +1,2359 @@
+# 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.
+
+# The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Gennemført.
+ unknown:
+ other: Ukendt fejl.
+ request_format_error:
+ other: Forespørgselsformat er ikke gyldigt.
+ unauthorized_error:
+ other: Uautoriseret.
+ database_error:
+ other: Data-server fejl.
+ forbidden_error:
+ other: Forbudt.
+ duplicate_request_error:
+ other: Duplilkeret indenselse.
+ action:
+ report:
+ other: Anmeld
+ edit:
+ other: Rediger
+ delete:
+ other: Slet
+ close:
+ other: Luk
+ reopen:
+ other: Genåbn
+ forbidden_error:
+ other: Forbudt.
+ pin:
+ other: Fastgør
+ hide:
+ other: Afliste
+ unpin:
+ other: Frigør
+ show:
+ other: Liste
+ invite_someone_to_answer:
+ other: Rediger
+ undelete:
+ other: Genopret
+ merge:
+ other: Merge
+ role:
+ name:
+ user:
+ other: Bruger
+ admin:
+ other: Administrator
+ moderator:
+ other: Moderator
+ description:
+ user:
+ other: Standard uden særlig adgang.
+ admin:
+ other: Hav den fulde magt til at få adgang til webstedet.
+ moderator:
+ other: Har adgang til alle indlæg undtagen administratorindstillinger.
+ privilege:
+ level_1:
+ description:
+ other: Niveau 1 (mindre omdømme kræves for private team, gruppe)
+ level_2:
+ description:
+ other: Niveau 2 (lav omdømme kræves for opstart fællesskab)
+ level_3:
+ description:
+ other: Niveau 3 (højt omdømme kræves for moden fællesskab)
+ level_custom:
+ description:
+ other: Brugerdefineret Niveau
+ rank_question_add_label:
+ other: Stil spørgsmål
+ rank_answer_add_label:
+ other: Skriv svar
+ rank_comment_add_label:
+ other: Skriv kommentar
+ rank_report_add_label:
+ other: Anmeld
+ rank_comment_vote_up_label:
+ other: Op-stem kommentar
+ rank_link_url_limit_label:
+ other: Skriv mere end 2 links ad gangen
+ rank_question_vote_up_label:
+ other: Op-stem spørgsmål
+ rank_answer_vote_up_label:
+ other: Op-stem svar
+ rank_question_vote_down_label:
+ other: Ned-stem spørgsmål
+ rank_answer_vote_down_label:
+ other: Ned-stem svar
+ rank_invite_someone_to_answer_label:
+ other: Inviter nogen til at svare
+ rank_tag_add_label:
+ other: Opret et nyt tag
+ rank_tag_edit_label:
+ other: Rediger tag beskrivelse (skal gennemgås)
+ rank_question_edit_label:
+ other: Rediger andres spørgsmål (skal gennemgås)
+ rank_answer_edit_label:
+ other: Redigere andres svar (skal gennemgås)
+ rank_question_edit_without_review_label:
+ other: Rediger andres spørgsmål uden gennemgang
+ rank_answer_edit_without_review_label:
+ other: Rediger andres svar uden gennemgang
+ rank_question_audit_label:
+ other: Gennemse spørgsmål redigeringer
+ rank_answer_audit_label:
+ other: Gennemgå svar redigeringer
+ rank_tag_audit_label:
+ other: Gennemse tag redigeringer
+ rank_tag_edit_without_review_label:
+ other: Rediger tag beskrivelse uden gennemgang
+ rank_tag_synonym_label:
+ other: Administrer tag synonymer
+ email:
+ other: E-mail
+ e_mail:
+ other: E-mail
+ password:
+ other: Adgangskode
+ pass:
+ other: Adgangskode
+ old_pass:
+ other: Current password
+ original_text:
+ other: Dette indlæg
+ email_or_password_wrong_error:
+ other: E-mail og adgangskode stemmer ikke overens.
+ error:
+ common:
+ invalid_url:
+ other: Ugyldig URL.
+ status_invalid:
+ other: Invalid status.
+ password:
+ space_invalid:
+ other: Adgangskoden må ikke indeholde mellemrum.
+ admin:
+ cannot_update_their_password:
+ other: Du kan ikke ændre din adgangskode.
+ cannot_edit_their_profile:
+ other: Du kan ikke ændre din profil.
+ cannot_modify_self_status:
+ other: Du kan ikke ændre din status.
+ email_or_password_wrong:
+ other: E-mail og adgangskode stemmer ikke overens.
+ answer:
+ not_found:
+ other: Svar ikke fundet.
+ cannot_deleted:
+ other: Ingen tilladelser til at slette.
+ cannot_update:
+ other: Ingen tilladelse til at opdatere.
+ question_closed_cannot_add:
+ other: Spørgsmål er lukket og kan ikke tilføjes.
+ content_cannot_empty:
+ other: Answer content cannot be empty.
+ comment:
+ edit_without_permission:
+ other: Kommentar er ikke tilladt at redigere.
+ not_found:
+ other: Kommentar ikke fundet.
+ cannot_edit_after_deadline:
+ other: Kommentaren er for gammel til at blive redigeret.
+ content_cannot_empty:
+ other: Comment content cannot be empty.
+ email:
+ duplicate:
+ other: Email eksisterer allerede.
+ need_to_be_verified:
+ other: E-mail skal bekræftes.
+ verify_url_expired:
+ other: Email bekræftet URL er udløbet. Send venligst e-mailen igen.
+ illegal_email_domain_error:
+ other: E-mail er ikke tilladt fra dette e-mail-domæne. Brug venligst et andet.
+ lang:
+ not_found:
+ other: Sprog-fil kunne ikke findes.
+ object:
+ captcha_verification_failed:
+ other: Captcha er forkert.
+ disallow_follow:
+ other: Du har ikke tilladelse til at følge.
+ disallow_vote:
+ other: Du har ikke tilladelse til at stemme.
+ disallow_vote_your_self:
+ other: Du kan ikke stemme på dit eget indlæg.
+ not_found:
+ other: Objekt ikke fundet.
+ verification_failed:
+ other: Verifikation mislykkedes.
+ email_or_password_incorrect:
+ other: E-mail og adgangskode stemmer ikke overens.
+ old_password_verification_failed:
+ other: Den gamle adgangskodebekræftelse mislykkedes
+ new_password_same_as_previous_setting:
+ other: Den nye adgangskode er den samme som den foregående.
+ already_deleted:
+ other: Dette indlæg er blevet slettet.
+ meta:
+ object_not_found:
+ other: Metaobjekt ikke fundet
+ question:
+ already_deleted:
+ other: Dette indlæg er blevet slettet.
+ under_review:
+ other: Dit indlæg afventer gennemgang. Det vil være synligt, når det er blevet godkendt.
+ not_found:
+ other: Spørgsmål ikke fundet.
+ cannot_deleted:
+ other: Ingen tilladelser til at slette.
+ cannot_close:
+ other: Ingen tilladelse til at lukke.
+ cannot_update:
+ other: Ingen tilladelse til at opdatere.
+ content_cannot_empty:
+ other: Content cannot be empty.
+ content_less_than_minimum:
+ other: Not enough content entered.
+ rank:
+ fail_to_meet_the_condition:
+ other: Omdømmelse rang opfylder ikke betingelsen.
+ vote_fail_to_meet_the_condition:
+ other: Tak for feedback. Du skal mindst have {{.Rank}} ry for at afgive en stemme.
+ no_enough_rank_to_operate:
+ other: Du skal mindst {{.Rank}} omdømme for at gøre dette.
+ report:
+ handle_failed:
+ other: Report handle failed.
+ not_found:
+ other: Rapport ikke fundet.
+ tag:
+ already_exist:
+ other: Tag findes allerede.
+ not_found:
+ other: Tag blev ikke fundet.
+ recommend_tag_not_found:
+ other: Anbefal tag eksisterer ikke.
+ recommend_tag_enter:
+ other: Indtast mindst et påkrævet tag.
+ not_contain_synonym_tags:
+ other: Må ikke indeholde synonym tags.
+ cannot_update:
+ other: Ingen tilladelse til at opdatere.
+ is_used_cannot_delete:
+ other: Du kan ikke slette et tag, der er i brug.
+ cannot_set_synonym_as_itself:
+ other: Du kan ikke indstille synonymet for det nuværende tag som sig selv.
+ minimum_count:
+ other: Not enough tags were entered.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: Fra-navnet kan ikke være en e-mail-adresse.
+ theme:
+ not_found:
+ other: Tema ikke fundet.
+ revision:
+ review_underway:
+ other: Kan ikke redigere i øjeblikket, der er en version i revisionskøen.
+ no_permission:
+ other: Ingen tilladelse til at revidere.
+ user:
+ external_login_missing_user_id:
+ other: Den tredjepartsplatform giver ikke et unikt UserID, så du kan ikke logge ind, kontakt venligst webstedsadministratoren.
+ external_login_unbinding_forbidden:
+ other: Angiv en adgangskode til din konto, før du fjerner dette login.
+ email_or_password_wrong:
+ other:
+ other: E-mail og adgangskode stemmer ikke overens.
+ not_found:
+ other: Bruger ikke fundet.
+ suspended:
+ other: Brugeren er suspenderet.
+ username_invalid:
+ other: Brugernavn er ugyldigt.
+ username_duplicate:
+ other: Brugernavn er allerede i brug.
+ set_avatar:
+ other: Avatar sæt mislykkedes.
+ cannot_update_your_role:
+ other: Du kan ikke ændre din rolle.
+ not_allowed_registration:
+ other: Webstedet er ikke åbent for registrering.
+ not_allowed_login_via_password:
+ other: I øjeblikket er det ikke tilladt at logge ind via adgangskode.
+ access_denied:
+ other: Adgang nægtet
+ page_access_denied:
+ other: Du har ikke adgang til denne side.
+ add_bulk_users_format_error:
+ other: "Fejl {{.Field}} format nær '{{.Content}}' i linje {{.Line}}. {{.ExtraMessage}}"
+ add_bulk_users_amount_error:
+ other: "Antallet af brugere du tilføjer på én gang skal være i intervallet 1 -{{.MaxAmount}}."
+ status_suspended_forever:
+ other: "This user was suspended forever. This user doesn't meet a community guideline."
+ status_suspended_until:
+ other: "This user was suspended until {{.SuspendedUntil}}. This user doesn't meet a community guideline."
+ status_deleted:
+ other: "This user was deleted."
+ status_inactive:
+ other: "This user is inactive."
+ config:
+ read_config_failed:
+ other: Kunne ikke læse konfigurationen
+ database:
+ connection_failed:
+ other: Database forbindelse mislykkedes
+ create_table_failed:
+ other: Tabellen kunne ikke oprettes
+ install:
+ create_config_failed:
+ other: Kan ikke oprette filen config.yaml.
+ upload:
+ unsupported_file_format:
+ other: Ikke understøttet filformat.
+ site_info:
+ config_not_found:
+ other: Site config ikke fundet.
+ badge:
+ object_not_found:
+ other: Badge object not found
+ reason:
+ spam:
+ name:
+ other: spam
+ desc:
+ other: Dette indlæg er en annonce eller vandalisme. Det er ikke nyttigt eller relevant for det aktuelle emne.
+ rude_or_abusive:
+ name:
+ other: uhøflig eller misbrug
+ desc:
+ other: "A reasonable person would find this content inappropriate for respectful discourse."
+ a_duplicate:
+ name:
+ other: en duplikering
+ desc:
+ other: Dette spørgsmål er blevet stillet før og har allerede et svar.
+ placeholder:
+ other: Indtast linket til eksisterende spørgsmål
+ not_a_answer:
+ name:
+ other: ikke et svar
+ desc:
+ other: "This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question,or deleted altogether."
+ no_longer_needed:
+ name:
+ other: ikke længere nødvendigt
+ desc:
+ other: Denne kommentar er forældet, samtale-agtig eller ikke relevant for dette indlæg.
+ something:
+ name:
+ other: noget andet
+ desc:
+ other: Dette indlæg kræver personalets opmærksomhed af en anden grund, som ikke er nævnt ovenfor.
+ placeholder:
+ other: Lad os vide specifikt, hvad du er bekymret over
+ community_specific:
+ name:
+ other: en fællesskabsspecifik årsag
+ desc:
+ other: Dette spørgsmål opfylder ikke en fællesskabsretningslinje.
+ not_clarity:
+ name:
+ other: kræver detaljer eller klarhed
+ desc:
+ other: Dette spørgsmål indeholder i øjeblikket flere spørgsmål i én. Det bør kun fokusere på ét problem.
+ looks_ok:
+ name:
+ other: ser OK ud
+ desc:
+ other: Dette indlæg er godt som er og ikke lav kvalitet.
+ needs_edit:
+ name:
+ other: har brug for redigering, og jeg gjorde det
+ desc:
+ other: Forbedre og ret selv problemer med dette indlæg.
+ needs_close:
+ name:
+ other: skal lukkes
+ desc:
+ other: Et lukket spørgsmål kan ikke besvares, men du kan stadig redigere, stemme og kommentere.
+ needs_delete:
+ name:
+ other: skal slettes
+ desc:
+ other: Dette indlæg bliver slettet.
+ question:
+ close:
+ duplicate:
+ name:
+ other: spam
+ desc:
+ other: Dette spørgsmål er blevet stillet før og har allerede et svar.
+ guideline:
+ name:
+ other: en fællesskabsspecifik årsag
+ desc:
+ other: Dette spørgsmål opfylder ikke en fællesskabsretningslinje.
+ multiple:
+ name:
+ other: kræver detaljer eller klarhed
+ desc:
+ other: This question currently includes multiple questions in one. It should focus on one problem only.
+ other:
+ name:
+ other: noget andet
+ desc:
+ other: Dette indlæg kræver en anden grund som ikke er nævnt ovenfor.
+ operation_type:
+ asked:
+ other: spurgt
+ answered:
+ other: besvaret
+ modified:
+ other: ændret
+ deleted_title:
+ other: Slettet spørgsmål
+ questions_title:
+ other: Spørgsmål
+ tag:
+ tags_title:
+ other: Tags
+ no_description:
+ other: Tag har ingen beskrivelse.
+ notification:
+ action:
+ update_question:
+ other: opdateret spørgsmål
+ answer_the_question:
+ other: besvaret spørgsmål
+ update_answer:
+ other: opdateret svar
+ accept_answer:
+ other: accepteret svar
+ comment_question:
+ other: kommenteret spørgsmål
+ comment_answer:
+ other: kommenteret svar
+ reply_to_you:
+ other: svarede dig
+ mention_you:
+ other: nævnte dig
+ your_question_is_closed:
+ other: Dit spørgsmål er blevet lukket
+ your_question_was_deleted:
+ other: Dit spørgsmål er blevet slettet
+ your_answer_was_deleted:
+ other: Dit svar er blevet slettet
+ your_comment_was_deleted:
+ other: Din kommentar er slettet
+ up_voted_question:
+ other: op-stemt spørgsmål
+ down_voted_question:
+ other: ned-stemt spørgsmål
+ up_voted_answer:
+ other: op-stemt svar
+ down_voted_answer:
+ other: ned-stemt svar
+ up_voted_comment:
+ other: op-stemt kommentar
+ invited_you_to_answer:
+ other: inviterede dig til at svare
+ earned_badge:
+ other: You've earned the "{{.BadgeName}}" badge
+ email_tpl:
+ change_email:
+ title:
+ other: "[{{.SiteName}}] Bekræft din nye e-mailadresse"
+ body:
+ other: "Confirm your new email address for {{.SiteName}} by clicking on the following link: \n{{.ChangeEmailUrl}} \n\nIf you did not request this change, please ignore this email. \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ new_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} besvarede dit spørgsmål"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ invited_you_to_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} inviterede dig til at svare"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \nI think you may know the answer. \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_comment:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} kommenterede dit indlæg"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.CommentSummary}} \nView it on {{.SiteName}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ new_question:
+ title:
+ other: "[{{.SiteName}}] Nyt spørgsmål: {{.QuestionTitle}}"
+ body:
+ other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen. \n\nUnsubscribe "
+ pass_reset:
+ title:
+ other: "[{{.SiteName }}] Nulstilling af adgangskode"
+ body:
+ other: "Somebody asked to reset your password on {{.SiteName}}. \n\nIf it was not you, you can safely ignore this email. \n\nClick the following link to choose a new password: \n{{.PassResetUrl}} \n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ register:
+ title:
+ other: "[{{.SiteName}}] Bekræft din nye konto"
+ body:
+ other: "Welcome to {{.SiteName}}! \n\nClick the following link to confirm and activate your new account: \n{{.RegisterUrl}} \n\nIf the above link is not clickable, try copying and pasting it into the address bar of your web browser.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ test:
+ title:
+ other: "[{{.SiteName}}] Test E-Mail"
+ body:
+ other: "This is a test email.\n \n\n-- \nNote: This is an automatic system email, please do not reply to this message as your response will not be seen."
+ action_activity_type:
+ upvote:
+ other: stem op
+ upvoted:
+ other: stemt op
+ downvote:
+ other: stem ned
+ downvoted:
+ other: stemt ned
+ accept:
+ other: acceptér
+ accepted:
+ other: accepteret
+ edit:
+ other: rediger
+ review:
+ queued_post:
+ other: Indlæg i kø
+ flagged_post:
+ other: Anmeldt indlæg
+ suggested_post_edit:
+ other: Foreslåede redigeringer
+ reaction:
+ tooltip:
+ other: "{{ .Names }} og {{ .Count }} mere..."
+ badge:
+ default_badges:
+ autobiographer:
+ name:
+ other: Autobiographer
+ desc:
+ other: Filled out profile information.
+ certified:
+ name:
+ other: Certified
+ desc:
+ other: Completed our new user tutorial.
+ editor:
+ name:
+ other: Editor
+ desc:
+ other: First post edit.
+ first_flag:
+ name:
+ other: First Flag
+ desc:
+ other: First flagged a post.
+ first_upvote:
+ name:
+ other: First Upvote
+ desc:
+ other: First up voted a post.
+ first_link:
+ name:
+ other: First Link
+ desc:
+ other: First added a link to another post.
+ first_reaction:
+ name:
+ other: First Reaction
+ desc:
+ other: First reacted to the post.
+ first_share:
+ name:
+ other: First Share
+ desc:
+ other: First shared a post.
+ scholar:
+ name:
+ other: Scholar
+ desc:
+ other: Asked a question and accepted an answer.
+ commentator:
+ name:
+ other: Commentator
+ desc:
+ other: Leave 5 comments.
+ new_user_of_the_month:
+ name:
+ other: New User of the Month
+ desc:
+ other: Outstanding contributions in their first month.
+ read_guidelines:
+ name:
+ other: Read Guidelines
+ desc:
+ other: Read the [community guidelines].
+ reader:
+ name:
+ other: Reader
+ desc:
+ other: Read every answers in a topic with more than 10 answers.
+ welcome:
+ name:
+ other: Welcome
+ desc:
+ other: Received a up vote.
+ nice_share:
+ name:
+ other: Nice Share
+ desc:
+ other: Shared a post with 25 unique visitors.
+ good_share:
+ name:
+ other: Good Share
+ desc:
+ other: Shared a post with 300 unique visitors.
+ great_share:
+ name:
+ other: Great Share
+ desc:
+ other: Shared a post with 1000 unique visitors.
+ out_of_love:
+ name:
+ other: Out of Love
+ desc:
+ other: Used 50 up votes in a day.
+ higher_love:
+ name:
+ other: Higher Love
+ desc:
+ other: Used 50 up votes in a day 5 times.
+ crazy_in_love:
+ name:
+ other: Crazy in Love
+ desc:
+ other: Used 50 up votes in a day 20 times.
+ promoter:
+ name:
+ other: Promoter
+ desc:
+ other: Invited a user.
+ campaigner:
+ name:
+ other: Campaigner
+ desc:
+ other: Invited 3 basic users.
+ champion:
+ name:
+ other: Champion
+ desc:
+ other: Invited 5 members.
+ thank_you:
+ name:
+ other: Thank You
+ desc:
+ other: Has 20 up voted posts and gave 10 up votes.
+ gives_back:
+ name:
+ other: Gives Back
+ desc:
+ other: Has 100 up voted posts and gave 100 up votes.
+ empathetic:
+ name:
+ other: Empathetic
+ desc:
+ other: Has 500 up voted posts and gave 1000 up votes.
+ enthusiast:
+ name:
+ other: Enthusiast
+ desc:
+ other: Visited 10 consecutive days.
+ aficionado:
+ name:
+ other: Aficionado
+ desc:
+ other: Visited 100 consecutive days.
+ devotee:
+ name:
+ other: Devotee
+ desc:
+ other: Visited 365 consecutive days.
+ anniversary:
+ name:
+ other: Anniversary
+ desc:
+ other: Active member for a year, posted at least once.
+ appreciated:
+ name:
+ other: Appreciated
+ desc:
+ other: Received 1 up vote on 20 posts.
+ respected:
+ name:
+ other: Respected
+ desc:
+ other: Received 2 up votes on 100 posts.
+ admired:
+ name:
+ other: Admired
+ desc:
+ other: Received 5 up votes on 300 posts.
+ solved:
+ name:
+ other: Solved
+ desc:
+ other: Have an answer be accepted.
+ guidance_counsellor:
+ name:
+ other: Guidance Counsellor
+ desc:
+ other: Have 10 answers be accepted.
+ know_it_all:
+ name:
+ other: Know-it-All
+ desc:
+ other: Have 50 answers be accepted.
+ solution_institution:
+ name:
+ other: Solution Institution
+ desc:
+ other: Have 150 answers be accepted.
+ nice_answer:
+ name:
+ other: Nice Answer
+ desc:
+ other: Answer score of 10 or more.
+ good_answer:
+ name:
+ other: Good Answer
+ desc:
+ other: Answer score of 25 or more.
+ great_answer:
+ name:
+ other: Great Answer
+ desc:
+ other: Answer score of 50 or more.
+ nice_question:
+ name:
+ other: Nice Question
+ desc:
+ other: Question score of 10 or more.
+ good_question:
+ name:
+ other: Good Question
+ desc:
+ other: Question score of 25 or more.
+ great_question:
+ name:
+ other: Great Question
+ desc:
+ other: Question score of 50 or more.
+ popular_question:
+ name:
+ other: Popular Question
+ desc:
+ other: Question with 500 views.
+ notable_question:
+ name:
+ other: Notable Question
+ desc:
+ other: Question with 1,000 views.
+ famous_question:
+ name:
+ other: Famous Question
+ desc:
+ other: Question with 5,000 views.
+ popular_link:
+ name:
+ other: Popular Link
+ desc:
+ other: Posted an external link with 50 clicks.
+ hot_link:
+ name:
+ other: Hot Link
+ desc:
+ other: Posted an external link with 300 clicks.
+ famous_link:
+ name:
+ other: Famous Link
+ desc:
+ other: Posted an external link with 100 clicks.
+ default_badge_groups:
+ getting_started:
+ name:
+ other: Getting Started
+ community:
+ name:
+ other: Community
+ posting:
+ name:
+ other: Posting
+# The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: Sådan formaterer du
+ desc: >-
+ mention a post: #post_id
to make links
<https://url.com> [Title](https://url.com)put returns between paragraphs
_italic_ or **bold **
indent code by 4 spaces
quote by placing > at start of line
backtick escapes `like _this_`
create code fences with backticks `
``` code here ```
+ pagination:
+ prev: Forrige
+ next: Næste
+ page_title:
+ question: Spørgsmål
+ questions: Spørgsmål
+ tag: Tag
+ tags: Tags
+ tag_wiki: tag wiki
+ create_tag: Opret tag
+ edit_tag: Rediger tag
+ ask_a_question: Create Question
+ edit_question: Rediger spørgsmål
+ edit_answer: Rediger Svar
+ search: Søg
+ posts_containing: Indlæg som indeholder
+ settings: Indstillinger
+ notifications: Notifikationer
+ login: Log Ind
+ sign_up: Tilmeld dig
+ account_recovery: Konto-gendannelse
+ account_activation: Aktivering af konto
+ confirm_email: Bekræft e-mail
+ account_suspended: Konto suspenderet
+ admin: Administrator
+ change_email: Ændre E-Mail
+ install: Answer Installation
+ upgrade: Answer Opgradering
+ maintenance: Vedligeholdelse af websted
+ users: Brugere
+ oauth_callback: Behandler
+ http_404: HTTP Fejl 404
+ http_50X: Http Fejl 500
+ http_403: HTTP Fejl 403
+ logout: Log Ud
+ posts: Posts
+ notifications:
+ title: Notifikationer
+ inbox: Indbakke
+ achievement: Bedrifter
+ new_alerts: Nye adviseringer
+ all_read: Markér alle som læst
+ show_more: Vis mere
+ someone: Nogen
+ inbox_type:
+ all: Alle
+ posts: Indlæg
+ invites: Invitationer
+ votes: Stemmer
+ answer: Answer
+ question: Question
+ badge_award: Badge
+ suspended:
+ title: Din konto er blevet suspenderet
+ until_time: "Din konto blev suspenderet indtil {{ time }}."
+ forever: Denne bruger blev suspenderet for evigt.
+ end: Du opfylder ikke en fællesskabsretningslinje.
+ contact_us: Kontakt os
+ editor:
+ blockquote:
+ text: Citatblok
+ bold:
+ text: Fed
+ chart:
+ text: Diagram
+ flow_chart: Flow- diagram
+ sequence_diagram: Sekvensdiagram
+ class_diagram: Klassediagram
+ state_diagram: Tilstands-diagram
+ entity_relationship_diagram: Enheds-forhold-diagram
+ user_defined_diagram: Brugerdefineret diagram
+ gantt_chart: Gantt- diagram
+ pie_chart: Cirkeldiagram
+ code:
+ text: Kode-eksempel
+ add_code: Tilføj kodeeksempel
+ form:
+ fields:
+ code:
+ label: Kode
+ msg:
+ empty: Kode skal udfyldes.
+ language:
+ label: Sprog
+ placeholder: Automatisk detektering
+ btn_cancel: Annuller
+ btn_confirm: Tilføj
+ formula:
+ text: Formel
+ options:
+ inline: Indlejret formel
+ block: Formel blok
+ heading:
+ text: Overskrift
+ options:
+ h1: Overskrift 1
+ h2: Overskrift 2
+ h3: Overskrift 3
+ h4: Overskrift 4
+ h5: Overskrift 5
+ h6: Overskrift 6
+ help:
+ text: Hjælp
+ hr:
+ text: Vandret streg
+ image:
+ text: Billede
+ add_image: Tilføj billede
+ tab_image: Upload billede
+ form_image:
+ fields:
+ file:
+ label: Billedfil
+ btn: Vælg billede
+ msg:
+ empty: Filen skal udfyldes.
+ only_image: Kun billedfiler er tilladt.
+ max_size: File size cannot exceed {{size}} MB.
+ desc:
+ label: Beskriveslse
+ tab_url: Billede-URL
+ form_url:
+ fields:
+ url:
+ label: Billede-URL
+ msg:
+ empty: Billede-URL skal udfyldes.
+ name:
+ label: Beskriveslse
+ btn_cancel: Annuller
+ btn_confirm: Tilføj
+ uploading: Uploader
+ indent:
+ text: Indrykning
+ outdent:
+ text: Udrykning
+ italic:
+ text: Fremhævning
+ link:
+ text: Link
+ add_link: Tilføj link
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL må ikke være tom.
+ name:
+ label: Beskriveslse
+ btn_cancel: Annuller
+ btn_confirm: Tilføj
+ ordered_list:
+ text: Nummereret liste
+ unordered_list:
+ text: Punktliste
+ table:
+ text: Tabel
+ heading: Overskrift
+ cell: Celle
+ file:
+ text: Attach files
+ not_supported: "Don’t support that file type. Try again with {{file_type}}."
+ max_size: "Attach files size cannot exceed {{size}} MB."
+ close_modal:
+ title: Jeg lukker dette indlæg fordi...
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ remark:
+ empty: skal udfyldes.
+ msg:
+ empty: Vælg en grund.
+ report_modal:
+ flag_title: Jeg markerer for at rapportere dette indlæg som...
+ close_title: Jeg lukker dette indlæg fordi...
+ review_question_title: Gennemgå spørgsmål
+ review_answer_title: Gennemgå svar
+ review_comment_title: Gennemgå kommentar
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ remark:
+ empty: skal udfyldes.
+ msg:
+ empty: Vælg en grund.
+ not_a_url: URL-format er forkert.
+ url_not_match: URL oprindelsen matcher ikke det aktuelle websted.
+ tag_modal:
+ title: Opret et nyt tag
+ form:
+ fields:
+ display_name:
+ label: Visnings-navn
+ msg:
+ empty: Visnings-navn skal udfyldes.
+ range: Visnings-navn på op til 35 tegn.
+ slug_name:
+ label: URL-slug
+ desc: URL slug op til 35 tegn.
+ msg:
+ empty: URL slug må ikke være tom.
+ range: URL slug op til 35 tegn.
+ character: URL slug indeholder ikke tilladte tegn.
+ desc:
+ label: Beskriveslse
+ revision:
+ label: Revision
+ edit_summary:
+ label: Rediger resumé
+ placeholder: >-
+ Forklar kort dine ændringer (korrigeret stavning, fast grammatik, forbedret formatering)
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ btn_post: Send nyt tag
+ tag_info:
+ created_at: Oprettet
+ edited_at: Redigeret
+ history: Historik
+ synonyms:
+ title: Synonymer
+ text: Følgende tags vil blive genmappet til
+ empty: Ingen synonymer fundet.
+ btn_add: Tilføj et synonym
+ btn_edit: Rediger
+ btn_save: Gem
+ synonyms_text: Følgende tags vil blive genmappet til
+ delete:
+ title: Slet dette tag
+ tip_with_posts: >-
+ Vi tillader ikke at slette tag med indlæg .
Fjern venligst dette tag fra indlæggene først.
+ tip_with_synonyms: >-
+ Vi tillader ikke at slette tag med indlæg .
Fjern venligst dette tag fra indlæggene først.
+ tip: Er du sikker på, at du vil slette?
+ close: Luk
+ merge:
+ title: Merge tag
+ source_tag_title: Source tag
+ source_tag_description: The source tag and its associated data will be remapped to the target tag.
+ target_tag_title: Target tag
+ target_tag_description: A synonym between these two tags will be created after merging.
+ no_results: No tags matched
+ btn_submit: Submit
+ btn_close: Close
+ edit_tag:
+ title: Rediger tag
+ default_reason: Rediger tag
+ default_first_reason: Tilføj tag
+ btn_save_edits: Gem ændringer
+ btn_cancel: Annuller
+ dates:
+ long_date: MMM D
+ long_date_with_year: "D MMMM, YYYY"
+ long_date_with_time: "MMM D, ÅÅÅÅ [at] HH:mm"
+ now: nu
+ x_seconds_ago: "{{count}}s siden"
+ x_minutes_ago: "{{count}}s siden"
+ x_hours_ago: "{{count}}t siden"
+ hour: time
+ day: dag
+ hours: timer
+ days: dag
+ month: month
+ months: months
+ year: year
+ reaction:
+ heart: hjerte
+ smile: smil
+ frown: rynke panden
+ btn_label: tilføj eller fjern reaktioner
+ undo_emoji: fortryd {{ emoji }} reaktion
+ react_emoji: reager med {{ emoji }}
+ unreact_emoji: ikke reager med {{ emoji }}
+ comment:
+ btn_add_comment: Tilføj kommentar
+ reply_to: Svar til
+ btn_reply: Svar
+ btn_edit: Rediger
+ btn_delete: Slet
+ btn_flag: Anmeld
+ btn_save_edits: Gem ændringer
+ btn_cancel: Annuller
+ show_more: "{{count}} flere kommentarer"
+ tip_question: >-
+ Brug kommentarer til at bede om mere information eller foreslå forbedringer. Undgå at besvare spørgsmål i kommentarer.
+ tip_answer: >-
+ Brug kommentarer til at svare andre brugere eller give dem besked om ændringer. Hvis du tilføjer nye oplysninger, skal du redigere dit indlæg i stedet for at kommentere.
+ tip_vote: Det tilføjer noget nyttigt til indlægget
+ edit_answer:
+ title: Rediger Svar
+ default_reason: Rediger svar
+ default_first_reason: Tilføj svar
+ form:
+ fields:
+ revision:
+ label: Revision
+ answer:
+ label: Svar
+ feedback:
+ characters: indhold skal være mindst 6 tegn.
+ edit_summary:
+ label: Rediger resumé
+ placeholder: >-
+ Forklar kort dine ændringer (korrigeret stavning, fast grammatik, forbedret formatering)
+ btn_save_edits: Gem ændringer
+ btn_cancel: Annuller
+ tags:
+ title: Tags
+ sort_buttons:
+ popular: Populære
+ name: Navn
+ newest: Nyeste
+ button_follow: Følg
+ button_following: Følger
+ tag_label: spørgsmål
+ search_placeholder: Filtrer efter tag-navn
+ no_desc: Tag har ingen beskrivelse.
+ more: Mere
+ wiki: Wiki
+ ask:
+ title: Create Question
+ edit_title: Rediger spørgsmål
+ default_reason: Rediger spørgsmål
+ default_first_reason: Create question
+ similar_questions: Lignende spørgsmål
+ form:
+ fields:
+ revision:
+ label: Revision
+ title:
+ label: Titel
+ placeholder: What's your topic? Be specific.
+ msg:
+ empty: Titel må ikke være tom.
+ range: Titel på op til 150 tegn
+ body:
+ label: Brødtekst
+ msg:
+ empty: Brødtekst skal udfyldes.
+ hint:
+ optional_body: Describe what the question is about.
+ minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ tags:
+ label: Tags
+ msg:
+ empty: Tags må ikke være tom.
+ answer:
+ label: Svar
+ msg:
+ empty: Svar må ikke være tomt.
+ edit_summary:
+ label: Rediger resumé
+ placeholder: >-
+ Forklar kort dine ændringer (korrigeret stavning, fast grammatik, forbedret formatering)
+ btn_post_question: Indsend dit spørgsmål
+ btn_save_edits: Gem ændringer
+ answer_question: Besvar dit eget spørgsmål
+ post_question&answer: Send dit spørgsmål og svar
+ tag_selector:
+ add_btn: Tilføj tag
+ create_btn: Opret et nyt tag
+ search_tag: Søg tag
+ hint: Describe what your content is about, at least one tag is required.
+ hint_zero_tags: Describe what your content is about.
+ hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ no_result: Ingen tags matchede
+ tag_required_text: Påkrævet tag (mindst én)
+ header:
+ nav:
+ question: Spørgsmål
+ tag: Tags
+ user: Brugere
+ badges: Badges
+ profile: Profil
+ setting: Indstillinger
+ logout: Log Ud
+ admin: Administrator
+ review: Gennemgå
+ bookmark: Bogmærker
+ moderation: Moderering
+ search:
+ placeholder: Søg
+ footer:
+ build_on: Powered by <1> Apache Answer 1>
+ upload_img:
+ name: Skift
+ loading: indlæser...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Skriv teksten ovenfor
+ msg:
+ empty: Captcha må ikke være tomt.
+ inactive:
+ first: >-
+ Du er næsten færdig! Vi har sendt en aktiveringsmail til {{mail}} . Følg venligst instruktionerne i mailen for at aktivere din konto.
+ info: "Hvis det ikke ankommer, tjek din spam-mappe."
+ another: >-
+ Vi har sendt endnu en aktiverings-e-mail til dig på {{mail}} . Det kan tage nogen få minutter før den når frem; kontrollér også din spam-mappe.
+ btn_name: Send aktiverings-e-mail igen
+ change_btn_name: Ændre e-mail
+ msg:
+ empty: skal udfyldes.
+ resend_email:
+ url_label: Er du sikker på, at du vil sende aktiveringse-mailen?
+ url_text: Du kan også give aktiveringslinket ovenfor til brugeren.
+ login:
+ login_to_continue: Log ind for at fortsætte
+ info_sign: Har du ikke en konto? <1>Tilmeld dig 1>
+ info_login: Har du allerede en konto? <1>Log ind 1>
+ agreements: Ved at registrere dig accepterer du <1>privacy policy1> og <3>terms of service 3>.
+ forgot_pass: Glemt adgangskode?
+ name:
+ label: Navn
+ msg:
+ empty: Navn må ikke være tomt.
+ range: Name must be between 2 to 30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ email:
+ label: E-mail
+ msg:
+ empty: E-mail skal udfyldes.
+ password:
+ label: Adgangskode
+ msg:
+ empty: Adgangskoden skal udfyldes.
+ different: De indtastede adgangskoder er ikke ens
+ account_forgot:
+ page_title: Glemt adgangskode
+ btn_name: Send mig gendannelsesmail
+ send_success: >-
+ Hvis en konto matcher {{mail}} , vil du modtage en e-mail med instruktioner om, hvordan du nulstiller din adgangskode.
+ email:
+ label: E-mail
+ msg:
+ empty: E-mail skal udfyldes.
+ change_email:
+ btn_cancel: Annuller
+ btn_update: Opdater e-mailadresse
+ send_success: >-
+ Hvis en konto matcher {{mail}} , vil du modtage en e-mail med instruktioner om, hvordan du nulstiller din adgangskode.
+ email:
+ label: Ny e-mail
+ msg:
+ empty: E-mail skal udfyldes.
+ oauth:
+ connect: Forbind med {{ auth_name }}
+ remove: Fjern {{ auth_name }}
+ oauth_bind_email:
+ subtitle: Tilføj en gendannelsese-mail til din konto.
+ btn_update: Opdater e-mailadresse
+ email:
+ label: E-mail
+ msg:
+ empty: E-mail skal udfyldes.
+ modal_title: Email eksisterer allerede.
+ modal_content: Denne e-mailadresse er allerede registreret. Er du sikker på, at du vil oprette forbindelse til den eksisterende konto?
+ modal_cancel: Ændre e-mail
+ modal_confirm: Opret forbindelse til den eksisterende konto
+ password_reset:
+ page_title: Nulstil adgangskode
+ btn_name: Nulstil min adgangskode
+ reset_success: >-
+ Du har ændret din adgangskode. Du vil blive omdirigeret til siden log ind.
+ link_invalid: >-
+ Beklager, dette link til nulstilling af adgangskode er ikke længere gyldigt. Måske er din adgangskode allerede nulstillet?
+ to_login: Fortsæt til log-ind siden
+ password:
+ label: Adgangskode
+ msg:
+ empty: Adgangskoden skal udfyldes.
+ length: Længden skal være mellem 8 og 32 tegn
+ different: De indtastede adgangskoder er ikke ens
+ password_confirm:
+ label: Bekræft den nye adgangskode
+ settings:
+ page_title: Indstillinger
+ goto_modify: Gå til at ændre
+ nav:
+ profile: Profil
+ notification: Notifikationer
+ account: Konto
+ interface: Grænseflade
+ profile:
+ heading: Profil
+ btn_name: Gem
+ display_name:
+ label: Visnings-navn
+ msg: Visnings-navn skal udfyldes.
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Brugernavn
+ caption: Man kan nævne dig som "@username".
+ msg: Brugernavn skal udfyldes.
+ msg_range: Username must be 2-30 characters in length.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profilbillede
+ gravatar: Gravatar
+ gravatar_text: Du kan ændre billede på
+ custom: Brugerdefineret
+ custom_text: Du kan uploade dit billede.
+ default: System
+ msg: Upload en avatar
+ bio:
+ label: Om mig
+ website:
+ label: Websted
+ placeholder: "https://example.com"
+ msg: Forkert format på websted
+ location:
+ label: Placering
+ placeholder: "By, land"
+ notification:
+ heading: Email-notifikationer
+ turn_on: Slå til
+ inbox:
+ label: Notifikationer i indbakken
+ description: Svar på dine spørgsmål, kommentarer, invitationer og mere.
+ all_new_question:
+ label: Alle nye spørgsmål
+ description: Få besked om alle nye spørgsmål. Op til 50 spørgsmål om ugen.
+ all_new_question_for_following_tags:
+ label: Alle nye spørgsmål til følgende tags
+ description: Få besked om nye spørgsmål til følgende tags.
+ account:
+ heading: Konto
+ change_email_btn: Ændre e-mail
+ change_pass_btn: Skift adgangskode
+ change_email_info: >-
+ Vi har sendt en e-mail til denne adresse. Følg venligst bekræftelsesinstruktionerne.
+ email:
+ label: E-mail
+ new_email:
+ label: Ny e-mail
+ msg: Ny e-mail skal udfyldes.
+ pass:
+ label: Nuværende adgangskode
+ msg: Adgangskoden skal udfyldes.
+ password_title: Adgangskode
+ current_pass:
+ label: Nuværende adgangskode
+ msg:
+ empty: Nuværende adgangskode skal udfyldes.
+ length: Længden skal være mellem 8 og 32 tegn.
+ different: De to indtastede adgangskoder er ikke ens.
+ new_pass:
+ label: Ny adgangskode
+ pass_confirm:
+ label: Bekræft den nye adgangskode
+ interface:
+ heading: Grænseflade
+ lang:
+ label: Grænseflade sprog
+ text: Brugergrænseflade sprog. Det vil ændres, når du opdaterer siden.
+ my_logins:
+ title: Mine log ind
+ label: Log ind eller tilmeld dig på dette websted ved hjælp af disse konti.
+ modal_title: Fjern login
+ modal_content: Er du sikker på, at du vil fjerne dette login fra din konto?
+ modal_confirm_btn: Slet
+ remove_success: Fjernet
+ toast:
+ update: opdatering gennemført
+ update_password: Adgangskoden er ændret.
+ flag_success: Tak for at anmelde.
+ forbidden_operate_self: Forbudt at operere på dig selv
+ review: Din revision vil blive vist efter gennemgang.
+ sent_success: Sendt
+ related_question:
+ title: Related
+ answers: svar
+ linked_question:
+ title: Linked
+ description: Posts linked to
+ no_linked_question: No contents linked from this content.
+ invite_to_answer:
+ title: Inviter personer
+ desc: Invitér personer, som du tror, kan svare.
+ invite: Inviter til at svare
+ add: Tilføj personer
+ search: Søg personer
+ question_detail:
+ action: Handling
+ created: Created
+ Asked: Spurgt
+ asked: spurgt
+ update: Ændret
+ Edited: Edited
+ edit: redigeret
+ commented: kommenteret
+ Views: Set
+ Follow: Følg
+ Following: Følger
+ follow_tip: Følg dette spørgsmål for at modtage notifikationer
+ answered: besvaret
+ closed_in: Lukket om
+ show_exist: Vis eksisterende spørgsmål.
+ useful: Nyttigt
+ question_useful: Det er nyttigt og klart
+ question_un_useful: Det er uklart eller ikke nyttigt
+ question_bookmark: Bogmærk dette spørgsmål
+ answer_useful: Det er nyttigt
+ answer_un_useful: Det er ikke nyttigt
+ answers:
+ title: Svar
+ score: Bedømmelse
+ newest: Nyeste
+ oldest: Ældste
+ btn_accept: Acceptér
+ btn_accepted: Accepteret
+ write_answer:
+ title: Dit Svar
+ edit_answer: Redigér mit eksisterende svar
+ btn_name: Indsend dit svar
+ add_another_answer: Tilføj endnu et svar
+ confirm_title: Fortsæt med at svare
+ continue: Forsæt
+ confirm_info: >-
+ Er du sikker på, at du vil tilføje et andet svar?
Du kan i stedet bruge redigeringslinket til at forfine og forbedre dit eksisterende svar.
+ empty: Svar skal udfyldes.
+ characters: indhold skal være mindst 6 tegn.
+ tips:
+ header_1: Tak for dit svar
+ li1_1: Vær sikker på at besvare spørgsmålet . Giv oplysninger og del din forskning.
+ li1_2: Begrund eventuelle udsagn med referencer eller personlige erfaringer.
+ header_2: Men undgå ...
+ li2_1: Spørger om hjælp, søger afklaring, eller reagerer på andre svar.
+ reopen:
+ confirm_btn: Genåbn
+ title: Genåbn dette indlæg
+ content: Er du sikker på, at du vil genåbne?
+ list:
+ confirm_btn: Liste
+ title: Sæt dette indlæg på listen
+ content: Er du sikker på du vil sætte på listen?
+ unlist:
+ confirm_btn: Fjern fra listen
+ title: Fjern dette indlæg fra listen
+ content: Er du sikker på at du vil fjerne fra listen?
+ pin:
+ title: Fastgør dette indlæg
+ content: Er du sikker på, at du ønsker at fastgøre globalt? Dette indlæg vises øverst på alle indlægs-lister.
+ confirm_btn: Fastgør
+ delete:
+ title: Slet dette indlæg
+ question: >-
+ Vi anbefaler ikke, at sletter spørgsmål med svar , fordi det fratager fremtidige læsere denne viden.Gentaget sletning af besvarede spørgsmål kan resultere i, at din konto bliver blokeret fra at spørge. Er du sikker på, at du ønsker at slette?
+ answer_accepted: >-
+
Vi anbefaler ikke at slette accepteret svar fordi det fratager fremtidige læsere denne viden.
Gentagen sletning af accepterede svar kan resultere i, at din konto bliver blokeret fra besvarelse. Er du sikker på, at du ønsker at slette?
+ other: Er du sikker på, at du vil slette?
+ tip_answer_deleted: Dette svar er blevet slettet
+ undelete_title: Genopret dette indlæg
+ undelete_desc: Er du sikker på du ønsker at genoprette?
+ btns:
+ confirm: Bekræft
+ cancel: Annuller
+ edit: Rediger
+ save: Gem
+ delete: Slet
+ undelete: Genopret
+ list: Sæt på liste
+ unlist: Fjern fra liste
+ unlisted: Fjernet fra liste
+ login: Log ind
+ signup: Opret konto
+ logout: Log Ud
+ verify: Verificér
+ create: Create
+ approve: Godkend
+ reject: Afvis
+ skip: Spring Over
+ discard_draft: Kassér udkast
+ pinned: Fastgjort
+ all: Alle
+ question: Spørgsmål
+ answer: Svar
+ comment: Kommentar
+ refresh: Genopfrisk
+ resend: Send igen
+ deactivate: Deaktiver
+ active: Aktiv
+ suspend: Suspendér
+ unsuspend: Ophæv suspendering
+ close: Luk
+ reopen: Genåbn
+ ok: Ok
+ light: Lys
+ dark: Mørk
+ system_setting: Systemindstilling
+ default: Standard
+ reset: Nulstil
+ tag: Tag
+ post_lowercase: indlæg
+ filter: Filtrer
+ ignore: Ignorér
+ submit: Indsend
+ normal: Normal
+ closed: Lukket
+ deleted: Slettet
+ deleted_permanently: Deleted permanently
+ pending: Ventende
+ more: Mere
+ view: View
+ card: Card
+ compact: Compact
+ display_below: Display below
+ always_display: Always display
+ or: or
+ back_sites: Back to sites
+ search:
+ title: Søgeresultater
+ keywords: Nøgleord
+ options: Muligheder
+ follow: Følg
+ following: Følger
+ counts: "{{count}} Resultater"
+ counts_loading: "... Results"
+ more: Mere
+ sort_btns:
+ relevance: Relevans
+ newest: Nyeste
+ active: Aktiv
+ score: Bedømmelse
+ more: Mere
+ tips:
+ title: Avancerede Søgetips
+ tag: "<1>[tag]1> søgning med et tag"
+ user: "<1>user:username1> søgning efter forfatter"
+ answer: "<1>answers:01> ubesvarede spørgsmål"
+ score: "<1>score:31> indlæg med 3+ score"
+ question: "<1>is:question1> søgespørgsmål"
+ is_answer: "<1>is:answer1> søgesvar"
+ empty: Vi kunne ikke finde noget. Prøv forskellige eller mindre specifikke søgeord.
+ share:
+ name: Del
+ copy: Kopiér link
+ via: Del indlæg via...
+ copied: Kopieret
+ facebook: Del på Facebook
+ twitter: Share to X
+ cannot_vote_for_self: Du kan ikke stemme på dit eget indlæg.
+ modal_confirm:
+ title: Fejl...
+ delete_permanently:
+ title: Delete permanently
+ content: Are you sure you want to delete permanently?
+ account_result:
+ success: Din nye konto er bekræftet. Du vil blive omdirigeret til hjemmesiden.
+ link: Fortsæt til startside
+ oops: Hovsa!
+ invalid: Linket, du brugte, virker ikke længere.
+ confirm_new_email: Din e-mail er blevet opdateret.
+ confirm_new_email_invalid: >-
+ Beklager, dette bekræftelseslink er ikke længere gyldigt. Måske blev din e-mail allerede ændret?
+ unsubscribe:
+ page_title: Afmeld
+ success_title: Afmelding Lykkedes
+ success_desc: Du er blevet fjernet fra denne abonnentliste og vil ikke modtage yderligere e-mails fra os.
+ link: Skift indstillinger
+ question:
+ following_tags: Følger Tags
+ edit: Rediger
+ save: Gem
+ follow_tag_tip: Følg tags for at udvælge dine spørgsmål.
+ hot_questions: Populære Spørgsmål
+ all_questions: Alle Spørgsmål
+ x_questions: "{{ count }} Spørgsmål"
+ x_answers: "{{ count }} svar"
+ x_posts: "{{ count }} Posts"
+ questions: Spørgsmål
+ answers: Svar
+ newest: Nyeste
+ active: Aktiv
+ hot: Populært
+ frequent: Frequent
+ recommend: Recommend
+ score: Bedømmelse
+ unanswered: Ubesvaret
+ modified: ændret
+ answered: besvaret
+ asked: spurgt
+ closed: lukket
+ follow_a_tag: Følg et tag
+ more: Mere
+ personal:
+ overview: Oversigt
+ answers: Svar
+ answer: svar
+ questions: Spørgsmål
+ question: spørgsmål
+ bookmarks: Bogmærker
+ reputation: Omdømme
+ comments: Kommentarer
+ votes: Stemmer
+ badges: Badges
+ newest: Nyeste
+ score: Bedømmelse
+ edit_profile: Rediger profil
+ visited_x_days: "Besøgte {{ count }} dage"
+ viewed: Set
+ joined: Tilmeldt
+ comma: ","
+ last_login: Set
+ about_me: Om Mig
+ about_me_empty: "// Hej, Verden!"
+ top_answers: Populære Svar
+ top_questions: Populære Spørgsmål
+ stats: Statistik
+ list_empty: Ingen indlæg fundet. Måske vil du vælge en anden fane?
+ content_empty: No posts found.
+ accepted: Accepteret
+ answered: besvaret
+ asked: spurgt
+ downvoted: nedstemt
+ mod_short: MOD
+ mod_long: Moderatorer
+ x_reputation: omdømme
+ x_votes: stemmer modtaget
+ x_answers: svar
+ x_questions: spørgsmål
+ recent_badges: Recent Badges
+ install:
+ title: Installation
+ next: Næste
+ done: Udført
+ config_yaml_error: Kan ikke oprette filen config.yaml.
+ lang:
+ label: Vælg et sprog
+ db_type:
+ label: Database type
+ db_username:
+ label: Brugernavn
+ placeholder: rod
+ msg: Brugernavn skal udfyldes.
+ db_password:
+ label: Adgangskode
+ placeholder: rod
+ msg: Adgangskoden skal udfyldes.
+ db_host:
+ label: Database host
+ placeholder: "db:3306"
+ msg: Database host skal udfyldes.
+ db_name:
+ label: Database navn
+ placeholder: answer
+ msg: Databasenavn skal udfyldes.
+ db_file:
+ label: Databasefil
+ placeholder: /data/answer.db
+ msg: Databasefil skal udfyldes.
+ ssl_enabled:
+ label: Enable SSL
+ ssl_enabled_on:
+ label: On
+ ssl_enabled_off:
+ label: Off
+ ssl_mode:
+ label: SSL Mode
+ ssl_root_cert:
+ placeholder: sslrootcert file path
+ msg: Path to sslrootcert file cannot be empty
+ ssl_cert:
+ placeholder: sslcert file path
+ msg: Path to sslcert file cannot be empty
+ ssl_key:
+ placeholder: sslkey file path
+ msg: Path to sslkey file cannot be empty
+ config_yaml:
+ title: Opret config.yaml
+ label: Filen config.yaml blev oprettet.
+ desc: >-
+ Du kan manuelt oprette filen <1>config.yaml1> i mappen <1>/var/wwww/xxx/1> og indsætte følgende tekst i den.
+ info: Når du har gjort det, skal du klikke på "Næste" knappen.
+ site_information: Websted Information
+ admin_account: Administrator Konto
+ site_name:
+ label: Websted navn
+ msg: Websted-navn skal udfyldes.
+ msg_max_length: Webstedsnavn kan ikke være længere end 30 tegn.
+ site_url:
+ label: Websted URL
+ text: Adressen på dit websted.
+ msg:
+ empty: Webstedets URL skal udfyldes.
+ incorrect: Websteds URL forkert format.
+ max_length: WebstedsURL skal højst være 512 tegn.
+ contact_email:
+ label: Kontakt e-mail
+ text: E-mailadresse på nøglekontakt ansvarlig for dette websted.
+ msg:
+ empty: Kontakt-e-mail skal udfyldes.
+ incorrect: Ugyldig kontakt e-mail adresse.
+ login_required:
+ label: Privat
+ switch: Log ind påkrævet
+ text: Kun brugere som er logget ind har adgang til dette fællesskab.
+ admin_name:
+ label: Navn
+ msg: Navn skal udfyldes.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ msg_max_length: Name must be between 2 to 30 characters in length.
+ admin_password:
+ label: Adgangskode
+ text: >-
+ Du skal bruge denne adgangskode for at logge ind. Opbevar den et sikkert sted.
+ msg: Adgangskoden skal udfyldes.
+ msg_min_length: Adgangskoden skal være mindst 8 tegn.
+ msg_max_length: Adgangskoden skal højst udgøre 32 tegn.
+ admin_confirm_password:
+ label: "Confirm Password"
+ text: "Please re-enter your password to confirm."
+ msg: "Confirm password does not match."
+ admin_email:
+ label: E-mail
+ text: Du skal bruge denne e-mail for at logge ind.
+ msg:
+ empty: E-mail skal udfyldes.
+ incorrect: Ugyldig e-mail adresse.
+ ready_title: Dit websted er klar
+ ready_desc: >-
+ Hvis du nogensinde har lyst til at ændre flere indstillinger, kan du besøge <1>admin-sektion1>; find det i site-menuen.
+ good_luck: "Hav det sjovt, og held og lykke!"
+ warn_title: Advarsel
+ warn_desc: >-
+ Filen <1>config.yaml1> findes allerede. Hvis du har brug for at nulstille en af konfigurationselementerne i denne fil, så slet den først.
+ install_now: Du kan prøve <1>at installere nu1>.
+ installed: Allerede installeret
+ installed_desc: >-
+ Du synes allerede at være installeret. For at geninstallere skal du først rydde dine gamle databasetabeller.
+ db_failed: Database forbindelse mislykkedes
+ db_failed_desc: >-
+ Det betyder enten, at databaseinformationen i din <1>config. aml1> fil er forkert eller at kontakt med databaseserveren ikke kunne etableres. Dette kan betyde, at din værts databaseserver er nede.
+ counts:
+ views: visninger
+ votes: stemmer
+ answers: svar
+ accepted: Accepteret
+ page_error:
+ http_error: HTTP Fejl {{ code }}
+ desc_403: Du har ikke adgang til denne side.
+ desc_404: Denne side findes desværre ikke.
+ desc_50X: Der skete en fejl på serveren og den kunne ikke fuldføre din anmodning.
+ back_home: Tilbage til forsiden
+ page_maintenance:
+ desc: "Vi laver vedligeholdelse, men er snart tilbage igen."
+ nav_menus:
+ dashboard: Kontrolpanel
+ contents: Indhold
+ questions: Spørgsmål
+ answers: Svar
+ users: Brugere
+ badges: Badges
+ flags: Anmeldelser
+ settings: Indstillinger
+ general: Generelt
+ interface: Brugerflade
+ smtp: SMTP
+ branding: Branding
+ legal: Jura
+ write: Skriv
+ terms: Terms
+ tos: Betingelser for brug
+ privacy: Privatliv
+ seo: SEO
+ customize: Tilpas
+ themes: Temaer
+ login: Log Ind
+ privileges: Rettigheder
+ plugins: Plugins
+ installed_plugins: Installerede Plugins
+ apperance: Appearance
+ website_welcome: Velkommen til {{site_name}}
+ user_center:
+ login: Log Ind
+ qrcode_login_tip: Brug {{ agentName }} til at scanne QR-koden og logge ind.
+ login_failed_email_tip: Log ind mislykkedes, tillad denne app at få adgang til dine e-mail-oplysninger, før du prøver igen.
+ badges:
+ modal:
+ title: Congratulations
+ content: You've earned a new badge.
+ close: Close
+ confirm: View badges
+ title: Badges
+ awarded: Awarded
+ earned_×: Earned ×{{ number }}
+ ×_awarded: "{{ number }} awarded"
+ can_earn_multiple: You can earn this multiple times.
+ earned: Earned
+ admin:
+ admin_header:
+ title: Administrator
+ dashboard:
+ title: Kontrolpanel
+ welcome: Velkommen til Administration!
+ site_statistics: Statistik for webstedet
+ questions: "Spørgsmål:"
+ resolved: "Resolved:"
+ unanswered: "Unanswered:"
+ answers: "Svar:"
+ comments: "Kommentarer:"
+ votes: "Stemmer:"
+ users: "Brugere:"
+ flags: "Anmeldelser:"
+ reviews: "Gennemgange:"
+ site_health: Websteds sundhed
+ version: "Version:"
+ https: "HTTPS:"
+ upload_folder: "Upload mappe:"
+ run_mode: "Kørselstilstand:"
+ private: Privat
+ public: Offentlig
+ smtp: "SMTP:"
+ timezone: "Tidszone:"
+ system_info: System information
+ go_version: "Go version:"
+ database: "Database:"
+ database_size: "Database størrelse:"
+ storage_used: "Anvendt lagerplads:"
+ uptime: "Oppetid:"
+ links: Links
+ plugins: Plugins
+ github: GitHub
+ blog: Blog
+ contact: Kontakt os
+ forum: Forum
+ documents: Dokumenter
+ feedback: Tilbagemelding
+ support: Support
+ review: Gennemgå
+ config: Konfiguration
+ update_to: Opdatér til
+ latest: Seneste
+ check_failed: Tjek mislykkedes
+ "yes": "Ja"
+ "no": "Nej"
+ not_allowed: Ikke tilladt
+ allowed: Tilladt
+ enabled: Aktiveret
+ disabled: Deaktiveret
+ writable: Skrivbar
+ not_writable: Ikke skrivbar
+ flags:
+ title: Anmeldelser
+ pending: Ventende
+ completed: Gennemført
+ flagged: Anmeldt
+ flagged_type: Anmeldt{{ type }}
+ created: Oprettet
+ action: Handling
+ review: Gennemgå
+ user_role_modal:
+ title: Skift brugerrolle til...
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ new_password_modal:
+ title: Angiv ny adgangskode
+ form:
+ fields:
+ password:
+ label: Adgangskode
+ text: Brugeren vil blive logget ud og skal logge ind igen.
+ msg: Adgangskoden skal være på 8- 32 tegn.
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ edit_profile_modal:
+ title: Rediger profil
+ form:
+ fields:
+ display_name:
+ label: Visnings-navn
+ msg_range: Display name must be 2-30 characters in length.
+ username:
+ label: Brugernavn
+ msg_range: Username must be 2-30 characters in length.
+ email:
+ label: E-mail
+ msg_invalid: Ugyldig E-Mail Adresse.
+ edit_success: Redigering lykkedes
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ user_modal:
+ title: Tilføj ny bruger
+ form:
+ fields:
+ users:
+ label: Masse-tilføj bruger
+ placeholder: "John Smith, john@example.com, BUSYopr2\nAlice, alice@example.com, fpDntV8q"
+ text: Adskil “navn, e-mail, adgangskode” med kommaer. Én bruger pr. linje.
+ msg: "Indtast venligst brugerens e-mail, en pr. linje."
+ display_name:
+ label: Visnings-navn
+ msg: Display name must be 2-30 characters in length.
+ email:
+ label: E-mail
+ msg: E-mail er ugyldig.
+ password:
+ label: Adgangskode
+ msg: Adgangskoden skal være 8- 32 tegn.
+ btn_cancel: Annuller
+ btn_submit: Indsend
+ users:
+ title: Brugere
+ name: Navn
+ email: E-mail
+ reputation: Omdømme
+ created_at: Created time
+ delete_at: Deleted time
+ suspend_at: Suspended time
+ suspend_until: Suspend until
+ status: Status
+ role: Rolle
+ action: Handling
+ change: Ændre
+ all: Alle
+ staff: Ansatte
+ more: Mere
+ inactive: Inaktiv
+ suspended: Suspenderet
+ deleted: Slettet
+ normal: Normal
+ Moderator: Moderator
+ Admin: Administrator
+ User: Bruger
+ filter:
+ placeholder: "Filtrer efter navn, user:id"
+ set_new_password: Angiv ny adgangskode
+ edit_profile: Rediger profil
+ change_status: Ændre status
+ change_role: Ændre rolle
+ show_logs: Vis logfiler
+ add_user: Tilføj bruger
+ deactivate_user:
+ title: Deaktiver bruger
+ content: En inaktiv bruger skal bekræfte deres e-mail igen.
+ delete_user:
+ title: Slet denne bruger
+ content: Er du sikker på, at du vil slette denne bruger? Dette er permanent!
+ remove: Fjern deres indhold
+ label: Fjern alle spørgsmål, svar, kommentarer osv.
+ text: Tjek ikke dette, hvis du kun ønsker at slette brugerens konto.
+ suspend_user:
+ title: Suspendér denne bruger
+ content: En suspenderet bruger kan ikke logge ind.
+ label: How long will the user be suspended for?
+ forever: Forever
+ questions:
+ page_title: Spørgsmål
+ unlisted: Fjernet fra liste
+ post: Indlæg
+ votes: Stemmer
+ answers: Svar
+ created: Oprettet
+ status: Status
+ action: Handling
+ change: Ændre
+ pending: Ventende
+ filter:
+ placeholder: "Filtrer efter titel, question:id"
+ answers:
+ page_title: Svar
+ post: Indlæg
+ votes: Stemmer
+ created: Oprettet
+ status: Status
+ action: Handling
+ change: Ændre
+ filter:
+ placeholder: "Filtrer efter titel, answer:id"
+ general:
+ page_title: Generelt
+ name:
+ label: Websted navn
+ msg: Websted-navn skal udfyldes.
+ text: "Navnet på dette websted, som bruges i title-tagget."
+ site_url:
+ label: Websted URL
+ msg: Websted-URL skal udfyldes.
+ validate: Angiv et gyldigt URL.
+ text: Adressen på dit websted.
+ short_desc:
+ label: Kort beskrivelse af websted
+ msg: Kort beskrivelse af websted skal udfyldes.
+ text: "Kort beskrivelse, som anvendt i title-tag på hjemmesiden."
+ desc:
+ label: Websted beskrivelse
+ msg: Webstedsbeskrivelse skal udfyldes.
+ text: "Beskriv dette websted i en sætning, som bruges i meta description tagget."
+ contact_email:
+ label: Kontakt e-mail
+ msg: Kontakt-e-mail skal udfyldes.
+ validate: Kontakt-e-mail er ugyldig.
+ text: E-mailadresse på nøglekontakt ansvarlig for dette websted.
+ check_update:
+ label: Opdatering af software
+ text: Søg automatisk efter opdateringer
+ interface:
+ page_title: Brugerflade
+ language:
+ label: Brugerflade sprog
+ msg: Brugerflade-sprog skal udfyldes.
+ text: Brugergrænseflade sprog. Det vil ændres, når du opdaterer siden.
+ time_zone:
+ label: Tidszone
+ msg: Tidszone skal udfyldes.
+ text: Vælg en by i samme tidszone som dig selv.
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: Gravatar base URL
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: Fra e-mail
+ msg: Fra e-mail skal udfyldes.
+ text: E-mail-adressen som e-mails sendes fra.
+ from_name:
+ label: Fra navn
+ msg: Fra navn skal udfyldes.
+ text: Navnet som e-mails sendes fra.
+ smtp_host:
+ label: SMTP host
+ msg: SMTP host skal udfyldes.
+ text: Din mail-server.
+ encryption:
+ label: Kryptering
+ msg: Kryptering skal udfyldes.
+ text: For de fleste servere er SSL den anbefalede indstilling.
+ ssl: SSL
+ tls: TLS
+ none: Ingen
+ smtp_port:
+ label: SMTP port
+ msg: SMTP port skal være nummer 1 ~ 65535.
+ text: Porten til din mailserver.
+ smtp_username:
+ label: SMTP brugernavn
+ msg: SMTP brugernavn skal udfyldes.
+ smtp_password:
+ label: SMTP adgangskode
+ msg: SMTP adgangskode skal udfyldes.
+ test_email_recipient:
+ label: Test e-mail modtagere
+ text: Angiv e-mail-adresse, der vil modtage test-beskedder.
+ msg: Test e-mail modtagere er ugyldige
+ smtp_authentication:
+ label: Aktiver autentificering
+ title: SMTP autentificering
+ msg: SMTP autentificering skal udfyldes.
+ "yes": "Ja"
+ "no": "Nej"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo
+ msg: Logo skal udfyldes.
+ text: Logoet billede øverst til venstre på dit websted. Brug et bredt rektangulært billede med en højde på 56 og et breddeforhold større end 3:1. Hvis efterladt tom, vil webstedets titeltekst blive vist.
+ mobile_logo:
+ label: Mobil logo
+ text: Logoet bruges på mobile version af dit websted. Brug et bredt rektangulært billede med en højde på 56. Hvis efterladt tom, vil billedet fra indstillingen "logo" blive brugt.
+ square_icon:
+ label: Kvadratisk ikon
+ msg: Kvadratisk ikon skal udfyldes.
+ text: Billede brugt som basis for metadata-ikoner. Bør være større end 512x512.
+ favicon:
+ label: Favicon
+ text: En favicon til dit websted. For at fungere korrekt over en CDN skal det være en png. Vil blive ændret til 32x32. Hvis efterladt tomt, vil "firkantet ikon" blive brugt.
+ legal:
+ page_title: Jura
+ terms_of_service:
+ label: Betingelser for brug
+ text: "Du kan tilføje servicevilkår her. Hvis du allerede har et dokument hostet et andet sted, så angiv den fulde URL her."
+ privacy_policy:
+ label: Privatlivspolitik
+ text: "Du kan tilføje privatlivspolitik indhold her. Hvis du allerede har et dokument hostet et andet sted, så angiv den fulde URL her."
+ external_content_display:
+ label: External content
+ text: "Content includes images, videos, and media embedded from external websites."
+ always_display: Always display external content
+ ask_before_display: Ask before displaying external content
+ write:
+ page_title: Skriv
+ min_content:
+ label: Minimum question body length
+ text: Minimum allowed question body length in characters.
+ restrict_answer:
+ title: Skriv svar
+ label: Hver bruger kan kun skrive et svar for det samme spørgsmål
+ text: "Slå fra for at give brugerne mulighed for at skrive flere svar på det samme spørgsmål, hvilket kan forårsage svar at være ufokuseret."
+ min_tags:
+ label: "Minimum tags per question"
+ text: "Minimum number of tags required in a question."
+ recommend_tags:
+ label: Anbefal tags
+ text: "Anbefal tags vil som standard blive vist i dropdown-listen."
+ msg:
+ contain_reserved: "anbefalede tags kan ikke indeholde reserverede tags"
+ required_tag:
+ title: Angiv påkrævede tags
+ label: Sæt “Anbefal tags” som påkrævede tags
+ text: "Hvert nyt spørgsmål skal have mindst et anbefalet tag."
+ reserved_tags:
+ label: Reserverede tags
+ text: "Reserverede tags kan kun bruges af moderator."
+ image_size:
+ label: Max image size (MB)
+ text: "The maximum image upload size."
+ attachment_size:
+ label: Max attachment size (MB)
+ text: "The maximum attachment files upload size."
+ image_megapixels:
+ label: Max image megapixels
+ text: "Maximum number of megapixels allowed for an image."
+ image_extensions:
+ label: Authorized image extensions
+ text: "A list of file extensions allowed for image display, separate with commas."
+ attachment_extensions:
+ label: Authorized attachment extensions
+ text: "A list of file extensions allowed for upload, separate with commas. WARNING: Allowing uploads may cause security issues."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Permalink
+ text: Brugerdefinerede URL-strukturer kan forbedre brugervenlighed og fremadrettet kompatibilitet af dine links.
+ robots:
+ label: robots.txt
+ text: Dette vil permanent tilsidesætte eventuelle relaterede webstedsindstillinger.
+ themes:
+ page_title: Temaer
+ themes:
+ label: Temaer
+ text: Vælg et eksisterende tema.
+ color_scheme:
+ label: Farveskema
+ navbar_style:
+ label: Navbar background style
+ primary_color:
+ label: Primær farve
+ text: Ændre farver, der bruges af dine temaer
+ css_and_html:
+ page_title: CSS og HTML
+ custom_css:
+ label: Brugerdefineret CSS
+ text: >
+
+ head:
+ label: Head
+ text: >
+
+ header:
+ label: Overskrift
+ text: >
+
+ footer:
+ label: Sidefod
+ text: Dette indsættes før </body>.
+ sidebar:
+ label: Sidebjælke
+ text: Dette vil indsætte i sidebjælken.
+ login:
+ page_title: Log Ind
+ membership:
+ title: Medlemskab
+ label: Tillad nye registreringer
+ text: Slå fra for at forhindre at nogen opretter en ny konto.
+ email_registration:
+ title: E-mail-registrering
+ label: Tillad e-mail registrering
+ text: Slå fra for at forhindre, at der oprettes en ny konto via e-mail.
+ allowed_email_domains:
+ title: Tilladte e-mail-domæner
+ text: E-mail-domæner som brugere skal registrere konti med. Et domæne pr. linje. Ignoreres når tomt.
+ private:
+ title: Privat
+ label: Log ind påkrævet
+ text: Kun brugere som er logget ind har adgang til dette fællesskab.
+ password_login:
+ title: Adgangskode log ind
+ label: Tillad e-mail og adgangskode login
+ text: "ADVARSEL: Hvis du slår fra, kan du muligvis ikke logge ind, hvis du ikke tidligere har konfigureret en anden loginmetode."
+ installed_plugins:
+ title: Installerede Plugins
+ plugin_link: Plugins udvider og udvider funktionaliteten. Du kan finde plugins i <1>Plugin Repository1>.
+ filter:
+ all: Alle
+ active: Aktiv
+ inactive: Inaktiv
+ outdated: Forældet
+ plugins:
+ label: Plugins
+ text: Vælg et eksisterende plugin.
+ name: Navn
+ version: Version
+ status: Status
+ action: Handling
+ deactivate: Deaktiver
+ activate: Aktivér
+ settings: Indstillinger
+ settings_users:
+ title: Brugere
+ avatar:
+ label: Standard avatar
+ text: For brugere uden en brugerdefineret avatar.
+ gravatar_base_url:
+ label: Gravatar base-URL
+ text: URL for Gravatar-udbyderens API-base. Ignoreres når tom.
+ profile_editable:
+ title: Profil redigerbar
+ allow_update_display_name:
+ label: Tillad brugere at ændre deres visningsnavn
+ allow_update_username:
+ label: Tillad brugere at ændre deres brugernavn
+ allow_update_avatar:
+ label: Tillad brugere at ændre deres profilbillede
+ allow_update_bio:
+ label: Tillad brugere at ændre deres om-mig
+ allow_update_website:
+ label: Tillad brugere at ændre deres hjemmeside
+ allow_update_location:
+ label: Tillad brugere at ændre deres placering
+ privilege:
+ title: Rettigheder
+ level:
+ label: Omdømme påkrævet niveau
+ text: Vælg det omdømme der kræves for rettighederne
+ msg:
+ should_be_number: input skal være et tal
+ number_larger_1: tal skal være lig med eller større end 1
+ badges:
+ action: Action
+ active: Active
+ activate: Activate
+ all: All
+ awards: Awards
+ deactivate: Deactivate
+ filter:
+ placeholder: Filter by name, badge:id
+ group: Group
+ inactive: Inactive
+ name: Name
+ show_logs: Show logs
+ status: Status
+ title: Badges
+ form:
+ optional: (valgfrit)
+ empty: skal udfyldes
+ invalid: er ugyldigt
+ btn_submit: Gem
+ not_found_props: "Nødvendig egenskab {{ key }} ikke fundet."
+ select: Vælg
+ page_review:
+ review: Gennemgå
+ proposed: foreslået
+ question_edit: Rediger spørgsmål
+ answer_edit: Svar redigér
+ tag_edit: Tag redigér
+ edit_summary: Rediger resumé
+ edit_question: Rediger spørgsmål
+ edit_answer: Rediger svar
+ edit_tag: Rediger tag
+ empty: Ingen gennemgangsopgaver tilbage.
+ approve_revision_tip: Godkender du denne revision?
+ approve_flag_tip: Godkender du denne anmeldelse?
+ approve_post_tip: Godkender du dette indlæg?
+ approve_user_tip: Godkender du denne bruger?
+ suggest_edits: Foreslåede redigeringer
+ flag_post: Anmeld indlæg
+ flag_user: Anmeld bruger
+ queued_post: Indlæg i kø
+ queued_user: Brugere i kø
+ filter_label: Type
+ reputation: omdømme
+ flag_post_type: Anmeld dette indlæg som {{ type }}.
+ flag_user_type: Anmeldte dette indlæg som {{ type }}.
+ edit_post: Rediger opslag
+ list_post: Sæt indlæg på liste
+ unlist_post: Fjern indlæg fra liste
+ timeline:
+ undeleted: genskabt
+ deleted: slettet
+ downvote: stem ned
+ upvote: stem op
+ accept: acceptér
+ cancelled: annulleret
+ commented: kommenteret
+ rollback: tilbagerul
+ edited: redigeret
+ answered: besvaret
+ asked: spurgt
+ closed: lukket
+ reopened: genåbnet
+ created: oprettet
+ pin: fastgjort
+ unpin: frigjort
+ show: sat på liste
+ hide: fjernet fra liste
+ title: "Historik for"
+ tag_title: "Tidslinje for"
+ show_votes: "Vis stemmer"
+ n_or_a: Ikke Relevant
+ title_for_question: "Tidslinje for"
+ title_for_answer: "Tidslinje for svar på {{ title }} af {{ author }}"
+ title_for_tag: "Tidslinje for tag"
+ datetime: Datetime
+ type: Type
+ by: Af
+ comment: Kommentar
+ no_data: "Vi kunne ikke finde noget."
+ users:
+ title: Brugere
+ users_with_the_most_reputation: Brugere med det højeste omdømme scorer denne uge
+ users_with_the_most_vote: Brugere, der stemte mest i denne uge
+ staffs: Vores fællesskabs personale
+ reputation: omdømme
+ votes: stemmer
+ prompt:
+ leave_page: Er du sikker på, at du vil forlade siden?
+ changes_not_save: Dine ændringer er muligvis ikke gemt.
+ draft:
+ discard_confirm: Er du sikker på, at du vil kassere dit udkast?
+ messages:
+ post_deleted: Dette indlæg er blevet slettet.
+ post_cancel_deleted: This post has been undeleted.
+ post_pin: Dette indlæg er blevet fastgjort.
+ post_unpin: Dette indlæg er blevet frigjort.
+ post_hide_list: Dette indlæg er blevet skjult fra listen.
+ post_show_list: Dette indlæg er blevet vist på listen.
+ post_reopen: Dette indlæg er blevet genåbnet.
+ post_list: Dette indlæg er blevet listet.
+ post_unlist: Dette indlæg er blevet aflistet.
+ post_pending: Dit indlæg afventer gennemgang. Dette er en forhåndsvisning, det vil være synligt, når det er blevet godkendt.
+ post_closed: This post has been closed.
+ answer_deleted: This answer has been deleted.
+ answer_cancel_deleted: This answer has been undeleted.
+ change_user_role: This user's role has been changed.
+ user_inactive: This user is already inactive.
+ user_normal: This user is already normal.
+ user_suspended: This user has been suspended.
+ user_deleted: This user has been deleted.
+ badge_activated: This badge has been activated.
+ badge_inactivated: This badge has been inactivated.
+ users_deleted: These users have been deleted.
+ posts_deleted: These questions have been deleted.
+ answers_deleted: These answers have been deleted.
+ copy: Copy to clipboard
+ copied: Copied
+ external_content_warning: External images/media are not displayed.
+
+
diff --git a/data/i18n/de_DE.yaml b/data/i18n/de_DE.yaml
new file mode 100644
index 000000000..3e89b8d16
--- /dev/null
+++ b/data/i18n/de_DE.yaml
@@ -0,0 +1,2359 @@
+# 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.
+
+# The following fields are used for back-end
+backend:
+ base:
+ success:
+ other: Erfolgreich.
+ unknown:
+ other: Unbekannter Fehler.
+ request_format_error:
+ other: Format der Anfrage ist ungültig.
+ unauthorized_error:
+ other: Nicht autorisiert.
+ database_error:
+ other: Datenbank-Fehler.
+ forbidden_error:
+ other: Verboten.
+ duplicate_request_error:
+ other: Doppelte Einreichung.
+ action:
+ report:
+ other: Melden
+ edit:
+ other: Bearbeiten
+ delete:
+ other: Löschen
+ close:
+ other: Schließen
+ reopen:
+ other: Wieder öffnen
+ forbidden_error:
+ other: Verboten.
+ pin:
+ other: Anpinnen
+ hide:
+ other: Von Liste nehmen
+ unpin:
+ other: Loslösen
+ show:
+ other: Liste
+ invite_someone_to_answer:
+ other: Bearbeiten
+ undelete:
+ other: Wiederherstellen
+ merge:
+ other: Zusammenführen
+ role:
+ name:
+ user:
+ other: Benutzer
+ admin:
+ other: Admin
+ moderator:
+ other: Moderator
+ description:
+ user:
+ other: Standard ohne speziellen Zugriff.
+ admin:
+ other: Habe die volle Berechtigung, auf die Seite zuzugreifen.
+ moderator:
+ other: Hat Zugriff auf alle Beiträge außer Admin-Einstellungen.
+ privilege:
+ level_1:
+ description:
+ other: Level 1 (weniger Reputation für privates Team, Gruppen)
+ level_2:
+ description:
+ other: Level 2 (niedrige Reputation für Startup-Community)
+ level_3:
+ description:
+ other: Level 3 (hohe Reputation für eine reife Community)
+ level_custom:
+ description:
+ other: Benutzerdefinierter Level
+ rank_question_add_label:
+ other: Fragen stellen
+ rank_answer_add_label:
+ other: Antwort schreiben
+ rank_comment_add_label:
+ other: Kommentar schreiben
+ rank_report_add_label:
+ other: Melden
+ rank_comment_vote_up_label:
+ other: Kommentar upvoten
+ rank_link_url_limit_label:
+ other: Mehr als 2 Links gleichzeitig posten
+ rank_question_vote_up_label:
+ other: Frage upvoten
+ rank_answer_vote_up_label:
+ other: Antwort upvoten
+ rank_question_vote_down_label:
+ other: Frage downvoten
+ rank_answer_vote_down_label:
+ other: Antwort downvoten
+ rank_invite_someone_to_answer_label:
+ other: Jemanden zum Antworten einladen
+ rank_tag_add_label:
+ other: Neuen Tag erstellen
+ rank_tag_edit_label:
+ other: Tag-Beschreibung bearbeiten (muss überprüft werden)
+ rank_question_edit_label:
+ other: Frage eines anderen bearbeiten (muss überarbeitet werden)
+ rank_answer_edit_label:
+ other: Antwort eines anderen bearbeiten (muss überarbeitet werden)
+ rank_question_edit_without_review_label:
+ other: Frage eines anderen ohne Überprüfung bearbeiten
+ rank_answer_edit_without_review_label:
+ other: Antwort eines anderen ohne Überprüfung bearbeiten
+ rank_question_audit_label:
+ other: Frageänderungen überprüfen
+ rank_answer_audit_label:
+ other: Bearbeitete Antworten überprüfen
+ rank_tag_audit_label:
+ other: Tag-Bearbeitungen überprüfen
+ rank_tag_edit_without_review_label:
+ other: Tag-Beschreibung ohne Überprüfung bearbeiten
+ rank_tag_synonym_label:
+ other: Tag-Synonyme verwalten
+ email:
+ other: E-Mail
+ e_mail:
+ other: E-Mail
+ password:
+ other: Passwort
+ pass:
+ other: Passwort
+ old_pass:
+ other: Aktuelles Passwort
+ original_text:
+ other: Dieser Beitrag
+ email_or_password_wrong_error:
+ other: E-Mail und Passwort stimmen nicht überein.
+ error:
+ common:
+ invalid_url:
+ other: Ungültige URL.
+ status_invalid:
+ other: Ungültiger Status.
+ password:
+ space_invalid:
+ other: Passwort darf keine Leerzeichen enthalten.
+ admin:
+ cannot_update_their_password:
+ other: Du kannst dein Passwort nicht ändern.
+ cannot_edit_their_profile:
+ other: Du kannst dein Profil nicht bearbeiten.
+ cannot_modify_self_status:
+ other: Du kannst deinen Status nicht ändern.
+ email_or_password_wrong:
+ other: E-Mail und Password stimmen nicht überein.
+ answer:
+ not_found:
+ other: Antwort nicht gefunden.
+ cannot_deleted:
+ other: Keine Berechtigung zum Löschen.
+ cannot_update:
+ other: Keine Berechtigung zum Aktualisieren.
+ question_closed_cannot_add:
+ other: Fragen sind geschlossen und können nicht hinzugefügt werden.
+ content_cannot_empty:
+ other: Die Antwort darf nicht leer sein.
+ comment:
+ edit_without_permission:
+ other: Kommentar kann nicht bearbeitet werden.
+ not_found:
+ other: Kommentar wurde nicht gefunden.
+ cannot_edit_after_deadline:
+ other: Die Kommentarzeit war zu lang, um sie zu ändern.
+ content_cannot_empty:
+ other: Der Kommentar darf nicht leer sein.
+ email:
+ duplicate:
+ other: E-Mail existiert bereits.
+ need_to_be_verified:
+ other: E-Mail muss überprüft werden.
+ verify_url_expired:
+ other: Die verifizierbare E-Mail-URL ist abgelaufen, bitte sende die E-Mail erneut.
+ illegal_email_domain_error:
+ other: E-Mails sind von dieser E-Mail-Domäne nicht erlaubt. Bitte verwende eine andere.
+ lang:
+ not_found:
+ other: Sprachdatei nicht gefunden.
+ object:
+ captcha_verification_failed:
+ other: Captcha ist falsch.
+ disallow_follow:
+ other: Es ist dir nicht erlaubt zu folgen.
+ disallow_vote:
+ other: Es ist dir nicht erlaubt abzustimmen.
+ disallow_vote_your_self:
+ other: Du kannst nicht für deinen eigenen Beitrag stimmen.
+ not_found:
+ other: Objekt nicht gefunden.
+ verification_failed:
+ other: Verifizierung fehlgeschlagen.
+ email_or_password_incorrect:
+ other: E-Mail und Passwort stimmen nicht überein.
+ old_password_verification_failed:
+ other: Die Überprüfung des alten Passworts ist fehlgeschlagen
+ new_password_same_as_previous_setting:
+ other: Das neue Passwort ist das gleiche wie das vorherige Passwort.
+ already_deleted:
+ other: Dieser Beitrag wurde gelöscht.
+ meta:
+ object_not_found:
+ other: Metaobjekt nicht gefunden
+ question:
+ already_deleted:
+ other: Dieser Beitrag wurde gelöscht.
+ under_review:
+ other: Ihr Beitrag wartet auf Überprüfung. Er wird sichtbar sein, nachdem er genehmigt wurde.
+ not_found:
+ other: Frage nicht gefunden.
+ cannot_deleted:
+ other: Keine Berechtigung zum Löschen.
+ cannot_close:
+ other: Keine Berechtigung zum Schließen.
+ cannot_update:
+ other: Keine Berechtigung zum Aktualisieren.
+ content_cannot_empty:
+ other: Der Inhalt darf nicht leer sein.
+ content_less_than_minimum:
+ other: Not enough content entered.
+ rank:
+ fail_to_meet_the_condition:
+ other: Ansehenssrang erfüllt die Bedingung nicht.
+ vote_fail_to_meet_the_condition:
+ other: Danke für dein Feedback. Du brauchst mindestens {{.Rank}} Ansehen, um eine Stimme abzugeben.
+ no_enough_rank_to_operate:
+ other: Dafür brauchst du mindestens {{.Rank}} Ansehen.
+ report:
+ handle_failed:
+ other: Bearbeiten der Meldung fehlgeschlagen.
+ not_found:
+ other: Meldung nicht gefunden.
+ tag:
+ already_exist:
+ other: Tag existiert bereits.
+ not_found:
+ other: Tag nicht gefunden.
+ recommend_tag_not_found:
+ other: Das Tag "Empfehlen" ist nicht vorhanden.
+ recommend_tag_enter:
+ other: Bitte gib mindestens einen erforderlichen Tag ein.
+ not_contain_synonym_tags:
+ other: Sollte keine Synonym-Tags enthalten.
+ cannot_update:
+ other: Keine Berechtigung zum Aktualisieren.
+ is_used_cannot_delete:
+ other: Du kannst keinen Tag löschen, der in Gebrauch ist.
+ cannot_set_synonym_as_itself:
+ other: Du kannst das Synonym des aktuellen Tags nicht als sich selbst festlegen.
+ minimum_count:
+ other: Not enough tags were entered.
+ smtp:
+ config_from_name_cannot_be_email:
+ other: Der Absendername kann keine E-Mail-Adresse sein.
+ theme:
+ not_found:
+ other: Design nicht gefunden.
+ revision:
+ review_underway:
+ other: Kann derzeit nicht bearbeitet werden, es existiert eine Version in der Überprüfungswarteschlange.
+ no_permission:
+ other: Keine Berechtigung zum Überarbeiten.
+ user:
+ external_login_missing_user_id:
+ other: Die Plattform des Drittanbieters stellt keine eindeutige UserID zur Verfügung, sodass du dich nicht anmelden kannst. Bitte wende dich an den Administrator der Website.
+ external_login_unbinding_forbidden:
+ other: Bitte setze ein Login-Passwort für dein Konto, bevor du dieses Login entfernst.
+ email_or_password_wrong:
+ other:
+ other: E-Mail und Passwort stimmen nicht überein.
+ not_found:
+ other: Benutzer nicht gefunden.
+ suspended:
+ other: Benutzer wurde gesperrt.
+ username_invalid:
+ other: Benutzername ist ungültig.
+ username_duplicate:
+ other: Benutzername wird bereits verwendet.
+ set_avatar:
+ other: Avatar setzen fehlgeschlagen.
+ cannot_update_your_role:
+ other: Du kannst deine Rolle nicht ändern.
+ not_allowed_registration:
+ other: Derzeit ist die Seite nicht für die Anmeldung geöffnet.
+ not_allowed_login_via_password:
+ other: Zurzeit ist es auf der Seite nicht möglich, sich mit einem Passwort anzumelden.
+ access_denied:
+ other: Zugriff verweigert
+ page_access_denied:
+ other: Du hast keinen Zugriff auf diese Seite.
+ add_bulk_users_format_error:
+ other: "Fehler {{.Field}}-Format in der Nähe von '{{.Content}}' in Zeile {{.Line}}. {{.ExtraMessage}}"
+ add_bulk_users_amount_error:
+ other: "Die Anzahl der Benutzer, die du auf einmal hinzufügst, sollte im Bereich von 1-{{.MaxAmount}} liegen."
+ status_suspended_forever:
+ other: "This user was suspended forever. This user doesn't meet a community guideline."
+ status_suspended_until:
+ other: "This user was suspended until {{.SuspendedUntil}}. This user doesn't meet a community guideline."
+ status_deleted:
+ other: "This user was deleted."
+ status_inactive:
+ other: "This user is inactive."
+ config:
+ read_config_failed:
+ other: Lesekonfiguration fehlgeschlagen
+ database:
+ connection_failed:
+ other: Datenbankverbindung fehlgeschlagen
+ create_table_failed:
+ other: Tabelle erstellen fehlgeschlagen
+ install:
+ create_config_failed:
+ other: Kann die config.yaml-Datei nicht erstellen.
+ upload:
+ unsupported_file_format:
+ other: Dateiformat nicht unterstützt.
+ site_info:
+ config_not_found:
+ other: Seiten-Konfiguration nicht gefunden.
+ badge:
+ object_not_found:
+ other: Abzeichen-Objekt nicht gefunden
+ reason:
+ spam:
+ name:
+ other: Spam
+ desc:
+ other: Dieser Beitrag ist eine Werbung oder Vandalismus. Er ist nicht nützlich oder relevant für das aktuelle Thema.
+ rude_or_abusive:
+ name:
+ other: unhöflich oder beleidigend
+ desc:
+ other: "Eine vernünftige Person würde diesen Inhalt im respektvoll diskutierten Diskurs für unangemessen halten."
+ a_duplicate:
+ name:
+ other: ein Duplikat
+ desc:
+ other: Diese Frage wurde schon einmal gestellt und hat bereits eine Antwort.
+ placeholder:
+ other: Gib den Link zur bestehenden Frage ein
+ not_a_answer:
+ name:
+ other: keine Antwort
+ desc:
+ other: "Die Antwort versucht nicht, die Frage zu beantworten. Sie sollte entweder bearbeitet, kommentiert, als weitere Frage gestellt oder ganz gelöscht werden."
+ no_longer_needed:
+ name:
+ other: nicht mehr benötigt
+ desc:
+ other: Dieser Kommentar ist veraltet oder nicht relevant für diesen Beitrag.
+ something:
+ name:
+ other: anderer Grund
+ desc:
+ other: Dieser Beitrag erfordert die Aufmerksamkeit der Temmitglieder aus einem anderen, oben nicht genannten Grund.
+ placeholder:
+ other: Lass uns wissen, worüber du dir Sorgen machst
+ community_specific:
+ name:
+ other: ein Community-spezifischer Grund
+ desc:
+ other: Diese Frage entspricht nicht den Gemeinschaftsrichtlinien.
+ not_clarity:
+ name:
+ other: benötigt Details oder Klarheit
+ desc:
+ other: Diese Frage enthält derzeit mehrere Fragen in einer. Sie sollte sich auf ein einziges Problem konzentrieren.
+ looks_ok:
+ name:
+ other: sieht OK aus
+ desc:
+ other: Dieser Beitrag ist gut so wie er ist und nicht von schlechter Qualität.
+ needs_edit:
+ name:
+ other: muss bearbeitet werden, und ich habe es getan
+ desc:
+ other: Verbessere und korrigiere Probleme mit diesem Beitrag selbst.
+ needs_close:
+ name:
+ other: muss geschlossen werden
+ desc:
+ other: Eine geschlossene Frage kann nicht beantwortet werden, aber du kannst sie trotzdem bearbeiten, abstimmen und kommentieren.
+ needs_delete:
+ name:
+ other: muss gelöscht werden
+ desc:
+ other: Dieser Beitrag wird gelöscht.
+ question:
+ close:
+ duplicate:
+ name:
+ other: Spam
+ desc:
+ other: Diese Frage ist bereits gestellt worden und hat bereits eine Antwort.
+ guideline:
+ name:
+ other: ein Community-spezifischer Grund
+ desc:
+ other: Diese Frage entspricht nicht einer Gemeinschaftsrichtlinie.
+ multiple:
+ name:
+ other: benötigt Details oder Klarheit
+ desc:
+ other: Diese Frage enthält derzeit mehrere Fragen in einer. Sie sollte sich auf ein einziges Problem konzentrieren.
+ other:
+ name:
+ other: etwas anderes
+ desc:
+ other: Dieser Beitrag erfordert einen anderen Grund, der oben nicht aufgeführt ist.
+ operation_type:
+ asked:
+ other: gefragt
+ answered:
+ other: beantwortet
+ modified:
+ other: geändert
+ deleted_title:
+ other: Gelöschte Frage
+ questions_title:
+ other: Fragen
+ tag:
+ tags_title:
+ other: Schlagwörter
+ no_description:
+ other: Diese Kategorie hat keine Beschreibung.
+ notification:
+ action:
+ update_question:
+ other: aktualisierte Frage
+ answer_the_question:
+ other: beantwortete Frage
+ update_answer:
+ other: aktualisierte Antwort
+ accept_answer:
+ other: akzeptierte Antwort
+ comment_question:
+ other: kommentierte Frage
+ comment_answer:
+ other: kommentierte Antwort
+ reply_to_you:
+ other: hat Ihnen geantwortet
+ mention_you:
+ other: hat dich erwähnt
+ your_question_is_closed:
+ other: Deine Frage wurde geschlossen
+ your_question_was_deleted:
+ other: Deine Frage wurde gelöscht
+ your_answer_was_deleted:
+ other: Deine Antwort wurde gelöscht
+ your_comment_was_deleted:
+ other: Dein Kommentar wurde gelöscht
+ up_voted_question:
+ other: positiv bewertete Frage
+ down_voted_question:
+ other: negativ bewertete Frage
+ up_voted_answer:
+ other: positiv bewertete Antwort
+ down_voted_answer:
+ other: negativ bewertete Antwort
+ up_voted_comment:
+ other: positiv bewerteter Kommentar
+ invited_you_to_answer:
+ other: hat dich eingeladen, zu antworten
+ earned_badge:
+ other: Du hast das "{{.BadgeName}}" Abzeichen verdient
+ email_tpl:
+ change_email:
+ title:
+ other: "[{{.SiteName}}] Bestätige deine neue E-Mail-Adresse"
+ body:
+ other: "Bestätigen Sie Ihre neue E-Mail-Adresse für {{.SiteName}} indem Sie auf den folgenden Link klicken: \n{{.ChangeEmailUrl}} \n\nWenn Sie diese Änderung nicht angefordert haben bitte diese E-Mail ignorieren. \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird."
+ new_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} hat deine Frage beantwortet"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.AnswerSummary}} \n\nAuf {{.SiteName}} anschauen \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird. \n\nAbmelden "
+ invited_you_to_answer:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} hat dich eingeladen zu antworten"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \nIch denke, Sie kennen die Antwort. \n {{.SiteName}} \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird. \n\nAbmelden "
+ new_comment:
+ title:
+ other: "[{{.SiteName}}] {{.DisplayName}} hat deinen Beitrag kommentiert"
+ body:
+ other: "{{.QuestionTitle}} \n\n{{.DisplayName}}: \n{{.CommentSummary}} \n\nAuf {{.SiteName}} anschauen \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird. \n\nAbmelden "
+ new_question:
+ title:
+ other: "[{{.SiteName}}] Neue Frage: {{.QuestionTitle}}"
+ body:
+ other: "{{.QuestionTitle}} \n{{.Tags}} \n\n-- \nHinweis: Dies ist eine automatische Systemnachricht, bitte antworten Sie nicht darauf. Antworten werden nicht gelesen oder bearbeitet. \n\nBenachrichtigung abbestellen "
+ pass_reset:
+ title:
+ other: "[{{.SiteName }}] Passwort zurücksetzen"
+ body:
+ other: "Jemand bat darum, Ihr Passwort auf {{.SiteName}}zurückzusetzen. \n\nWenn Sie es nicht waren, können Sie diese E-Mail sicher ignorieren. \n\nKlicken Sie auf den folgenden Link, um ein neues Passwort auszuwählen: \n{{.PassResetUrl}} \n\n \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird."
+ register:
+ title:
+ other: "[{{.SiteName}}] Bestätige dein neues Konto"
+ body:
+ other: "Willkommen in {{.SiteName}}! \n\nKlicken Sie auf den folgenden Link, um Ihr neues Konto zu bestätigen und zu aktivieren: \n{{.RegisterUrl}} \n\nWenn der obige Link nicht anklickbar ist kopieren und in die Adressleiste Ihres Webbrowsers einfügen.\n \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht sichtbar ist."
+ test:
+ title:
+ other: "[{{.SiteName}}] Test-E-Mail"
+ body:
+ other: "Dies ist eine Test-E-Mail.\n \n\n-- \nHinweis: Dies ist eine automatische System-E-Mail, Bitte antworten Sie nicht auf diese Nachricht, da Ihre Antwort nicht angezeigt wird."
+ action_activity_type:
+ upvote:
+ other: positiv bewerten
+ upvoted:
+ other: positiv bewertet
+ downvote:
+ other: negativ bewerten
+ downvoted:
+ other: negativ bewertet
+ accept:
+ other: akzeptieren
+ accepted:
+ other: akzeptiert
+ edit:
+ other: bearbeiten
+ review:
+ queued_post:
+ other: Post in der Warteschlange
+ flagged_post:
+ other: Beiträge gemeldet
+ suggested_post_edit:
+ other: Änderungsvorschläge
+ reaction:
+ tooltip:
+ other: "{{ .Names }} Und {{ .Count }} mehr..."
+ badge:
+ default_badges:
+ autobiographer:
+ name:
+ other: Autobiograph
+ desc:
+ other: Gefüllt mit Profil Informationen.
+ certified:
+ name:
+ other: Zertifiziert
+ desc:
+ other: Erledigte unser neues Benutzerhandbuch.
+ editor:
+ name:
+ other: Editor
+ desc:
+ other: Erster Beitrag bearbeiten.
+ first_flag:
+ name:
+ other: Erste Meldung
+ desc:
+ other: Erste Meldung eines Beitrags.
+ first_upvote:
+ name:
+ other: Erster Upvote
+ desc:
+ other: Erste Like eines Beitrags.
+ first_link:
+ name:
+ other: Erster Link
+ desc:
+ other: Hat erstmals einen Link zu einem anderen Beitrag hinzugefügt.
+ first_reaction:
+ name:
+ other: Erste Reaktion
+ desc:
+ other: Zuerst reagierte auf den Beitrag.
+ first_share:
+ name:
+ other: Erstes Teilen
+ desc:
+ other: Zuerst einen Beitrag geteilt.
+ scholar:
+ name:
+ other: Gelehrter
+ desc:
+ other: Hat eine Frage gestellt und eine Antwort akzeptiert.
+ commentator:
+ name:
+ other: Kommentator
+ desc:
+ other: Hinterlassen Sie 5 Kommentare.
+ new_user_of_the_month:
+ name:
+ other: Neuer Benutzer des Monats
+ desc:
+ other: Ausstehende Beiträge in ihrem ersten Monat.
+ read_guidelines:
+ name:
+ other: Lesen Sie die Richtlinien
+ desc:
+ other: Lesen Sie die [Community-Richtlinien].
+ reader:
+ name:
+ other: Leser
+ desc:
+ other: Lesen Sie alle Antworten in einem Thema mit mehr als 10 Antworten.
+ welcome:
+ name:
+ other: Willkommen
+ desc:
+ other: Du hast eine positive Abstimmung erhalten.
+ nice_share:
+ name:
+ other: Schöne teilen
+ desc:
+ other: Hat einen Beitrag mit 25 einzigartigen Besuchern freigegeben.
+ good_share:
+ name:
+ other: Gut geteilt
+ desc:
+ other: Hat einen Beitrag mit 300 einzigartigen Besuchern freigegeben.
+ great_share:
+ name:
+ other: Großartiges Teilen
+ desc:
+ other: Hat einen Beitrag mit 1000 einzigartigen Besuchern freigegeben.
+ out_of_love:
+ name:
+ other: Aus Liebe
+ desc:
+ other: Hat an einem Tag 50 Upvotes verwendet.
+ higher_love:
+ name:
+ other: Höhere Liebe
+ desc:
+ other: Hat an einem Tag 50 Upvotes 5 Mal verwendet.
+ crazy_in_love:
+ name:
+ other: Im siebten Himmel
+ desc:
+ other: Hat an einem Tag 50 Upvotes 20 Mal verwendet.
+ promoter:
+ name:
+ other: Förderer
+ desc:
+ other: Hat einen Benutzer eingeladen.
+ campaigner:
+ name:
+ other: Kampagnenleiter
+ desc:
+ other: Lade 3 einfache Benutzer ein.
+ champion:
+ name:
+ other: Champion
+ desc:
+ other: Hat 5 Mitglieder eingeladen.
+ thank_you:
+ name:
+ other: Vielen Dank
+ desc:
+ other: Beitrag mit 20 Upvotes und 10 abgegebenen Upvotes.
+ gives_back:
+ name:
+ other: Feedback geben
+ desc:
+ other: Beitrag mit 100 Upvotes und 100 abgegebenen Upvotes.
+ empathetic:
+ name:
+ other: Einfühlsam
+ desc:
+ other: Beitrag mit 500 Upvotes und 1000 abgegebenen Upvotes.
+ enthusiast:
+ name:
+ other: Enthusiast
+ desc:
+ other: Besucht 10 aufeinander folgende Tage.
+ aficionado:
+ name:
+ other: Aficionado
+ desc:
+ other: Besucht 100 aufeinander folgende Tage.
+ devotee:
+ name:
+ other: Anhänger
+ desc:
+ other: 365 aufeinander folgende Tage besucht.
+ anniversary:
+ name:
+ other: Jahrestag
+ desc:
+ other: Aktives Mitglied für ein Jahr, mindestens einmal veröffentlicht.
+ appreciated:
+ name:
+ other: Gewertschätzt
+ desc:
+ other: Erhalten 1 up vote für 20 posts.
+ respected:
+ name:
+ other: Respektiert
+ desc:
+ other: Erhalten 2 up vote für 100 posts.
+ admired:
+ name:
+ other: Bewundert
+ desc:
+ other: 5 upvotes für 300 posts erhalten.
+ solved:
+ name:
+ other: Gelöst
+ desc:
+ other: Eine Antwort wurde akzeptiert.
+ guidance_counsellor:
+ name:
+ other: Anleitungsberater
+ desc:
+ other: 10 Antworten wurden akzeptiert.
+ know_it_all:
+ name:
+ other: Alleswisser
+ desc:
+ other: 50 Antworten wurden akzeptiert.
+ solution_institution:
+ name:
+ other: Lösungsfinder
+ desc:
+ other: 150 Antworten wurden akzeptiert.
+ nice_answer:
+ name:
+ other: Nette Antwort
+ desc:
+ other: Die Antwortpunktzahl beträgt mehr als 10 Punkte.
+ good_answer:
+ name:
+ other: Gute Antwort
+ desc:
+ other: Die Antwortpunktzahl beträgt mehr als 25 Punkte.
+ great_answer:
+ name:
+ other: Großartige Antwort
+ desc:
+ other: Die Antwortpunktzahl beträgt mehr als 50 Punkte.
+ nice_question:
+ name:
+ other: Schöne Frage
+ desc:
+ other: Fragenpunktzahl von 10 oder mehr.
+ good_question:
+ name:
+ other: Gute Frage
+ desc:
+ other: Fragen mit 25 oder mehr Punkten.
+ great_question:
+ name:
+ other: Große Frage
+ desc:
+ other: Frage mit 50 oder mehr Punkten.
+ popular_question:
+ name:
+ other: Populäre Frage
+ desc:
+ other: Frage mit 500 Ansichten.
+ notable_question:
+ name:
+ other: Bemerkenswerte Frage
+ desc:
+ other: Frage mit 1.000 Ansichten.
+ famous_question:
+ name:
+ other: Erstklassige Frage
+ desc:
+ other: Frage mit 5.000 Ansichten.
+ popular_link:
+ name:
+ other: Populärer Link
+ desc:
+ other: Hat einen externen Link mit 50 Klicks gepostet.
+ hot_link:
+ name:
+ other: Heißer Link
+ desc:
+ other: Geschrieben einen externen Link mit 300 Klicks.
+ famous_link:
+ name:
+ other: Berühmter Link
+ desc:
+ other: Geschrieben einen externen Link mit 100 Klicks.
+ default_badge_groups:
+ getting_started:
+ name:
+ other: Erste Schritte
+ community:
+ name:
+ other: Gemeinschaft
+ posting:
+ name:
+ other: Freigeben
+# The following fields are used for interface presentation(Front-end)
+ui:
+ how_to_format:
+ title: Wie man formatiert
+ desc: >-
+ einen Beitrag erwähnen: #post_id
um Links
<https://url.com> [Titel](https://url.com)Zwischen den Absätzen Zeilenumbrüche einfügen
_italic_ oder **fett **
Code um 4 Leerzeichen einrücken
Zitat durch Setzen von > am Anfang der Zeile
Backtick-Escapes `wie _this_`
Codeumrandungen mit Backticks `
` Code hier ``
+ pagination:
+ prev: Zurück
+ next: Weiter
+ page_title:
+ question: Frage
+ questions: Fragen
+ tag: Schlagwort
+ tags: Schlagwörter
+ tag_wiki: tag Wiki
+ create_tag: Tag erstellen
+ edit_tag: Tag bearbeiten
+ ask_a_question: Create Question
+ edit_question: Frage bearbeiten
+ edit_answer: Antwort bearbeiten
+ search: Suchen
+ posts_containing: Beiträge enthalten
+ settings: Einstellungen
+ notifications: Benachrichtigungen
+ login: Anmelden
+ sign_up: Registrieren
+ account_recovery: Konto-Wiederherstellung
+ account_activation: Account Aktivierung
+ confirm_email: Bestätigungs-E-Mail
+ account_suspended: Konto gesperrt
+ admin: Verwaltung
+ change_email: E-Mails ändern
+ install: Installation beantworten
+ upgrade: Antwort-Upgrade
+ maintenance: Website-Wartung
+ users: Benutzer
+ oauth_callback: In Bearbeitung
+ http_404: HTTP-Fehler 404
+ http_50X: HTTP-Fehler 500
+ http_403: HTTP Fehler 403
+ logout: Ausloggen
+ posts: Posts
+ notifications:
+ title: Benachrichtigungen
+ inbox: Posteingang
+ achievement: Erfolge
+ new_alerts: Neue Benachrichtigungen
+ all_read: Alle als gelesen markieren
+ show_more: Mehr anzeigen
+ someone: Jemand
+ inbox_type:
+ all: Alle
+ posts: Beiträge
+ invites: Einladungen
+ votes: Abstimmungen
+ answer: Antwort
+ question: Frage
+ badge_award: Abzeichen
+ suspended:
+ title: Dein Konto wurde gesperrt
+ until_time: "Dein Konto wurde bis zum {{ time }} gesperrt."
+ forever: Dieser Benutzer wurde für immer gesperrt.
+ end: Du erfüllst keine Community-Richtlinie.
+ contact_us: Kontaktiere uns
+ editor:
+ blockquote:
+ text: Blockzitat
+ bold:
+ text: Stark
+ chart:
+ text: Bestenliste
+ flow_chart: Flussdiagramm
+ sequence_diagram: Sequenzdiagramm
+ class_diagram: Klassen Diagramm
+ state_diagram: Zustandsdiagramm
+ entity_relationship_diagram: Entitätsbeziehungsdiagramm
+ user_defined_diagram: Benutzerdefiniertes Diagramm
+ gantt_chart: Gantt-Diagramm
+ pie_chart: Kuchendiagramm
+ code:
+ text: Code Beispiel
+ add_code: Code-Beispiel hinzufügen
+ form:
+ fields:
+ code:
+ label: Code
+ msg:
+ empty: Code kann nicht leer sein.
+ language:
+ label: Sprache
+ placeholder: Automatische Erkennung
+ btn_cancel: Abbrechen
+ btn_confirm: Hinzufügen
+ formula:
+ text: Formel
+ options:
+ inline: Inline Formel
+ block: Block Formel
+ heading:
+ text: Überschrift
+ options:
+ h1: Überschrift 1
+ h2: Überschrift 2
+ h3: Überschrift 3
+ h4: Überschrift 4
+ h5: Überschrift 5
+ h6: Überschrift 6
+ help:
+ text: Hilfe
+ hr:
+ text: Horizontale Richtlinie
+ image:
+ text: Bild
+ add_image: Bild hinzufügen
+ tab_image: Bild hochladen
+ form_image:
+ fields:
+ file:
+ label: Bilddatei
+ btn: Bild auswählen
+ msg:
+ empty: Datei darf nicht leer sein.
+ only_image: Nur Bilddateien sind erlaubt.
+ max_size: Dateigröße darf {{size}} MB nicht überschreiten.
+ desc:
+ label: Beschreibung
+ tab_url: Bild URL
+ form_url:
+ fields:
+ url:
+ label: Bild URL
+ msg:
+ empty: Bild-URL darf nicht leer sein.
+ name:
+ label: Beschreibung
+ btn_cancel: Abbrechen
+ btn_confirm: Hinzufügen
+ uploading: Hochladen
+ indent:
+ text: Einzug
+ outdent:
+ text: Ausrücken
+ italic:
+ text: Hervorhebung
+ link:
+ text: Hyperlink
+ add_link: Hyperlink hinzufügen
+ form:
+ fields:
+ url:
+ label: URL
+ msg:
+ empty: URL darf nicht leer sein.
+ name:
+ label: Beschreibung
+ btn_cancel: Abbrechen
+ btn_confirm: Hinzufügen
+ ordered_list:
+ text: Nummerierte Liste
+ unordered_list:
+ text: Aufzählungsliste
+ table:
+ text: Tabelle
+ heading: Überschrift
+ cell: Zelle
+ file:
+ text: Datei anhängen
+ not_supported: "Diesen Dateityp nicht unterstützen. Versuchen Sie es erneut mit {{file_type}}."
+ max_size: "Dateigröße anhängen darf {{size}} MB nicht überschreiten."
+ close_modal:
+ title: Ich schließe diesen Beitrag als...
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ remark:
+ empty: Kann nicht leer sein.
+ msg:
+ empty: Bitte wähle einen Grund aus.
+ report_modal:
+ flag_title: Ich melde diesen Beitrag als...
+ close_title: Ich schließe diesen Beitrag wegen ...
+ review_question_title: Frage prüfen
+ review_answer_title: Antwort prüfen
+ review_comment_title: Kommentar prüfen
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ remark:
+ empty: Kann nicht leer sein.
+ msg:
+ empty: Bitte wähle einen Grund aus.
+ 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:
+ fields:
+ display_name:
+ label: Anzeigename
+ msg:
+ empty: Anzeigename darf nicht leer sein.
+ range: Anzeige des Namens mit bis zu 35 Zeichen.
+ slug_name:
+ label: URL-Slug
+ desc: 'Muss den Zeichensatz "a-z", "0-9", "+ # - " verwenden.'
+ msg:
+ empty: URL-Slug darf nicht leer sein.
+ range: URL-Slug mit bis zu 35 Zeichen.
+ character: URL-Slug enthält nicht erlaubten Zeichensatz.
+ desc:
+ label: Beschreibung
+ revision:
+ label: Version
+ edit_summary:
+ label: Zusammenfassung bearbeiten
+ placeholder: >-
+ Erkläre kurz deine Änderungen (korrigierte Rechtschreibung, korrigierte Grammatik, verbesserte Formatierung)
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ btn_post: Neuen Tag erstellen
+ tag_info:
+ created_at: Erstellt
+ edited_at: Bearbeitet
+ history: Verlauf
+ synonyms:
+ title: Synonyme
+ text: Die folgenden Tags werden neu zugeordnet zu
+ empty: Keine Synonyme gefunden.
+ btn_add: Synonym hinzufügen
+ btn_edit: Bearbeiten
+ btn_save: Speichern
+ synonyms_text: Die folgenden Tags werden neu zugeordnet zu
+ delete:
+ title: Diesen Tag löschen
+ tip_with_posts: >-
+ Wir erlauben es nicht, Tags mit Beiträgen zu löschen.
Bitte entfernen Sie dieses Tag zuerst aus den Beiträgen.
+ tip_with_synonyms: >-
+ Wir erlauben nicht Tags mit Synonymen zu löschen.
Bitte entfernen Sie zuerst die Synonyme von diesem Schlagwort.
+ tip: Bist du sicher, dass du löschen möchtest?
+ close: Schließen
+ merge:
+ title: Tags zusammenführen
+ source_tag_title: Quell-Tag
+ source_tag_description: Das Quell-Tag und seine zugehörigen Daten werden dem Ziel-Tag zugeordnet.
+ target_tag_title: Ziel-Tag
+ target_tag_description: Ein Synonym zwischen diesen beiden Tags wird nach dem Zusammenführen erstellt.
+ no_results: Keine zusammenpassenden Tags gefunden
+ btn_submit: Absenden
+ btn_close: Schließen
+ edit_tag:
+ title: Tag bearbeiten
+ default_reason: Tag bearbeiten
+ default_first_reason: Tag hinzufügen
+ btn_save_edits: Änderungen speichern
+ btn_cancel: Abbrechen
+ dates:
+ long_date: DD. MMM
+ long_date_with_year: "DD. MMM YYYY"
+ long_date_with_time: "DD. MMM YYYY [at] HH:mm"
+ now: Gerade eben
+ x_seconds_ago: "Vor {{count}}s"
+ x_minutes_ago: "Vor {{count}}m"
+ x_hours_ago: "Vor {{count}}h"
+ hour: Stunde
+ day: tag
+ hours: Stunden
+ days: Tage
+ month: month
+ months: months
+ year: year
+ reaction:
+ heart: Herz
+ smile: Lächeln
+ frown: Stirnrunzeln
+ btn_label: Reaktionen hinzufügen oder entfernen
+ undo_emoji: '{{ emoji }} Reaktion rückgängig machen'
+ react_emoji: mit {{ emoji }} reagieren
+ unreact_emoji: '{{ emoji }} Reaktion entfernen'
+ comment:
+ btn_add_comment: Einen Kommentar hinzufügen
+ reply_to: Antwort an
+ btn_reply: Antwort
+ btn_edit: Bearbeiten
+ btn_delete: Löschen
+ btn_flag: Melden
+ btn_save_edits: Änderungen speichern
+ btn_cancel: Abbrechen
+ show_more: "{{count}} mehr Kommentare"
+ tip_question: >-
+ Verwende Kommentare, um nach weiteren Informationen zu fragen oder Verbesserungen vorzuschlagen. Vermeide es, Fragen in Kommentaren zu beantworten.
+ tip_answer: >-
+ Verwende Stellungsnahmen, um anderen Nutzern zu antworten oder sie über Änderungen zu informieren. Wenn du neue Informationen hinzufügst, bearbeite deinen Beitrag, anstatt zu kommentieren.
+ tip_vote: Es fügt dem Beitrag etwas Nützliches hinzu
+ edit_answer:
+ title: Antwort bearbeiten
+ default_reason: Antwort bearbeiten
+ default_first_reason: Antwort hinzufügen
+ form:
+ fields:
+ revision:
+ label: Version
+ answer:
+ label: Antwort
+ feedback:
+ characters: der Inhalt muss mindestens 6 Zeichen lang sein.
+ edit_summary:
+ label: Zusammenfassung bearbeiten
+ placeholder: >-
+ Erkläre kurz deine Änderungen (korrigierte Rechtschreibung, korrigierte Grammatik, verbesserte Formatierung)
+ btn_save_edits: Änderungen speichern
+ btn_cancel: Abbrechen
+ tags:
+ title: Schlagwörter
+ sort_buttons:
+ popular: Beliebt
+ name: Name
+ newest: Neueste
+ button_follow: Folgen
+ button_following: Folgend
+ tag_label: fragen
+ search_placeholder: Nach Tagnamen filtern
+ no_desc: Der Tag hat keine Beschreibung.
+ more: Mehr
+ wiki: Wiki
+ ask:
+ title: Create Question
+ edit_title: Frage bearbeiten
+ default_reason: Frage bearbeiten
+ default_first_reason: Create question
+ similar_questions: Ähnliche Fragen
+ form:
+ fields:
+ revision:
+ label: Version
+ title:
+ label: Titel
+ placeholder: What's your topic? Be specific.
+ msg:
+ empty: Der Titel darf nicht leer sein.
+ range: Titel bis zu 150 Zeichen
+ body:
+ label: Körper
+ msg:
+ empty: Körper darf nicht leer sein.
+ hint:
+ optional_body: Describe what the question is about.
+ minimum_characters: "Describe what the question is about, at least {{min_content_length}} characters are required."
+ tags:
+ label: Stichworte
+ msg:
+ empty: Tags dürfen nicht leer sein.
+ answer:
+ label: Antwort
+ msg:
+ empty: Antwort darf nicht leer sein.
+ edit_summary:
+ label: Zusammenfassung bearbeiten
+ placeholder: >-
+ Erkläre kurz deine Änderungen (korrigierte Rechtschreibung, korrigierte Grammatik, verbesserte Formatierung)
+ btn_post_question: Poste deine Frage
+ btn_save_edits: Änderungen speichern
+ answer_question: Eigene Frage beantworten
+ post_question&answer: Poste deine Frage und Antwort
+ tag_selector:
+ add_btn: Schlagwort hinzufügen
+ create_btn: Neuen Tag erstellen
+ search_tag: Tag suchen
+ hint: Describe what your content is about, at least one tag is required.
+ hint_zero_tags: Describe what your content is about.
+ hint_more_than_one_tag: "Describe what your content is about, at least {{min_tags_number}} tags are required."
+ no_result: Keine Tags gefunden
+ tag_required_text: Benötigter Tag (mindestens eins)
+ header:
+ nav:
+ question: Fragen
+ tag: Schlagwörter
+ user: Benutzer
+ badges: Abzeichen
+ profile: Profil
+ setting: Einstellungen
+ logout: Ausloggen
+ admin: Administrator
+ review: Überprüfung
+ bookmark: Lesezeichen
+ moderation: Moderation
+ search:
+ placeholder: Suchen
+ footer:
+ build_on: Powered by <1> Apache Answer 1>
+ upload_img:
+ name: Ändern
+ loading: wird geladen...
+ pic_auth_code:
+ title: Captcha
+ placeholder: Gib den Text oben ein
+ msg:
+ empty: Captcha darf nicht leer sein.
+ inactive:
+ first: >-
+ Du bist fast fertig! Wir haben eine Aktivierungsmail an {{mail}} geschickt. Bitte folge den Anweisungen in der Mail, um dein Konto zu aktivieren.
+ info: "Wenn sie nicht ankommt, überprüfe deinen Spam-Ordner."
+ another: >-
+ Wir haben dir eine weitere Aktivierungs-E-Mail an {{mail}} geschickt. Es kann ein paar Minuten dauern, bis sie ankommt; überprüfe daher deinen Spam-Ordner.
+ btn_name: Aktivierungs Mail erneut senden
+ change_btn_name: E-Mail ändern
+ msg:
+ empty: Kann nicht leer sein.
+ resend_email:
+ url_label: Bist du sicher, dass du die Aktivierungs-E-Mail erneut senden willst?
+ url_text: Du kannst auch den Aktivierungslink oben an den Nutzer weitergeben.
+ login:
+ login_to_continue: Anmelden, um fortzufahren
+ info_sign: Du verfügst noch nicht über ein Konto? Registrieren
+ info_login: Du hast bereits ein Konto? <1>Anmelden1>
+ agreements: Wenn du dich registrierst, stimmst du der <1>Datenschutzrichtlinie1> und den <3>Nutzungsbedingungen3> zu.
+ forgot_pass: Passwort vergessen?
+ name:
+ label: Name
+ msg:
+ empty: Der Name darf nicht leer sein.
+ range: Der Name muss zwischen 2 und 30 Zeichen lang sein.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ email:
+ label: E-Mail
+ msg:
+ empty: E-Mail-Feld darf nicht leer sein.
+ password:
+ label: Passwort
+ msg:
+ empty: Passwort-Feld darf nicht leer sein.
+ different: Die beiden eingegebenen Passwörter stimmen nicht überein
+ account_forgot:
+ page_title: Dein Passwort vergessen
+ btn_name: Schicke mir eine E-Mail zur Wiederherstellung
+ send_success: >-
+ Wenn ein Konto mit {{mail}} übereinstimmt, solltest du in Kürze eine E-Mail mit Anweisungen erhalten, wie du dein Passwort zurücksetzen kannst.
+ email:
+ label: E-Mail
+ msg:
+ empty: E-Mail darf nicht leer sein.
+ change_email:
+ btn_cancel: Stornieren
+ btn_update: E-Mail Adresse aktualisieren
+ send_success: >-
+ Wenn ein Konto mit {{mail}} übereinstimmt, solltest du in Kürze eine E-Mail mit Anweisungen erhalten, wie du dein Passwort zurücksetzen kannst.
+ email:
+ label: Neue E-Mail
+ msg:
+ empty: E-Mail darf nicht leer sein.
+ oauth:
+ connect: Mit {{ auth_name }} verbinden
+ remove: '{{ auth_name }} entfernen'
+ oauth_bind_email:
+ subtitle: Wiederherstellungs-E-Mail zu deinem Konto hinzufügen.
+ btn_update: E-Mail aktualisieren
+ email:
+ label: E-Mail
+ msg:
+ empty: E-Mail darf nicht leer sein.
+ modal_title: E-Mail existiert bereits.
+ modal_content: Diese E-Mail ist bereits registriert. Bist du sicher, dass du dich mit dem bestehenden Konto verbinden möchtest?
+ modal_cancel: E-Mail ändern
+ modal_confirm: Mit dem bestehenden Konto verbinden
+ password_reset:
+ page_title: Passwort zurücksetzen
+ btn_name: Setze mein Passwort zurück
+ reset_success: >-
+ Du hast dein Passwort erfolgreich geändert; du wirst zur Anmeldeseite weitergeleitet.
+ link_invalid: >-
+ Dieser Link zum Zurücksetzen des Passworts ist leider nicht mehr gültig. Vielleicht ist dein Passwort bereits zurückgesetzt?
+ to_login: Weiter zur Anmeldeseite
+ password:
+ label: Passwort
+ msg:
+ empty: Passwort kann nicht leer sein.
+ length: Die Länge muss zwischen 8 und 32 liegen
+ different: Die auf beiden Seiten eingegebenen Passwörter sind inkonsistent
+ password_confirm:
+ label: Neues Passwort bestätigen
+ settings:
+ page_title: Einstellungen
+ goto_modify: Zum Ändern
+ nav:
+ profile: Profil
+ notification: Benachrichtigungen
+ account: Konto
+ interface: Benutzeroberfläche
+ profile:
+ heading: Profil
+ btn_name: Speichern
+ display_name:
+ label: Anzeigename
+ msg: Anzeigename darf nicht leer sein.
+ msg_range: Der Anzeigename muss zwischen 2 und 30 Zeichen lang sein.
+ username:
+ label: Nutzername
+ caption: Leute können dich als "@Benutzername" erwähnen.
+ msg: Benutzername darf nicht leer sein.
+ msg_range: Der Benutzername muss zwischen 2 und 30 Zeichen lang sein.
+ character: 'Must use the character set "a-z", "0-9", "- . _"'
+ avatar:
+ label: Profilbild
+ gravatar: Gravatar
+ gravatar_text: Du kannst das Bild ändern auf
+ custom: Benutzerdefiniert
+ custom_text: Du kannst dein Bild hochladen.
+ default: System
+ msg: Bitte lade einen Avatar hoch
+ bio:
+ label: Über mich
+ website:
+ label: Webseite
+ placeholder: "https://example.com"
+ msg: Website falsches Format
+ location:
+ label: Standort
+ placeholder: "Stadt, Land"
+ notification:
+ heading: E-Mail-Benachrichtigungen
+ turn_on: Aktivieren
+ inbox:
+ label: Posteingangsbenachrichtigungen
+ description: Antworten auf deine Fragen, Kommentare, Einladungen und mehr.
+ all_new_question:
+ label: Alle neuen Fragen
+ description: Lass dich über alle neuen Fragen benachrichtigen. Bis zu 50 Fragen pro Woche.
+ all_new_question_for_following_tags:
+ label: Alle neuen Fragen für folgende Tags
+ description: Lass dich über neue Fragen zu folgenden Tags benachrichtigen.
+ account:
+ heading: Konto
+ change_email_btn: E-Mail ändern
+ change_pass_btn: Passwort ändern
+ 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:
+ label: Aktuelles Passwort
+ msg: Passwort kann nicht leer sein.
+ password_title: Passwort
+ current_pass:
+ label: Aktuelles Passwort
+ msg:
+ empty: Das aktuelle Passwort darf nicht leer sein.
+ length: Die Länge muss zwischen 8 und 32 liegen.
+ different: Die beiden eingegebenen Passwörter stimmen nicht überein.
+ new_pass:
+ label: Neues Passwort
+ pass_confirm:
+ label: Neues Passwort bestätigen
+ interface:
+ heading: Benutzeroberfläche
+ lang:
+ label: Sprache der Benutzeroberfläche
+ text: Sprache der Benutzeroberfläche. Sie ändert sich, wenn du die Seite aktualisierst.
+ my_logins:
+ title: Meine Anmeldungen
+ label: Melde dich mit diesen Konten an oder registriere dich auf dieser Seite.
+ modal_title: Login entfernen
+ modal_content: Bist du sicher, dass du dieses Login aus deinem Konto entfernen möchtest?
+ modal_confirm_btn: Entfernen
+ remove_success: Erfolgreich entfernt
+ toast:
+ update: Aktualisierung erfolgreich
+ update_password: Das Kennwort wurde erfolgreich geändert.
+ flag_success: Danke fürs Markieren.
+ forbidden_operate_self: Verboten, an sich selbst zu operieren
+ review: Deine Überarbeitung wird nach der Überprüfung angezeigt.
+ sent_success: Erfolgreich gesendet
+ related_question:
+ title: Related
+ answers: antworten
+ linked_question:
+ title: Linked
+ description: Posts linked to
+ no_linked_question: No contents linked from this content.
+ invite_to_answer:
+ 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
+ question_detail:
+ action: Aktion
+ created: Created
+ Asked: Gefragt
+ asked: gefragt
+ update: Geändert
+ Edited: Edited
+ edit: bearbeitet
+ commented: kommentiert
+ Views: Gesehen
+ Follow: Folgen
+ Following: Folgend
+ follow_tip: Folge dieser Frage, um Benachrichtigungen zu erhalten
+ answered: beantwortet
+ closed_in: Abgeschlossen in
+ show_exist: Bestehende Frage anzeigen.
+ useful: Nützlich
+ question_useful: Es ist nützlich und klar
+ question_un_useful: Es ist unklar oder nicht nützlich
+ question_bookmark: Lesezeichen für diese Frage
+ answer_useful: Es ist nützlich
+ answer_un_useful: Es ist nicht nützlich
+ answers:
+ title: Antworten
+ score: Punkte
+ newest: Neueste
+ oldest: Älteste
+ btn_accept: Akzeptieren
+ btn_accepted: Akzeptiert
+ write_answer:
+ title: Deine Antwort
+ edit_answer: Meine existierende Antwort bearbeiten
+ btn_name: Poste deine Antwort
+ add_another_answer: Weitere Antwort hinzufügen
+ confirm_title: Antworten fortsetzen
+ continue: Weitermachen
+ confirm_info: >-
+ Bist du sicher, dass du eine weitere Antwort hinzufügen willst?
Du könntest stattdessen den Bearbeiten-Link verwenden, um deine existierende Antwort zu verfeinern und zu verbessern.
+ empty: Antwort darf nicht leer sein.
+ characters: der Inhalt muss mindestens 6 Zeichen lang sein.
+ tips:
+ header_1: Danke für deine Antwort
+ li1_1: Bitte stelle sicher, dass du die Frage beantwortest . Gib Details an und erzähle von deiner Recherche.
+ li1_2: Untermauere alle Aussagen, die du erstellst, mit Referenzen oder persönlichen Erfahrungen.
+ header_2: Aber vermeide ...
+ li2_1: Bitte um Hilfe, um Klarstellung oder um Antwort auf andere Antworten.
+ reopen:
+ confirm_btn: Wieder öffnen
+ title: Diesen Beitrag erneut öffnen
+ content: Bist du sicher, dass du wieder öffnen willst?
+ list:
+ confirm_btn: Liste
+ title: Diesen Beitrag auflisten
+ content: Möchten Sie diesen Beitrag wirklich in der Liste anzeigen?
+ unlist:
+ confirm_btn: Von Liste nehmen
+ title: Diesen Beitrag von der Liste nehmen
+ content: Möchten Sie diesen Beitrag wirklich aus der Liste ausblenden?
+ pin:
+ title: Diesen Beitrag anpinnen
+ content: Bist du sicher, dass du den Beitrag global anheften möchtest? Dieser Beitrag wird in allen Beitragslisten ganz oben erscheinen.
+ confirm_btn: Anheften
+ delete:
+ title: Diesen Beitrag löschen
+ question: >-
+ Wir raten davon ab, Fragen mit Antworten zu löschen , weil dadurch zukünftigen Lesern dieses Wissen vorenthalten wird.Wiederholtes Löschen von beantworteten Fragen kann dazu führen, dass dein Konto für Fragen gesperrt wird. Bist du sicher, dass du löschen möchtest?
+ answer_accepted: >-
+
Wir empfehlen nicht, akzeptierte Antworten zu löschen , denn dadurch wird zukünftigen Lesern dieses Wissen vorenthalten.
Das wiederholte Löschen von akzeptierten Antworten kann dazu führen, dass dein Konto für die Beantwortung gesperrt wird. Bist du sicher, dass du löschen möchtest?
+ other: Bist du sicher, dass du löschen möchtest?
+ tip_answer_deleted: Diese Antwort wurde gelöscht
+ undelete_title: Diesen Beitrag wiederherstellen
+ undelete_desc: Bist du sicher, dass du die Löschung umkehren willst?
+ btns:
+ confirm: Bestätigen
+ cancel: Abbrechen
+ edit: Bearbeiten
+ save: Speichern
+ delete: Löschen
+ undelete: Wiederherstellen
+ list: Liste
+ unlist: Verstecken
+ unlisted: Versteckt
+ login: Einloggen
+ signup: Registrieren
+ logout: Ausloggen
+ verify: Überprüfen
+ create: Erstellen
+ approve: Genehmigen
+ reject: Ablehnen
+ skip: Überspringen
+ discard_draft: Entwurf verwerfen
+ pinned: Angeheftet
+ all: Alle
+ question: Frage
+ answer: Antwort
+ comment: Kommentar
+ refresh: Aktualisieren
+ resend: Erneut senden
+ deactivate: Deaktivieren
+ active: Aktiv
+ suspend: Sperren
+ unsuspend: Entsperren
+ close: Schließen
+ reopen: Wieder öffnen
+ ok: Okay
+ light: Hell
+ dark: Dunkel
+ system_setting: System-Einstellung
+ default: Standard
+ reset: Zurücksetzen
+ tag: Tag
+ post_lowercase: post
+ filter: Filter
+ ignore: Ignorieren
+ submit: Absenden
+ normal: Normal
+ closed: Geschlossen
+ deleted: Gelöscht
+ deleted_permanently: Dauerhaft gelöscht
+ pending: Ausstehend
+ more: Mehr
+ view: Betrachten
+ card: Karte
+ compact: Kompakt
+ display_below: Unten anzeigen
+ always_display: Immer anzeigen
+ or: oder
+ back_sites: Zurück zur Website
+ search:
+ title: Suchergebnisse
+ keywords: Schlüsselwörter
+ options: Optionen
+ follow: Folgen
+ following: Folgend
+ counts: "{{count}} Ergebnisse"
+ counts_loading: "... Results"
+ more: Mehr
+ sort_btns:
+ relevance: Relevanz
+ newest: Neueste
+ active: Aktiv
+ score: Punktzahl
+ more: Mehr
+ tips:
+ title: Erweiterte Suchtipps
+ tag: "<1>[tag]1> Suche mit einem Tag"
+ user: "<1>user:username1> Suche nach Autor"
+ answer: "<1>Antworten:01> unbeantwortete Fragen"
+ score: "<1>score:31> Beiträge mit einer 3+ Punktzahl"
+ question: "<1>is:question1> Suchfragen"
+ is_answer: "<1>ist:answer1> Suchantworten"
+ empty: Wir konnten nichts finden. Versuche es mit anderen oder weniger spezifischen Keywords.
+ share:
+ name: Teilen
+ copy: Link kopieren
+ via: Beitrag teilen über...
+ copied: Kopiert
+ facebook: Auf Facebook teilen
+ twitter: Auf X teilen
+ cannot_vote_for_self: Du kannst nicht für deinen eigenen Beitrag stimmen.
+ modal_confirm:
+ title: Fehler...
+ delete_permanently:
+ title: Endgültig löschen
+ content: Sind Sie sicher, dass Sie den Inhalt endgültig löschen möchten?
+ account_result:
+ success: Dein neues Konto ist bestätigt; du wirst zur Startseite weitergeleitet.
+ link: Weiter zur Startseite
+ oops: Hoppla!
+ invalid: Der Link, den Sie verwendet haben, funktioniert nicht mehr.
+ confirm_new_email: Deine E-Mail wurde aktualisiert.
+ confirm_new_email_invalid: >-
+ Dieser Bestätigungslink ist leider nicht mehr gültig. Vielleicht wurde deine E-Mail-Adresse bereits geändert?
+ unsubscribe:
+ page_title: Abonnement entfernen
+ success_title: Erfolgreich vom Abo abgemeldet
+ success_desc: Du wurdest erfolgreich aus der Abonnentenliste gestrichen und wirst keine weiteren E-Mails von uns erhalten.
+ link: Einstellungen ändern
+ question:
+ following_tags: Folgende Tags
+ edit: Bearbeiten
+ save: Speichern
+ follow_tag_tip: Folge den Tags, um deine Liste mit Fragen zu erstellen.
+ hot_questions: Angesagte Fragen
+ all_questions: Alle Fragen
+ x_questions: "{{ count }} Fragen"
+ x_answers: "{{ count }} Antworten"
+ x_posts: "{{ count }} Posts"
+ questions: Fragen
+ answers: Antworten
+ newest: Neueste
+ active: Aktiv
+ hot: Heiß
+ frequent: Häufig
+ recommend: Empfehlen
+ score: Punktzahl
+ unanswered: Unbeantwortet
+ modified: geändert
+ answered: beantwortet
+ asked: gefragt
+ closed: schließen
+ follow_a_tag: Einem Tag folgen
+ more: Mehr
+ personal:
+ overview: Übersicht
+ answers: Antworten
+ answer: antwort
+ questions: Fragen
+ question: frage
+ bookmarks: Lesezeichen
+ reputation: Ansehen
+ comments: Kommentare
+ votes: Stimmen
+ badges: Abzeichen
+ newest: Neueste
+ score: Punktzahl
+ edit_profile: Profil bearbeiten
+ visited_x_days: "{{ count }} Tage besucht"
+ viewed: Gesehen
+ joined: Beigetreten
+ comma: ","
+ last_login: Gesehen
+ about_me: Über mich
+ about_me_empty: "// Hallo Welt !"
+ top_answers: Top-Antworten
+ top_questions: Top-Fragen
+ stats: Statistiken
+ list_empty: Keine Beiträge gefunden. Vielleicht möchtest du einen anderen Reiter auswählen?
+ content_empty: Keine Posts gefunden.
+ accepted: Akzeptiert
+ answered: antwortete
+ asked: gefragt
+ downvoted: negativ bewertet
+ mod_short: MOD
+ mod_long: Moderatoren
+ x_reputation: ansehen
+ x_votes: Stimmen erhalten
+ x_answers: Antworten
+ x_questions: Fragen
+ recent_badges: Neueste Abzeichen
+ install:
+ title: Installation
+ next: Nächste
+ done: Erledigt
+ config_yaml_error: Die Datei config.yaml kann nicht erstellt werden.
+ lang:
+ label: Bitte wähle eine Sprache
+ db_type:
+ label: Datenbank-Engine
+ db_username:
+ label: Nutzername
+ placeholder: wurzel
+ msg: Benutzername darf nicht leer sein.
+ db_password:
+ label: Passwort
+ placeholder: wurzel
+ msg: Passwort kann nicht leer sein.
+ db_host:
+ label: Datenbank-Host
+ placeholder: "db:3306"
+ msg: Datenbank-Host darf nicht leer sein.
+ db_name:
+ label: Datenbankname
+ placeholder: antworten
+ msg: Der Datenbankname darf nicht leer sein.
+ db_file:
+ label: Datenbank-Datei
+ placeholder: /data/answer.Weder noch
+ msg: Datenbankdatei kann nicht leer sein.
+ ssl_enabled:
+ label: SSL aktivieren
+ ssl_enabled_on:
+ label: On
+ ssl_enabled_off:
+ label: Off
+ ssl_mode:
+ label: SSL-Modus
+ ssl_root_cert:
+ placeholder: SSL-Root-Zertifikat Pfad
+ msg: Pfad zum Ssl-Root-Zertifikat darf nicht leer sein
+ ssl_cert:
+ placeholder: SSL-Zertifikat Pfad
+ msg: Pfad zum SSL-Zertifikat darf nicht leer sein
+ ssl_key:
+ placeholder: SSL-Key Pfad
+ msg: Der Pfad zum SSL-Key darf nicht leer sein
+ config_yaml:
+ title: config.yaml erstellen
+ label: Die erstellte config.yaml-Datei.
+ desc: >-
+ Du kannst die Datei <1>config.yaml1> manuell im Verzeichnis <1>/var/wwww/xxx/1> erstellen und den folgenden Text dort einfügen.
+ info: Nachdem du das getan hast, klickst du auf die Schaltfläche "Weiter".
+ site_information: Standortinformationen
+ admin_account: Administratorkonto
+ site_name:
+ label: Seitenname
+ msg: Standortname darf nicht leer sein.
+ msg_max_length: Der Name der Website darf maximal 30 Zeichen lang sein.
+ site_url:
+ label: Seiten-URL
+ text: Die Adresse deiner Website.
+ msg:
+ empty: Die Website-URL darf nicht leer sein.
+ incorrect: Falsches Format der Website-URL.
+ max_length: Die URL der Website darf maximal 512 Zeichen lang sein.
+ contact_email:
+ label: Kontakt E-Mail
+ text: E-Mail-Adresse des Hauptkontakts, der für diese Website verantwortlich ist.
+ msg:
+ empty: Kontakt-E-Mail kann nicht leer sein.
+ incorrect: Falsches Format der Kontakt-E-Mail.
+ login_required:
+ label: Privat
+ switch: Anmeldung erforderlich
+ text: Nur eingeloggte Benutzer können auf diese Community zugreifen.
+ admin_name:
+ label: Name
+ msg: Der Name darf nicht leer sein.
+ character: 'Must use the character set "a-z", "0-9", " - . _"'
+ msg_max_length: Der Name muss zwischen 2 und 30 Zeichen lang sein.
+ admin_password:
+ label: Passwort
+ text: >-
+ Du brauchst dieses Passwort, um dich einzuloggen. Bitte bewahre es an einem sicheren Ort auf.
+ msg: Passwort kann nicht leer sein.
+ msg_min_length: Passwort muss mindestens 8 Zeichen lang sein.
+ msg_max_length: Das Passwort darf maximal 32 Zeichen lang sein.
+ admin_confirm_password:
+ label: "Passwort bestätigen"
+ text: "Bitte geben Sie Ihr Passwort erneut ein, um es zu bestätigen."
+ msg: "Passwortbestätigung stimmt nicht überein!"
+ admin_email:
+ label: E-Mail
+ text: Du brauchst diese E-Mail, um dich einzuloggen.
+ msg:
+ empty: E-Mail darf nicht leer sein.
+ incorrect: E-Mail falsches Format.
+ ready_title: Ihre Seite ist bereit
+ ready_desc: >-
+ Wenn du noch mehr Einstellungen ändern möchtest, besuche den <1>Admin-Bereich1>; du findest ihn im Seitenmenü.
+ good_luck: "Viel Spaß und viel Glück!"
+ warn_title: Warnung
+ warn_desc: >-
+ Die Datei <1>config.yaml1> existiert bereits. Wenn du einen der Konfigurationspunkte in dieser Datei zurücksetzen musst, lösche sie bitte zuerst.
+ install_now: Du kannst versuchen, <1>jetzt zu installieren1>.
+ installed: Bereits installiert
+ installed_desc: >-
+ Du scheinst es bereits installiert zu haben. Um neu zu installieren, lösche bitte zuerst deine alten Datenbanktabellen.
+ db_failed: Datenbankverbindung fehlgeschlagen
+ db_failed_desc: >-
+ Das bedeutet entweder, dass die Datenbankinformationen in deiner <1>config.yaml1> Datei falsch sind oder dass der Kontakt zum Datenbankserver nicht hergestellt werden konnte. Das könnte bedeuten, dass der Datenbankserver deines Hosts ausgefallen ist.
+ counts:
+ views: Ansichten
+ votes: Stimmen
+ answers: Antworten
+ accepted: Akzeptiert
+ page_error:
+ http_error: HTTP Fehler {{ code }}
+ desc_403: Du hast keine Berechtigung, auf diese Seite zuzugreifen.
+ desc_404: Leider existiert diese Seite nicht.
+ desc_50X: Der Server ist auf einen Fehler gestoßen und konnte deine Anfrage nicht vollständig abschließen.
+ back_home: Zurück zur Startseite
+ page_maintenance:
+ desc: "Wir werden gewartet, wir sind bald wieder da."
+ nav_menus:
+ dashboard: Dashboard
+ contents: Inhalt
+ questions: Fragen
+ answers: Antworten
+ users: Benutzer
+ badges: Abzeichen
+ flags: Meldungen
+ settings: Einstellungen
+ general: Allgemein
+ interface: Benutzeroberfläche
+ smtp: SMTP
+ branding: Branding
+ legal: Rechtliches
+ write: Schreiben
+ terms: Terms
+ tos: Nutzungsbedingungen
+ privacy: Privatsphäre
+ seo: SEO
+ customize: Anpassen
+ themes: Themen
+ login: Anmeldung
+ privileges: Berechtigungen
+ plugins: Erweiterungen (Plugins)
+ installed_plugins: Installierte Plugins
+ apperance: Erscheinungsbild
+ website_welcome: Willkommen auf {{site_name}}
+ user_center:
+ login: Anmelden
+ qrcode_login_tip: Bitte verwende {{ agentName }}, um den QR-Code zu scannen und dich einzuloggen.
+ login_failed_email_tip: Anmeldung ist fehlgeschlagen. Bitte erlaube dieser App, auf deine E-Mail-Informationen zuzugreifen, bevor du es erneut versuchst.
+ badges:
+ modal:
+ title: Glückwunsch
+ content: Sie haben sich ein neues Abzeichen verdient.
+ close: Schließen
+ confirm: Abzeichen ansehen
+ title: Abzeichen
+ awarded: Verliehen
+ earned_×: Verdiente ×{{ number }}
+ ×_awarded: "verliehen {{ number }} "
+ can_earn_multiple: Du kannst das mehrmals verdienen.
+ earned: Verdient
+ admin:
+ admin_header:
+ title: Administrator
+ dashboard:
+ title: Dashboard
+ welcome: Willkommen im Admin Bereich!
+ site_statistics: Website-Statistiken
+ questions: "Fragen:"
+ resolved: "Belöst:"
+ unanswered: "Nicht beantwortet:"
+ answers: "Antworten:"
+ comments: "Kommentare:"
+ votes: "Stimmen:"
+ users: "Nutzer:"
+ flags: "Meldungen:"
+ reviews: "Rezension:"
+ site_health: Gesundheit der Website
+ version: "Version:"
+ https: "HTTPS:"
+ upload_folder: "Hochladeverzeichnis:"
+ run_mode: "Betriebsmodus:"
+ private: Privat
+ public: Öffentlich
+ smtp: "SMTP:"
+ timezone: "Zeitzone:"
+ system_info: Systeminformationen
+ go_version: "Go Version:"
+ database: "Datenbank:"
+ database_size: "Datenbankgröße:"
+ storage_used: "Verwendeter Speicher:"
+ uptime: "Betriebszeit:"
+ links: Links
+ plugins: Plugins
+ github: GitHub
+ blog: Blog
+ contact: Kontakt
+ forum: Forum
+ documents: Dokumentation
+ feedback: Rückmeldung
+ support: Unterstützung
+ review: Überprüfung
+ config: Konfig
+ update_to: Aktualisieren zu
+ latest: Aktuell
+ check_failed: Prüfung fehlgeschlagen
+ "yes": "Ja"
+ "no": "Nein"
+ not_allowed: Nicht erlaubt
+ allowed: Erlaubt
+ enabled: Aktiviert
+ disabled: Deaktiviert
+ writable: Schreibbar
+ not_writable: Nicht schreibbar
+ flags:
+ title: Meldungen
+ pending: Ausstehend
+ completed: Abgeschlossen
+ flagged: Gekennzeichnet
+ flagged_type: '{{ type }} gemeldet'
+ created: Erstellt
+ action: Aktion
+ review: Überprüfung
+ user_role_modal:
+ title: Benutzerrolle ändern zu...
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ new_password_modal:
+ title: Neues Passwort festlegen
+ form:
+ fields:
+ password:
+ label: Passwort
+ text: Der Nutzer wird abgemeldet und muss sich erneut anmelden.
+ msg: Das Passwort muss mindestens 8-32 Zeichen lang sein.
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ edit_profile_modal:
+ title: Profil bearbeiten
+ form:
+ fields:
+ display_name:
+ label: Anzeigename
+ msg_range: Der Anzeigename muss zwischen 2 und 30 Zeichen lang sein.
+ username:
+ label: Nutzername
+ msg_range: Der Benutzername muss 2-30 Zeichen lang sein.
+ email:
+ label: E-Mail
+ msg_invalid: Ungültige E-Mail-Adresse.
+ edit_success: Erfolgreich bearbeitet
+ btn_cancel: Abbrechen
+ btn_submit: Absenden
+ user_modal:
+ title: Neuen Benutzer hinzufügen
+ form:
+ fields:
+ users:
+ label: Masse Benutzer hinzufügen
+ placeholder: "John Smith, john@example.com, BUSYopr2\nAlice, alice@example.com, fpDntV8q"
+ text: Trenne "Name, E-Mail, Passwort" mit Kommas. Ein Benutzer pro Zeile.
+ msg: "Bitte gib die E-Mail des Nutzers ein, eine pro Zeile."
+ display_name:
+ label: Anzeigename
+ msg: Der Anzeigename muss zwischen 2 und 30 Zeichen lang sein.
+ email:
+ label: E-Mail
+ msg: Die E-Mail ist nicht gültig.
+ password:
+ label: Passwort
+ msg: Das Passwort muss mindestens 8-32 Zeichen lang sein.
+ btn_cancel: Abbrechen
+ btn_submit: Senden
+ users:
+ title: Benutzer
+ name: Name
+ email: E-Mail
+ reputation: Ansehen
+ created_at: Angelegt am
+ delete_at: Löschzeit
+ suspend_at: Sperrzeit
+ suspend_until: Suspend until
+ status: Status
+ role: Rolle
+ action: Aktion
+ change: Ändern
+ all: Alle
+ staff: Teammitglieder
+ more: Mehr
+ inactive: Inaktiv
+ suspended: Gesperrt
+ deleted: Gelöscht
+ normal: Normal
+ Moderator: Moderation
+ Admin: Administrator
+ User: Benutzer
+ filter:
+ placeholder: "Nach Namen, user:id filtern"
+ set_new_password: Neues Passwort festlegen
+ edit_profile: Profil bearbeiten
+ change_status: Status ändern
+ change_role: Rolle wechseln
+ show_logs: Protokolle anzeigen
+ add_user: Benutzer hinzufügen
+ deactivate_user:
+ title: Benutzer deaktivieren
+ content: Ein inaktiver Nutzer muss seine E-Mail erneut bestätigen.
+ delete_user:
+ title: Diesen Benutzer löschen
+ content: Bist du sicher, dass du diesen Benutzer löschen willst? Das ist dauerhaft!
+ remove: Ihren Inhalt entfernen
+ label: Alle Fragen, Antworten, Kommentare, etc. entfernen
+ text: Aktiviere diese Option nicht, wenn du nur das Benutzerkonto löschen möchtest.
+ suspend_user:
+ title: Diesen Benutzer sperren
+ content: Ein gesperrter Benutzer kann sich nicht einloggen.
+ label: How long will the user be suspended for?
+ forever: Forever
+ questions:
+ page_title: Fragen
+ unlisted: Nicht gelistet
+ post: Beitrag
+ votes: Stimmen
+ answers: Antworten
+ created: Erstellt
+ status: Status
+ action: Aktion
+ change: Ändern
+ pending: Ausstehend
+ filter:
+ placeholder: "Filtern nach Titel, Frage:Id"
+ answers:
+ page_title: Antworten
+ post: Beitrag
+ votes: Stimmen
+ created: Erstellt
+ status: Status
+ action: Aktion
+ change: Ändern
+ filter:
+ placeholder: "Filtern nach Titel, Antwort: id"
+ general:
+ page_title: Allgemein
+ name:
+ label: Seitenname
+ msg: Der Site-Name darf nicht leer sein.
+ text: "Der Name dieser Website, wie er im Titel-Tag verwendet wird."
+ site_url:
+ label: Seiten-URL
+ msg: Die Website-Url darf nicht leer sein.
+ validate: Bitte gib eine gültige URL ein.
+ text: Die Adresse deiner Website.
+ short_desc:
+ label: Kurze Seitenbeschreibung
+ msg: Die kurze Website-Beschreibung darf nicht leer sein.
+ text: "Kurze Beschreibung, wie im Titel-Tag auf der Homepage verwendet."
+ desc:
+ label: Seitenbeschreibung
+ msg: Die Websitebeschreibung darf nicht leer sein.
+ text: "Beschreibe diese Seite in einem Satz, wie er im Meta Description Tag verwendet wird."
+ contact_email:
+ label: Kontakt E-Mail
+ msg: Kontakt-E-Mail darf nicht leer sein.
+ validate: Kontakt-E-Mail ist ungültig.
+ text: E-Mail-Adresse des Hauptkontakts, der für diese Website verantwortlich ist.
+ check_update:
+ label: Softwareaktualisierungen
+ text: Automatisch auf Updates prüfen
+ interface:
+ page_title: Benutzeroberfläche
+ language:
+ label: Interface Sprache
+ msg: Sprache der Benutzeroberfläche darf nicht leer sein.
+ text: Sprache der Benutzeroberfläche. Sie ändert sich, wenn du die Seite aktualisierst.
+ time_zone:
+ label: Zeitzone
+ msg: Die Zeitzone darf nicht leer sein.
+ text: Wähle eine Stadt in der gleichen Zeitzone wie du.
+ avatar:
+ label: Default avatar
+ text: For users without a custom avatar of their own.
+ gravatar_base_url:
+ label: Gravatar base URL
+ text: URL of the Gravatar provider's API base. Ignored when empty.
+ smtp:
+ page_title: SMTP
+ from_email:
+ label: Von E-Mail
+ msg: Von E-Mail darf nicht leer sein.
+ text: Die E-Mail-Adresse, von der E-Mails gesendet werden.
+ from_name:
+ label: Von Name
+ msg: Absendername darf nicht leer sein.
+ text: Der Name, von dem E-Mails gesendet werden.
+ smtp_host:
+ label: SMTP-Host
+ msg: Der SMTP-Host darf nicht leer sein.
+ text: Dein Mailserver.
+ encryption:
+ label: Verschlüsselung
+ msg: Verschlüsselung darf nicht leer sein.
+ text: Für die meisten Server ist SSL die empfohlene Option.
+ ssl: SSL
+ tls: TLS
+ none: Keine
+ smtp_port:
+ label: SMTP-Port
+ msg: SMTP-Port muss Nummer 1 ~ 65535 sein.
+ text: Der Port zu deinem Mailserver.
+ smtp_username:
+ label: SMTP-Benutzername
+ msg: Der SMTP-Benutzername darf nicht leer sein.
+ smtp_password:
+ label: SMTP-Kennwort
+ msg: Das SMTP-Passwort darf nicht leer sein.
+ test_email_recipient:
+ label: Test-E-Mail-Empfänger
+ text: Gib die E-Mail-Adresse an, an die Testsendungen gesendet werden sollen.
+ msg: Test-E-Mail-Empfänger ist ungültig
+ smtp_authentication:
+ label: Authentifizierung aktivieren
+ title: SMTP-Authentifizierung
+ msg: Die SMTP-Authentifizierung darf nicht leer sein.
+ "yes": "Ja"
+ "no": "Nein"
+ branding:
+ page_title: Branding
+ logo:
+ label: Logo
+ msg: Logo darf nicht leer sein.
+ text: Das Logobild oben links auf deiner Website. Verwende ein breites rechteckiges Bild mit einer Höhe von 56 und einem Seitenverhältnis von mehr als 3:1. Wenn du es leer lässt, wird der Text des Website-Titels angezeigt.
+ mobile_logo:
+ label: Mobiles Logo
+ text: Das Logo wird auf der mobilen Version deiner Website verwendet. Verwende ein breites rechteckiges Bild mit einer Höhe von 56. Wenn du nichts angibst, wird das Bild aus der Einstellung "Logo" verwendet.
+ square_icon:
+ label: Quadratisches Symbol
+ msg: Quadratisches Symbol darf nicht leer sein.
+ text: Bild, das als Basis für Metadatensymbole verwendet wird. Sollte idealerweise größer als 512x512 sein.
+ favicon:
+ label: Favicon
+ text: Ein Favicon für deine Website. Um korrekt über ein CDN zu funktionieren, muss es ein png sein. Es wird auf 32x32 verkleinert. Wenn du es leer lässt, wird das "quadratische Symbol" verwendet.
+ legal:
+ page_title: Rechtliches
+ terms_of_service:
+ label: Nutzungsbedingungen
+ text: "Du kannst hier Inhalte zu den Nutzungsbedingungen hinzufügen. Wenn du bereits ein Dokument hast, das anderswo gehostet wird, gib hier die vollständige URL an."
+ privacy_policy:
+ label: Datenschutzbestimmungen
+ text: "Du kannst hier Inhalte zur Datenschutzerklärung hinzufügen. Wenn du bereits ein Dokument hast, das anderswo gehostet wird, gib hier die vollständige URL an."
+ external_content_display:
+ label: Externer Inhalt
+ text: "Inhalte umfassen Bilder, Videos und Medien, die von externen Websites eingebettet sind."
+ always_display: Externen Inhalt immer anzeigen
+ ask_before_display: Vor der Anzeige externer Inhalte fragen
+ write:
+ page_title: Schreiben
+ min_content:
+ label: Minimum question body length
+ text: Minimum allowed question body length in characters.
+ restrict_answer:
+ title: Antwort bearbeiten
+ label: Jeder Benutzer kann für jede Frage nur eine Antwort schreiben
+ text: "Schalten Sie aus, um es Benutzern zu ermöglichen, mehrere Antworten auf dieselbe Frage zu schreiben, was dazu führen kann, dass Antworten nicht im Fokus stehen."
+ min_tags:
+ label: "Minimum tags per question"
+ text: "Minimum number of tags required in a question."
+ recommend_tags:
+ label: Empfohlene Tags
+ text: "Empfohlene Tags werden standardmäßig in der Dropdown-Liste angezeigt."
+ msg:
+ contain_reserved: "empfohlene Tags dürfen keine reservierten Tags enthalten"
+ required_tag:
+ title: Benötigte Tags festlegen
+ label: '"Empfohlene Tags" als erforderliche Tags festlegen'
+ text: "Jede neue Frage muss mindestens ein Empfehlungs-Tag haben."
+ reserved_tags:
+ label: Reservierte Tags
+ text: "Reservierte Tags können nur vom Moderator verwendet werden."
+ image_size:
+ label: Maximale Bildgröße (MB)
+ text: "Die maximale Bildladegröße."
+ attachment_size:
+ label: Maximale Anhanggröße (MB)
+ text: "Die maximale Dateigröße für Dateianhänge."
+ image_megapixels:
+ label: Max. BildmePixel
+ text: "Maximale Anzahl an Megapixeln für ein Bild."
+ image_extensions:
+ label: Autorisierte Bilderweiterungen
+ text: "Eine Liste von Dateierweiterungen, die für die Anzeige von Bildern erlaubt sind, getrennt durch Kommata."
+ attachment_extensions:
+ label: Autorisierte Anhänge Erweiterungen
+ text: "Eine Liste von Dateierweiterungen, die für das Hochladen erlaubt sind, getrennt mit Kommas. WARNUNG: Erlaubt Uploads kann Sicherheitsprobleme verursachen."
+ seo:
+ page_title: SEO
+ permalink:
+ label: Dauerlink
+ text: Benutzerdefinierte URL-Strukturen können die Benutzerfreundlichkeit und die Vorwärtskompatibilität deiner Links verbessern.
+ robots:
+ label: robots.txt
+ text: Dadurch werden alle zugehörigen Site-Einstellungen dauerhaft überschrieben.
+ themes:
+ page_title: Themen
+ themes:
+ label: Themen
+ text: Wähle ein bestehendes Thema aus.
+ color_scheme:
+ label: Farbschema
+ navbar_style:
+ label: Hintergrundstil der Navigationsleiste
+ primary_color:
+ label: Primäre Farbe
+ text: Ändere die Farben, die von deinen Themes verwendet werden
+ css_and_html:
+ page_title: CSS und HTML
+ custom_css:
+ label: Benutzerdefinierte CSS
+ text: >
+
+ head:
+ label: Kopf
+ text: >
+
+ header:
+ label: Header
+ text: >
+
+ footer:
+ label: Fusszeile
+ text: Dies wird vor
+ footer:
+ label: Footer
+ text: This will insert before