From 7e4edb18d4ab6fd0bd637f87daf6853b6679b420 Mon Sep 17 00:00:00 2001 From: shuai Date: Mon, 4 Aug 2025 11:21:13 +0800 Subject: [PATCH 01/51] fix: remove question detail page pinned icon --- .../Questions/Detail/components/Question/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx index 86c1ba83a..93134578b 100644 --- a/ui/src/pages/Questions/Detail/components/Question/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx @@ -30,7 +30,6 @@ import { Comment, FormatTime, htmlRender, - Icon, ImgViewer, } from '@/components'; import { useRenderHtmlPlugin } from '@/utils/pluginKit'; @@ -104,16 +103,6 @@ const Index: FC = ({ data, initPage, hasAnswer, isLogged }) => {
- {data?.pin === 2 && ( -
- - {t('pinned', { keyPrefix: 'btns' })} -
- )} Date: Wed, 6 Aug 2025 00:17:00 +0800 Subject: [PATCH 02/51] fix: failed to build plugins in windows --- internal/cli/build.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cli/build.go b/internal/cli/build.go index 43a7aa9fe..a5a4d938e 100644 --- a/internal/cli/build.go +++ b/internal/cli/build.go @@ -484,6 +484,7 @@ func copyDirEntries(sourceFs fs.FS, sourceDir, targetDir string, ignoreDir ...st // Construct the absolute path for the source file/directory srcPath := filepath.Join(sourceDir, path) + srcPath = filepath.ToSlash(srcPath) // Construct the absolute path for the destination file/directory dstPath := filepath.Join(targetDir, path) From af80380f230d2633ec8e62ca11c820430e898d31 Mon Sep 17 00:00:00 2001 From: hgaol Date: Wed, 6 Aug 2025 00:17:00 +0800 Subject: [PATCH 03/51] fix: failed to build plugins in windows --- internal/cli/build.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cli/build.go b/internal/cli/build.go index 43a7aa9fe..a5a4d938e 100644 --- a/internal/cli/build.go +++ b/internal/cli/build.go @@ -484,6 +484,7 @@ func copyDirEntries(sourceFs fs.FS, sourceDir, targetDir string, ignoreDir ...st // Construct the absolute path for the source file/directory srcPath := filepath.Join(sourceDir, path) + srcPath = filepath.ToSlash(srcPath) // Construct the absolute path for the destination file/directory dstPath := filepath.Join(targetDir, path) From 19aed9f9c8221e5ae3f67f160954898a072f976b Mon Sep 17 00:00:00 2001 From: shuai Date: Fri, 8 Aug 2025 10:20:46 +0800 Subject: [PATCH 04/51] feat: schemeForm support tagSelector component --- .../SchemaForm/components/TagSelector.tsx | 66 +++++++++++++++++++ .../components/SchemaForm/components/index.ts | 2 + ui/src/components/SchemaForm/index.tsx | 13 +++- ui/src/components/SchemaForm/types.ts | 6 +- 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 ui/src/components/SchemaForm/components/TagSelector.tsx diff --git a/ui/src/components/SchemaForm/components/TagSelector.tsx b/ui/src/components/SchemaForm/components/TagSelector.tsx new file mode 100644 index 000000000..f23b14f65 --- /dev/null +++ b/ui/src/components/SchemaForm/components/TagSelector.tsx @@ -0,0 +1,66 @@ +/* + * 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 } from 'react'; + +import { TagSelector } from '@/components'; +import type * as Type from '@/common/interface'; + +interface Props { + maxTagLength?: number; + description?: string; + fieldName: string; + onChange?: (fd: Type.FormDataType) => void; + formData: Type.FormDataType; +} +const Index: FC = ({ + description, + maxTagLength, + fieldName, + onChange, + formData, +}) => { + const fieldObject = formData[fieldName]; + const handleChange = (data: Type.Tag[]) => { + const state = { + ...formData, + [fieldName]: { + ...formData[fieldName], + value: data, + isInvalid: false, + }, + }; + if (typeof onChange === 'function') { + onChange(state); + } + }; + + return ( + + ); +}; + +export default Index; diff --git a/ui/src/components/SchemaForm/components/index.ts b/ui/src/components/SchemaForm/components/index.ts index df28f4ac2..2a75307d1 100644 --- a/ui/src/components/SchemaForm/components/index.ts +++ b/ui/src/components/SchemaForm/components/index.ts @@ -27,6 +27,7 @@ import Textarea from './Textarea'; import Input from './Input'; import Button from './Button'; import InputGroup from './InputGroup'; +import TagSelector from './TagSelector'; export { Legend, @@ -39,4 +40,5 @@ export { Input, Button, InputGroup, + TagSelector, }; diff --git a/ui/src/components/SchemaForm/index.tsx b/ui/src/components/SchemaForm/index.tsx index 6b7cbbf02..421e0b1e1 100644 --- a/ui/src/components/SchemaForm/index.tsx +++ b/ui/src/components/SchemaForm/index.tsx @@ -51,6 +51,7 @@ import { Input, Button as SfButton, InputGroup, + TagSelector, } from './components'; export * from './types'; @@ -258,6 +259,7 @@ const SchemaForm: ForwardRefRenderFunction = ( description, enum: enumValues = [], enumNames = [], + max_length = 0, } = properties[key]; const { 'ui:widget': widget = 'input', 'ui:options': uiOpt } = uiSchema?.[key] || {}; @@ -413,11 +415,20 @@ const SchemaForm: ForwardRefRenderFunction = ( /> ) : null} + {widget === 'tag_selector' ? ( + + ) : null} {/* Unified handling of `Feedback` and `Text` */} {fieldState?.errorMsg} - {description ? ( + {description && widget !== 'tag_selector' ? ( ) : null} diff --git a/ui/src/components/SchemaForm/types.ts b/ui/src/components/SchemaForm/types.ts index 8347b98fa..db1aa8a5b 100644 --- a/ui/src/components/SchemaForm/types.ts +++ b/ui/src/components/SchemaForm/types.ts @@ -44,12 +44,13 @@ export interface JSONSchema { required?: string[]; properties: { [key: string]: { - type: 'string' | 'boolean' | 'number'; + type?: 'string' | 'boolean' | 'number'; title: string; description?: string; enum?: Array; enumNames?: string[]; default?: string | boolean | number | any[]; + max_length?: number; }; }; } @@ -152,7 +153,8 @@ export type UIWidget = | 'switch' | 'legend' | 'button' - | 'input_group'; + | 'input_group' + | 'tag_selector'; export interface UISchema { [key: string]: { 'ui:widget'?: UIWidget; From bfa48a8ca919c1d1c7b53b05a5ec713197833003 Mon Sep 17 00:00:00 2001 From: shuai Date: Fri, 8 Aug 2025 10:22:13 +0800 Subject: [PATCH 05/51] fix: footer remove background color --- ui/src/components/Footer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/Footer/index.tsx b/ui/src/components/Footer/index.tsx index 4e4b3aa00..ad6e27559 100644 --- a/ui/src/components/Footer/index.tsx +++ b/ui/src/components/Footer/index.tsx @@ -32,7 +32,7 @@ const Index = () => { const cc = `${fullYear} ${siteName}`; return ( -
+

{/* Link to Terms of Service with right margin */} From dc582eec54aa6aa2b633ff159f00035497d4a08e Mon Sep 17 00:00:00 2001 From: shuai Date: Tue, 12 Aug 2025 10:10:56 +0800 Subject: [PATCH 06/51] style: question details page UI optimization --- i18n/en_US.yaml | 2 + i18n/zh_CN.yaml | 2 + .../Comment/components/ActionBar/index.tsx | 4 +- ui/src/components/Comment/index.tsx | 28 +++- ui/src/components/Operate/index.tsx | 133 ++++++++++++------ ui/src/components/Share/index.tsx | 16 ++- ui/src/components/UserCard/index.tsx | 32 ++++- ui/src/index.scss | 4 + .../Detail/components/Answer/index.tsx | 95 +++++-------- .../Detail/components/Question/index.tsx | 82 ++++------- .../Detail/components/Reactions/index.tsx | 7 +- ui/src/pages/Questions/Detail/index.tsx | 1 - 12 files changed, 221 insertions(+), 185 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index da34fcb16..2d8049182 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1407,9 +1407,11 @@ ui: search: Search people question_detail: action: Action + created: Created Asked: Asked asked: asked update: Modified + Edited: Edited edit: edited commented: commented Views: Viewed diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index b62fd4700..351620455 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -1381,9 +1381,11 @@ ui: search: 搜索人员 question_detail: action: 操作 + created: 创建于 Asked: 提问于 asked: 提问于 update: 修改于 + Edited: 编辑于 edit: 编辑于 commented: 评论 Views: 阅读次数 diff --git a/ui/src/components/Comment/components/ActionBar/index.tsx b/ui/src/components/Comment/components/ActionBar/index.tsx index c55bf40e2..692b0d208 100644 --- a/ui/src/components/Comment/components/ActionBar/index.tsx +++ b/ui/src/components/Comment/components/ActionBar/index.tsx @@ -64,7 +64,9 @@ const ActionBar = ({ }`} onClick={onVote}> - {voteCount > 0 && {voteCount}} + {voteCount > 0 && ( + {voteCount} + )} ); - } - return ( - - ); - })} - {secondAction.length > 0 && ( - - - {t('action', { keyPrefix: 'question_detail' })} - - - {secondAction.map((item) => { - return ( - handleAction(item.action)}> - {item.name} - - ); - })} - - - )} -

+ })} + {secondAction.length > 0 && ( + + + + + + {secondAction.map((item) => { + return ( + handleAction(item.action)}> + {item.name} + + ); + })} + + + )} + +
+ {memberActions.length > 0 && ( + + + + + + + {[...firstAction, ...secondAction].map((item) => { + return ( + handleAction(item.action)}> + {item.name} + + ); + })} + + + )} +
+ ); }; diff --git a/ui/src/components/Share/index.tsx b/ui/src/components/Share/index.tsx index 89ae05cea..d63147b19 100644 --- a/ui/src/components/Share/index.tsx +++ b/ui/src/components/Share/index.tsx @@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'; import { FacebookShareButton, TwitterShareButton } from 'next-share'; import copy from 'copy-to-clipboard'; +import classNames from 'classnames'; import { BASE_ORIGIN } from '@/router/alias'; import { loggedUserInfoStore } from '@/stores'; @@ -32,10 +33,12 @@ interface IProps { qid: any; aid?: any; title: string; + className?: string; + mode?: 'normal' | 'mobile'; // slugTitle: string; } -const Index: FC = ({ type, qid, aid, title }) => { +const Index: FC = ({ type, qid, aid, title, className, mode }) => { const user = loggedUserInfoStore((state) => state.user); const [show, setShow] = useState(false); const [showTip, setShowTip] = useState(false); @@ -78,12 +81,21 @@ const Index: FC = ({ type, qid, aid, title }) => { setSystemShareState(true); } }, []); + + if (mode === 'mobile') { + if (canSystemShare) { + return ( + {t('share.name')} + ); + } + return null; + } return ( setShow(true)} style={{ lineHeight: '23px' }}> {t('share.name')} diff --git a/ui/src/components/UserCard/index.tsx b/ui/src/components/UserCard/index.tsx index 44bd19e0f..cc7e883c6 100644 --- a/ui/src/components/UserCard/index.tsx +++ b/ui/src/components/UserCard/index.tsx @@ -28,10 +28,12 @@ import { formatCount } from '@/utils'; interface Props { data: any; time: number; - preFix: string; + preFix?: string; isLogged: boolean; timelinePath: string; className?: string; + updateTime?: number; + updateTimePrefix?: string; } const Index: FC = ({ @@ -41,6 +43,8 @@ const Index: FC = ({ isLogged, timelinePath, className = '', + updateTime = 0, + updateTimePrefix = '', }) => { return (
@@ -81,7 +85,7 @@ const Index: FC = ({ /> )} -
+
{data?.status !== 'deleted' ? ( = ({ preFix={preFix} className="link-secondary" /> + {updateTime > 0 && ( + <> + + + + )} ) : ( - + <> + + {updateTime > 0 && ( + <> + + + + )} + ))}
diff --git a/ui/src/index.scss b/ui/src/index.scss index fb1e06a79..e3153fd25 100644 --- a/ui/src/index.scss +++ b/ui/src/index.scss @@ -422,3 +422,7 @@ img[src=''] { display: none; line-height: 1; } + +.inherit { + color: inherit !important; +} diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx index 825f698da..957a60df0 100644 --- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx @@ -20,7 +20,7 @@ import { memo, FC, useEffect, useRef } from 'react'; import { Button, Alert, Badge } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import { Link, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import { Actions, @@ -28,7 +28,6 @@ import { UserCard, Icon, Comment, - FormatTime, htmlRender, ImgViewer, } from '@/components'; @@ -110,22 +109,34 @@ const Index: FC = ({ {t('post_pending', { keyPrefix: 'messages' })} )} - - {data?.accepted === 2 && ( -
- - - Best answer - +
+
+
- )} + + {data?.accepted === 2 && ( +
+ + + Best answer + +
+ )} +
-
+
= ({ )}
-
-
- -
-
- {data.update_user_info && - data.update_user_info?.username !== data.user_info?.username ? ( - - ) : isLogged ? ( - - - - ) : ( - - )} -
-
- -
-
- + commentId={String(searchParams.get('commentId'))}> + +
); }; diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx index 93134578b..19b73b065 100644 --- a/ui/src/pages/Questions/Detail/components/Question/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx @@ -26,7 +26,7 @@ import { Tag, Actions, Operate, - UserCard, + BaseUserCard, Comment, FormatTime, htmlRender, @@ -40,11 +40,10 @@ import { pathFactory } from '@/router/pathFactory'; interface Props { data: any; hasAnswer: boolean; - isLogged: boolean; initPage: (type: string) => void; } -const Index: FC = ({ data, initPage, hasAnswer, isLogged }) => { +const Index: FC = ({ data, initPage, hasAnswer }) => { const { t } = useTranslation('translation', { keyPrefix: 'question_detail', }); @@ -90,7 +89,7 @@ const Index: FC = ({ data, initPage, hasAnswer, isLogged }) => { return (
-

+

= ({ data, initPage, hasAnswer, isLogged }) => {

-
+
+ + + {data?.view_count > 0 && (
{t('Views')} {formatCount(data.view_count)} @@ -131,19 +133,21 @@ const Index: FC = ({ data, initPage, hasAnswer, isLogged }) => {
-
- {data?.tags?.map((item: any) => { - return ; - })} -
+
+
+ {data?.tags?.map((item: any) => { + return ; + })} +
+ = ({ data, initPage, hasAnswer, isLogged }) => { }} /> -
-
+
+ = ({ data, initPage, hasAnswer, isLogged }) => { isAccepted={Boolean(data?.accepted_answer_id)} callback={initPage} /> -
-
- {data.update_user_info && - data.update_user_info?.username !== data.user_info?.username ? ( - - ) : isLogged ? ( - - - - ) : ( - - )} -
-
- -
+
- -
); }; diff --git a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx index 9c747cf83..9386b94c8 100644 --- a/ui/src/pages/Questions/Detail/components/Reactions/index.tsx +++ b/ui/src/pages/Questions/Detail/components/Reactions/index.tsx @@ -21,8 +21,6 @@ import { FC, memo, useEffect, useState } from 'react'; import { Button, OverlayTrigger, Popover, Tooltip } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; -import classNames from 'classnames'; - import { Icon } from '@/components'; import { queryReactions, updateReaction } from '@/services'; import { tryNormalLogged } from '@/utils/guard'; @@ -111,10 +109,7 @@ const Index: FC = ({ ); return ( -
+
{showAddCommentBtn && (