This is an automated email from the ASF dual-hosted git repository. linkinstar pushed a commit to branch dev in repository https://gitbox.apache.org/repos/asf/answer.git
commit 9154b9ba5b04329171b8dd6c8cf5c8475e8762c5 Author: Dinesht04 <[email protected]> AuthorDate: Fri Oct 17 16:48:22 2025 +0530 feat(ui,internal): add optional question body --- i18n/en_US.yaml | 9 +++++-- internal/base/reason/reason.go | 1 + internal/schema/question_schema.go | 26 +++--------------- internal/service/content/question_service.go | 40 ++++++++++++++++++++++++++++ internal/service/question_common/question.go | 8 ++++++ ui/src/pages/Questions/Ask/index.tsx | 26 ++++++++++++++---- 6 files changed, 80 insertions(+), 30 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 5f694ea2..91bfa2af 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -235,6 +235,8 @@ backend: other: No permission to update. content_cannot_empty: other: Content cannot be empty. + content_less_than_minumum: + other: Not enough content entered. rank: fail_to_meet_the_condition: other: Reputation rank fail to meet the condition. @@ -1160,6 +1162,9 @@ ui: label: Body msg: empty: Body cannot be empty. + hint: + optional_body: Share what the question is about. + minimum_characters: "Share what the question is about, at least {{ min_content_length }} characters are required." tags: label: Tags msg: @@ -1181,8 +1186,8 @@ ui: 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: 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) diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go index 5a34d921..42e29f4c 100644 --- a/internal/base/reason/reason.go +++ b/internal/base/reason/reason.go @@ -47,6 +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" AnswerNotFound = "error.answer.not_found" AnswerCannotDeleted = "error.answer.cannot_deleted" AnswerCannotUpdate = "error.answer.cannot_update" diff --git a/internal/schema/question_schema.go b/internal/schema/question_schema.go index 5092ddf8..84b97b83 100644 --- a/internal/schema/question_schema.go +++ b/internal/schema/question_schema.go @@ -79,7 +79,7 @@ type QuestionAdd struct { // question title Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` // content - Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` + Content string `validate:"gte=0,lte=65535" json:"content"` // html HTML string `json:"-"` // tags @@ -100,12 +100,6 @@ func (req *QuestionAdd) Check() (errFields []*validator.FormErrorField, err erro tag.ParsedText = converter.Markdown2HTML(tag.OriginalText) } } - if req.HTML == "" { - return append(errFields, &validator.FormErrorField{ - ErrorField: "content", - ErrorMsg: reason.QuestionContentCannotEmpty, - }), errors.BadRequest(reason.QuestionContentCannotEmpty) - } return nil, nil } @@ -113,7 +107,7 @@ type QuestionAddByAnswer struct { // question title Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` // content - Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` + Content string `validate:"gte=0,lte=65535" json:"content"` // html HTML string `json:"-"` AnswerContent string `validate:"required,notblank,gte=6,lte=65535" json:"answer_content"` @@ -138,19 +132,11 @@ func (req *QuestionAddByAnswer) Check() (errFields []*validator.FormErrorField, tag.ParsedText = converter.Markdown2HTML(tag.OriginalText) } } - if req.HTML == "" { - errFields = append(errFields, &validator.FormErrorField{ - ErrorField: "content", - ErrorMsg: reason.QuestionContentCannotEmpty, - }) - } if req.AnswerHTML == "" { errFields = append(errFields, &validator.FormErrorField{ ErrorField: "answer_content", ErrorMsg: reason.AnswerContentCannotEmpty, }) - } - if req.HTML == "" || req.AnswerHTML == "" { return errFields, errors.BadRequest(reason.QuestionContentCannotEmpty) } return nil, nil @@ -195,7 +181,7 @@ type QuestionUpdate struct { // question title Title string `validate:"required,notblank,gte=6,lte=150" json:"title"` // content - Content string `validate:"required,notblank,gte=6,lte=65535" json:"content"` + Content string `validate:"gte=0,lte=65535" json:"content"` // html HTML string `json:"-"` InviteUser []string `validate:"omitempty" json:"invite_user"` @@ -227,12 +213,6 @@ type QuestionUpdateInviteUser struct { func (req *QuestionUpdate) Check() (errFields []*validator.FormErrorField, err error) { req.HTML = converter.Markdown2HTML(req.Content) - if req.HTML == "" { - return append(errFields, &validator.FormErrorField{ - ErrorField: "content", - ErrorMsg: reason.QuestionContentCannotEmpty, - }), errors.BadRequest(reason.QuestionContentCannotEmpty) - } return nil, nil } diff --git a/internal/service/content/question_service.go b/internal/service/content/question_service.go index 6832437d..1d1d1af3 100644 --- a/internal/service/content/question_service.go +++ b/internal/service/content/question_service.go @@ -242,6 +242,19 @@ func (qs *QuestionService) CheckAddQuestion(ctx context.Context, req *schema.Que err = errors.BadRequest(reason.TagMinCount) return errorlist, err } + minimumContentLength, err := qs.questioncommon.GetMinimumContentLength(ctx) + if err != nil { + return + } + if len(req.Content) < minimumContentLength { + errorlist := make([]*validator.FormErrorField, 0) + errorlist = append(errorlist, &validator.FormErrorField{ + ErrorField: "content", + ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionContentLessThanMinimum), + }) + err = errors.BadRequest(reason.QuestionContentLessThanMinimum) + return errorlist, err + } recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags) if err != nil { return @@ -301,6 +314,19 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question err = errors.BadRequest(reason.TagMinCount) return errorlist, err } + minimumContentLength, err := qs.questioncommon.GetMinimumContentLength(ctx) + if err != nil { + return + } + if len(req.Content) < minimumContentLength { + errorlist := make([]*validator.FormErrorField, 0) + errorlist = append(errorlist, &validator.FormErrorField{ + ErrorField: "content", + ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionContentLessThanMinimum), + }) + err = errors.BadRequest(reason.QuestionContentLessThanMinimum) + return errorlist, err + } recommendExist, err := qs.tagCommon.ExistRecommend(ctx, req.Tags) if err != nil { return @@ -907,6 +933,20 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest question.UserID = dbinfo.UserID question.LastEditUserID = req.UserID + minimumContentLength, err := qs.questioncommon.GetMinimumContentLength(ctx) + if err != nil { + return + } + if len(req.Content) < minimumContentLength { + errorlist := make([]*validator.FormErrorField, 0) + errorlist = append(errorlist, &validator.FormErrorField{ + ErrorField: "content", + ErrorMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionContentLessThanMinimum), + }) + err = errors.BadRequest(reason.QuestionContentLessThanMinimum) + return errorlist, err + } + oldTags, tagerr := qs.tagCommon.GetObjectEntityTag(ctx, question.ID) if tagerr != nil { return questionInfo, tagerr diff --git a/internal/service/question_common/question.go b/internal/service/question_common/question.go index 92392048..feb5626e 100644 --- a/internal/service/question_common/question.go +++ b/internal/service/question_common/question.go @@ -899,3 +899,11 @@ func (qs *QuestionCommon) tryToGetQuestionIDFromMsg(ctx context.Context, closeMs questionID = uid.DeShortID(questionID) return questionID } + +func (qs *QuestionCommon) GetMinimumContentLength(ctx context.Context) (int, error) { + siteInfo, err := qs.siteInfoService.GetSiteWrite(ctx) + if err != nil { + return 6, err + } + return siteInfo.MinimumContent, nil +} diff --git a/ui/src/pages/Questions/Ask/index.tsx b/ui/src/pages/Questions/Ask/index.tsx index 149373ed..d7804195 100644 --- a/ui/src/pages/Questions/Ask/index.tsx +++ b/ui/src/pages/Questions/Ask/index.tsx @@ -28,6 +28,7 @@ import isEqual from 'lodash/isEqual'; import debounce from 'lodash/debounce'; import fm from 'front-matter'; +import { writeSettingStore } from '@/stores'; import { usePageTags, usePromptWithUnload } from '@/hooks'; import { Editor, EditorRef, TagSelector } from '@/components'; import type * as Type from '@/common/interface'; @@ -120,6 +121,7 @@ const Ask = () => { handleTagsChange(resp); }); }; + const writeInfo = writeSettingStore((state) => state.write); const isEdit = qid !== undefined; @@ -423,6 +425,24 @@ const Ask = () => { usePageTags({ title: pageTitle, }); + + const handleContentHint = () => { + if ( + !writeInfo || + writeInfo.min_content === undefined || + !writeInfo.min_content + ) { + return t(`form.fields.body.hint.optional_body`); + } + + let str: string = t(`form.fields.body.hint.minimum_characters`); + str = str.replace( + `{{ min_content_length }}`, + writeInfo.min_content.toString(), + ); + return str; + }; + return ( <div className="pt-4 mb-5"> <h3 className="mb-4">{isEdit ? t('edit_title') : t('title')}</h3> @@ -451,7 +471,6 @@ const Ask = () => { </Form.Select> </Form.Group> )} - <Form.Group controlId="title" className="mb-3"> <Form.Label>{t('form.fields.title.label')}</Form.Label> <Form.Control @@ -468,7 +487,6 @@ const Ask = () => { </Form.Control.Feedback> {bool && <SearchQuestion similarQuestions={similarQuestions} />} </Form.Group> - <Form.Group controlId="content"> <Form.Label>{t('form.fields.body.label')}</Form.Label> <Editor @@ -487,11 +505,11 @@ const Ask = () => { }} ref={editorRef} /> + <Form.Text>{handleContentHint()}</Form.Text> <Form.Control.Feedback type="invalid"> {formData.content.errorMsg} </Form.Control.Feedback> </Form.Group> - <Form.Group controlId="tags" className="my-3"> <Form.Label>{t('form.fields.tags.label')}</Form.Label> <TagSelector @@ -503,7 +521,6 @@ const Ask = () => { errMsg={formData.tags.errorMsg} /> </Form.Group> - {!isEdit && ( <> <Form.Switch @@ -544,7 +561,6 @@ const Ask = () => { )} </> )} - {isEdit && ( <Form.Group controlId="edit_summary" className="my-3"> <Form.Label>{t('form.fields.edit_summary.label')}</Form.Label>
