diff --git a/web/src/components/ChatBox/ChatInputBox/Addon.tsx b/web/src/components/ChatBox/ChatInputBox/Addon.tsx index 3b6e14c3..1b477aed 100644 --- a/web/src/components/ChatBox/ChatInputBox/Addon.tsx +++ b/web/src/components/ChatBox/ChatInputBox/Addon.tsx @@ -1,23 +1,40 @@ -import { chatInputActions } from '@/plugin/common'; +import { FileSelector } from '@/components/FileSelector'; +import { pluginChatInputActions } from '@/plugin/common'; import { Icon } from '@iconify/react'; import { Dropdown, Menu } from 'antd'; import React from 'react'; +import { t } from 'tailchat-shared'; import { useChatInputActionContext } from './context'; +import { uploadMessageImage } from './utils'; export const ChatInputAddon: React.FC = React.memo(() => { const actionContext = useChatInputActionContext(); - - if (chatInputActions.length === 0) { - return null; - } - if (actionContext === null) { return null; } + const handleSendImage = (files: FileList) => { + // 发送图片 + const image = files[0]; + if (image) { + // 发送图片 + uploadMessageImage(image).then((imageRemoteUrl) => { + // TODO: not good, should bind with plugin bbcode + actionContext.sendMsg(`[img]${imageRemoteUrl}[/img]`); + }); + } + }; + const menu = ( - {chatInputActions.map((item, i) => ( + + {t('发送图片')} + + + {pluginChatInputActions.map((item, i) => ( item.onClick(actionContext)} diff --git a/web/src/components/ChatBox/ChatInputBox/index.tsx b/web/src/components/ChatBox/ChatInputBox/index.tsx index c5fc2625..e2b02150 100644 --- a/web/src/components/ChatBox/ChatInputBox/index.tsx +++ b/web/src/components/ChatBox/ChatInputBox/index.tsx @@ -1,13 +1,11 @@ -import { closeModal, openModal } from '@/components/Modal'; -import { ImageUploadPreviewer } from '@/components/modals/ImageUploadPreviewer'; -import { fileToDataUrl } from '@/utils/file-helper'; import { isEnterHotkey } from '@/utils/hot-key'; import { Input } from 'antd'; import React, { useCallback, useRef, useState } from 'react'; -import { t, uploadFile } from 'tailchat-shared'; +import { t } from 'tailchat-shared'; import { ChatInputAddon } from './Addon'; import { ClipboardHelper } from './clipboard-helper'; import { ChatInputActionContext } from './context'; +import { uploadMessageImage } from './utils'; interface ChatInputBoxProps { onSendMsg: (msg: string) => void; @@ -38,20 +36,9 @@ export const ChatInputBox: React.FC = React.memo((props) => { if (image) { // 上传图片 e.preventDefault(); - fileToDataUrl(image).then((imageLocalUrl) => { - const key = openModal( - { - const fileInfo = await uploadFile(image); - const imageRemoteUrl = fileInfo.url; - - // TODO: not good, should bind with plugin bbcode - props.onSendMsg(`[img]${imageRemoteUrl}[/img]`); - closeModal(key); - }} - /> - ); + uploadMessageImage(image).then((imageRemoteUrl) => { + // TODO: not good, should bind with plugin bbcode + props.onSendMsg(`[img]${imageRemoteUrl}[/img]`); }); } }, diff --git a/web/src/components/ChatBox/ChatInputBox/utils.tsx b/web/src/components/ChatBox/ChatInputBox/utils.tsx new file mode 100644 index 00000000..1583bbc2 --- /dev/null +++ b/web/src/components/ChatBox/ChatInputBox/utils.tsx @@ -0,0 +1,27 @@ +import { closeModal, openModal } from '@/components/Modal'; +import { ImageUploadPreviewer } from '@/components/modals/ImageUploadPreviewer'; +import { fileToDataUrl } from '@/utils/file-helper'; +import React from 'react'; +import { uploadFile } from 'tailchat-shared'; + +/** + * 上传聊天图片,并返回图片地址 + */ +export function uploadMessageImage(image: File): Promise { + return new Promise((resolve) => { + fileToDataUrl(image).then((imageLocalUrl) => { + const key = openModal( + { + const fileInfo = await uploadFile(image); + const imageRemoteUrl = fileInfo.url; + + resolve(imageRemoteUrl); + closeModal(key); + }} + /> + ); + }); + }); +} diff --git a/web/src/components/FileSelector.tsx b/web/src/components/FileSelector.tsx new file mode 100644 index 00000000..f04f13ee --- /dev/null +++ b/web/src/components/FileSelector.tsx @@ -0,0 +1,53 @@ +import React, { useCallback, useRef } from 'react'; +import _isFunction from 'lodash/isFunction'; +import _isNil from 'lodash/isNil'; + +/** + * 文件选择器 + */ + +interface FileSelectorProps { + fileProps?: React.DetailedHTMLProps< + React.InputHTMLAttributes, + HTMLInputElement + >; + /** + * 选中文件 + */ + onSelected: (files: FileList) => void; +} +export const FileSelector: React.FC = React.memo((props) => { + const fileInputRef = useRef(null); + const handleSelect = useCallback(() => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } + }, []); + + const handleChange = useCallback(() => { + if (_isNil(fileInputRef.current)) { + return; + } + + const files = fileInputRef.current.files; + if (_isNil(files)) { + return; + } + + _isFunction(props.onSelected) && props.onSelected(files); + }, [props.onSelected]); + + return ( +
+ {props.children} + +
+ ); +}); +FileSelector.displayName = 'FileSelector'; diff --git a/web/src/plugin/common/reg.ts b/web/src/plugin/common/reg.ts index 83afb05d..362031e5 100644 --- a/web/src/plugin/common/reg.ts +++ b/web/src/plugin/common/reg.ts @@ -64,7 +64,7 @@ interface ChatInputAction { onClick: (actions: ChatInputActionContextProps) => void; } export type { ChatInputActionContextProps }; -export const [chatInputActions, regChatInputAction] = +export const [pluginChatInputActions, regChatInputAction] = buildRegList(); export { regSocketEventListener };