Skip to content

Commit 7a009e0

Browse files
author
mcallegari10
committed
Merge branch 'master' into issue-212-profile-avatar-user
2 parents dfa29ee + 015477e commit 7a009e0

File tree

19 files changed

+320
-30
lines changed

19 files changed

+320
-30
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ yarn-error.log
1111

1212
# Test files
1313
coverage
14+
15+
# vscode
16+
.vscode

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ import logo from './logo.svg';
115115

116116
function App() {
117117
useEffect(() => {
118-
addResponseMessage('Welcome to this awesome chat!');
118+
addResponseMessage('Welcome to this **awesome** chat!');
119119
}, []);
120120

121121
const handleNewUserMessage = (newMessage) => {
@@ -167,6 +167,9 @@ export default App;
167167
|**sendButtonAlt**|string|NO|'Send'|Send button alt for a11y purposes|
168168
|**handleTextInputChange**|(event) => any|NO| |Prop that triggers on input change|
169169
|**handleSubmit**|(event) => any|NO| |Prop that triggers when a message is submitted, used for custom validation|
170+
|**resizable**|boolean|NO|false|Prop that allows to resize the widget by dragging it's left border|
171+
|**emojis**|boolean|NO|false|enable emoji picker|
172+
|**showBadge**|boolean|NO|true|Prop that allows to show or hide the unread message badge|
170173

171174
#### Styles
172175

@@ -192,13 +195,13 @@ As of v3.0, messages now have an optional ID that can be added on creation.If yo
192195

193196
- **addResponseMessage**
194197
- params:
195-
- text: string
198+
- text: string (supports markdown)
196199
- id: string (optional)
197200
- Method to add a new message written as a response to a user input.
198201

199202
- **addUserMessage**
200203
- params:
201-
- text: string
204+
- text: string (supports markdown)
202205
- id: string (optional)
203206
- This method will add a new message written as a user. Keep in mind it will not trigger the prop handleNewUserMessage()
204207

assets/icon-smiley.svg

+1
Loading

dev/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export default class App extends Component {
4646
handleQuickButtonClicked={this.handleQuickButtonClicked}
4747
imagePreview
4848
handleSubmit={this.handleSubmit}
49+
emojis
50+
resizable
4951
/>
5052
);
5153
}

package-lock.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"dependencies": {
2222
"classnames": "^2.2.6",
2323
"date-fns": "^2.11.1",
24+
"emoji-mart": "^3.0.1",
2425
"markdown-it": "^8.4.1",
2526
"markdown-it-link-attributes": "^2.1.0",
2627
"markdown-it-sanitizer": "^0.4.3",

src/components/Widget/components/Conversation/components/Messages/components/Message/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Props = {
1515
}
1616

1717
function Message({ message, showTimeStamp }: Props) {
18-
const sanitizedHTML = markdownIt()
18+
const sanitizedHTML = markdownIt({ break: true })
1919
.use(markdownItClass, {
2020
img: ['rcw-message-img']
2121
})
@@ -26,7 +26,7 @@ function Message({ message, showTimeStamp }: Props) {
2626

2727
return (
2828
<div className={`rcw-${message.sender}`}>
29-
<div className="rcw-message-text" dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
29+
<div className="rcw-message-text" dangerouslySetInnerHTML={{ __html: sanitizedHTML.replace(/\n$/,'') }} />
3030
{showTimeStamp && <span className="rcw-timestamp">{format(message.timestamp, 'hh:mm')}</span>}
3131
</div>
3232
);

src/components/Widget/components/Conversation/components/Messages/components/Message/styles.scss

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
.rcw-message {
55
margin: 10px;
66
display: flex;
7+
white-space: pre-wrap;
78
word-wrap: break-word;
89

910
&-client {
@@ -23,6 +24,9 @@
2324

2425
.rcw-message-text {
2526
@include message-bubble($turqois-2);
27+
28+
white-space: pre-wrap;
29+
word-wrap: break-word;
2630
}
2731

2832
.rcw-timestamp {
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { useRef, useEffect } from 'react';
1+
import { useRef, useEffect, useState, forwardRef, useImperativeHandle } from 'react';
22
import { useSelector } from 'react-redux';
33
import cn from 'classnames';
44

55
import { GlobalState } from 'src/store/types';
66

7+
import { getCaretIndex, isFirefox, updateCaret, insertNodeAtCaret, getSelection } from '../../../../../../utils/contentEditable'
78
const send = require('../../../../../../../assets/send_button.svg') as string;
9+
const emoji = require('../../../../../../../assets/icon-smiley.svg') as string;
10+
const brRegex = /<br>/g;
811

912
import './style.scss';
1013

@@ -14,41 +17,123 @@ type Props = {
1417
autofocus: boolean;
1518
sendMessage: (event: any) => void;
1619
buttonAlt: string;
20+
onPressEmoji: () => void;
21+
onChangeSize: (event: any) => void;
1722
onTextInputChange?: (event: any) => void;
1823
}
1924

20-
function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInputChange, buttonAlt }: Props) {
25+
function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInputChange, buttonAlt, onPressEmoji, onChangeSize }: Props, ref) {
2126
const showChat = useSelector((state: GlobalState) => state.behavior.showChat);
22-
const inputRef = useRef<HTMLSpanElement>(null);
27+
const inputRef = useRef<HTMLDivElement>(null!);
28+
const refContainer = useRef<HTMLDivElement>(null);
29+
const [enter, setEnter]= useState(false)
30+
const [firefox, setFirefox] = useState(false);
31+
const [height, setHeight] = useState(0)
2332
// @ts-ignore
2433
useEffect(() => { if (showChat && autofocus) inputRef.current?.focus(); }, [showChat]);
34+
useEffect(() => { setFirefox(isFirefox())}, [])
35+
36+
useImperativeHandle(ref, () => {
37+
return {
38+
onSelectEmoji: handlerOnSelectEmoji,
39+
};
40+
});
2541

2642
const handlerOnChange = (event) => {
2743
onTextInputChange && onTextInputChange(event)
2844
}
2945

3046
const handlerSendMessage = () => {
31-
const { current } = inputRef
32-
if(current?.innerHTML) {
33-
sendMessage(current.innerText);
34-
current.innerHTML = ''
47+
const el = inputRef.current;
48+
if(el.innerHTML) {
49+
sendMessage(el.innerText);
50+
el.innerHTML = ''
3551
}
3652
}
3753

54+
const handlerOnSelectEmoji = (emoji) => {
55+
const el = inputRef.current;
56+
const { start, end } = getSelection(el)
57+
if(el.innerHTML) {
58+
const firstPart = el.innerHTML.substring(0, start);
59+
const secondPart = el.innerHTML.substring(end);
60+
el.innerHTML = (`${firstPart}${emoji.native}${secondPart}`)
61+
} else {
62+
el.innerHTML = emoji.native
63+
}
64+
updateCaret(el, start, emoji.native.length)
65+
}
66+
3867
const handlerOnKeyPress = (event) => {
68+
const el = inputRef.current;
69+
3970
if(event.charCode == 13 && !event.shiftKey) {
4071
event.preventDefault()
4172
handlerSendMessage();
4273
}
74+
if(event.charCode === 13 && event.shiftKey) {
75+
event.preventDefault()
76+
insertNodeAtCaret(el);
77+
setEnter(true)
78+
}
79+
}
80+
81+
// TODO use a context for checkSize and toggle picker
82+
const checkSize = () => {
83+
const senderEl = refContainer.current
84+
if(senderEl && height !== senderEl.clientHeight) {
85+
const {clientHeight} = senderEl;
86+
setHeight(clientHeight)
87+
onChangeSize(clientHeight ? clientHeight -1 : 0)
88+
}
89+
}
90+
91+
const handlerOnKeyUp = (event) => {
92+
const el = inputRef.current;
93+
if(!el) return true;
94+
// Conditions need for firefox
95+
if(firefox && event.key === 'Backspace') {
96+
if(el.innerHTML.length === 1 && enter) {
97+
el.innerHTML = '';
98+
setEnter(false);
99+
}
100+
else if(brRegex.test(el.innerHTML)){
101+
el.innerHTML = el.innerHTML.replace(brRegex, '');
102+
}
103+
}
104+
checkSize();
105+
}
106+
107+
const handlerOnKeyDown= (event) => {
108+
const el = inputRef.current;
109+
110+
if( event.key === 'Backspace' && el){
111+
const caretPosition = getCaretIndex(inputRef.current);
112+
const character = el.innerHTML.charAt(caretPosition - 1);
113+
if(character === "\n") {
114+
event.preventDefault();
115+
event.stopPropagation();
116+
el.innerHTML = (el.innerHTML.substring(0, caretPosition - 1) + el.innerHTML.substring(caretPosition))
117+
updateCaret(el, caretPosition, -1)
118+
}
119+
}
120+
}
121+
122+
const handlerPressEmoji = () => {
123+
onPressEmoji();
124+
checkSize();
43125
}
44126

45127
return (
46-
<div className="rcw-sender">
128+
<div ref={refContainer} className="rcw-sender">
129+
<button className='rcw-picker-btn' type="submit" onClick={handlerPressEmoji}>
130+
<img src={emoji} className="rcw-picker-icon" alt="" />
131+
</button>
47132
<div className={cn('rcw-new-message', {
48133
'rcw-message-disable': disabledInput,
49134
})
50135
}>
51-
<span
136+
<div
52137
spellCheck
53138
className="rcw-input"
54139
role="textbox"
@@ -57,7 +142,10 @@ function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInpu
57142
placeholder={placeholder}
58143
onInput={handlerOnChange}
59144
onKeyPress={handlerOnKeyPress}
145+
onKeyUp={handlerOnKeyUp}
146+
onKeyDown={handlerOnKeyDown}
60147
/>
148+
61149
</div>
62150
<button type="submit" className="rcw-send" onClick={handlerSendMessage}>
63151
<img src={send} className="rcw-send-icon" alt={buttonAlt} />
@@ -66,4 +154,4 @@ function Sender({ sendMessage, placeholder, disabledInput, autofocus, onTextInpu
66154
);
67155
}
68156

69-
export default Sender;
157+
export default forwardRef(Sender);

src/components/Widget/components/Conversation/components/Sender/style.scss

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
min-height: 45px;
1111
overflow: hidden;
1212
padding: 10px;
13+
position: relative;
1314

1415
&.expand {
1516
height: 55px;
@@ -36,8 +37,12 @@
3637
.rcw-input {
3738
display: block;
3839
height: 100%;
39-
max-height: 75px;
40+
line-height: 20px;
41+
max-height: 78px;
4042
overflow-y: auto;
43+
user-select: text;
44+
white-space: pre-wrap;
45+
word-wrap: break-word;
4146

4247
&:focus-visible {
4348
outline: none;
@@ -49,9 +54,10 @@
4954
}
5055
}
5156

52-
.rcw-send {
57+
.rcw-send, .rcw-picker-btn {
5358
background: $grey-2;
5459
border: 0;
60+
cursor: pointer;
5561

5662
.rcw-send-icon {
5763
height: 25px;

0 commit comments

Comments
 (0)