diff --git a/package.json b/package.json index 3868bafb9..d4980db37 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "typescript": "^5.1.3" }, "dependencies": { - "dompurify": "^3.1.6" + "dompurify": "^3.1.6", + "marked": "latest" } } diff --git a/packages/react/src/store/messageStore.js b/packages/react/src/store/messageStore.js index c359a0c8e..58368594d 100644 --- a/packages/react/src/store/messageStore.js +++ b/packages/react/src/store/messageStore.js @@ -10,6 +10,7 @@ const useMessageStore = create((set, get) => ({ editMessage: {}, messagesOffset: 0, quoteMessage: [], + previewMessage: [], deleteMessageRoles: {}, deleteOwnMessageRoles: {}, forceDeleteMessageRoles: {}, @@ -100,6 +101,14 @@ const useMessageStore = create((set, get) => ({ })), clearQuoteMessages: () => set({ quoteMessage: [] }), + addPreviewMessage: (previewMessage) => + set((state) => ({ + previewMessage: [...state.previewMessage, previewMessage], + })), + removePreviewMessage: (previewMessage) => + set((state) => ({ + previewMessage: state.previewMessage.filter((i) => i !== previewMessage), + })), setMessageToReport: (messageId) => set(() => ({ messageToReport: messageId })), toggleShowReportMessage: () => { diff --git a/packages/react/src/views/ChatInput/ChatInput.js b/packages/react/src/views/ChatInput/ChatInput.js index ca748520a..7d3aa0c9c 100644 --- a/packages/react/src/views/ChatInput/ChatInput.js +++ b/packages/react/src/views/ChatInput/ChatInput.js @@ -34,6 +34,7 @@ import useShowCommands from '../../hooks/useShowCommands'; import useSearchMentionUser from '../../hooks/useSearchMentionUser'; import formatSelection from '../../lib/formatSelection'; import { parseEmoji } from '../../lib/emoji'; +import PreviewMessage from '../PreviewMessage/PreviewMessage'; const ChatInput = ({ scrollToBottom }) => { const { styleOverrides, classNames } = useComponentOverrides('ChatInput'); @@ -97,6 +98,7 @@ const ChatInput = ({ scrollToBottom }) => { editMessage, setEditMessage, quoteMessage, + previewMessage, isRecordingMessage, upsertMessage, replaceMessage, @@ -106,6 +108,7 @@ const ChatInput = ({ scrollToBottom }) => { editMessage: state.editMessage, setEditMessage: state.setEditMessage, quoteMessage: state.quoteMessage, + previewMessage: state.previewMessage, isRecordingMessage: state.isRecordingMessage, upsertMessage: state.upsertMessage, replaceMessage: state.replaceMessage, @@ -519,6 +522,13 @@ const ChatInput = ({ scrollToBottom }) => { ))} +
+ {previewMessage && + previewMessage.length > 0 && + previewMessage.map((message, index) => ( + + ))} +
{editMessage.msg || editMessage.attachments || isChannelReadOnly ? ( state.isRecordingMessage ); + const addPreviewMessage = useMessageStore((state) => state.addPreviewMessage); + const previewMessage = useMessageStore((state) => state.previewMessage); + const removePreviewMessage = useMessageStore( + (state) => state.removePreviewMessage + ); + const [isEmojiOpen, setEmojiOpen] = useState(false); const [isInsertLinkOpen, setInsertLinkOpen] = useState(false); const [isPopoverOpen, setPopoverOpen] = useState(false); @@ -144,7 +158,7 @@ const ChatInputFormattingToolbar = ({ }} > - file + File ) : ( @@ -173,7 +187,7 @@ const ChatInputFormattingToolbar = ({ }} > - link + Link ) : ( @@ -190,6 +204,41 @@ const ChatInputFormattingToolbar = ({ ), + preview: + isPopoverOpen && popOverItems.includes('preview') ? ( + { + if (isRecordingMessage || !messageRef.current?.value) return; + if (previewMessage) { + removePreviewMessage(previewMessage[0]); + } + addPreviewMessage(messageRef.current.value); + }} + > + + Preview + + ) : ( + + { + if (isRecordingMessage || !messageRef.current?.value) return; + if (previewMessage) { + removePreviewMessage(previewMessage[0]); + } + addPreviewMessage(messageRef.current.value); + }} + > + + + + ), formatter: formatters .map((name) => formatter.find((item) => item.name === name)) .map((item) => diff --git a/packages/react/src/views/PreviewMessage/PreviewMessage.js b/packages/react/src/views/PreviewMessage/PreviewMessage.js new file mode 100644 index 000000000..293e0b83a --- /dev/null +++ b/packages/react/src/views/PreviewMessage/PreviewMessage.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { + useComponentOverrides, + useTheme, + Box, + ActionButton, + Icon, +} from '@embeddedchat/ui-elements'; +import { marked } from 'marked'; +import Dompurify from 'dompurify'; +import getPreviewMessageStyles from './PreviewMessage.styles'; +import { useMessageStore } from '../../store'; + +const PreviewMessage = ({ className = '', style = {}, message }) => { + const { theme } = useTheme(); + const styles = getPreviewMessageStyles(theme); + const { classNames, styleOverrides } = useComponentOverrides('QuoteMessage'); + const removePreviewMessage = useMessageStore( + (state) => state.removePreviewMessage + ); + + const formatMessage = () => { + const markedText = marked.parse(message); + const sanitizedText = Dompurify.sanitize(markedText); + return
; + }; + + return ( + + + removePreviewMessage(message)} + size="small" + > + + + + {formatMessage()} + + ); +}; + +export default PreviewMessage; diff --git a/packages/react/src/views/PreviewMessage/PreviewMessage.styles.js b/packages/react/src/views/PreviewMessage/PreviewMessage.styles.js new file mode 100644 index 000000000..4df4cabf0 --- /dev/null +++ b/packages/react/src/views/PreviewMessage/PreviewMessage.styles.js @@ -0,0 +1,43 @@ +import { css } from '@emotion/react'; + +const getPreviewMessageStyles = (theme) => { + const styles = { + messageContainer: css` + margin: 0.2rem 1.9rem; + position: relative; + font-size: 0.85rem; + background-color: ${theme.colors.background}; + color: ${theme.colors.foreground}; + padding: 0.5rem; + z-index: 1200; + border: 1px solid ${theme.colors.border}; + border-radius: ${theme.radius}; + max-width: 100%; + box-sizing: border-box; + `, + + avatarContainer: css` + padding: 0.25rem; + display: flex; + gap: 0.5rem; + `, + + message: css` + padding: 0.25rem; + overflow-wrap: break-word; + word-break: break-word; + white-space: normal; + width: 100%; + `, + + actionBtn: css` + position: absolute; + top: 0.75rem; + right: 0.75rem; + `, + }; + + return styles; +}; + +export default getPreviewMessageStyles; diff --git a/yarn.lock b/yarn.lock index d3dec11be..2b3d72944 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15208,6 +15208,7 @@ __metadata: esbuild: ^0.17.19 husky: ^9.0.11 lerna: ^6.6.2 + marked: latest typescript: ^5.1.3 languageName: unknown linkType: soft @@ -22445,6 +22446,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:latest": + version: 15.0.6 + resolution: "marked@npm:15.0.6" + bin: + marked: bin/marked.js + checksum: 5218363ac4f6cd1893318ad8b1efacdc8a416f87da28cbcffb419d97602168935249351a3fbe2c59221e7e9955862c6a038ec00f19bf4a6d7e8e0e2e9643d154 + languageName: node + linkType: hard + "material-colors@npm:^1.2.1": version: 1.2.6 resolution: "material-colors@npm:1.2.6"