feat: click to preview in widget
This commit is contained in:
+4
-4
@@ -14,7 +14,7 @@
|
||||
"@toast-ui/editor": "^3.2.2",
|
||||
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.1.0",
|
||||
"@toast-ui/react-editor": "^3.2.3",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/node": "^20.4.4",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@udecode/plate": "16.8",
|
||||
@@ -67,7 +67,7 @@
|
||||
"react-textarea-autosize": "^8.5.2",
|
||||
"react-use-wizard": "^2.2.1",
|
||||
"react-viewport-list": "^7.1.1",
|
||||
"react-virtuoso": "^4.4.1",
|
||||
"react-virtuoso": "^4.4.2",
|
||||
"resolve": "^1.22.3",
|
||||
"rooks": "^7.14.1",
|
||||
"slate": "^0.94.1",
|
||||
@@ -79,7 +79,7 @@
|
||||
"terser-webpack-plugin": "^5.3.9",
|
||||
"tippy.js": "^6.3.7",
|
||||
"typescript": "^5.1.6",
|
||||
"wavesurfer.js": "^7.0.3",
|
||||
"wavesurfer.js": "^7.0.6",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-manifest-plugin": "^5.0.0",
|
||||
@@ -147,7 +147,7 @@
|
||||
"lint-staged": "^13.2.3",
|
||||
"md5-file": "^5.0.0",
|
||||
"patch-package": "^7.0.2",
|
||||
"postcss": "^8.4.26",
|
||||
"postcss": "^8.4.27",
|
||||
"postcss-loader": "^7.3.3",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^3.0.0",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#667085" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5 21H19C20.1046 21 21 20.1046 21 19V5C21 3.89543 20.1046 3 19 3H5C3.89543 3 3 3.89543 3 5V19C3 20.1046 3.89543 21 5 21ZM5 21L16 10L21 15M10 8.5C10 9.32843 9.32843 10 8.5 10C7.67157 10 7 9.32843 7 8.5C7 7.67157 7.67157 7 8.5 7C9.32843 7 10 7.67157 10 8.5Z"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 472 B |
@@ -0,0 +1,30 @@
|
||||
import { useState } from "react";
|
||||
import IconClose from "@/assets/icons/close.circle.svg";
|
||||
type Props = {
|
||||
thumbnail?: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
const Image = ({ thumbnail, content }: Props) => {
|
||||
const [originalVisible, setOriginalVisible] = useState(false);
|
||||
const toggleVisible = () => {
|
||||
setOriginalVisible((prev) => !prev);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{originalVisible && (
|
||||
<div className="fixed top-0 left-0 w-full h-full bg-black/50 flex items-center justify-center">
|
||||
<div className="relative">
|
||||
<img className="max-w-full" src={content || thumbnail} />
|
||||
<button className="absolute -top-2 -right-2" onClick={toggleVisible}>
|
||||
<IconClose className="w-5 h-5 dark:fill-gray-300" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<img onClick={toggleVisible} className="max-w-xs cursor-pointer" src={thumbnail || content} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Image;
|
||||
@@ -5,6 +5,8 @@ import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import Text from "./Text";
|
||||
import Image from "./Image";
|
||||
import { ContentType } from "@/types/message";
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@@ -12,7 +14,8 @@ export interface IWidgetMessage {
|
||||
mid: number;
|
||||
uid: number;
|
||||
host?: boolean;
|
||||
type?: "text";
|
||||
type?: ContentType;
|
||||
thumbnail?: string;
|
||||
content: string;
|
||||
create_time: number;
|
||||
sending: boolean;
|
||||
@@ -30,12 +33,20 @@ const Time = ({ time }: { time: number }) => {
|
||||
};
|
||||
const Index = (props: IWidgetMessage) => {
|
||||
const { logo } = useAppSelector((store) => store.server);
|
||||
const { host = false, type = "text", content, uid, create_time, sending } = props;
|
||||
const { host = false, type, content, thumbnail = "", uid, create_time, sending } = props;
|
||||
let contentContainer = null;
|
||||
console.log("render message", type, content);
|
||||
switch (type) {
|
||||
case "text":
|
||||
case "text/plain":
|
||||
contentContainer = <Text sending={sending} content={content} host={host} uid={uid} />;
|
||||
break;
|
||||
case "vocechat/file":
|
||||
{
|
||||
console.log("image file", content);
|
||||
|
||||
contentContainer = <Image thumbnail={thumbnail} content={content} />;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -53,10 +53,17 @@ const MessageFeed = ({ hostId }: Props) => {
|
||||
{mids.map((mid) => {
|
||||
const currMsg = messageMap[mid];
|
||||
if (!currMsg) return null;
|
||||
const lastMsg = messageMap[mids[mids.length - 1]];
|
||||
console.log("lll", lastMsg);
|
||||
// const lastMsg = messageMap[mids[mids.length - 1]];
|
||||
|
||||
const { content, created_at = 0, from_uid = 0, sending = false } = currMsg;
|
||||
const {
|
||||
content_type,
|
||||
thumbnail = "",
|
||||
content,
|
||||
created_at = 0,
|
||||
from_uid = 0,
|
||||
sending = false
|
||||
} = currMsg;
|
||||
console.log("lll", content, content_type);
|
||||
return (
|
||||
<Message
|
||||
uid={from_uid}
|
||||
@@ -64,8 +71,9 @@ const MessageFeed = ({ hostId }: Props) => {
|
||||
sending={sending}
|
||||
key={mid}
|
||||
mid={mid}
|
||||
type="text"
|
||||
type={content_type}
|
||||
content={content as string}
|
||||
thumbnail={thumbnail}
|
||||
create_time={created_at}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { memo, useRef, useState } from "react";
|
||||
import { ChangeEvent, memo, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
|
||||
import IconSend from "@/assets/icons/send.svg";
|
||||
import IconImage from "@/assets/icons/image.svg";
|
||||
import useSendMessage from "../../hooks/useSendMessage";
|
||||
import { useWidget } from "../WidgetContext";
|
||||
import useUploadFile from "@/hooks/useUploadFile";
|
||||
import { Wobble } from "@uiball/loaders";
|
||||
import { getImageSize } from "@/utils";
|
||||
|
||||
type Props = {
|
||||
from: number;
|
||||
@@ -12,6 +16,8 @@ type Props = {
|
||||
};
|
||||
let isComposing = false;
|
||||
const MessageInput = (props: Props) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const { uploadFile, isUploading, isSuccess: uploadSuccess } = useUploadFile();
|
||||
const { t } = useTranslation("widget");
|
||||
const { color } = useWidget();
|
||||
const { from, to } = props;
|
||||
@@ -36,6 +42,36 @@ const MessageInput = (props: Props) => {
|
||||
});
|
||||
setContent("");
|
||||
};
|
||||
const handleImageSelect = () => {
|
||||
if (inputRef?.current) {
|
||||
inputRef.current.click();
|
||||
}
|
||||
};
|
||||
const handleFileChange = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = evt.target.files;
|
||||
if (!files || files?.length == 0) return;
|
||||
const [file] = Array.from(files);
|
||||
const result = await uploadFile(file);
|
||||
if (result) {
|
||||
// send message
|
||||
const properties: any = await getImageSize(URL.createObjectURL(file));
|
||||
console.log("uploaded", result, properties);
|
||||
const { path } = result;
|
||||
sendMessage({
|
||||
ignoreLocal: true,
|
||||
type: "file",
|
||||
content: { path },
|
||||
properties
|
||||
});
|
||||
}
|
||||
};
|
||||
// useEffect(() => {
|
||||
// if(uploadSuccess){
|
||||
// inputRef.current.value="";
|
||||
// }
|
||||
|
||||
// }, [uploadSuccess])
|
||||
|
||||
return (
|
||||
<div className="relative border-t border-gray-300 dark:border-gray-600 w-full">
|
||||
<div className={"px-3 py-2 min-h-[48px] flex items-center gap-2"}>
|
||||
@@ -69,13 +105,35 @@ const MessageInput = (props: Props) => {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={content.trim().length === 0}
|
||||
className="px-2 py-1 disabled:opacity-60"
|
||||
>
|
||||
<IconSend className="dark:stroke-white w-4 h-4" />
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleImageSelect}
|
||||
disabled={isUploading}
|
||||
className="p-1 disabled:opacity-60"
|
||||
>
|
||||
{isUploading ? (
|
||||
<Wobble size={16} />
|
||||
) : (
|
||||
<IconImage className="dark:stroke-gray-100 w-4 h-4" />
|
||||
)}
|
||||
<input
|
||||
onChange={handleFileChange}
|
||||
accept="image/*"
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
name="image"
|
||||
id="image"
|
||||
hidden
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={content.trim().length === 0}
|
||||
className="p-1 disabled:opacity-60"
|
||||
>
|
||||
<IconSend className="dark:fill-gray-100 w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2880,11 +2880,16 @@
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^20.4.2":
|
||||
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
|
||||
version "20.4.2"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9"
|
||||
integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==
|
||||
|
||||
"@types/node@^20.4.4":
|
||||
version "20.4.4"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/@types/node/-/node-20.4.4.tgz#c79c7cc22c9d0e97a7944954c9e663bcbd92b0cb"
|
||||
integrity sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@@ -10150,7 +10155,7 @@ postcss@^7.0.35:
|
||||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.3.5, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.4:
|
||||
postcss@^8.3.5, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.4:
|
||||
version "8.4.26"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
|
||||
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
|
||||
@@ -10159,6 +10164,15 @@ postcss@^8.3.5, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.27:
|
||||
version "8.4.27"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/postcss/-/postcss-8.4.27.tgz#234d7e4b72e34ba5a92c29636734349e0d9c3057"
|
||||
integrity sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.6"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postinstall-postinstall@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
|
||||
@@ -10739,10 +10753,10 @@ react-viewport-list@^7.1.1:
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/react-viewport-list/-/react-viewport-list-7.1.1.tgz#2f36ad1db8124e8a960bc006b95228b696cc81d6"
|
||||
integrity sha512-O3gxykg3DgpcyYH+/X2kFwWFaZjckJ0FK3UBb4vFhZe+CsGLcX8OVJb0VSYI+IupEuQ9pl8dvJak8JRkIuvNjw==
|
||||
|
||||
react-virtuoso@^4.4.1:
|
||||
version "4.4.1"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/react-virtuoso/-/react-virtuoso-4.4.1.tgz#43d7ac35346c4eba947b40858b375d5844b5ae9f"
|
||||
integrity sha512-QrZ0JLnZFH8ltMw6q+S7U1+V2vUcSHzoIfLRzQKSv4nMJhEdjiZ+e9PqWCI7xJiy2AmSCAgo7g1V5osuurJo2Q==
|
||||
react-virtuoso@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/react-virtuoso/-/react-virtuoso-4.4.2.tgz#752438e5031a9f72722371a52ae5ac7265f3e1c0"
|
||||
integrity sha512-9uChSRSK5bTbKTxzuta4RqhfrlZSVHI/v8fHwmv3jmImS8FjdZ4Mzgqz9JKUp/yQo3dIHkR2QwidQeqxgYzPhQ==
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
@@ -12500,10 +12514,10 @@ watchpack@^2.4.0:
|
||||
glob-to-regexp "^0.4.1"
|
||||
graceful-fs "^4.1.2"
|
||||
|
||||
wavesurfer.js@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/wavesurfer.js/-/wavesurfer.js-7.0.3.tgz#d3e0b07056c08d43db19d4950d2de68681f48606"
|
||||
integrity sha512-gJ3P+Bd3Q4E8qETjjg0pneaVqm2J7jegG2Cc6vqEF5YDDKQ3m8sKsvVfgVhJkacKkO9jFAGDu58Hw4zLr7xD0A==
|
||||
wavesurfer.js@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://mirrors.cloud.tencent.com/npm/wavesurfer.js/-/wavesurfer.js-7.0.6.tgz#e0a214df7497f0503e18fb67959e4bca356c388a"
|
||||
integrity sha512-Pkg0iBnIsgl9k4F938S9/RBEqklt9jPeeo59mmKimbUoxcKr11l7rsu9qHkGJvkl1hhx33lcSz0cA9F/qwFngQ==
|
||||
|
||||
wbuf@^1.1.0, wbuf@^1.7.3:
|
||||
version "1.7.3"
|
||||
|
||||
Reference in New Issue
Block a user