Skip to content

Commit d14c174

Browse files
feat: Add a Preview Feature to check the text after formatting it. (#889)
* fix * add_css * fix * add_marked_package * formatText * Update QuoteMessage.js * fic * resolve conflicts * add disable * fix
1 parent 63c2360 commit d14c174

7 files changed

Lines changed: 174 additions & 4 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"typescript": "^5.1.3"
2626
},
2727
"dependencies": {
28-
"dompurify": "^3.1.6"
28+
"dompurify": "^3.1.6",
29+
"marked": "latest"
2930
}
3031
}

packages/react/src/store/messageStore.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const useMessageStore = create((set, get) => ({
1010
editMessage: {},
1111
messagesOffset: 0,
1212
quoteMessage: [],
13+
previewMessage: [],
1314
deleteMessageRoles: {},
1415
deleteOwnMessageRoles: {},
1516
forceDeleteMessageRoles: {},
@@ -100,6 +101,14 @@ const useMessageStore = create((set, get) => ({
100101
})),
101102

102103
clearQuoteMessages: () => set({ quoteMessage: [] }),
104+
addPreviewMessage: (previewMessage) =>
105+
set((state) => ({
106+
previewMessage: [...state.previewMessage, previewMessage],
107+
})),
108+
removePreviewMessage: (previewMessage) =>
109+
set((state) => ({
110+
previewMessage: state.previewMessage.filter((i) => i !== previewMessage),
111+
})),
103112
setMessageToReport: (messageId) =>
104113
set(() => ({ messageToReport: messageId })),
105114
toggleShowReportMessage: () => {

packages/react/src/views/ChatInput/ChatInput.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import useShowCommands from '../../hooks/useShowCommands';
3434
import useSearchMentionUser from '../../hooks/useSearchMentionUser';
3535
import formatSelection from '../../lib/formatSelection';
3636
import { parseEmoji } from '../../lib/emoji';
37+
import PreviewMessage from '../PreviewMessage/PreviewMessage';
3738

3839
const ChatInput = ({ scrollToBottom }) => {
3940
const { styleOverrides, classNames } = useComponentOverrides('ChatInput');
@@ -97,6 +98,7 @@ const ChatInput = ({ scrollToBottom }) => {
9798
editMessage,
9899
setEditMessage,
99100
quoteMessage,
101+
previewMessage,
100102
isRecordingMessage,
101103
upsertMessage,
102104
replaceMessage,
@@ -106,6 +108,7 @@ const ChatInput = ({ scrollToBottom }) => {
106108
editMessage: state.editMessage,
107109
setEditMessage: state.setEditMessage,
108110
quoteMessage: state.quoteMessage,
111+
previewMessage: state.previewMessage,
109112
isRecordingMessage: state.isRecordingMessage,
110113
upsertMessage: state.upsertMessage,
111114
replaceMessage: state.replaceMessage,
@@ -519,6 +522,13 @@ const ChatInput = ({ scrollToBottom }) => {
519522
<QuoteMessage message={message} key={index} />
520523
))}
521524
</div>
525+
<div>
526+
{previewMessage &&
527+
previewMessage.length > 0 &&
528+
previewMessage.map((message, index) => (
529+
<PreviewMessage message={message} key={index} />
530+
))}
531+
</div>
522532
{editMessage.msg || editMessage.attachments || isChannelReadOnly ? (
523533
<ChannelState
524534
status={

packages/react/src/views/ChatInput/ChatInputFormattingToolbar.js

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ const ChatInputFormattingToolbar = ({
2222
inputRef,
2323
triggerButton,
2424
optionConfig = {
25-
surfaceItems: ['emoji', 'formatter', 'link', 'audio', 'video', 'file'],
25+
surfaceItems: [
26+
'emoji',
27+
'formatter',
28+
'link',
29+
'audio',
30+
'video',
31+
'file',
32+
'preview',
33+
],
2634
formatters: ['bold', 'italic', 'strike', 'code', 'multiline'],
2735
smallScreenSurfaceItems: ['emoji', 'video', 'audio', 'file'],
2836
popOverItems: ['formatter', 'link'],
@@ -46,6 +54,12 @@ const ChatInputFormattingToolbar = ({
4654
(state) => state.isRecordingMessage
4755
);
4856

57+
const addPreviewMessage = useMessageStore((state) => state.addPreviewMessage);
58+
const previewMessage = useMessageStore((state) => state.previewMessage);
59+
const removePreviewMessage = useMessageStore(
60+
(state) => state.removePreviewMessage
61+
);
62+
4963
const [isEmojiOpen, setEmojiOpen] = useState(false);
5064
const [isInsertLinkOpen, setInsertLinkOpen] = useState(false);
5165
const [isPopoverOpen, setPopoverOpen] = useState(false);
@@ -144,7 +158,7 @@ const ChatInputFormattingToolbar = ({
144158
}}
145159
>
146160
<Icon name="attachment" size="1rem" />
147-
<span>file</span>
161+
<span>File</span>
148162
</Box>
149163
) : (
150164
<Tooltip text="Upload File" position="top" key="file">
@@ -173,7 +187,7 @@ const ChatInputFormattingToolbar = ({
173187
}}
174188
>
175189
<Icon name="link" size="1rem" />
176-
<span>link</span>
190+
<span>Link</span>
177191
</Box>
178192
) : (
179193
<Tooltip text="Link" position="top" key="link">
@@ -190,6 +204,41 @@ const ChatInputFormattingToolbar = ({
190204
</ActionButton>
191205
</Tooltip>
192206
),
207+
preview:
208+
isPopoverOpen && popOverItems.includes('preview') ? (
209+
<Box
210+
key="preview"
211+
css={styles.popOverItemStyles}
212+
disabled={isRecordingMessage || !messageRef.current?.value}
213+
onClick={() => {
214+
if (isRecordingMessage || !messageRef.current?.value) return;
215+
if (previewMessage) {
216+
removePreviewMessage(previewMessage[0]);
217+
}
218+
addPreviewMessage(messageRef.current.value);
219+
}}
220+
>
221+
<Icon name="eyeopen" size="1rem" />
222+
<span>Preview</span>
223+
</Box>
224+
) : (
225+
<Tooltip text="Preview" position="top" key="preview">
226+
<ActionButton
227+
square
228+
ghost
229+
disabled={isRecordingMessage || !messageRef.current?.value}
230+
onClick={() => {
231+
if (isRecordingMessage || !messageRef.current?.value) return;
232+
if (previewMessage) {
233+
removePreviewMessage(previewMessage[0]);
234+
}
235+
addPreviewMessage(messageRef.current.value);
236+
}}
237+
>
238+
<Icon name="eyeopen" size="1.25rem" />
239+
</ActionButton>
240+
</Tooltip>
241+
),
193242
formatter: formatters
194243
.map((name) => formatter.find((item) => item.name === name))
195244
.map((item) =>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import {
3+
useComponentOverrides,
4+
useTheme,
5+
Box,
6+
ActionButton,
7+
Icon,
8+
} from '@embeddedchat/ui-elements';
9+
import { marked } from 'marked';
10+
import Dompurify from 'dompurify';
11+
import getPreviewMessageStyles from './PreviewMessage.styles';
12+
import { useMessageStore } from '../../store';
13+
14+
const PreviewMessage = ({ className = '', style = {}, message }) => {
15+
const { theme } = useTheme();
16+
const styles = getPreviewMessageStyles(theme);
17+
const { classNames, styleOverrides } = useComponentOverrides('QuoteMessage');
18+
const removePreviewMessage = useMessageStore(
19+
(state) => state.removePreviewMessage
20+
);
21+
22+
const formatMessage = () => {
23+
const markedText = marked.parse(message);
24+
const sanitizedText = Dompurify.sanitize(markedText);
25+
return <div dangerouslySetInnerHTML={{ __html: sanitizedText }} />;
26+
};
27+
28+
return (
29+
<Box
30+
className={`ec-quote-msg ${className} ${classNames}`}
31+
style={{ ...styleOverrides, ...style }}
32+
css={styles.messageContainer}
33+
>
34+
<Box css={styles.actionBtn}>
35+
<ActionButton
36+
ghost
37+
onClick={() => removePreviewMessage(message)}
38+
size="small"
39+
>
40+
<Icon name="cross" size="0.75rem" />
41+
</ActionButton>
42+
</Box>
43+
<Box css={styles.message}>{formatMessage()}</Box>
44+
</Box>
45+
);
46+
};
47+
48+
export default PreviewMessage;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { css } from '@emotion/react';
2+
3+
const getPreviewMessageStyles = (theme) => {
4+
const styles = {
5+
messageContainer: css`
6+
margin: 0.2rem 2rem;
7+
position: relative;
8+
font-size: 0.85rem;
9+
background-color: ${theme.colors.background};
10+
color: ${theme.colors.foreground};
11+
padding: 0.5rem;
12+
z-index: 1200;
13+
border: 1px solid ${theme.colors.border};
14+
border-radius: ${theme.radius};
15+
max-width: 100%;
16+
box-sizing: border-box;
17+
`,
18+
19+
avatarContainer: css`
20+
padding: 0.25rem;
21+
display: flex;
22+
gap: 0.5rem;
23+
`,
24+
25+
message: css`
26+
padding: 0.25rem;
27+
overflow-wrap: break-word;
28+
word-break: break-word;
29+
white-space: normal;
30+
width: 100%;
31+
`,
32+
33+
actionBtn: css`
34+
position: absolute;
35+
top: 0.75rem;
36+
right: 0.75rem;
37+
`,
38+
};
39+
40+
return styles;
41+
};
42+
43+
export default getPreviewMessageStyles;

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15208,6 +15208,7 @@ __metadata:
1520815208
esbuild: ^0.17.19
1520915209
husky: ^9.0.11
1521015210
lerna: ^6.6.2
15211+
marked: latest
1521115212
typescript: ^5.1.3
1521215213
languageName: unknown
1521315214
linkType: soft
@@ -22445,6 +22446,15 @@ __metadata:
2244522446
languageName: node
2244622447
linkType: hard
2244722448

22449+
"marked@npm:latest":
22450+
version: 15.0.6
22451+
resolution: "marked@npm:15.0.6"
22452+
bin:
22453+
marked: bin/marked.js
22454+
checksum: 5218363ac4f6cd1893318ad8b1efacdc8a416f87da28cbcffb419d97602168935249351a3fbe2c59221e7e9955862c6a038ec00f19bf4a6d7e8e0e2e9643d154
22455+
languageName: node
22456+
linkType: hard
22457+
2244822458
"material-colors@npm:^1.2.1":
2244922459
version: 1.2.6
2245022460
resolution: "material-colors@npm:1.2.6"

0 commit comments

Comments
 (0)