diff --git a/src/app/config.js b/src/app/config.js index fbd4c907..b06b1982 100644 --- a/src/app/config.js +++ b/src/app/config.js @@ -1,12 +1,10 @@ // const BASE_URL = `${location.origin}/api`; const BASE_URL = `https://dev.rustchat.com/api`; -export const CACHE_VERSION = `0.3.2`; +export const CACHE_VERSION = `0.3.3`; // const BASE_URL = `https://rustchat.net/api`; export const ContentTypes = { text: "text/plain", markdown: "text/markdown", - image: "image/png", - imageJPG: "image/jpeg", file: "rustchat/file", formData: "multipart/form-data", json: "application/json", @@ -26,6 +24,7 @@ export const googleClientID = // "840319286941-6ds7lbvk55eq8mjortf68cb2ll65lprt.apps.googleusercontent.com"; export const tokenHeader = "X-API-Key"; export const FILE_SLICE_SIZE = 1000 * 200 * 8; //200kb +export const FILE_IMAGE_SIZE = 1000 * 10000 * 8; //10mb export const KEY_TOKEN = "RUSTCHAT_TOKEN"; export const KEY_EXPIRE = "RUSTCHAT_TOKEN_EXPIRE"; export const KEY_REFRESH_TOKEN = "RUSTCHAT_REFRESH_TOKEN"; diff --git a/src/app/services/channel.js b/src/app/services/channel.js index b248cd96..0cfa48e0 100644 --- a/src/app/services/channel.js +++ b/src/app/services/channel.js @@ -46,6 +46,33 @@ export const channelApi = createApi({ } }, }), + getHistoryMessages: builder.query({ + query: ({ gid, mid = 0, limit = 50 }) => ({ + url: `/group/${gid}/history?before=${mid}&limit=${limit}`, + }), + // async onQueryStarted(id, { dispatch, getState, queryFulfilled }) { + // const { + // ui: { channelSetting }, + // channelMessage, + // } = getState(); + // try { + // await queryFulfilled; + // dispatch(removeChannel(id)); + // if (id == channelSetting) { + // dispatch(toggleChannelSetting()); + // } + // // 删掉该channel下的所有消息&reaction + // const mids = channelMessage[id]; + // if (mids) { + // dispatch(removeChannelSession(id)); + // dispatch(removeMessage(mids)); + // dispatch(removeReactionMessage(mids)); + // } + // } catch { + // console.log("remove channel error"); + // } + // }, + }), removeChannel: builder.query({ query: (id) => ({ url: `group/${id}`, @@ -82,7 +109,7 @@ export const channelApi = createApi({ }, url: `group/${id}/send`, method: "POST", - body: content, + body: type == "file" ? JSON.stringify(content) : content, }), async onQueryStarted(param1, param2) { await onMessageSendStarted.call(this, param1, param2, "channel"); @@ -106,6 +133,7 @@ export const channelApi = createApi({ }); export const { + useLazyGetHistoryMessagesQuery, useGetChannelQuery, useUpdateChannelMutation, useLazyRemoveChannelQuery, diff --git a/src/app/services/contact.js b/src/app/services/contact.js index 7ebbac5b..0d90bbce 100644 --- a/src/app/services/contact.js +++ b/src/app/services/contact.js @@ -95,7 +95,7 @@ export const contactApi = createApi({ }, url: `user/${id}/send`, method: "POST", - body: content, + body: type == "file" ? JSON.stringify(content) : content, }), async onQueryStarted(param1, param2) { await onMessageSendStarted.call(this, param1, param2, "user"); diff --git a/src/app/services/handlers.js b/src/app/services/handlers.js index 0db71588..bdf96134 100644 --- a/src/app/services/handlers.js +++ b/src/app/services/handlers.js @@ -17,6 +17,8 @@ export const onMessageSendStarted = async ( from = "channel" ) => { // id: who send to ,from_uid: who sent + console.log("handlers data", content, type, properties); + const isImage = properties.file_type?.startsWith("image"); const ts = properties.local_id || new Date().getTime(); // let imageData = null; // if (type == "image") { @@ -31,7 +33,7 @@ export const onMessageSendStarted = async ( // } // } const tmpMsg = { - content: type == "image" ? URL.createObjectURL(content) : content, + content: isImage ? content.path : content, content_type: ContentTypes[type], created_at: ts, properties, diff --git a/src/app/services/message.js b/src/app/services/message.js index 264741f0..be0ea666 100644 --- a/src/app/services/message.js +++ b/src/app/services/message.js @@ -1,7 +1,7 @@ import { createApi } from "@reduxjs/toolkit/query/react"; // import { batch } from "react-redux"; -import BASE_URL, { ContentTypes } from "../config"; +import { ContentTypes } from "../config"; import { updateReadChannels, updateReadUsers } from "../slices/footprint"; import { onMessageSendStarted } from "./handlers"; @@ -59,19 +59,6 @@ export const messageApi = createApi({ return data ? data : {}; }, }), - uploadImage: builder.mutation({ - query: (image) => ({ - headers: { - "content-type": ContentTypes.image, - }, - url: `/resource/image`, - method: "POST", - body: image, - }), - transformResponse: (image_id) => { - return `${BASE_URL}/resource/image?id=${encodeURIComponent(image_id)}`; - }, - }), replyMessage: builder.mutation({ query: ({ reply_mid, content, type = "text" }) => ({ headers: { @@ -93,29 +80,16 @@ export const messageApi = createApi({ }), async onQueryStarted(data, { dispatch, queryFulfilled }) { const { users = null, groups = null } = data; - // const { readUsers, readChannels } = getState().footprint; - // const prevUsers = users.map(({ uid }) => { - // return { uid, mid: readUsers[uid] }; - // }); - // const prevChannels = users.map(({ gid }) => { - // return { gid, mid: readChannels[gid] }; - // }); - // batch(() => { if (users) { dispatch(updateReadUsers(users)); } if (groups) { dispatch(updateReadChannels(groups)); } - // }); try { await queryFulfilled; } catch { // todo - // batch(() => { - // dispatch(updateReadChannels(prevChannels)); - // dispatch(updateReadUsers(prevUsers)); - // }); } }, }), @@ -125,7 +99,6 @@ export const messageApi = createApi({ export const { usePrepareUploadFileMutation, useUploadFileMutation, - useUploadImageMutation, useEditMessageMutation, useReactMessageMutation, useReplyMessageMutation, diff --git a/src/app/slices/message.js b/src/app/slices/message.js index d5f32250..c74363a7 100644 --- a/src/app/slices/message.js +++ b/src/app/slices/message.js @@ -1,5 +1,6 @@ import { createSlice } from "@reduxjs/toolkit"; import BASE_URL, { ContentTypes } from "../config"; +import { isImage } from "../../common/utils"; const initialState = { replying: {}, }; @@ -19,27 +20,25 @@ const messageSlice = createSlice({ }, addMessage(state, action) { const data = action.payload; - const { mid, sending, content_type, content } = data; + const { mid, sending, content_type, content, properties = {} } = data; // 如果是正发送,并且已存在,则不覆盖 if (sending && state[mid]) return; - const isImage = content_type.startsWith("image"); const isFile = content_type == ContentTypes.file; - // file if (!sending && isFile) { data.file_path = content; data.content = `${BASE_URL}/resource/file?file_path=${encodeURIComponent( data.file_path )}`; - } - // image - if (!sending && isImage) { - data.image_id = content; - data.content = `${BASE_URL}/resource/image?id=${encodeURIComponent( - data.image_id - )}`; - data.thumbnail = `${BASE_URL}/resource/thumbnail?id=${encodeURIComponent( - data.image_id - )}`; + data.download = `${BASE_URL}/resource/file?file_path=${encodeURIComponent( + data.file_path + )}&download=true`; + // image + const isPic = isImage(properties.file_type, properties.size); + if (isPic) { + data.thumbnail = `${BASE_URL}/resource/file?file_path=${encodeURIComponent( + data.file_path + )}&thumbnail=true`; + } } state[mid] = data; }, diff --git a/src/common/component/MarkdownEditor/index.js b/src/common/component/MarkdownEditor/index.js index fed17716..9e771060 100644 --- a/src/common/component/MarkdownEditor/index.js +++ b/src/common/component/MarkdownEditor/index.js @@ -6,22 +6,22 @@ import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js"; import StyledWrapper from "./styled"; -import { useUploadImageMutation } from "../../../app/services/message"; +import useUploadFile from "../../hook/useUploadFile"; import Button from "../../component/styled/Button"; function MarkdownEditor({ placeholder, sendMarkdown, setEditorInstance }) { const editorRef = useRef(undefined); + const { uploadFile } = useUploadFile(); // const [pHolder, setPHolder] = useState(placeholder); - const [uploadImage] = useUploadImageMutation(); useEffect(() => { const editor = editorRef?.current; if (editor) { const editorInstance = editor.getInstance(); editorInstance.removeHook("addImageBlobHook"); editorInstance.addHook("addImageBlobHook", async (blob, callback) => { - const { data: url } = await uploadImage(blob); - callback(url); + const { thumbnail = "" } = await uploadFile(blob); + callback(thumbnail); }); setEditorInstance(editorInstance); } diff --git a/src/common/component/Message/Reply.js b/src/common/component/Message/Reply.js index d54fc17f..ecea38f4 100644 --- a/src/common/component/Message/Reply.js +++ b/src/common/component/Message/Reply.js @@ -4,7 +4,7 @@ import { useSelector } from "react-redux"; import MrakdownRender from "../MrakdownRender"; import { ContentTypes } from "../../../app/config"; -import { getFileIcon } from "../../utils"; +import { getFileIcon, isImage } from "../../utils"; import Avatar from "../Avatar"; const Styled = styled.div` cursor: pointer; @@ -98,20 +98,20 @@ const renderContent = (data) => { ); break; - case ContentTypes.image: - case ContentTypes.imageJPG: - res = ; - break; case ContentTypes.file: { - const { file_type, name } = properties; + const { file_type, name, size } = properties; const icon = getFileIcon(file_type, name); - res = ( - <> - {icon} - {name} - - ); + if (isImage(file_type, size)) { + res = ; + } else { + res = ( + <> + {icon} + {name} + + ); + } } break; diff --git a/src/common/component/Message/renderContent.js b/src/common/component/Message/renderContent.js index e7fa8f90..7ca538ea 100644 --- a/src/common/component/Message/renderContent.js +++ b/src/common/component/Message/renderContent.js @@ -6,7 +6,7 @@ import Linkit from "react-linkify"; import dayjs from "dayjs"; import Mention from "./Mention"; import MrakdownRender from "../MrakdownRender"; -import { getDefaultSize } from "../../utils"; +import { getDefaultSize, isImage } from "../../utils"; import FileBox from "../FileBox"; import URLPreview from "./URLPreview"; import reactStringReplace from "react-string-replace"; @@ -68,35 +68,38 @@ const renderContent = ({ ctn = ; } break; - case "image/png": - case "image/jpeg": - { - const { name, size, type } = properties; - const { width, height } = getDefaultSize(properties); - ctn = ( - - ); - } - break; case "rustchat/file": { const { size, name, file_type } = properties; - ctn = ( - - ); + if (isImage(file_type, size)) { + const { width, height } = getDefaultSize(properties); + ctn = ( + + ); + } else { + ctn = ( + + ); + } } break; diff --git a/src/common/component/MixedInput/index.js b/src/common/component/MixedInput/index.js index 11e96bfb..b98b0690 100644 --- a/src/common/component/MixedInput/index.js +++ b/src/common/component/MixedInput/index.js @@ -27,6 +27,7 @@ import Styled from "./styled"; import ImageElement from "./ImageElement"; import { CONFIG } from "./config"; import Contact from "../Contact"; +import useUploadFile from "../../hook/useUploadFile"; // import Mention from "./Mention"; export const TEXT_EDITOR_PREFIX = "rustchat_text_editor"; export const setEditorFocus = (edtr) => { @@ -55,6 +56,9 @@ const Plugins = ({ sendMessages, members = [], }) => { + const enableMentions = members.length > 0; + const { uploadFile } = useUploadFile(); + const filesRef = useRef([]); // const plateEditor = usePlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`); const contactData = useSelector((store) => store.contacts.byId); const [msgs, setMsgs] = useState([]); @@ -66,6 +70,7 @@ const Plugins = ({ className: "box", placeholder, }; + useKey( "Enter", (evt) => { @@ -96,36 +101,44 @@ const Plugins = ({ target: editableRef, } ); - const plugins = createPlugins( - [ - createParagraphPlugin(), - createImagePlugin(), - createNodeIdPlugin(), - createSoftBreakPlugin(CONFIG.softBreak), - createTrailingBlockPlugin(CONFIG.trailingBlock), - createExitBreakPlugin(CONFIG.exitBreak), - createComboboxPlugin(), - createMentionPlugin({ - // component: Mention, - // handlers: { - // onKeyDown: ({ query }) => { - // console.log("mention", query); - // return true; - // }, - // }, - options: { - createMentionNode: (item) => { - console.log("mention", item); - const { - text, - data: { uid }, - } = item; - return { value: `@${text}`, uid }; - }, - insertSpaceAfterMention: true, + const pluginArr = [ + createParagraphPlugin(), + createImagePlugin({ + options: { + uploadImage: async (dataUrl) => { + const resp = await fetch(dataUrl); + const blob = await resp.blob(); + const { thumbnail, ...rest } = await uploadFile(blob); + const { name, file_type, size, path, hash } = rest; + filesRef.current.push({ name, file_type, size, path, hash }); + return thumbnail; }, - }), - ], + }, + }), + createNodeIdPlugin(), + createSoftBreakPlugin(CONFIG.softBreak), + createTrailingBlockPlugin(CONFIG.trailingBlock), + createExitBreakPlugin(CONFIG.exitBreak), + ]; + const plugins = createPlugins( + enableMentions + ? pluginArr.concat([ + createComboboxPlugin(), + createMentionPlugin({ + options: { + createMentionNode: (item) => { + console.log("mention", item); + const { + text, + data: { uid }, + } = item; + return { value: `@${text}`, uid }; + }, + insertSpaceAfterMention: true, + }, + }), + ]) + : pluginArr, { components, } @@ -146,27 +159,40 @@ const Plugins = ({ }); return { value: arr.join(""), mentions }; }; - for await (const v of val) { + for (const v of val) { if (v.type == "img") { // img - const resp = await fetch(v.url); - const value = await resp.blob(); - tmps.push({ type: "image", value }); + const url = v.url; + const file_path = decodeURIComponent( + new URL(url).searchParams.get("file_path") + ); + console.log("files", filesRef.current, file_path); + const json = filesRef.current.find((f) => f.path == file_path) || {}; + const { name, size, hash, path, ...rest } = json; + tmps.push({ + type: "file", + content: { name, size, hash, path }, + properties: rest, + }); } else { // p const { value, mentions } = getMixedText(v.children); const prev = tmps[tmps.length - 1]; if (!prev) { - tmps.push([{ type: "text", value, mentions }]); + tmps.push([ + { type: "text", content: value, properties: { mentions } }, + ]); } else { if (Array.isArray(prev)) { tmps[tmps.length - 1].push({ type: "text", - value, - mentions, + content: value, + properties: { mentions }, }); } else { - tmps.push([{ type: "text", value, mentions }]); + tmps.push([ + { type: "text", content: value, properties: { mentions } }, + ]); } } } @@ -175,14 +201,14 @@ const Plugins = ({ return Array.isArray(tmp) ? { type: "text", - value: tmp.map((t) => t.value).join("\n"), - mentions: tmp.map((t) => t.mentions).flat(), + content: tmp.map((t) => t.content).join("\n"), + properties: { mentions: tmp.map((t) => t.mentions).flat() }, } : tmp; }); - const msgs = arr.filter(({ value }) => !!value); + const msgs = arr.filter(({ content }) => !!content); setMsgs(msgs); - console.log("tmps", val, tmps, msgs); + console.log("tmps", tmps, arr, msgs); }, [msgs] ); @@ -197,26 +223,28 @@ const Plugins = ({ initialValue={initialValue} plugins={plugins} > - { - console.log("wtf", item); - return ; - }} - items={members.map((id) => { - const data = contactData[id]; - if (!data) return null; - const { uid, name, ...rest } = data; - return { - key: uid, - text: name, - data: { - uid, - ...rest, - }, - }; - })} - /> + {enableMentions ? ( + { + console.log("wtf", item); + return ; + }} + items={members.map((id) => { + const data = contactData[id]; + if (!data) return null; + const { uid, name, ...rest } = data; + return { + key: uid, + text: name, + data: { + uid, + ...rest, + }, + }; + })} + /> + ) : null} ); diff --git a/src/common/component/MrakdownRender.js b/src/common/component/MrakdownRender.js index 1b904507..31bdcdd1 100644 --- a/src/common/component/MrakdownRender.js +++ b/src/common/component/MrakdownRender.js @@ -34,10 +34,15 @@ export default function MrakdownRender({ content }) { "click", (evt) => { console.log(evt); + evt.stopPropagation(); const { target } = evt; // 图片 if (target.nodeName == "IMG") { - const data = { originUrl: target.dataset.origin || target.src }; + const urlObj = new URL(target.src); + const originUrl = `${urlObj.origin}${ + urlObj.pathname + }?file_path=${urlObj.searchParams.get("file_path")}`; + const data = { originUrl }; setPreviewImage(data); } }, diff --git a/src/common/component/Send/Replying.js b/src/common/component/Send/Replying.js index c45bdb53..51012740 100644 --- a/src/common/component/Send/Replying.js +++ b/src/common/component/Send/Replying.js @@ -4,7 +4,7 @@ import { ContentTypes } from "../../../app/config"; import MrakdownRender from "../MrakdownRender"; import closeIcon from "../../../assets/icons/close.circle.svg?url"; import pictureIcon from "../../../assets/icons/picture.svg?url"; -import { getFileIcon } from "../../utils"; +import { getFileIcon, isImage } from "../../utils"; import { removeReplyingMessage } from "../../../app/slices/message"; import styled from "styled-components"; const Styled = styled.div` @@ -97,20 +97,20 @@ const renderContent = (data) => { ); break; - case ContentTypes.image: - case ContentTypes.imageJPG: - res = ; - break; case ContentTypes.file: { - const { file_type, name } = properties; - const icon = getFileIcon(file_type, name); - res = ( - <> - {icon} - {name} - - ); + const { file_type, name, size } = properties; + if (isImage(file_type, size)) { + res = ; + } else { + const icon = getFileIcon(file_type, name); + res = ( + <> + {icon} + {name} + + ); + } } break; default: diff --git a/src/common/component/Send/index.js b/src/common/component/Send/index.js index 947d7cfd..0a2aa03a 100644 --- a/src/common/component/Send/index.js +++ b/src/common/component/Send/index.js @@ -75,7 +75,8 @@ function Send({ const handleSendMessage = async (msgs = []) => { if (!msgs || msgs.length == 0 || !id) return; for await (const msg of msgs) { - const { type: content_type, value: content, mentions = [] } = msg; + console.log("send msg", msg); + const { type: content_type, content, properties = {} } = msg; if (replying_mid) { console.log("replying", replying_mid); await replyMessage({ @@ -93,7 +94,7 @@ function Send({ type: content_type, content, from_uid, - properties: { local_id: new Date().getTime(), mentions }, + properties, }); } } diff --git a/src/common/component/UploadModal/index.js b/src/common/component/UploadModal/index.js index cd4bc384..9f75a8ac 100644 --- a/src/common/component/UploadModal/index.js +++ b/src/common/component/UploadModal/index.js @@ -1,12 +1,12 @@ import { useEffect } from "react"; import { useSelector } from "react-redux"; import FileItem from "./FileItem"; -import useSendImageMessage from "../../hook/useSendImageMessage"; -import useSendFileMessage from "../../hook/useSendFileMessage"; +import useUploadFile from "../../hook/useUploadFile"; import Modal from "../Modal"; import Button from "../styled/Button"; -import { isTreatAsImage } from "../../utils"; +// import { isTreatAsImage } from "../../utils"; import StyledWrapper from "./styled"; +import useSendMessage from "../../hook/useSendMessage"; export default function UploadModal({ context = "user", @@ -16,42 +16,45 @@ export default function UploadModal({ }) { const from_uid = useSelector((store) => store.authData.uid); const { - sendImageMessage, - isSending: isSendingImage, - isSuccess: sendImageSuccess, - } = useSendImageMessage({ + sendMessage, + isSuccess: sendMessageSuccess, + isSending, + } = useSendMessage({ context, from: from_uid, to: sendTo, }); const { - sendFileMessage, + data, + uploadFile, progress, - isSending: isSendingFile, - isSuccess: sendFileSuccess, - } = useSendFileMessage({ - context, - from: from_uid, - to: sendTo, - }); + isUploading, + isSuccess: uploadSuccess, + } = useUploadFile(); const handleUpload = () => { const file = files[0]; - // const { type } = file; - if (isTreatAsImage(file)) { - sendImageMessage(file); - } else { - sendFileMessage(file); - } + uploadFile(file); }; useEffect(() => { - if (sendFileSuccess || sendImageSuccess) { + if (uploadSuccess) { + // 把已经上传的东西当做消息发出去 + const { size, path, name, hash, ...rest } = data; + sendMessage({ + type: "file", + content: { size, name, path, hash }, + properties: rest, + }); + } + }, [uploadSuccess, data]); + useEffect(() => { + if (sendMessageSuccess) { closeModal(); } - }, [sendImageSuccess, sendFileSuccess]); + }, [sendMessageSuccess]); if (!sendTo) return null; console.log("upload file modal", files, sendTo); - const isSending = isSendingFile || isSendingImage; + const sending = isUploading || isSending; return ( } diff --git a/src/common/hook/useSendImageMessage.js b/src/common/hook/useSendMessage.js similarity index 66% rename from src/common/hook/useSendImageMessage.js rename to src/common/hook/useSendMessage.js index b518a62c..c0b25661 100644 --- a/src/common/hook/useSendImageMessage.js +++ b/src/common/hook/useSendMessage.js @@ -1,7 +1,7 @@ // import second from 'first' import { useSendChannelMsgMutation } from "../../app/services/channel"; import { useSendMsgMutation } from "../../app/services/contact"; -export default function useSendImageMessage({ +export default function useSendMessage({ context = "user", from = null, to = null, @@ -18,20 +18,18 @@ export default function useSendImageMessage({ sendUserMsg, { isLoading: userSending, isSuccess: userSuccess, isError: userError }, ] = useSendMsgMutation(); - const uploadFn = context == "user" ? sendUserMsg : sendChannelMsg; - const sendImageMessage = (file) => { - if (!file) return; - const { name, size, type } = file; - uploadFn({ + const sendFn = context == "user" ? sendUserMsg : sendChannelMsg; + const sendMessage = ({ type = "text", content, properties = {} }) => { + sendFn({ id: to, - content: file, - properties: { name, size, type, local_id: new Date().getTime() }, - type: "image", + content, + properties: { ...properties, local_id: new Date().getTime() }, + type, from_uid: from, }); }; return { - sendImageMessage, + sendMessage, isError: channelError || userError, isSending: userSending || channelSending, isSuccess: channelSuccess || userSuccess, diff --git a/src/common/hook/useSendFileMessage.js b/src/common/hook/useUploadFile.js similarity index 62% rename from src/common/hook/useSendFileMessage.js rename to src/common/hook/useUploadFile.js index 3bd0f371..fc3b0cb0 100644 --- a/src/common/hook/useSendFileMessage.js +++ b/src/common/hook/useUploadFile.js @@ -1,57 +1,41 @@ import { useState, useRef } from "react"; // import { ContentTypes } from "../../app/config"; -// import { sliceFile } from "../utils"; -import { FILE_SLICE_SIZE } from "../../app/config"; +import BASE_URL, { FILE_SLICE_SIZE } from "../../app/config"; import { usePrepareUploadFileMutation, useUploadFileMutation, } from "../../app/services/message"; -import { useSendMsgMutation } from "../../app/services/contact"; -import { useSendChannelMsgMutation } from "../../app/services/channel"; -export default function useUploadImageMessage({ - context = "user", - from = null, - to = null, -}) { - // const slicedRef = useRef(false); +export default function useUploadFile() { + const [data, setData] = useState(null); const [uploadingFile, setUploadingFile] = useState(false); const sliceUploadedCountRef = useRef(0); const totalSliceCountRef = useRef(1); - // const [uploadedSliceCount, setUploadedSliceCount] = useState(0) const [ prepareUploadFile, { isLoading: isPreparing, isSuccess: isPrepared }, ] = usePrepareUploadFileMutation(); const [ - uploadFile, + uploadFileFn, { isLoading: isUploading, isSuccess: isUploaded, isError: uploadFileError }, ] = useUploadFileMutation(); - const [ - sendChannelMsg, - { - isLoading: channelSending, - isSuccess: channelSuccess, - isError: channelError, - }, - ] = useSendChannelMsgMutation(); - const [ - sendUserMsg, - { isLoading: userSending, isSuccess: userSuccess, isError: userError }, - ] = useSendMsgMutation(); - const sendFn = context == "user" ? sendUserMsg : sendChannelMsg; const uploadChunk = async (data) => { const { file_id, chunk, is_last } = data; const formData = new FormData(); formData.append("file_id", file_id); formData.append("chunk_data", chunk); formData.append("chunk_is_last", is_last); - return uploadFile(formData); + return uploadFileFn(formData); }; - const sendFileMessage = async (file) => { + const uploadFile = async (file) => { if (!file) return; - const { name, type: file_type, size: file_size } = file; + setData(null); + const { + name = `rustchat-${new Date().getTime()}.${file.type.split("/")[1]}`, + type: file_type, + size: file_size, + } = file; // 拿file id const { data: file_id } = await prepareUploadFile({ content_type: file_type, @@ -101,30 +85,30 @@ export default function useUploadImageMessage({ const { data: { path, size, hash }, } = uploadResult; - const content = JSON.stringify({ + const encodedPath = encodeURIComponent(path); + const res = { name, + file_type, + path, size, hash, - path, - }); - console.log("upload content", content); - await sendFn({ - id: to, - content, - type: "file", - properties: { file_type }, - from_uid: from, - }); + url: `${BASE_URL}/resource/file?file_path=${encodedPath}`, + thumbnail: file_type.startsWith("image") + ? `${BASE_URL}/resource/file?file_path=${encodedPath}&thumbnail=true` + : "", + download: `${BASE_URL}/resource/file?file_path=${encodedPath}&download=true`, + }; + setData(res); + return res; }; - const isSending = - userSending || channelSending || isPreparing || uploadingFile; return { - progress: isSending - ? sliceUploadedCountRef.current / totalSliceCountRef.current - : 1, - sendFileMessage, - isError: channelError || userError || uploadFileError, - isSending, - isSuccess: (channelSuccess || userSuccess) && isPrepared, + data, + isUploading: uploadingFile, + progress: Number( + (sliceUploadedCountRef.current / totalSliceCountRef.current) * 100 + ).toFixed(2), + uploadFile, + isError: uploadFileError, + isSuccess: !!data, }; } diff --git a/src/common/utils.js b/src/common/utils.js index da504e65..dd939181 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -1,3 +1,4 @@ +import { FILE_IMAGE_SIZE } from "../app/config"; import IconPdf from "../assets/icons/file.pdf.svg"; import IconAudio from "../assets/icons/file.audio.svg"; import IconVideo from "../assets/icons/file.video.svg"; @@ -5,6 +6,9 @@ import IconUnkown from "../assets/icons/file.unkown.svg"; import IconDoc from "../assets/icons/file.doc.svg"; import IconCode from "../assets/icons/file.code.svg"; import IconImage from "../assets/icons/file.image.svg"; +export const isImage = (file_type = "", size = 0) => { + return file_type.startsWith("image") && size <= FILE_IMAGE_SIZE; +}; export const isObjectEqual = (obj1, obj2) => { let o1 = Object.entries(obj1 ?? {}) .sort() diff --git a/src/routes/chat/utils.js b/src/routes/chat/utils.js index 2ffc343f..09c5a427 100644 --- a/src/routes/chat/utils.js +++ b/src/routes/chat/utils.js @@ -1,5 +1,6 @@ import React from "react"; import dayjs from "dayjs"; +import { isImage } from "../../common/utils"; import { ContentTypes } from "../../app/config"; import Divider from "../../common/component/Divider"; import Message from "../../common/component/Message"; @@ -37,20 +38,13 @@ export function getUnreadCount({ } export const renderPreviewMessage = (message = null) => { if (!message) return null; - const { content_type, content } = message; + const { content_type, content, properties = {} } = message; let res = null; switch (content_type) { case ContentTypes.text: { res = content; } - break; - case ContentTypes.imageJPG: - case ContentTypes.image: - { - res = `[image]`; - } - break; case ContentTypes.markdown: { @@ -60,7 +54,11 @@ export const renderPreviewMessage = (message = null) => { break; case ContentTypes.file: { - res = `[file]`; + if (isImage(properties.file_type, properties.size)) { + res = `[image]`; + } else { + res = `[file]`; + } } break;