diff --git a/client/web/plugins/com.msgbyte.bbcode/src/index.tsx b/client/web/plugins/com.msgbyte.bbcode/src/index.tsx index fb23726b..e189613c 100644 --- a/client/web/plugins/com.msgbyte.bbcode/src/index.tsx +++ b/client/web/plugins/com.msgbyte.bbcode/src/index.tsx @@ -19,7 +19,8 @@ regMessageRender((message) => { }); regMessageTextDecorators(() => ({ - url: (plain) => `[url]${plain}[/url]`, + url: (url, label?) => + label ? `[url=${url}]${label}[/url]` : `[url]${url}[/url]`, image: (plain, attrs) => { if (attrs.height && attrs.width) { return `[img height=${attrs.height} width=${attrs.width}]${plain}[/img]`; diff --git a/client/web/plugins/com.msgbyte.bbcode/src/tags/UrlTag.tsx b/client/web/plugins/com.msgbyte.bbcode/src/tags/UrlTag.tsx index 14080818..1f17b7a6 100644 --- a/client/web/plugins/com.msgbyte.bbcode/src/tags/UrlTag.tsx +++ b/client/web/plugins/com.msgbyte.bbcode/src/tags/UrlTag.tsx @@ -1,3 +1,4 @@ +import { Link } from '@capital/component'; import React from 'react'; import type { TagProps } from '../bbcode/type'; @@ -6,6 +7,11 @@ export const UrlTag: React.FC = React.memo((props) => { const text = node.content.join(''); const url = node.attrs.url ?? text; + if (url.startsWith('/') || url.startsWith(window.location.origin)) { + // 内部地址,使用 react-router 进行导航 + return {text}; + } + return ( {text} diff --git a/client/web/src/App.tsx b/client/web/src/App.tsx index 34ac9795..00f56cbf 100644 --- a/client/web/src/App.tsx +++ b/client/web/src/App.tsx @@ -142,7 +142,7 @@ export const App: React.FC = React.memo(() => { } /> - } /> + } /> diff --git a/client/web/src/components/ChatBox/ChatInputBox/MentionCommandItem.tsx b/client/web/src/components/ChatBox/ChatInputBox/MentionCommandItem.tsx new file mode 100644 index 00000000..86ff6b12 --- /dev/null +++ b/client/web/src/components/ChatBox/ChatInputBox/MentionCommandItem.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Icon } from 'tailchat-design'; + +/** + * 提及命令列表项 + */ +export const MentionCommandItem: React.FC<{ + icon: string; + label: string; +}> = React.memo((props) => { + return ( +
+ + +
{props.label}
+
+ ); +}); +MentionCommandItem.displayName = 'MentionCommandItem'; diff --git a/client/web/src/components/ChatBox/ChatInputBox/context.tsx b/client/web/src/components/ChatBox/ChatInputBox/context.tsx index c7220a4d..2623d43a 100644 --- a/client/web/src/components/ChatBox/ChatInputBox/context.tsx +++ b/client/web/src/components/ChatBox/ChatInputBox/context.tsx @@ -24,6 +24,7 @@ export function useChatInputActionContext() { */ interface ChatInputMentionsContextProps extends PropsWithChildren { users: SuggestionDataItem[]; + panels: SuggestionDataItem[]; placeholder?: string; disabled?: boolean; } @@ -47,6 +48,7 @@ export function useChatInputMentionsContext(): ChatInputMentionsContextProps { return { users: context?.users ?? [], + panels: context?.panels ?? [], placeholder: context?.placeholder, disabled: context?.disabled, }; diff --git a/client/web/src/components/ChatBox/ChatInputBox/input.tsx b/client/web/src/components/ChatBox/ChatInputBox/input.tsx index e11b9b26..c22eca77 100644 --- a/client/web/src/components/ChatBox/ChatInputBox/input.tsx +++ b/client/web/src/components/ChatBox/ChatInputBox/input.tsx @@ -6,6 +6,7 @@ import { Mention, MentionsInput } from 'react-mentions'; import { t } from 'tailchat-shared'; import { useChatInputMentionsContext } from './context'; import './input.less'; +import { MentionCommandItem } from './MentionCommandItem'; interface ChatInputBoxInputProps extends Omit< @@ -18,7 +19,8 @@ interface ChatInputBoxInputProps } export const ChatInputBoxInput: React.FC = React.memo( (props) => { - const { users, placeholder, disabled } = useChatInputMentionsContext(); + const { users, panels, placeholder, disabled } = + useChatInputMentionsContext(); return ( = React.memo( )} markup={getMessageTextDecorators().mention('__id__', '__display__')} /> + `#${display}`} + appendSpaceOnAdd={true} + renderSuggestion={(suggestion) => ( + + )} + markup={getMessageTextDecorators().url('__id__', '#__display__')} + /> ); } diff --git a/client/web/src/components/Panel/group/TextPanel.tsx b/client/web/src/components/Panel/group/TextPanel.tsx index 683801a8..7d0326cd 100644 --- a/client/web/src/components/Panel/group/TextPanel.tsx +++ b/client/web/src/components/Panel/group/TextPanel.tsx @@ -11,6 +11,8 @@ import { useInterval, useHasGroupPermission, PERMISSION, + useGroupInfo, + GroupPanelType, } from 'tailchat-shared'; import { GroupPanelWrapper } from './Wrapper'; @@ -68,10 +70,15 @@ interface TextPanelProps { } export const TextPanel: React.FC = React.memo( ({ groupId, panelId }) => { + const group = useGroupInfo(groupId); const groupMembers = useGroupMemberInfos(groupId); const panelInfo = useGroupPanelInfo(groupId, panelId); const { disabled, placeholder } = useChatInputInfo(groupId); + if (!group) { + return null; + } + if (!panelInfo) { return null; } @@ -83,6 +90,12 @@ export const TextPanel: React.FC = React.memo( id: m._id, display: m.nickname, }))} + panels={group.panels + .filter((p) => p.type !== GroupPanelType.GROUP) + .map((p) => ({ + id: `/main/group/${groupId}/${p.id}`, + display: p.name, + }))} disabled={disabled} placeholder={placeholder} > diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts index 9d568f46..99bae0f1 100644 --- a/client/web/src/plugin/common/reg.ts +++ b/client/web/src/plugin/common/reg.ts @@ -117,7 +117,7 @@ export const [getMessageRender, regMessageRender] = buildRegFn< * 输入消息,返回渲染节点 */ const defaultMessageTextDecorators = { - url: (plain: string) => plain, + url: (url: string, label?: string) => url, image: (plain: string, attrs: Record) => plain, mention: (userId: string, userName: string) => `@${userName}`, emoji: (emojiCode: string) => emojiCode, diff --git a/client/web/src/plugin/component/index.tsx b/client/web/src/plugin/component/index.tsx index a043ea4f..41ddba6e 100644 --- a/client/web/src/plugin/component/index.tsx +++ b/client/web/src/plugin/component/index.tsx @@ -27,6 +27,7 @@ export { createMetaFormSchema, metaFormFieldSchema, } from 'tailchat-design'; +export { Link } from 'react-router-dom'; export { Image } from '@/components/Image'; export { IconBtn } from '@/components/IconBtn';