diff --git a/client/shared/utils/upload-helper.ts b/client/shared/utils/upload-helper.ts index 2e534d8c94a..968480a93c5 100644 --- a/client/shared/utils/upload-helper.ts +++ b/client/shared/utils/upload-helper.ts @@ -51,7 +51,7 @@ export async function uploadFile( return data; } catch (e) { - showToasts(`${t('上传失败')}: ${t('可能是图片体积过大')}`, 'error'); + showToasts(`${t('上传失败')}: ${t('可能是文件体积过大')}`, 'error'); console.error(`${t('上传失败')}: ${_get(e, 'message')}`); throw e; } diff --git a/client/web/plugins/com.msgbyte.bbcode/src/index.tsx b/client/web/plugins/com.msgbyte.bbcode/src/index.tsx index e189613cb4c..39447a21eec 100644 --- a/client/web/plugins/com.msgbyte.bbcode/src/index.tsx +++ b/client/web/plugins/com.msgbyte.bbcode/src/index.tsx @@ -28,6 +28,14 @@ regMessageTextDecorators(() => ({ return `[img]${plain}[/img]`; }, + card: (plain, attrs) => { + const h = [ + 'card', + ...Object.entries(attrs).map(([k, v]) => `${k}=${v}`), + ].join(' '); + + return `[${h}]${plain}[/card]`; + }, mention: (userId, userName) => `[at=${userId}]${userName}[/at]`, emoji: (emojiCode) => `[emoji]${stripColons(emojiCode)}[/emoji]`, serialize: (plain: string) => (serialize ? serialize(plain) : plain), diff --git a/client/web/plugins/com.msgbyte.bbcode/src/tags/CardTag.tsx b/client/web/plugins/com.msgbyte.bbcode/src/tags/CardTag.tsx new file mode 100644 index 00000000000..a33399b36e6 --- /dev/null +++ b/client/web/plugins/com.msgbyte.bbcode/src/tags/CardTag.tsx @@ -0,0 +1,17 @@ +import { Card } from '@capital/component'; +import React from 'react'; +import type { TagProps } from '../bbcode/type'; + +export const CardTag: React.FC = React.memo((props) => { + const { node } = props; + const label = node.content.join(''); + const attrs = node.attrs ?? {}; + + const payload: any = { + label, + ...attrs, + }; + + return ; +}); +CardTag.displayName = 'CardTag'; diff --git a/client/web/plugins/com.msgbyte.bbcode/src/tags/__all__.ts b/client/web/plugins/com.msgbyte.bbcode/src/tags/__all__.ts index eb71f9f99fb..b706f12bd20 100644 --- a/client/web/plugins/com.msgbyte.bbcode/src/tags/__all__.ts +++ b/client/web/plugins/com.msgbyte.bbcode/src/tags/__all__.ts @@ -10,6 +10,7 @@ import { BoldTag } from './BoldTag'; import { ItalicTag } from './ItalicTag'; import { UnderlinedTag } from './UnderlinedTag'; import { DeleteTag } from './DeleteTag'; +import { CardTag } from './CardTag'; import './styles.less'; @@ -28,3 +29,4 @@ registerBBCodeTag('at', MentionTag); registerBBCodeTag('emoji', EmojiTag); registerBBCodeTag('markdown', MarkdownTag); registerBBCodeTag('md', MarkdownTag); // alias +registerBBCodeTag('card', CardTag); // alias diff --git a/client/web/src/components/Card/FileCard.tsx b/client/web/src/components/Card/FileCard.tsx new file mode 100644 index 00000000000..21de8b7d9c2 --- /dev/null +++ b/client/web/src/components/Card/FileCard.tsx @@ -0,0 +1,45 @@ +import { downloadUrl } from '@/utils/file-helper'; +import React from 'react'; +import { Icon } from 'tailchat-design'; +import { useMemoizedFn, t } from 'tailchat-shared'; +import { IconBtn } from '../IconBtn'; +import { CardWrapper } from './Wrapper'; + +export interface FileCardPayload { + label: string; + url: string; +} + +export const FileCard: React.FC<{ + payload: FileCardPayload; +}> = React.memo((props) => { + const payload = props.payload ?? {}; + + const handleDownload = useMemoizedFn(() => { + downloadUrl(payload.url, payload.label); + }); + + return ( + +
+
+
+ + {t('文件')} +
+ +
+ {payload.label} +
+
+ + +
+
+ ); +}); +FileCard.displayName = 'FileCard'; diff --git a/client/web/src/components/Card/Wrapper.tsx b/client/web/src/components/Card/Wrapper.tsx new file mode 100644 index 00000000000..e7d48b73b53 --- /dev/null +++ b/client/web/src/components/Card/Wrapper.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export const CardWrapper: React.FC = React.memo( + (props) => { + return ( +
+
+ {props.children} +
+
+ ); + } +); +CardWrapper.displayName = 'CardWrapper'; diff --git a/client/web/src/components/Card/index.tsx b/client/web/src/components/Card/index.tsx new file mode 100644 index 00000000000..cebabf49b8e --- /dev/null +++ b/client/web/src/components/Card/index.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { t } from 'tailchat-shared'; +import { FileCard, FileCardPayload } from './FileCard'; +import { CardWrapper } from './Wrapper'; + +interface Props { + type: 'file'; + payload: FileCardPayload; +} +export const Card: React.FC = React.memo((props) => { + if (props.type === 'file') { + return ; + } + + return {t('未知的卡片类型')}; +}); +Card.displayName = 'Card'; diff --git a/client/web/src/components/ChatBox/ChatInputBox/Addon.tsx b/client/web/src/components/ChatBox/ChatInputBox/Addon.tsx index 26938c5a425..a1e4c331003 100644 --- a/client/web/src/components/ChatBox/ChatInputBox/Addon.tsx +++ b/client/web/src/components/ChatBox/ChatInputBox/Addon.tsx @@ -8,7 +8,7 @@ import { Dropdown, Menu } from 'antd'; import React, { useState } from 'react'; import { t } from 'tailchat-shared'; import { useChatInputActionContext } from './context'; -import { uploadMessageImage } from './utils'; +import { uploadMessageFile, uploadMessageImage } from './utils'; import clsx from 'clsx'; export const ChatInputAddon: React.FC = React.memo(() => { @@ -31,6 +31,19 @@ export const ChatInputAddon: React.FC = React.memo(() => { } }; + const handleSendFile = (files: FileList) => { + // 发送文件 + const file = files[0]; + if (file) { + // 发送图片 + uploadMessageFile(file).then(({ name, url }) => { + actionContext.sendMsg( + getMessageTextDecorators().card(name, { type: 'file', url }) + ); + }); + } + }; + const menu = ( { {t('发送图片')} + + {t('发送文件')} + + {pluginChatInputActions.map((item, i) => ( { const actionContext = useChatInputActionContext(); const handleDrop = useMemoizedFn((files: File[]) => { - const images = files.filter((f) => f.type.startsWith('image/')); - if (images.length > 0) { - // 目前只取一张 - const img = images[0]; - uploadMessageImage(img).then(({ url, width, height }) => { + const file = files[0]; + if (file.type.startsWith('image/')) { + // 发送图片 + uploadMessageImage(file).then(({ url, width, height }) => { actionContext?.sendMsg( getMessageTextDecorators().image(url, { width, height }) ); }); + } else { + // 发送文件 + uploadMessageFile(file).then(({ url, name }) => { + actionContext?.sendMsg( + getMessageTextDecorators().card(name, { type: 'file', url }) + ); + }); } }); diff --git a/client/web/src/components/ChatBox/ChatInputBox/utils.tsx b/client/web/src/components/ChatBox/ChatInputBox/utils.tsx index 2e3e38ce864..70ea6ec05af 100644 --- a/client/web/src/components/ChatBox/ChatInputBox/utils.tsx +++ b/client/web/src/components/ChatBox/ChatInputBox/utils.tsx @@ -50,3 +50,18 @@ export function uploadMessageImage(image: File): Promise<{ }); }); } + +/** + * 上传文件,并返回地址 + */ +export async function uploadMessageFile(file: File): Promise<{ + name: string; + url: string; +}> { + const fileInfo = await uploadFile(file); + + return { + name: file.name || fileInfo.etag, + url: fileInfo.url, + }; +} diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts index a558e5ae43f..3818fee57a1 100644 --- a/client/web/src/plugin/common/reg.ts +++ b/client/web/src/plugin/common/reg.ts @@ -131,6 +131,7 @@ export const [getMessageRender, regMessageRender] = buildRegFn< const defaultMessageTextDecorators = { url: (url: string, label?: string) => url, image: (plain: string, attrs: Record) => plain, + card: (plain: string, payload: Record) => plain, mention: (userId: string, userName: string) => `@${userName}`, emoji: (emojiCode: string) => emojiCode, serialize: (plain: string) => plain, diff --git a/client/web/src/plugin/component/index.tsx b/client/web/src/plugin/component/index.tsx index 971939aad0e..9fa69bfabb7 100644 --- a/client/web/src/plugin/component/index.tsx +++ b/client/web/src/plugin/component/index.tsx @@ -59,3 +59,4 @@ export { UserAvatar } from '@/components/UserAvatar'; export { UserName } from '@/components/UserName'; export { Markdown } from '@/components/Markdown'; export { Webview, WebviewKeepAlive } from '@/components/Webview'; +export { Card } from '@/components/Card'; diff --git a/client/web/src/utils/file-helper.ts b/client/web/src/utils/file-helper.ts index 3c7db8de417..bb876502d9d 100644 --- a/client/web/src/utils/file-helper.ts +++ b/client/web/src/utils/file-helper.ts @@ -87,11 +87,11 @@ export async function blobUrlToFile( } /** - * 下载Bloburl + * 通过url下载文件 */ -export async function downloadBlobUrl(blobUrl: string, fileName: string) { +export function downloadUrl(url: string, fileName: string) { const a = document.createElement('a'); - a.href = blobUrl; + a.href = url; a.download = fileName; // 这里填保存成的文件名 a.click(); } @@ -99,9 +99,9 @@ export async function downloadBlobUrl(blobUrl: string, fileName: string) { /** * 下载Blob文件 */ -export async function downloadBlob(blob: Blob, fileName: string) { +export function downloadBlob(blob: Blob, fileName: string) { const url = String(URL.createObjectURL(blob)); - downloadBlobUrl(url, fileName); + downloadUrl(url, fileName); URL.revokeObjectURL(url); }